Skip to content

Phase 1 / Forge / forge_3d orchestration: PhysicsWorld and ECS Transform synchronisation - #72

Draft
guysenpai wants to merge 18 commits into
mainfrom
phase-1/forge/physics-world-orchestration
Draft

Phase 1 / Forge / forge_3d orchestration: PhysicsWorld and ECS Transform synchronisation#72
guysenpai wants to merge 18 commits into
mainfrom
phase-1/forge/physics-world-orchestration

Conversation

@guysenpai

Copy link
Copy Markdown
Contributor

Brief

briefs/m1.1.15-physics-world-orchestration.md

DRAFT, opened at the first gate as engine-development-workflow.md §4.4 requires: ci.yml fires on pull_request and not on a branch push, so a gate whose exit depends on a matrix cell is unreachable until the PR exists. Gate A's exit is exactly that — the eight committed determinism witnesses byte-identical on the twelve-cell matrix, and the four CHAIN witnesses are x86_64 artifacts that no local corner can verify.

No Witness-regen: trailer appears anywhere in this branch, and none will. A red witness here is a defect to isolate, not an act to declare.

Summary

forge_3d had every mechanism the arena needs and no owner for the tick that runs them. This milestone creates that owner: PhysicsWorld executes the eleven steps of engine-physics-solver.md §1.7 in their frozen order, wires the pieces the body store cannot wire from inside, and connects the solver to the ECS in both directions.

Gate progress

Gate State
APhysicsWorld and the tick delivered, awaiting the matrix reading
B — proxies and body lifetime pending
C — wake composition pending
D — ECS synchronisation pending
E — precision crossing, interface file, false premises pending
F — closure pending

Gate A — what landed

  • The eleven-step cycle MOVED out of tests/solver_test.zig — where its only composition lived, driven by ten suites — into forge_3d/world.zig. Call sequence, arguments and arithmetic unchanged: the reparenting changes who calls, never what is computed.
  • step() reads as the cycle: nine stage methods in order, plus the two code-free anchors as comments where they would run (3 read-only, 5 bis the empty composite seam of §1.7.3). Each stage method opens with its own enter(), which is what binds the recorded order to the executed one.
  • Step 10 bis is now UNCONDITIONAL. The harness gated it on a sensors_on flag defaulting to false; in production that reads "sensors silently do not work". SensorState.update takes a *const BodyManager, so the pass cannot alter a bit of body state and the change is witness-neutral by construction.
  • SolverStats.warm_start_injections — real injections, not loop turns. Documented as NOT part of what §1.8.2 reports; see the brief's Notes, and flagged there for your call.

Gate A — measurements

Determinism instrument, all four local corners green ({Debug, ReleaseSafe} × {f32, f64}, macOS aarch64): self-reproducible, the four discrete traces byte-identical to the committed witnesses, no divergence within K = 60. The four chain witnesses read REPORTED, not gated on this host because level 1 is intra-ISA — that is the matrix's half of the exit.

Counter-factuals, four RUN, and the fourth is a residual rather than a confirmation:

Probe Mutation Measured
A swap the calls of stages 10 and 10 bis 1 failed of 561 — the order test, that adjacency's only guard
B hoist the warm start out of the substep loop cadence test: expected 16, found 4, plus 6 physics tests
D swap the calls of stages 4 and 5 4 failed, 35 crashed — where order matters physically, dozens fire
C swap two stage BODIES, leave each enter() whole suite green — the residual, written into the code

Probe C is the honest limit: a record separated from its work is undetectable by any test here. What bounds it is structural — each enter() is the first statement of the stage method carrying its name — and that is a smaller claim than "the order is verified", stated as such in world.zig and in the test.

Counts, with their denominators. zig build test-forge-3d collects 561 on this branch (560 pass, 1 skip) against 555 on main at e02d27b, measured in a worktree, both macOS aarch64: +6, the six blocks added in tests/world_test.zig. Full suite 1875 collected. The dead-tests declared floor was re-derived from the SUITE (1875), never from the closure's own arithmetic.

One test assertion removed, declared in the brief's Recorded deviations: expect(s.world.sensors_on) in the determinism scenario had no flag left to read. It is replaced by two assertions obtained by different mechanisms — the scenario's trigger asserted on the body's ROLE, and the unconditional pass asserted on the STATE it produces, in tests/world_test.zig.

