InspectorPanel shows EditorState.Selected's Transform (Position/Scale as
draggable Vector3 widgets, Rotation shown read-only as a quaternion — a
real rotate control needs Euler conversion or a gizmo, out of scope for
just the Inspector) and, below it, every attached Component's public
fields via reflection. No per-component-type drawer code exists anywhere:
int/float/bool/string/Vector3 fields get a live-editable widget, anything
else falls back to a read-only ToString(), and a brand new component type
in any plugin gets an Inspector for free the moment it's attached — that
genericity is the entire reason to do this by reflection instead of a
registry.
Field edits write straight back through FieldInfo.SetValue, same gap
GameObject.AddComponent's doc comment already names: components are plain
fields with nothing to intercept a direct mutation, so this needs no
SystemAccessScope declaration any more than a hand-written `component.
Value = 5` would.
EditorPlugin now defaults EditorState.Selected to the first root once,
if nothing's been clicked yet — an empty Inspector on every fresh launch
had nothing useful to show. Verified by screenshot: Inspector displays
"Quad"'s real Position (0,0,0) and Scale (2,2,1), matching scene.json
exactly, plus a QuadRenderer header correctly showing "(no fields)" since
it has none yet.
Full suite still green: 65 tests.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N1qPfzq8TDCUMFMV3UwV5N
HierarchyPanel walks IWorld.Roots/GameObject.Children recursively as
ImGui tree nodes, clicking one sets EditorState.Selected — the shared
selection the not-yet-built Inspector panel will read from the same
instance. PushID(go.GetHashCode()) scopes each node's ID by object
identity rather than name, since nothing stops two sibling GameObjects
sharing a Name and ImGui's default label-based IDs would otherwise merge
their open/selected state.
Also gives samples/WindowDemo/scene.json a child GameObject (offset,
half-scale, parented under Quad) — the existing scene only had one root,
nothing to show a tree with. Verified by screenshot: both quads render at
their correct composed WorldMatrix (the child visibly smaller and offset,
confirming parent/child composition is still correct through this
change), and Hierarchy lists "Quad" as a collapsible node.
Full suite still green: 65 tests.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N1qPfzq8TDCUMFMV3UwV5N
Introduces engine.editor, a new plugin that renders an ImGui panel on top
of whatever the scene is drawing, using Silk.NET.OpenGL.Extensions.ImGui's
ImGuiController against the same GL context and window engine.render
already owns.
Getting the overlay to actually show up in the same frame (not delayed by
one, which is what happens if UI draws after SwapBuffers) required
splitting engine.render's Draw system: SwapBuffers moves out into its own
Present system on a new Stage.Present, run by Engine.Host after every
Stage.Render system — this plugin's Draw and, now, engine.editor's ImGui
pass — has drawn into the same back buffer. Stage.Present is the second
addition to the frame stage set (after FixedUpdate was scoped out for M4);
docs/kernel-contract.md already committed to the set being fixed and
kernel-defined, not plugin-extensible, and to only adding a stage when
there's something real to run in it — SwapBuffers already needed
somewhere to live once a second Render-stage system existed to race it.
Also extends IEngineInput with a Native IInputContext property, the same
escape hatch IEngineWindow.Native already is — ImGuiController's
constructor needs the raw Silk.NET input context directly.
Verified with a real windowed run: --screenshot after 5 frames shows the
"Lingua" ImGui panel (FPS/frame counters) correctly composited over the
still-correct 3D checkerboard quad from the M3 camera pipeline, in the
same frame. Full suite still green: 65 tests.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N1qPfzq8TDCUMFMV3UwV5N