rusting_engine 2.0.2

Vulkan 3D game engine with GPU-accelerated physics for massive physics-heavy scenes
# Editor overhaul

The owner asked for a large design and feature update of the egui editor,
aiming at a real-engine editor. The visual and interaction direction is
Blender + Godot. The work is split into waves; each wave ships working,
tested code before the next begins.

## Wave 1 (in progress): visual pass, inspector, hierarchy

### Visual design pass

- New `EditorTheme` palette: Godot-style dark neutral panels with one accent
  color, Blender-style compact spacing and area headers.
- Every dock area gets a header strip with an editor-type switcher (the
  `dock.rs` area tree stays the layout model).
- One toolbar style and one icon set across all panels.
- Theme and UI scale persist per user (roadmap Milestone 6, "DPI scaling,
  font configuration, and theme persistence").

### Inspector (property-row system)

- Module `src/editor/inspector/`:
  - `widgets.rs`: Godot-style property rows (label left, widget right) and
    shared widgets: vec3 with colored X/Y/Z fields, color, angle in degrees,
    enum, asset handle.
  - `json.rs`: custom (game-defined) components are stored as JSON. Their
    values render as typed widgets: numbers as drag values, bools as
    checkboxes, 3-number arrays as vec3, strings as text, nested objects as
    sub-sections. Raw JSON text editing is removed from the normal path.
  - `mod.rs`: the Inspector panel; built-in components are drawn as
    collapsible sections using the shared widgets.
- Every edit still flows through the snapshot undo system.

### Hierarchy

- Search/filter box.
- Per-type icons (camera, light, mesh, empty).
- Blender-style visibility eye toggle.
- Double-click inline rename.
- Drag and drop reparenting (roadmap Milestone 6).
- Right-click context menu: add child, duplicate, rename, delete.
- Multi-select (Ctrl toggles, Shift selects a range). The selection has a
  primary entity; the gizmo and Inspector act on the primary, delete and
  duplicate act on the whole selection.

### Wave 1 progress

Done (unit tests in `editor::hierarchy`, `editor::inspector::{widgets, json}`):

- Palette, compact density, flat list rows, area chrome.
- Hierarchy: search that keeps ancestors of matches, per-type icons, eye
  toggle (`EntityRequest::SetVisible`, undoable), double-click / F2 rename,
  Delete deletes the selection, dragging a selected row reparents the
  whole selection, multi-select with Ctrl and Shift and a
  primary object (`EditorState::selection` + `selected`), delete and
  duplicate act on the whole selection.
- Inspector module with property rows, axis-colored vec3, color, angle,
  choice, and Godot-style collapsible sections; custom JSON components are
  edited as typed fields live, and drags coalesce into one Undo step.

- UI scale and text size persist per user (`EditorPreferences`, View >
  UI Scale / Text Size). There is one palette, so no theme choice is stored
  yet. Widgets with a hard-coded `FontId` ignore the text size.
- Editor-type icon in each area header.
- Hiding a parent hides its children in rendering.
- Viewport shows bounds for every selected object (primary in yellow, others
  in orange); Shift-click in the viewport adds, activates, or deselects
  Blender style.
- Hierarchy rows hidden by themselves or an ancestor are dimmed.
- Scene View mouse navigation: middle drag orbits around a pivot in front of
  the camera, Shift + middle drag pans, the wheel dollies toward the pivot,
  and F (focus) moves the pivot onto the selected object. Right drag still
  flies.
- Project panel "Scene Rendering" section: Quality (Auto, Eco, Balanced,
  High) and Culling (Auto, Off, Frustum, Frustum + Occlusion). Both are
  saved in the scene file (format 6) and load with the scene in the
  exported game. Edits are undoable scene changes (unit test
  `scene_render_settings_edits_are_undoable_scene_changes`).
- Inspector "Material" section for the selected Mesh Renderer: model, alpha
  mode and cutoff, base color and opacity, metallic, roughness, emissive,
  and the five texture slots. Each slot picks a loaded texture or loads an
  image file (copied into the project `assets` folder). "New Material"
  gives the object its own default material. Editing a material that other
  renderers share copies it first. Edits are undoable (unit test
  `material_edits_copy_shared_materials_and_edit_owned_ones_in_place`).
- Native file dialogs run on a worker thread (`editor::file_dialogs`), so
  the window keeps answering the window manager and is not reported as "not
  responding". While a dialog is open, a small modal blocks the panels.
- Cameras and lights draw Blender-style wire shapes in the Scene View
  (camera frustum with an up triangle, sun with rays and a direction line,
  point light circles, spot cone) in the selection colors. Clicking within
  6 points of a shape selects the object (unit test
  `clicks_near_light_and_camera_shapes_select_them`).
- glTF models are added to the scene like Blender's importer: one new empty
  object named after the file holds the file's node tree (names, local
  transforms, meshes, materials, cameras, lights). "Add to Scene" in the
  Assets panel and "Import Files..." both do this, select the new root, and
  are undoable. The old "Imported glTF primitives / Use on Selected" list is
  gone. Primitives without normals get flat face normals, as glTF requires
  (unit test `added_models_keep_their_node_tree_under_one_saved_root`).
