Start M0 editor workflow polish
This commit is contained in:
parent
3e053a81ab
commit
19c4d017dc
16
.github/pull_request_template.md
vendored
Normal file
16
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
## Summary
|
||||||
|
|
||||||
|
-
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
-
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [ ] I updated the root README for user controls, run commands, or troubleshooting changes.
|
||||||
|
- [ ] I updated `docs/editor/` for editor workflow or feature-design changes.
|
||||||
|
- [ ] I added or updated an ADR for architecture, policy, scene format, rendering path, PIE semantics, settings schema, or netcode decisions.
|
||||||
|
- [ ] I indexed new documentation in `docs/README.md`.
|
||||||
|
- [ ] If this is Jackdaw-inspired work, I identified it as design-inspired, API-inspired, or code-derived.
|
||||||
|
- [ ] If this includes copied or closely translated source, I included source URL, upstream commit/release, license compatibility notes, and required file-level notices.
|
||||||
@ -117,7 +117,7 @@ VS Code tasks: **target cleanup (dry run)** and **target cleanup (safe apply)**.
|
|||||||
| Select Project Sun | Inspect project default lighting; create a scene sun override |
|
| Select Project Sun | Inspect project default lighting; create a scene sun override |
|
||||||
| Asset Browser project/file views | Browse `assets/`, search/filter/sort, switch grid/list, expand model subassets with generated thumbnails, inspect staged import/material settings, drag assets/submeshes into the viewport |
|
| Asset Browser project/file views | Browse `assets/`, search/filter/sort, switch grid/list, expand model subassets with generated thumbnails, inspect staged import/material settings, drag assets/submeshes into the viewport |
|
||||||
| Asset Browser context/details actions | Apply textures/materials, regenerate thumbnails, reimport models, place assets/submeshes, or move file assets to `assets/.trash/` |
|
| Asset Browser context/details actions | Apply textures/materials, regenerate thumbnails, reimport models, place assets/submeshes, or move file assets to `assets/.trash/` |
|
||||||
| `Ctrl+P` | Command palette (`scene.reset_lighting`, `selection.group`, `selection.focus`, play commands) |
|
| `Ctrl+P` | Command palette; type to filter, use arrow keys to select, Enter to run (`scene.reset_lighting`, `selection.group`, `selection.focus`, `selection.reset_transform`, play commands) |
|
||||||
| `F7` | While paused in Play: advance one sim tick |
|
| `F7` | While paused in Play: advance one sim tick |
|
||||||
| Shift/Ctrl + click (Hierarchy) | Additive selection |
|
| Shift/Ctrl + click (Hierarchy) | Additive selection |
|
||||||
| Hierarchy context | Reparent to other selection / Unparent |
|
| Hierarchy context | Reparent to other selection / Unparent |
|
||||||
|
|||||||
@ -8,7 +8,7 @@ use crate::state::{EditorMode, PlayPossession};
|
|||||||
use crate::ui::helpers::{
|
use crate::ui::helpers::{
|
||||||
reset_scene_lighting_to_project_defaults, toggle_play_mode, toggle_play_paused,
|
reset_scene_lighting_to_project_defaults, toggle_play_mode, toggle_play_paused,
|
||||||
};
|
};
|
||||||
use crate::ui::selection_ops::focus_editor_camera_on_selection;
|
use crate::ui::selection_ops::{focus_editor_camera_on_selection, reset_selected_transforms};
|
||||||
use crate::ui::theme::{apply_editor_theme, PANEL_BG, TEXT, TEXT_DIM};
|
use crate::ui::theme::{apply_editor_theme, PANEL_BG, TEXT, TEXT_DIM};
|
||||||
use crate::ui::UiState;
|
use crate::ui::UiState;
|
||||||
|
|
||||||
@ -127,6 +127,8 @@ pub struct CommandPalette {
|
|||||||
pub open: bool,
|
pub open: bool,
|
||||||
pub filter: String,
|
pub filter: String,
|
||||||
pub pending_run: Option<String>,
|
pub pending_run: Option<String>,
|
||||||
|
pub focus_filter: bool,
|
||||||
|
pub selected_index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ExtensibilityPlugin;
|
pub struct ExtensibilityPlugin;
|
||||||
@ -160,6 +162,7 @@ fn register_builtin_commands(mut registry: ResMut<EditorCommandRegistry>) {
|
|||||||
registry.register(Box::new(ResetLightingCommand));
|
registry.register(Box::new(ResetLightingCommand));
|
||||||
registry.register(Box::new(GroupSelectionCommand));
|
registry.register(Box::new(GroupSelectionCommand));
|
||||||
registry.register(Box::new(FocusSelectionCommand));
|
registry.register(Box::new(FocusSelectionCommand));
|
||||||
|
registry.register(Box::new(ResetSelectionTransformCommand));
|
||||||
registry.register(Box::new(CreatePostProcessVolumeCommand));
|
registry.register(Box::new(CreatePostProcessVolumeCommand));
|
||||||
registry.register(Box::new(FocusActiveVolumesCommand));
|
registry.register(Box::new(FocusActiveVolumesCommand));
|
||||||
registry.register(Box::new(SelectVolumesAtCameraCommand));
|
registry.register(Box::new(SelectVolumesAtCameraCommand));
|
||||||
@ -272,6 +275,18 @@ impl EditorCommand for FocusSelectionCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ResetSelectionTransformCommand;
|
||||||
|
|
||||||
|
impl EditorCommand for ResetSelectionTransformCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"selection.reset_transform"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(&self, world: &mut World) {
|
||||||
|
reset_selected_transforms(world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct CreatePostProcessVolumeCommand;
|
struct CreatePostProcessVolumeCommand;
|
||||||
|
|
||||||
impl EditorCommand for CreatePostProcessVolumeCommand {
|
impl EditorCommand for CreatePostProcessVolumeCommand {
|
||||||
@ -322,6 +337,8 @@ fn command_palette_ui(
|
|||||||
|
|
||||||
if ctx.input(|input| input.modifiers.command && input.key_pressed(egui::Key::P)) {
|
if ctx.input(|input| input.modifiers.command && input.key_pressed(egui::Key::P)) {
|
||||||
palette.open = true;
|
palette.open = true;
|
||||||
|
palette.focus_filter = true;
|
||||||
|
palette.selected_index = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !palette.open {
|
if !palette.open {
|
||||||
@ -339,25 +356,71 @@ fn command_palette_ui(
|
|||||||
.color(TEXT_DIM)
|
.color(TEXT_DIM)
|
||||||
.small(),
|
.small(),
|
||||||
);
|
);
|
||||||
ui.add(
|
let filter_response = ui.add(
|
||||||
egui::TextEdit::singleline(&mut palette.filter)
|
egui::TextEdit::singleline(&mut palette.filter)
|
||||||
.hint_text("Type to filter...")
|
.hint_text("Type to filter...")
|
||||||
.desired_width(f32::INFINITY),
|
.desired_width(f32::INFINITY),
|
||||||
);
|
);
|
||||||
|
if palette.focus_filter {
|
||||||
|
filter_response.request_focus();
|
||||||
|
palette.focus_filter = false;
|
||||||
|
}
|
||||||
|
if filter_response.changed() {
|
||||||
|
palette.selected_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let filter = palette.filter.to_lowercase();
|
||||||
|
let filtered_names: Vec<String> = registry
|
||||||
|
.names()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|name| filter.is_empty() || name.to_lowercase().contains(&filter))
|
||||||
|
.collect();
|
||||||
|
if !filtered_names.is_empty() {
|
||||||
|
palette.selected_index = palette.selected_index.min(filtered_names.len() - 1);
|
||||||
|
} else {
|
||||||
|
palette.selected_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.input_mut(|input| input.consume_key(egui::Modifiers::NONE, egui::Key::ArrowDown))
|
||||||
|
&& !filtered_names.is_empty()
|
||||||
|
{
|
||||||
|
palette.selected_index = (palette.selected_index + 1) % filtered_names.len();
|
||||||
|
}
|
||||||
|
if ui.input_mut(|input| input.consume_key(egui::Modifiers::NONE, egui::Key::ArrowUp))
|
||||||
|
&& !filtered_names.is_empty()
|
||||||
|
{
|
||||||
|
palette.selected_index = if palette.selected_index == 0 {
|
||||||
|
filtered_names.len() - 1
|
||||||
|
} else {
|
||||||
|
palette.selected_index - 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ui.input_mut(|input| input.consume_key(egui::Modifiers::NONE, egui::Key::Enter)) {
|
||||||
|
if let Some(name) = filtered_names.get(palette.selected_index) {
|
||||||
|
palette.pending_run = Some(name.clone());
|
||||||
|
palette.open = false;
|
||||||
|
ui.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ui.input_mut(|input| input.consume_key(egui::Modifiers::NONE, egui::Key::Escape)) {
|
||||||
|
palette.open = false;
|
||||||
|
ui.close();
|
||||||
|
}
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
egui::ScrollArea::vertical()
|
egui::ScrollArea::vertical()
|
||||||
.max_height(280.0)
|
.max_height(280.0)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
for name in registry.names() {
|
for (index, name) in filtered_names.iter().enumerate() {
|
||||||
if !palette.filter.is_empty()
|
let selected = index == palette.selected_index;
|
||||||
&& !name.to_lowercase().contains(&palette.filter.to_lowercase())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let response =
|
let response =
|
||||||
ui.selectable_label(false, egui::RichText::new(&name).color(TEXT));
|
ui.selectable_label(selected, egui::RichText::new(name).color(TEXT));
|
||||||
|
if selected {
|
||||||
|
response.scroll_to_me(Some(egui::Align::Center));
|
||||||
|
}
|
||||||
if response.clicked() {
|
if response.clicked() {
|
||||||
palette.pending_run = Some(name);
|
palette.selected_index = index;
|
||||||
|
palette.pending_run = Some(name.clone());
|
||||||
palette.open = false;
|
palette.open = false;
|
||||||
ui.close();
|
ui.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -637,9 +637,42 @@ pub fn set_transform_with_history(
|
|||||||
if transform_nearly_eq(&old, &new) {
|
if transform_nearly_eq(&old, &new) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if let Some(mut transform) = world.get_mut::<Transform>(entity) {
|
||||||
|
*transform = new;
|
||||||
|
}
|
||||||
push_history(world, EditorCommand::SetTransform { entity, old, new });
|
push_history(world, EditorCommand::SetTransform { entity, old, new });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_transform_group_with_history(
|
||||||
|
world: &mut World,
|
||||||
|
changes: impl IntoIterator<Item = (Entity, Transform, Transform)>,
|
||||||
|
) {
|
||||||
|
let mut entities = Vec::new();
|
||||||
|
let mut olds = Vec::new();
|
||||||
|
let mut news = Vec::new();
|
||||||
|
for (entity, old, new) in changes {
|
||||||
|
if transform_nearly_eq(&old, &new) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(mut transform) = world.get_mut::<Transform>(entity) {
|
||||||
|
*transform = new;
|
||||||
|
entities.push(entity);
|
||||||
|
olds.push(old);
|
||||||
|
news.push(new);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !entities.is_empty() {
|
||||||
|
push_history(
|
||||||
|
world,
|
||||||
|
EditorCommand::SetTransformGroup {
|
||||||
|
entities,
|
||||||
|
olds,
|
||||||
|
news,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_actor_kind_with_history(
|
pub fn set_actor_kind_with_history(
|
||||||
world: &mut World,
|
world: &mut World,
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
@ -1219,7 +1252,7 @@ fn capture_gizmo_transform_edits(world: &mut World) {
|
|||||||
let start_group =
|
let start_group =
|
||||||
active.map(|(entity, transform)| selected_transforms(world, &selection, entity, transform));
|
active.map(|(entity, transform)| selected_transforms(world, &selection, entity, transform));
|
||||||
|
|
||||||
let finished_edit = {
|
let (finished_edit, live_edit) = {
|
||||||
let mut tracker = world.resource_mut::<TransformEditTracker>();
|
let mut tracker = world.resource_mut::<TransformEditTracker>();
|
||||||
if tracker.active.is_none() {
|
if tracker.active.is_none() {
|
||||||
if let (Some((entity, transform)), Some(group)) = (active, start_group) {
|
if let (Some((entity, transform)), Some(group)) = (active, start_group) {
|
||||||
@ -1228,15 +1261,26 @@ fn capture_gizmo_transform_edits(world: &mut World) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
match tracker.active.take() {
|
match tracker.active.take() {
|
||||||
Some((primary, old, group)) if active.is_none() => Some((primary, old, group)),
|
Some((primary, old, group)) if active.is_none() => (Some((primary, old, group)), None),
|
||||||
Some(state) if active.is_some() => {
|
Some(state) if active.is_some() => {
|
||||||
|
let live = Some(state.clone());
|
||||||
tracker.active = Some(state);
|
tracker.active = Some(state);
|
||||||
None
|
(None, live)
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => (None, None),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some((primary, old_primary, group)) = live_edit {
|
||||||
|
let Some(new_primary) = world.get::<Transform>(primary).copied() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if group.len() > 1 {
|
||||||
|
let changes = transform_group_changes(old_primary, new_primary, group);
|
||||||
|
apply_transform_group(world, &changes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some((primary, old_primary, group)) = finished_edit {
|
if let Some((primary, old_primary, group)) = finished_edit {
|
||||||
let Some(new_primary) = world.get::<Transform>(primary).copied() else {
|
let Some(new_primary) = world.get::<Transform>(primary).copied() else {
|
||||||
return;
|
return;
|
||||||
@ -1245,34 +1289,36 @@ fn capture_gizmo_transform_edits(world: &mut World) {
|
|||||||
set_transform_with_history(world, primary, old_primary, new_primary);
|
set_transform_with_history(world, primary, old_primary, new_primary);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let changes = transform_group_changes(old_primary, new_primary, group);
|
||||||
|
set_transform_group_with_history(world, changes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_group_changes(
|
||||||
|
old_primary: Transform,
|
||||||
|
new_primary: Transform,
|
||||||
|
group: Vec<(Entity, Transform)>,
|
||||||
|
) -> Vec<(Entity, Transform, Transform)> {
|
||||||
let translation_delta = new_primary.translation - old_primary.translation;
|
let translation_delta = new_primary.translation - old_primary.translation;
|
||||||
let rotation_delta = new_primary.rotation * old_primary.rotation.inverse();
|
let rotation_delta = new_primary.rotation * old_primary.rotation.inverse();
|
||||||
let scale_ratio = new_primary.scale / old_primary.scale;
|
let scale_ratio = new_primary.scale / old_primary.scale;
|
||||||
let mut entities = Vec::new();
|
group
|
||||||
let mut olds = Vec::new();
|
.into_iter()
|
||||||
let mut news = Vec::new();
|
.map(|(entity, old)| {
|
||||||
for (entity, old) in group {
|
|
||||||
let new = Transform {
|
let new = Transform {
|
||||||
translation: old.translation + translation_delta,
|
translation: old.translation + translation_delta,
|
||||||
rotation: rotation_delta * old.rotation,
|
rotation: rotation_delta * old.rotation,
|
||||||
scale: old.scale * scale_ratio,
|
scale: old.scale * scale_ratio,
|
||||||
};
|
};
|
||||||
if let Some(mut transform) = world.get_mut::<Transform>(entity) {
|
(entity, old, new)
|
||||||
*transform = new;
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
entities.push(entity);
|
|
||||||
olds.push(old);
|
fn apply_transform_group(world: &mut World, changes: &[(Entity, Transform, Transform)]) {
|
||||||
news.push(new);
|
for (entity, _, new) in changes {
|
||||||
}
|
if let Some(mut transform) = world.get_mut::<Transform>(*entity) {
|
||||||
if !entities.is_empty() {
|
*transform = *new;
|
||||||
push_history(
|
|
||||||
world,
|
|
||||||
EditorCommand::SetTransformGroup {
|
|
||||||
entities,
|
|
||||||
olds,
|
|
||||||
news,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,8 +5,11 @@ use bevy_inspector_egui::bevy_inspector::hierarchy::SelectedEntities;
|
|||||||
use shared::LevelObject;
|
use shared::LevelObject;
|
||||||
|
|
||||||
use crate::camera::EditorCamera;
|
use crate::camera::EditorCamera;
|
||||||
use crate::history::{delete_entities_with_history, duplicate_entities_with_history};
|
use crate::history::{
|
||||||
|
delete_entities_with_history, duplicate_entities_with_history, set_transform_group_with_history,
|
||||||
|
};
|
||||||
use crate::selection::SelectedEntity;
|
use crate::selection::SelectedEntity;
|
||||||
|
use crate::ui::UiState;
|
||||||
|
|
||||||
pub fn focus_editor_camera_on_selection(world: &mut World) {
|
pub fn focus_editor_camera_on_selection(world: &mut World) {
|
||||||
let Some(entity) = world.resource::<SelectedEntity>().0 else {
|
let Some(entity) = world.resource::<SelectedEntity>().0 else {
|
||||||
@ -58,3 +61,19 @@ pub fn duplicate_selection(world: &mut World, selected: &SelectedEntities) {
|
|||||||
let entities = selected_level_entities(world, selected);
|
let entities = selected_level_entities(world, selected);
|
||||||
duplicate_entities_with_history(world, &entities);
|
duplicate_entities_with_history(world, &entities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reset_selected_transforms(world: &mut World) {
|
||||||
|
let changes: Vec<_> = world
|
||||||
|
.resource::<UiState>()
|
||||||
|
.selected_entities
|
||||||
|
.iter()
|
||||||
|
.filter(|entity| is_level_object(world, *entity))
|
||||||
|
.filter_map(|entity| {
|
||||||
|
world
|
||||||
|
.get::<Transform>(entity)
|
||||||
|
.copied()
|
||||||
|
.map(|old| (entity, old, Transform::default()))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
set_transform_group_with_history(world, changes);
|
||||||
|
}
|
||||||
|
|||||||
@ -34,6 +34,7 @@ Immutable-style log of significant decisions. Add a new numbered ADR when changi
|
|||||||
| [0017](adr/0017-normalized-static-mesh-assets.md) | Normalized static mesh assets and renderer placement |
|
| [0017](adr/0017-normalized-static-mesh-assets.md) | Normalized static mesh assets and renderer placement |
|
||||||
| [0018](adr/0018-componentized-actor-inspector-and-materials.md) | Componentized actor inspector, imported asset refs, collider split, material overrides |
|
| [0018](adr/0018-componentized-actor-inspector-and-materials.md) | Componentized actor inspector, imported asset refs, collider split, material overrides |
|
||||||
| [0019](adr/0019-local-bevy-render-timeout-patch.md) | Local `bevy_render` patch for transient Linux swapchain timeouts |
|
| [0019](adr/0019-local-bevy-render-timeout-patch.md) | Local `bevy_render` patch for transient Linux swapchain timeouts |
|
||||||
|
| [0020](adr/0020-jackdaw-inspired-editor-roadmap.md) | Jackdaw-inspired roadmap source policy and attribution requirements |
|
||||||
|
|
||||||
## Editor framework
|
## Editor framework
|
||||||
|
|
||||||
@ -58,6 +59,7 @@ Detailed milestone and feature plans live in [`.cursor/plans/`](../.cursor/plans
|
|||||||
| `rendering_unification_*.plan.md` | Unified rendering stack, Hybrid Auto GI, emissive materials |
|
| `rendering_unification_*.plan.md` | Unified rendering stack, Hybrid Auto GI, emissive materials |
|
||||||
| `static_mesh_asset_refactor_*.plan.md` | Static mesh renderer, normalized model artifacts, inspector redesign |
|
| `static_mesh_asset_refactor_*.plan.md` | Static mesh renderer, normalized model artifacts, inspector redesign |
|
||||||
| `component_system_refactor_*.plan.md` | Component registry, imported asset refs, collider split, material overrides |
|
| `component_system_refactor_*.plan.md` | Component registry, imported asset refs, collider split, material overrides |
|
||||||
|
| `jackdaw_feature_roadmap_*.plan.md` | Jackdaw-inspired production roadmap; tracked in Gitea as `BS-JD-*` issues |
|
||||||
|
|
||||||
## Crate responsibilities (quick reference)
|
## Crate responsibilities (quick reference)
|
||||||
|
|
||||||
|
|||||||
49
docs/adr/0020-jackdaw-inspired-editor-roadmap.md
Normal file
49
docs/adr/0020-jackdaw-inspired-editor-roadmap.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
Status: Accepted
|
||||||
|
|
||||||
|
Context
|
||||||
|
=======
|
||||||
|
|
||||||
|
Blacksite is evolving from a game-specific editor scaffold into a reusable
|
||||||
|
Bevy editor sandbox for future game work. The Jackdaw editor provides useful
|
||||||
|
public reference material for editor workflows such as brush geometry, material
|
||||||
|
browser organization, terrain tools, physics placement, project launchers, and
|
||||||
|
extension boundaries.
|
||||||
|
|
||||||
|
The roadmap should use those references as product and workflow inspiration
|
||||||
|
without accidentally turning Blacksite into a fork, importing code without
|
||||||
|
attribution, or overriding Blacksite's existing authoring/hydration model.
|
||||||
|
|
||||||
|
Decision
|
||||||
|
========
|
||||||
|
|
||||||
|
Treat Jackdaw as an inspiration source, not as an architecture or format
|
||||||
|
authority. Blacksite keeps its current foundations: Bevy DynamicScene RON,
|
||||||
|
schema stamping and migration, authoring-only validation, hydration into runtime
|
||||||
|
components, in-process PIE, and the unified viewport.
|
||||||
|
|
||||||
|
Each Jackdaw-inspired ticket must identify whether the work is:
|
||||||
|
|
||||||
|
- Design-inspired: adapted from public behavior, screenshots, docs, or workflow
|
||||||
|
descriptions.
|
||||||
|
- API-inspired: adapted from public API shape or concepts without copied code.
|
||||||
|
- Code-derived: containing copied or closely translated source code.
|
||||||
|
|
||||||
|
Code-derived work requires a source URL, upstream commit or release reference,
|
||||||
|
compatible license confirmation, and preserved file-level notices when required
|
||||||
|
by the source license. Prefer clean-room implementation for geometry, terrain,
|
||||||
|
operators, and extension APIs because Blacksite's scene and runtime ownership
|
||||||
|
model differs from Jackdaw's.
|
||||||
|
|
||||||
|
Consequences
|
||||||
|
============
|
||||||
|
|
||||||
|
The Jackdaw roadmap can be used for planning and prioritization while keeping
|
||||||
|
Blacksite's architecture coherent and auditable.
|
||||||
|
|
||||||
|
M0-M5 planning details live in `.cursor/plans/` until implemented. Durable
|
||||||
|
decisions move into `docs/adr/` or `docs/editor/`; user-visible workflows move
|
||||||
|
into the root README. The Gitea issue tracker is the production task board, but
|
||||||
|
docs remain the long-term source for accepted policy and architecture.
|
||||||
|
|
||||||
|
Reviewers should reject Jackdaw-inspired changes that do not declare their
|
||||||
|
inspiration level or that copy source without license and source attribution.
|
||||||
@ -34,6 +34,7 @@ Docs for the in-process egui editor (`crates/editor/`). Update this index when a
|
|||||||
## Design intentions (stable)
|
## Design intentions (stable)
|
||||||
|
|
||||||
- **EditorOnly** helper entities never appear in hierarchy or saved scenes. Visualizer proxies are editor-only but selectable; selection resolves to their source entity.
|
- **EditorOnly** helper entities never appear in hierarchy or saved scenes. Visualizer proxies are editor-only but selectable; selection resolves to their source entity.
|
||||||
|
- **Jackdaw-inspired roadmap work** follows ADR 0020: use Jackdaw as design/API inspiration only unless a ticket explicitly marks code-derived work with source, license, and upstream commit notes. Clean-room implementation is preferred for geometry, terrain, operator, and extension systems.
|
||||||
- **Dedicated egui `Camera2d`** at full window — never attach `PrimaryEguiContext` to a viewport-cropped 3D camera (NaN layout panic).
|
- **Dedicated egui `Camera2d`** at full window — never attach `PrimaryEguiContext` to a viewport-cropped 3D camera (NaN layout panic).
|
||||||
- **Unified viewport** uses one render-to-texture target for both the editor fly camera and the possessed player camera so HDR/atmosphere is not broken by sub-viewport cropping.
|
- **Unified viewport** uses one render-to-texture target for both the editor fly camera and the possessed player camera so HDR/atmosphere is not broken by sub-viewport cropping.
|
||||||
- **PIE:** F8 possess/eject while sim runs; **F6** pauses/resumes simulation in Play; project settings drive shared rendering for the active viewport camera.
|
- **PIE:** F8 possess/eject while sim runs; **F6** pauses/resumes simulation in Play; project settings drive shared rendering for the active viewport camera.
|
||||||
@ -48,7 +49,7 @@ Docs for the in-process egui editor (`crates/editor/`). Update this index when a
|
|||||||
- **World lighting** has two layers: project default sun/ambient settings, and optional scene-authored directional `LightDesc` overrides. **Scene → Lighting** menu and **Rendering** panel (Window) restore project sun or bake a scene directional from project settings. **Apply** in Project Settings updates ambient and sun illuminance live.
|
- **World lighting** has two layers: project default sun/ambient settings, and optional scene-authored directional `LightDesc` overrides. **Scene → Lighting** menu and **Rendering** panel (Window) restore project sun or bake a scene directional from project settings. **Apply** in Project Settings updates ambient and sun illuminance live.
|
||||||
- **Rendering** — `GiMode` + post-process volumes; see [rendering.md](rendering.md). **Window → Rendering** shows requested/effective active-camera stack, fallback reason, Solari eligibility, viewport GI badge, and volume HUD.
|
- **Rendering** — `GiMode` + post-process volumes; see [rendering.md](rendering.md). **Window → Rendering** shows requested/effective active-camera stack, fallback reason, Solari eligibility, viewport GI badge, and volume HUD.
|
||||||
- **Inspector** shows a responsive header, **Transform**, registered authoring component cards, and a pinned bottom Add Component footer (`actor_inspector`); only the component body scrolls. Rows wrap or stack inside narrow docks instead of forcing the panel wider, and legacy authoring sections use the same component-card shell. Component card carets collapse/expand for the current editor session, status dots toggle persisted active state, and triple-dot menus provide reset, copy/paste values, move, remove, and documentation slots. Inactive authoring components remain in the scene but are skipped by hydration/runtime queries, stripping their generated meshes, lights, physics, or post-process effects. `StaticMeshRenderer` uses concept-style slot cards with model thumbnails, imported mesh/material selectors, visibility, shadow flags, and per-actor material overrides. Empty actor material refs display the inherited source material from the generated static mesh artifact as **Source default**; selector **Browse** assigns explicit normalized imported sub-assets, **Locate** focuses the owning asset in the Asset Browser, and **Clear** returns optional material slots to the inherited source default. `ColliderDesc` and `RigidBodyDesc` are separate component cards. Game crates add sections via `ActorInspectorSection` + `game::editor_ext::actor_inspector_section_ids()`.
|
- **Inspector** shows a responsive header, **Transform**, registered authoring component cards, and a pinned bottom Add Component footer (`actor_inspector`); only the component body scrolls. Rows wrap or stack inside narrow docks instead of forcing the panel wider, and legacy authoring sections use the same component-card shell. Component card carets collapse/expand for the current editor session, status dots toggle persisted active state, and triple-dot menus provide reset, copy/paste values, move, remove, and documentation slots. Inactive authoring components remain in the scene but are skipped by hydration/runtime queries, stripping their generated meshes, lights, physics, or post-process effects. `StaticMeshRenderer` uses concept-style slot cards with model thumbnails, imported mesh/material selectors, visibility, shadow flags, and per-actor material overrides. Empty actor material refs display the inherited source material from the generated static mesh artifact as **Source default**; selector **Browse** assigns explicit normalized imported sub-assets, **Locate** focuses the owning asset in the Asset Browser, and **Clear** returns optional material slots to the inherited source default. `ColliderDesc` and `RigidBodyDesc` are separate component cards. Game crates add sections via `ActorInspectorSection` + `game::editor_ext::actor_inspector_section_ids()`.
|
||||||
- **Command palette** (`Ctrl+P`): `scene.reset_lighting`, `rendering.create_volume`, `rendering.select_volumes_at_camera`, `rendering.focus_active_volumes`, `selection.group`, `selection.focus`, and play commands.
|
- **Command palette** (`Ctrl+P`): focuses the filter on open, selects the first filtered command, supports arrow-key selection and Enter to run. Commands include `scene.reset_lighting`, `rendering.create_volume`, `rendering.select_volumes_at_camera`, `rendering.focus_active_volumes`, `selection.group`, `selection.focus`, `selection.reset_transform`, and play commands.
|
||||||
- **ActorKind** is required on saved level objects (schema v2). Save strips hydrated ECS before writing `.scn.ron`.
|
- **ActorKind** is required on saved level objects (schema v2). Save strips hydrated ECS before writing `.scn.ron`.
|
||||||
- **Scene visualizers** expose actor root icons, colliders, lights, player spawns, prefab/model anchors, Project Sun, and runtime player/camera markers without changing saved scene data. Viewport options include actor icon size and transform gizmo size sliders.
|
- **Scene visualizers** expose actor root icons, colliders, lights, player spawns, prefab/model anchors, Project Sun, and runtime player/camera markers without changing saved scene data. Viewport options include actor icon size and transform gizmo size sliders.
|
||||||
- **Clean game-view overlay** (`G`) hides editor chrome, grid, selection outlines, transform gizmos, actor root icons, visualizers, and selectable proxies without changing camera ownership; `Ctrl+G` toggles grid.
|
- **Clean game-view overlay** (`G`) hides editor chrome, grid, selection outlines, transform gizmos, actor root icons, visualizers, and selectable proxies without changing camera ownership; `Ctrl+G` toggles grid.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user