Local gates

  • zig build clean
  • zig build test — 1875 collected, 1856 pass, 19 skip
  • zig build test-forge-3d — 561 collected, 560 pass, 1 skip
  • zig fmt --check green over src/ tests/ bench/ tools/ build.zig
  • zig build lint green, dead-tests conservation reconciled at 1875
  • zig build forge-determinism green on the four local corners
  • the twelve-cell matrix — this PR's reason for existing at Gate A

guysenpai and others added 18 commits August 21, 2026 22:59
Moves the eleven-step cycle of `engine-physics-solver.md` §1.7 out of the
acceptance suite that held the only composition of it and into
`forge_3d/world.zig`. The call sequence, the arguments and the arithmetic are
the ones the eight committed witnesses were taken over: the reparenting changes
who calls, never what is computed.

Cycle ownership:
- `PhysicsWorld` holds the shape store, the body store, the broadphase, the
  warm-start cache, the island partition and the per-tick scratches
- `step()` reads as the cycle: nine stage methods in the frozen order, with the
  two code-free anchors (3 read-only, 5 bis empty) as comments where they run
- each stage method begins with its own `enter()`, so a stage cannot be moved
  without its record moving — a recorder wired at the call site would be blind
  to the one mutation it exists to catch
- `tests/solver_test.zig` keeps the name `World` as an alias, so the ten suites
  that drive a world all drive the same owner

Step 10 bis is UNCONDITIONAL:
- the harness gated it on a `sensors_on` flag defaulting to false, which for a
  production world means sensors silently do not work
- `SensorState.update` takes a `*const BodyManager`, so the pass cannot alter a
  bit of body state and making it unconditional leaves every witness identical

Telemetry:
- `SolverStats.warm_start_injections` counts constraint POINTS injected, summed
  over substeps — not part of what §1.8.2 reports, and the only place the
  application half of warm start is observable
- `applyWarmStartRange` returns the count it injected

Tests:
- `tests/world_test.zig`: the step order read as an order, the substep cadence
  with its paired one-substep negative, and step 10 bis on a world that was
  never told about sensors
- the declared per-platform test floor re-derived from the suite: 1869 -> 1875
F1 — probe C claimed "a record moved away from its work is undetectable by
any test here", which its own batch refutes: swapping two stage BODIES
executes the work in the same order as swapping their CALLS, so on a
consequential pair the physics guards fire identically. Measured, not
deduced — probe C' on (build_constraints, island_partition) gives 3 failed,
35 crashed with the order test passing, i.e. probe D minus the order test.
Both pairs are now named, the claim is bounded to the physically harmless
case, and what the test guards ALONE is stated: the 10 / 10 bis adjacency,
where probe A reports 1 failed of 561.

F2 — `warm_start_injections` moves into a named `not_reported` sub-struct of
`SolverStats`. §1.8.2's exclusion was carried by a doc comment, so it rested
on whoever later maps the struct onto the reported telemetry surface; it is
now carried by the type and visible at every read site. The counter stays
where the injection happens.

F3 — the order test is renamed to say what it measures: the nine CODED steps
of an eleven-anchor cycle, not eleven steps.
The counter-factual refuted its own prediction and had to be read further:
under the "tag before publishing" defect BOTH halves of the sleeping test
fail, because the unpublished final velocity stays in the ECS and sync-in
pushes it back as an activating write, waking the sleeper on 29 of 30 ticks.
Closing that channel separates them — with Velocity removed the immobility
half passes while the published pose is off by 1.88e-5 m, which is the value
half doing the work the structural claim credits it with.

Also records the review's 157-line correction with its instrument, so the
figure and the recipe above it stop naming different byte strings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gate D's count was copied from Gate C and read "unchanged" while the true
value had moved: Gate C bis added the moveKinematic test and journaled no
count, so the comparison ran against a baseline one gate stale. Measured in
a worktree at each of the four heads, the series is 561 / 564 / 570 / 571 /
571, and the missing Gate C bis line is added — leaving a gate without its
count is the mechanism, not the arithmetic.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant