native/physics-native/lingua_physics.c wraps Box3D at a specific pinned
commit (47d7f7c — Box3D has no 1.0 release yet, and its only tag, v0.1.0,
already diverges from this API, confirmed by diffing headers rather than
assuming). The wrapper is deliberately narrow: Box3D's own b3WorldDef/
b3BodyDef/b3ShapeDef are large structs with function pointers, and
b3BoxHull's own doc comment says it "has data hanging off the end and
cannot be directly copied" — none of that crosses the P/Invoke boundary.
Every exported Lingua_* function takes and returns only plain int32/float/
bool scalars, and handles are this shim's own array-index handles, not
Box3D's id structs. C# binds them with classic DllImport rather than the
newer LibraryImport specifically because LibraryImport's generated
marshalling needs AllowUnsafeBlocks even for an all-scalar signature like
every one of these — DllImport needs none, keeping this plugin inside the
kernel's "no unsafe in the v1 hot path" rule with no exception required.
engine.physics adds Rigidbody/BoxCollider/SphereCollider components and
PhysicsWorld, which diffs Query<Rigidbody>() against its own tracked set
every Stage.FixedUpdate (there's no destruction event to hook) to create
and destroy native bodies, steps Box3D once per invocation — Engine.Host's
accumulator decides how many times that runs per frame, not this plugin —
and writes each body's resulting transform back to GameObject.Transform.
IPhysicsService exposes ApplyLinearImpulse/Get/SetLinearVelocity for
gameplay code.
Verified twice: a standalone C smoke test against the native shim alone
(a box dropped from y=5 onto a static ground settles at y≈1.0, exactly
where the two half-heights sum to), and the full pipeline through Engine.
Host — a headless run with a real scene, --dump showing the same box
settling at y=0.9999 after physics, scene load, and Stage.FixedUpdate all
went through the real kernel. 5 new automated tests in Engine.Physics.
Tests cover the same settling behavior for both shapes, a missing-collider
warning that fires once and doesn't throw, cleanup after a GameObject is
destroyed mid-simulation, and that an applied impulse actually changes
velocity — all passed on the first run.
Physics-enabled GameObjects must be root-level for now: PhysicsWorld
writes Box3D's world-space transform straight into LocalPosition/
LocalRotation, correct only when local and world space are the same
thing. A parented rigidbody needs the same parent-WorldMatrix-inverse
handling GizmoMath.WorldToLocalPosition already does for the gizmo — real,
not-yet-done work, not silently wrong.
Full suite: 81 tests.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N1qPfzq8TDCUMFMV3UwV5N
IPlayModeController (Engine.Editor.Contracts) wraps IWorld.Snapshot()/
Restore() as EnterPlay/ExitPlay — the entire mechanism is that one
snapshot, per Snapshot's own doc comment. Exposed as a service, not just
something engine.editor's UI calls directly, so a future non-UI driver
(a test harness, a headless "play for N frames" CLI command) can drive
Play mode without depending on ImGui.
Engine.Host now looks up IPlayModeController once and checks IsPlaying
every frame before running Stage.Update: Edit mode still renders the
scene (so the view isn't frozen and the editor UI stays responsive) but
never ticks it, same distinction Unity draws between its Scene and Game
views. A project with no engine.editor loaded sees no behavior change —
Update runs unconditionally, same as before this existed.
Added "play"/"stop" as stdin commands alongside the existing "r" and
"screenshot", both real controls (not just test scaffolding) for driving
Play mode without a mouse — which is also how this got verified: an
interactive run sent "play", screenshotted, sent "stop", and the log
shows "Entered Play mode in 13.2ms", the real editor path exercising the
same under-100ms budget WorldSnapshotTests already proves at the kernel
level. The Lingua Editor panel's button/label flip Play/(Edit mode) to
Stop/(Playing) correctly across both screenshots.
Also fixes a real layout bug hit while verifying this: SetNextWindowPos
with ImGuiCond.FirstUseEver only applies with no prior imgui.ini entry
for that window title, and this project had already accumulated one from
earlier runs (all three panels stacked exactly on top of each other) —
gave each panel its own default position and .gitignore'd imgui.ini,
which is per-machine session state, not source.
Full suite still green: 65 tests.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N1qPfzq8TDCUMFMV3UwV5N
Lay out the design that everything else builds on: a small frozen
kernel plugins treat as a shared language, the plugin contract, the
two-assembly hot-reload model, and the build order through M4.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N1qPfzq8TDCUMFMV3UwV5N