- Assets area is a Godot-style FileSystem tree (`editor::assets_panel`):
  folders first and foldable, type icons, colored type badges, a filter
  that opens folders on the way to matches, and hidden `.rmesh`/`.rtexture`
  caches. Double-click adds a model to the scene or puts an image on the
  selected object as its base color; right-click lists the actions; models
  drag into the Scene View. The "Loaded textures" text list is gone (unit
  test `rows_put_folders_first_hide_caches_and_fold`).
- Image rows show a thumbnail (larger on hover) and drag onto a Hierarchy
  object (base color) or an Inspector texture slot (unit tests
  `image_rows_decode_a_few_thumbnails_per_frame`,
  `dropping_an_image_on_hierarchy_rows_and_texture_slots_assigns_it`).

Open:

- Toolbar consistency pass.
- egui's built-in Ctrl +/- zoom is not persisted; only View > UI Scale is.
- Asset handle fields show the raw key; show the asset path and accept drops
  once the asset browser exists.

## Backlog (later waves)

Kept here so it is not lost between sessions. Roadmap milestones that own an
item are named in parentheses.

### Scene editing UX

- Numpad views: no auto-orthographic on a numpad view (Blender's
  auto-perspective), and the orthographic state is not saved with the editor layout. An orthographic
  view clips objects behind the camera; pull the camera back when zoomed in
  close. Home frames only objects with a mesh.
- Selection outline is a wire box of the bounds, not a pixel silhouette
  of the mesh (M6).
- Camera and light shapes: the camera frame is fixed at 16:9, and there
  are no per-type icons.
- Gizmo polish: visual restyle; snap increments are saved only with
  Save Layout (`editor_layout.json`, `snap` field), not automatically (M6).
