A second AI session read the whole codebase in parallel (read-only, no
code changes) and left a handoff doc. Addressed the correctness findings:
- PhysicsWorld.Sync's early return compared _bodies.Count to live.Count,
not their contents — same size, different membership (destroy one
tracked GameObject, gain one untracked-because-no-collider one; or any
Restore where the scene has both a Rigidbody+collider object and a
Rigidbody-without-one) skipped cleanup entirely, leaking the native
Box3D body forever. Fixed by checking the actual stale set. Two new
regression tests reproduce the review's own two scenarios via a new
Lingua_GetBodyCount native export, asserting on the native table's own
count rather than PhysicsWorld's C#-side bookkeeping.
- TryCreateBody stored handle -1 (native shim refused: invalid world, or
its 8192-slot body table full) as if it were real — every later
GetBodyTransform on it silently teleported the GameObject to the
origin with a degenerate rotation, no error anywhere. Now checked and
warned once, same as the missing-collider case.
- PluginHost.Load didn't roll back anything when Configure threw partway
through: Schedule.Add/Events.Subscribe registrations it already made
stayed forever, and its ALC was never unloaded — neither loaded (no
_loaded entry) nor cleanly unloadable. Fixed with try/catch: best-effort
Shutdown (the only thing that knows which services this plugin
provided), RemoveAllFrom on both Schedule and EventBus, best-effort ALC
unload, rethrow. New fixture plugin (sandbox.failing-configure, mirrors
sandbox.echo's own real-load pattern) registers a system against a
shared Ping component then throws, so FailedConfigureRollbackTests can
assert the dangling system actually stops firing — an earlier version
tried to prove this via AssemblyLoadContext.All instead, which passed
even against the deliberately-reverted buggy code (the ALC turned out
to get collected either way once its only references went out of
scope); watching the dangling system is what actually distinguishes
rolled-back from not, confirmed by deliberately reverting the fix and
watching this specific test fail before restoring it.
- Engine.Host's "r <id>" left a plugin unloaded on a failed reload with
no honest indication of that, and retrying threw "not loaded" instead
of ever reaching Load again. Added PluginHost.IsLoaded so the handler
only calls Unload when there's something to unload, and the failure
message now says the plugin is unloaded, not just "failed."
- AssetService.ReloadWithRetry's `when (attempt < 4)` guard meant the
5th and final IOException fell out of the loop and propagated from a
discarded fire-and-forget Task — no log, no event, nothing. Now logged.
- EditorState.Selected kept pointing at a GameObject Restore had already
destroyed after ExitPlay, so Inspector/gizmo would silently keep
editing something no longer in the world. EditorPlugin.DrawUi now
compares IsPlaying against its own previous frame (not just reacting to
the Stop button) so this is caught whether Play was exited via the
button or the "stop" stdin command — the same stdin-vs-real-control gap
already hit once earlier this session — and re-resolves the selection
by name.
- README.md and kernel-contract.md's Event Bus/Time rows had fallen a
milestone behind (still said "no asset system yet" and "no fixed-step
accumulator yet" after both shipped).
Full suite: 98 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