Headless 3D SoA world over Box3D, state read in place #1
Loading…
Reference in a new issue
No description provided.
Delete branch "nibu/soa-world"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Smallest vertical slice for Lodur then Bad Day Simulator: a headless 3D Box3D world stepped deterministically, with a structure-of-arrays state view read in place. No solver of ours; every physical decision is Box3D's (pinned v0.1.0). Base:
main(8d02b1e), no other open PRs.Survey (what existed, and what it changed)
Ymir.Box3Dnet8.0,Ymir.Core) over a C shim (ymir_box3d.c, CMake builds the pinnedextern/box3dsubmodule at 8441b4a). Native ABI v5, P/Invoke withLibraryImport. Tests:Ymir.Box3D.Parity(20, native oracle) andYmir.Tests(34, needs the CultLib 2.0.0 worktree).gravity = 0,motionLocks.linearY/angularX/angularZ, yaw only, spheres only, and state leaves bycopy_bodies(array of structs).YmirSoAWorldinYmir.Coreis a managed 2D snapshot with clones, not a native view.ymir_box3d_soa.c, ABI 1), not an extension of the session; the Aetheria session contract is untouched. The cost is a second creation path for a Box3D world; it replaces nothing and is the liability to review.What it adds
ymir_box3d_soa_create(capacity, gravity) / add_sphere / add_static_box / step / get_view / destroy. Capacity is fixed, so lane pointers are stable for the world's life. Lanes: stable id, kind, position xyz, rotation quaternion xyzw, linear and angular velocity xyz, one contiguous array per component. One Box3D worker, insertion order.Box3DSoaWorld/Box3DSoaView(internal) expose each lane asReadOnlySpan<T>over native memory. Alsodocs/soa-world.md, a README entry, and the new test project inYmir.slnx.Tests (
tests/Ymir.Box3D.Soa.Tests, 6): a sphere falls under gravity and rests on the slab, read through one held view; the span address equals the native lane and is unchanged after stepping, and a view taken before stepping shows later state (not a snapshot); two runs of 25 colliding spheres on a slab are byte-equal at six checkpoints over 300 steps; a one-ulp input difference diverges (the equality can fail); bad input, duplicate id, full world, disposed world.Verified here (Linux x64, .NET 10.0.403, GCC 14.2): Soa 6/6, Parity 20/20, Ymir.Tests 34/34 (with
-p:CultLibRootat the pinned tag). Hand mutants: no refresh in step, gravity ignored, quaternion lane swapped, all killed.workerCount = 4survives: Box3D gave identical bytes anyway, so the single-worker setting is not shown to matter here; kept as a safe default.Countis documented as taken at view time.Could not run: Windows (the repo's primary target) and macOS; any other CPU or compiler, so cross-platform bit-identity is not claimed; the Unity/Aetheria consumers; Lodur and Bad Day Simulator themselves; no performance measurement (24 spheres only);
dotnet test Ymir.slnxas a whole (the slnx lists../CultLib, which this workspace lacks; I ran the three projects separately).Not done and recorded as a follow-up: joints, body layout beyond spheres/static boxes, torque/force write path, contact facts, and a public
Ymir.Coresurface. Merge nothing.🤖 Generated with Claude Code
Self-review of #1 (read the C and the C#, ran the 6 tests on Linux x64: pass). Defects, most important first. Fixed ones are in the stacked draft #2.
ymir_box3d_soa.cheader anddocs/soa-world.mdsay "bodies stepped in insertion order". Box3D orders work by its own constraint graph; insertion order is not what the solver follows. The guarantee we rely on is one worker plus Box3D's own deterministic design, on one build. Reword; do not claim an ordering we do not control. Open.get_view_sizeplus the version number catch a size change, not two pointer lanes swapped. No per-lane offset check. Open (#2 adds descriptor size checks, same limit).b3CreateWorldtrapped (SIGILL). #1's one-class suite does not hit it; any second test class will. #2 serialises the test assembly and documents it. Constraint for any consumer that builds worlds on several threads.Box3DSoaViewholds raw pointers; a view taken beforeDisposereads freed memory afterwards and nothing guards it. Documented, not enforced. Open, acceptable while the type is internal.stable_idgoes into shapeuserDatathrough(uintptr_t). Truncates on a 32-bit target (Unity 32-bit Android would). Open, minor.Not measured here: Windows build and Unity consumption (unrun), cross-platform bit-identity (unclaimed).
🤖 Generated with Claude Code
Steward check before merge (nibu, 2026-11-10).
dotnet test tests/Ymir.Box3D.Soa.Testsat the head: 6/6 pass (native shim built with cmake).tests/Ymir.Box3D.Parity(the existing suite, same native CMakeLists touched by this PR): 20/20 pass.Ymir.Testsand the full.slnxneed a pinned CultLib sibling that this host lacks; this PR does not touchYmir.Core.def.gravityfails 2 of 6 tests, so gravity direction and read-in-place rest height are pinned.