- Multiple viewports and orthographic views (M20).
- Game components the editor has no registration for (for example a
  game's `coin_run.spin`) load as `UnregisteredComponents`, save back
  unchanged, and show in the Inspector as read-only JSON that can be
  removed. Editing them needs JSON validated on the next game load.
- Scene instances (prefabs): changes to placed objects save as overrides,
  but nothing shows which fields are overridden. The Inspector should mark
  overridden fields with a per-field revert arrow, as Godot does (only
  whole-object and whole-instance revert exist). Unpack is always complete;
  a one-level unpack that keeps nested instances linked is missing, and
  Undo does not restore a source file written by Apply to Source. The
  Hierarchy should mark instance roots and tint their children,
  editing `source` in the Inspector should re-read the scene at once, and
  scenes should also be draggable into the Scene View like models.
- Reflected Inspector gaps: a string-keyed map (such as `TileMap.tiles`)
  adds entries by key and removes them, but renaming a key is a remove and
  an add; the asset drop-down
  for handle fields lists only assets that are already loaded, with no file
  browser or drag from the Assets panel.

### Asset workflow

- Asset browser: a grid view, thumbnails decoded on a worker and refreshed
  when the file changes, and dropped models placed under the cursor instead
  of at the origin (M6).
- glTF import: vertex colors (`COLOR_0`), `KHR_texture_transform`, and
  import options (scale, up axis, "apply transforms") are not read.
  Re-adding a file imports its meshes again instead of sharing them.
- Typed asset assignment follow-up: standalone reusable collider shape
  assets do not exist yet. Mesh colliders use the renderer's typed mesh
  handle, while primitive collider shapes are typed enum variants.
- Per-asset import settings and re-import (M20). The library and CLI side
  exists (`crate::asset_import`, `rusting asset import|reimport|list`,
  `<file>.rmeta` sidecars with ID, settings, dependencies and provenance).
  The asset browser has a Replace… action with a dry-run preview and hides
  `.rmeta` sidecars. It still needs an import dialog with provenance fields
  (its Import button copies files without a sidecar), license/changed/
  not-imported badges, and a Generate dialog that runs a `project.json`
  generator hook with a prompt (`rusting asset generate` covers it today).
- Art-direction presets: `rusting preset apply` has no editor picker yet.
  Presets should skip ACES tone mapping and keep HUD size hierarchy on 2D
  unlit scenes; the starter exercise corrects both by patch today.
- Image textures always load as sRGB, both from the Inspector and from
  scene files. Normal, metal/rough, and occlusion maps need a linear load
  path, and the scene file needs to store the color space per slot.
- Material assets as files (`.rmaterial`) that several scenes share. Today
  every scene stores its materials inline.
- Data assets: Inspector edits to a `.rdata` file are file writes with no
  undo. The Handle field picker lists only data assets that are already
  loaded, so it cannot pick an unloaded `.rdata` file. Renamed or removed
  fields fail to load instead of migrating. A unique value cannot be
  saved out as a new `.rdata` file yet (Godot's Save As on a sub-resource),
  and every scene load or Inspector edit of a unique value adds a copy
  while the old one stays in memory. A `.rdata` file open in the Inspector
  does not re-read changes made outside the editor; the next edit
  overwrites them.
- LOD group authoring: edit a mesh's `.rlod` levels and hand-over values in
  the Inspector, preview the active level in the viewport, and hot reload
  `.rlod` files. Import glTF `MSFT_lod` into LOD groups.

### Panels and tools

- Console: engine code (renderer, physics, runtime) has no log sink, so
  only editor messages reach it. Status lines get their level from
  keywords; give `scene_message` a level instead.
- Profiler follow-up: add per-system CPU spans, capture/export, and dedicated
  GPU allocation accounting to the M6 dockable history panel (M20).
- Render settings and physics diagnostics follow-up: expose more optional
  device capabilities and explain capacity warnings inline. The M6 panels
  already show `RenderCapacityDiagnostics`, including missing asset counts.
- Shortcut follow-ups: each action has at most two keys; make modal keys
  (Escape cancels a gizmo drag) rebindable.
- Global scaling at arbitrary object angles is axis weighted because scene
  transforms store rotation and scale without shear; support exact global
  scaling if the scene transform format gains a shear component.
- Project settings: the Project panel is a card page built from
  `gui_elements::kit` (banner, category rail, setting rows, switches,
  segmented controls, stat tiles). Follow-ups: move the Inspector, Render
  Settings and Physics Diagnostics panels onto the same kit, add physics and
  input map categories (M20), and wire `render_scale` and `background_color`
  into the page.
- Project-wide search (M20).
- 2D path: the Inspector's Tile Painter paints, draws lines, fills
  rectangles and flood fills the selected tile map in the Scene View, and
  outlines the target cells. Flood fill stays inside the grid's used area.
  The brush palette shows each tile's color, and D, R, L and B pick the
  tools while a brush is active. Follow-up: texture thumbnails in the
  palette.

### Play workflow

- Native Stop/Restart and embedded Preview with Pause/Step are implemented
  in M6. Preview runs the editor's ECS systems and restores the authored
  scene, undo history, and transient entities on Stop.
- Reload Code: reload automatically when a project `.rs` file is saved.
  Keep resources across a reload (they need a reflection registration
  first). A reload restarts the game process, so the window and GPU
  resources are created again; the Windows path, where a running game's
  executable is locked, has not been tried.
- Remote scene tree and inspector for a running game (M20).
- Show `RuntimeUi` in the embedded Preview. Update systems already draw
  into the App's `RuntimeUi` pass while Preview runs, but the Game area does
  not paint that output or forward pointer and key input to it. Native Play
  shows it because the game window runner paints it.

### Rendering integration

- Smoke-test editor window resize and close-button shutdown by hand after
  the `EditorWindowRunner::draw` split; an 8-second launch passed (M1).
- Engine-owned egui painter is done (`rendering::egui_painter`). Texture
  uploads wait on their own submission; replace with a staging ring if the
  stall shows up in profiles.
- Golden-image test for editor compositing (M6 exit gate).
- Profiler area with per-pass GPU timestamps and CPU/GPU history is done.
  Add per-pass history selection and frame capture/export (M20).
- Anti-aliasing and shadow quality in Render Settings are session
  options. Save them in project settings (or `SceneRenderSettings`, which
  needs cooked scene format 9 with a legacy struct for format 8).
- Atmosphere (fog, bloom, ambient occlusion): the Scene view's viewport
  menu switches each effect off for editing, and a selected Fog draws its
  full-density and 1/e heights as squares over the grid. Dragging a square
  sets `height` or `height_falloff`, with one Undo step. Fog is
  applied twice where refraction or SSR samples an already fogged scene
  color; subtract it when that shows.
- The depth prepass for ambient occlusion misses objects that come into
  view on an occlusion-culling frame, for one frame. Redraw the late
  batches into the prepass if the flicker shows.
- Render Bounds and reflection probe handles edit only the active object
  in a multi-selection. A dragged handle jumps by up to the pick radius
  (it snaps to the pointer, not the grab offset).

### Deferred from the `issues.md` audit

Items the audit found but left open, with the reason for each.

- Partial instance-buffer uploads. They need one instance buffer per frame
  in flight plus dirty tracking. The unused `dirty_ranges` code was removed.
- Reuse scratch `Vec`s in extraction, and avoid cloning blended instances
  before sorting. Minor cost.
- Sweep file-backed meshes and textures after a scene `Replace` load. Ten
  call sites load with `Replace`, and game code can hold path-loaded handles
  without `retain`, so an automatic sweep could free live assets. It needs
  handle ownership first.
- Make `Assets::len()` O(1) and move the hot-reload file scan to a worker.
  Both scans are small today.
- Remove the legacy engine's double fence wait. Removing it creates a
  write-after-read hazard on the indirect and physics buffers against the
  previous frame's draw. Check on real hardware with validation layers.
- Rewrite the legacy camera sign convention and remove `camera_rotate`. Needs
  visual checks. Only the `cgmath` removal was done.
- Replace the remaining `unwrap` calls in the legacy engine with errors.
- Enable device features at device creation instead of assuming them.
- Cache the Hierarchy tree and rebuild it only on change detection.
- Check the resource-state hazard with validation layers on real hardware.
- A reflection probe's face handles resize its box symmetrically. Add
  Ctrl-drag to move one face only (it shifts the probe's position too).
- Use the GPU pose in the editor during Play. Bodies with
  `PhysicsSyncMode::SelectedState` or `FullState` get a `GpuStateMirror`,
  but picking, focus, the gizmo, and Save still read the authored
  `Transform`. Picking and focus should prefer the mirror when present; a
  "Keep simulated pose" action should copy it into `Transform` through undo.

### Outside scene edits

- The editor reloads a clean scene when `rusting scene patch` or another
  tool changes its file, and refuses one Save when it has unsaved edits.
  Add a conflict dialog that shows the patch diff and offers keep mine,
  take theirs, or a per-field merge. The watch compares the file time
  first; switch to a file watcher if coarse clocks miss changes.
- Agent panel (roadmap Milestone L4): a Blender-style area that lists the
  operation journal of agent edits, shows each pending diff with accept and
  reject, highlights the affected entities in the Hierarchy and Scene View,
  and shows the last scenario results. Accept and reject go through snapshot
  undo like any other scene edit.

### Agent panel design

An area type in the editor-type switcher beside Hierarchy and Console,
named "Agent". Blender-style: compact header, flat rows, no modal dialogs.

- **Header:** area switcher, a Journal / Pending / Results tab strip, and a
  `Pause agent edits` toggle. While paused, outside writes queue as Pending
  instead of reloading.
- **Journal:** one row per outside write the editor saw, newest first: time,
  source (`rusting scene patch`, `rusting mcp`, other), the
  `OutsideChange` counts (added, changed, removed), and the Undo step it
  made. Click a row to select its touched entities (`highlight_scene_ids`).
  Kept in memory for the session, capped like `EditorHistory::MAX_SNAPSHOTS`.
- **Pending:** a diff the editor has not applied: the file changed while the
  scene had unsaved edits, or edits are paused. Each entity shows per-field
  rows (path, old, new) from the same leaf diff `rusting diff` uses. Buttons:
  Accept (apply behind one snapshot undo), Reject (keep the GUI scene and
  record the reject), and Accept all. Accept applies only the chosen
  entities' fields, so a field-level merge replaces the keep-mine or
  take-theirs dialog above.
- **Results:** the last `rusting test --json` run for the open project:
  one row per scenario with pass or fail, tick, and the failing step's
  `expected` and `actual`; perf and budgets from the `perf` report. Read
  from the newest report file, refreshed when it changes.
- **State:** `AgentPanelState` in `EditorState` holds the journal, the
  pending diffs and the pause flag. Widgets come from `gui_elements` and
  colors from `EditorTheme`; no per-panel colors.
- **Rules:** every Accept and Reject goes through `remember_scene_before_edit`;
  an external write never replaces unsaved GUI work without a Pending row.
- **Tests (no GPU):** a clean write journals one row and highlights its
  entities; a dirty-scene write becomes Pending and leaves the scene
  untouched; Accept applies one entity's fields and Undo reverts them;
  Reject leaves the scene and the file unchanged.
- **Order of work:** the journal first (data already exists in
  `reload_external_scene_change`), then Pending with per-field Accept, then
  Results.

## Deferred: materials and mirrors

- Glass / Mirror presets in the Material section (Opacity + roughness + reflectivity in one click).
- A true planar mirror (second camera render into a texture). Today Reflection Probe + low roughness only approximates one.
- Reword the Reflection Probe environment picker text.