Conversation
The frozen section as produced by Claude.ai, with the eight specs of the reading list ticked off in the living section. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`Mat4(T)` is declared in `src/foundation/math/mat4.zig` and `rtti` names it
by alias, which preserves the type IDENTITY `classifyField` matches on. Its
storage is `extern struct { m: [16]T }` and not `Mat3`'s `cols: [N]Vec`,
because it carries a frozen C twin that a `@Vector` layout cannot serve.
The twin had no assertion at all — `WeldMat4` has zero use sites and was tied
to `rtti.Mat4` by a doc comment — so a field-by-field comparison lands with a
witness that it can return false.
`affineInverse` dotted the column where the formula wants the row; a diagonal
fixture cannot tell the two apart, the rotated non-uniform one did.
The pose layout is decided on the bench, not on intuition: SoA-3 wins blend
AND forward kinematics at 32/64/128 bones over three runs. The per-channel
form, the only one that vectorises, loses the blend by 75% — which refutes
the stated argument for SoA while confirming SoA. An instrument defect was
corrected first: the AoS body was written scalar against a vector SoA body,
and equalising it moved the 32-bone ratio from 0.936x to 1.006x at identical
checksums.
Test floor 2290 -> 2300 / 2298, re-derived from the suite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The three layouts differed in two things at once — memory order AND element representation — so the gap could belong to either. `AoS-vec` holds both fixed but the order: same 48 bytes, same alignment, `@Vector` members. It reverses the verdict. Interleaved beats split on blend 12 times out of 12 across four runs and ties it on FK, so SoA-3 is dominated. What the earlier reading had credited to the split was the `[3]f32` element: the corpus's own AoS candidate, one Tier 0 `Transform` per bone, is the slowest viable form by about 10% on both operations, an axis neither document named. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`src/interfaces/AnimationModule.zig` declares the types its own signatures name and attests its non-freeze with a test that fails the day a protocol version appears. The assert block covers what an implementation owes today and grows with it: asserting an entry nothing can satisfy forces a body that returns a plausible answer, which hides worse than an absence. `init` reaches `world` and `persistent_allocator` and nothing else, registers the `Skeleton` component there, and registers no system — a system with no body to run is a mechanism nothing executes. Poses live outside the ECS and the component carries a handle. Instance slots are never recycled: an id is a position, so a reused slot would make a stale handle address a different skeleton and read as working. Counter-factual run: recycling reddens exactly that test and no other. Test floor 2300 -> 2315 / 2313. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Loading refuses a malformed hierarchy rather than repairing one: a loader that reroots an orphan or clamps an index makes every downstream invariant untestable. The topological order is checked here, which makes a cycle unrepresentable rather than detected — a cycle must contain a forward reference and a forward reference is what this refuses, so the two are one check and one code. `error.NoRoot` was written and removed: the test for it failed, and reading why showed no input reaches it. Bone zero has no index below it, so it is the root or the hierarchy was already refused. An error nobody can provoke is an assertion, and it is now one, with the argument at the site. The inverse bind pose is stored, not derived. Deriving would force the bind pose and the rest pose to coincide; they legitimately differ, which is why glTF ships both. Test floor 2315 -> 2329 / 2327. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pass produces MODEL space — each bone relative to the skeleton root — and never world: the entity transform enters at the consumers, and baking it here would make the skin matrices and the socket reads both wrong in a way that looks right at the origin. Rig and instance are two stores, so one hierarchy backs many poses. A fresh instance stands in its bind pose: a skeleton at the identity is a heap of bones at the origin and reads as a sampling bug. The single pass is asserted twice and neither is timing — the topological invariant read off the loaded rig, and the fixed point, since a second pass would move something if a parent were evaluated after its child. A coverage hole found by counter-factual: reversing the rotation operands left the suite green, because the acceptance chain rotates about one axis and its leaf carries the identity, and quaternions commute in both cases. Closed with two non-parallel rotations, a sequential-application oracle, and a discrimination guard. Test floor 2329 -> 2339 / 2337. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The attack was written first and it caught the plausible implementation: a profile mapping twenty-two of twenty-three roles, the gap in the middle. A resolver indexing the mapping array by ordinal answers foot_l's bone for the unmapped calf_l — a neighbour, which is the failure the invariant names — and the positive test stays green under it, because a resolver right for twenty-two roles passes every ordinary test. Two bones of one name are refused at load. Otherwise a name addresses two bones and the resolver has to pick, which is acting on possibly the wrong one. It caught a real duplicate on its first run. The counter-proof corpus pins the addressing type: a bare string, a bare index and a bare ordinal must not compile in a reference position, while both legitimate variants must. Both halves were shown to redden — and the first attempt to show it was a bad counter-factual that moved the guard one level in instead of removing it. Test floor 2339 -> 2348 / 2346. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`bindModelPose` had no caller and no test, duplicated what `instantiate` does inline, and carried an unstated aliasing contract — it passed one buffer as both input and output of the kinematics pass. `resolveRole` and `resolveName` were public beside a header claiming `resolveBone` is the one path. Two public entries that map a role or a name are two more paths, in the module's own surface, at the place the invariant is written down. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deviations, blockers, the closing self-audit and the final measurements. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three defects in shipped behaviour. `Mat4.approxEql` compared NaN EQUAL — `@abs(x-y) > tol` is false for NaN — so four assertions could not fail on a total failure of the inverse. `rig()` returned a pointer the next `loadRig` frees, which the shipped scenario holds across its whole `main`. Neither new process entry installed the float environment, and the bench is the one whose published numbers decided the layout. Eight guards that could not fail, each verified by mutation. The two "single linear pass" tests passed against a kinematics reduced to a copy; the `id == len` boundary was never probed; three refusals of `parse` were unreachable from `parse`; the C-twin's negative witness differed in width, so the field loop it exists for never ran. `fromTrs`'s third column was pinned by nothing, and the first repair was blind to two columns for a different reason — a quarter turn makes their diagonal terms exactly zero. What ships is a half turn plus a generic rotation checked against `rotateVec3` plus a guard that refuses a fixture with a near-zero basis term. All nine terms now detected. And the class this milestone had already closed twice, committed while closing it: `resolveName` stayed public under a header saying it was not. Scope: the module now publishes a resource and registers one `fixed_update` system, driven through a real `dispatchFrame`; the seven unimplemented interface entries are present and refuse; `parse` validates its float payload. Test floor 2348 -> 2357 / 2355. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Thirteen cells green. The windows ReleaseSafe f64 cell cleared its 55-minute budget by 45 seconds, which is the cost this brief predicted for a gate touching a depended-upon Tier 0 header. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The same two cells took 49m40 and 54m15 cold, then 4m28 and 4m26 warm — a factor of 11 to 12 on a branch whose only intervening change is one line of journal. The 54 minutes were compilation, not test execution. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found by external review after this milestone's own adversarial pass had closed. `compose` built a model-space `BoneTransform` from two others, and that composition is exact for the position and WRONG for the scale: it multiplies the two scales componentwise, which is the right answer only when no rotation between the two bones reorients the scale axes. MEASURED before the fix, on a root scaled `(3, 1, 1)` with a quarter-turned child: the pass put the grandchild at `(0.0000, 3.0000, 0.0000)` where the matrix product puts it at `(0.0000, 1.0000, 0.0000)`. Wrong BY THE SCALE FACTOR, finite and plausible, on a rig no artist would call unusual. What let it through is written at `skeleton.zig`'s header: a comment on that composition documented an approximation about SHEAR — genuinely inexpressible in TRS — and a documented approximation on one quantity does not cover an error on another. It reads as an argument, which is this repository's costliest defect class. `ModelPose` is declared on the interface, `compose` is deleted, `forwardKinematics` composes `Mat4` and the local poses stay TRS — a clip samples translation, rotation and scale, and nothing is gained by widening what it writes. `pose.zig` gains `allocModel`/`freeModel`; `root.zig`, the tests, the demo and the layout bench follow. The pin is a test named for the defect: an accumulated scale must not survive a rotated child. Its oracle is the matrix product, computed by walking each bone up to the root — not the pass's own arithmetic, which would agree with itself whatever it did. AND THE BENCH'S FK ARM STOPPED DISCRIMINATING, which is reported rather than quietly re-run: it measured a TRS output, and against `[]Mat4` the ratios straddle 1.0 (0.870-1.153) with the sign flipping between bone counts inside one run, matrix composition dominating the read cost. The layout verdict stands on the BLEND arm, unchanged and stable across three runs. Recorded in the bench header and in `bench/results/pose_layout.md`, where the FK row is marked superseded rather than left to read as current. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found by external review after this milestone's own adversarial pass had closed. `validatePayload` refused a stored rotation whose length missed one by more than `unit_rotation_tolerance` and did nothing to the ones it admitted — so a quaternion legitimately inside the band stayed off-unit, the matrix built from it stretched, and the stretch COMPOUNDED down the hierarchy onto a consumer that had been told the payload was validated. The tolerance is NOT tightened. It exists to refuse the absurd — a null quaternion, an arbitrary one — and it keeps that job; what it never was is a guarantee of unit length. The two run in order, and the order is load-bearing: a null norm NORMALISED is a NaN, so the refusal must come first. Both halves are pinned, and the counter-factual that inverts them reddens two tests with `expected error.NonUnitRotation, found error.NonFiniteTransform` — the NaN arriving exactly where the argument says it would. THE FIRST ATTACK WRITTEN FOR THIS MEASURED ALMOST NOTHING AND IS THE REASON THE FIX IS WHERE IT IS. A 128-deep chain at half the tolerance passed at 1.000321, so rather than escalate the fixture blindly the exposure was PROBED: drift is governed by the rotation ANGLE and not by the norm error alone. At the band's edge a 1.2 rad per-bone rotation reaches 8.5 % stretch over 128 bones (5.7 % about (1,1,1)); the same error at 0.1 rad reaches 0.06 %. The first attack used 0.1 rad, which is why it proved the opposite of what it claimed. Tightening the band would not have closed this. The attack ships at the measured worst case, and it asserts BOTH halves: the stored rotation is unit AFTER LOAD — the direct property, which does not depend on a chain length — and the FK tip is unit-length to 1e-3 after 128 compositions. Its non-vacuity is established in the same execution: the same chain rebuilt from the unnormalised quaternion is required to stretch past 1.05, so the assertions above it are about the normalisation and not about a fixture that could never have drifted. Suite floor re-derived FROM THE SUITE: 2357 -> 2359 on macOS, 2355 -> 2357 on windows, the closure agreeing independently at 2359. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found by external review after this milestone's own adversarial pass had closed. `rig()` returned a `Rig` BY VALUE, and that copy carries the store's own slice handles: `borrowed.parents[1] = 127` wrote into the hierarchy the loader had validated, and `borrowed.deinit(gpa)` freed the module's memory while the store went on holding the same slices. Both compiled. Both were silent. THE PREVIOUS REMEDY IS WHAT PRODUCED THIS ONE, and it is the finding's real content. `rig()` first returned a `*const Rig`, which dangled the moment another rig was loaded, and the repair changed the SHAPE of the return — pointer to value — which closed the dangle and left ownership exactly where it was. A remedy that changes the form of a return moves the defect instead of closing it; what was needed was a second TYPE. So: two types, not one. `Rig` stays the owner — mutable slices, `deinit`, internal to the store, and its doc now says that is what it is. `RigView` is what leaves: every slice `[]const`, no destructor, and no `profile_mappings` at all, that field being the backing storage `deinit` frees and having no meaning to a reader. `resolveBone` takes the view too — resolution is a READ and has no business receiving a destructor. The counter-proof is a NON-COMPILATION, in the corpus that already drives one: `case-view-write` fails with `cannot assign to constant` and `case-view-deinit` with `no field or member function named 'deinit' in 'skeleton.RigView'`. The first mark is generic in isolation, so it is paired with its own file name — a mark that any constant assignment anywhere would satisfy discriminates nothing. The control compiles in the same execution and now READS through the view: bone count, bone name, parents, bind pose, inverse bind and the profile. Without that half, two refusals would prove the type useless rather than tight. AND THE HARNESS IS SHOWN TO FIRE. With `rig()` restored to returning a `Rig`, both new cases COMPILE and the corpus reddens: `process exited with code 0 (expected exited with code 1)`, twice. The two lines this commit makes impossible were legal code on the branch that was about to be tagged. The corpus keeps its addressing name for a second family rather than growing a rename: the path is prescribed by the brief's frozen section, and a cosmetic divergence from it is still a divergence. What is NOT claimed is that no code can name the owning record — Zig has no module-private declaration. What is checkable, and checked, is that the owning type never appears in an entry's return type. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The hotfix was reviewed GO MERGE with one objection: quaternion norm is multiplicative, so |q1*...*qn| = (1+e)^n independent of angle, therefore "drift is governed by the angle" is false and the two fixtures must have had norm errors 100x apart. MEASURED, both objects in one execution, delta measured after construction rather than declared, over 128 bones. The quaternion-product norm is FLAT to five figures across angles 0.1 to 3.0 — 1.066054 to 1.066059 — while the matrix chain at the same delta runs 1.000641 to 1.289770. The reviewer's theorem is exact (Euler's four-square identity, verified over 200000 random pairs against this repo's own Quat.mul at 7.6e-16) and their arithmetic is right at every step. It governs Quat.mul, which src/modules/kinesis/ calls ZERO times. forwardKinematics composes Mat4, and Mat4.fromTrs writes an unscaled 1 on each diagonal term while every off-diagonal term carries the full |q|^2 — so M(k*u) = k^2*R(u) + (1-k^2)*I, an affine mix with the identity, verified elementwise to 1 ULP. The decisive cell is theta = 0: delta fully present, stretch exactly 1.000000, where a law independent of angle demands 1.066. BUT THE OBJECTION WAS RIGHT ABOUT THE SENTENCE IT ATTACKED. "Tightening the band would not have closed it" is FALSE — a non-sequitur, since the drift is the product n*delta*(1-cos theta) and a product closes by shrinking any factor; 100x tighter takes the 128-bone stretch from 8.5% to 0.08%. What is true and stronger is that the band is defeated by DEPTH: max_bones is 4096, and at the band's own edge such a chain stretches 13.6x at 1.2 rad and 3583x at pi. Both false sentences are deleted here, not annotated. AND THE FIXTURE WAS ADMISSIBLE BY ONE ULP AND BY LUCK — a second defect the objection's wrong number nevertheless landed on. k = sqrt(1 + tolerance) names a length the band REFUSES: 1 + 1e-3 is unrepresentable in f32 and rounds up to 8389 ULP where the predicate admits 8388. It loaded only because five roundings happened to land one ULP low at that angle; the identical construction is refused at 15.2% of angles in (0,3], non-monotonically — 1.80 rad refused between an accepted 1.50 and an accepted 2.00. Repaired, and the attack is STRONGER: 7550 ULP under the 8388 ceiling against a 2 ULP construction spread, 0% of angles refused, 3.0 rad instead of 1.2, unnormalised stretch 1.257397 instead of 1.085067, floor 1.05 -> 1.2. The 0.9 factor is written as a rule (factor <= 1 - N*2^-23/tol) and not as a number, because it stops working at a tighter tolerance. rotationIsUnitEnough is extracted from validatePayload as the ONE declarant of the band arithmetic, whose summation order decides the last ULP. The in-test assertion is TWO-SIDED, since "inside the band" alone is satisfied by a unit quaternion under which the test is vacuous — counter-factual: forcing k = 1.0 aborts it. Mat4.fromTrs now declares the unit precondition its twins Mat3.fromQuat and Quat.rotateVec3 both declare and it did not, with the consequence rather than the bare word: the mix is anisotropic, so no scale factor describes it. Reported and NOT fixed here, verified on the source in both directions: body_manager.setRotation stores a rotation raw, reachable through the FROZEN setBodyTransform and through sync_in.zig's per-tick ECS seam, while addBody normalises and integration.zig skips exactly the .gameplay bodies syncIn writes to. Forge, frozen surface, needs an M1.D number and an owner. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Opens the Kinesis module and delivers the bottom of its dependency chain: a skeleton asset, its hierarchy, its bind pose, forward kinematics, and the one addressing type every later bone reference must pass through. Brief:
briefs/m1.2.0-skeleton-foundations.md.The layout ruling, and it was wrong twice before it was right
PoseBufferfreezes withAnimationModuleand is snapshotted by rollback, so its layout is cheap to choose now and a save-format migration to change later. Three documents asserted one and none had measured one.The bench decided it — and two instrument defects had to be found first, the second reversing the verdict the first had already corrected:
0.936×to1.006×at identical checksums.@Vectormembers — separated them and reversed the ruling.Verdict: interleaved, with
@Vector-backed elements. It wins the blend at every bone count (0.895–0.911×against AoS) and ties forward kinematics, where the sign flips between runs and no verdict is registered. The corpus's argument for SoA was vectorisation; the only form that vectorises loses the blend by a factor of 1.8. The corpus's literal AoS — one Tier 0Transformper bone — is the slowest viable form, by ~10 % on the blend and 6–7 % on the kinematics.What else the milestone establishes
Mat4descends intofoundation/mathas an alias, preserving the type identityclassifyFieldmatches on. Its C twin had no assertion at all — related by a doc comment, zero use sites — and now has a field-by-field one.error.NoRootwas written and removed: no input can reach it, and an error nobody can provoke is an assertion.resolveBoneis the one resolution path. A role the profile does not map resolves to absence, never to a neighbouring bone. Two bones of one name are refused at load, so a name never addresses two.Method, which is most of the value
Eleven counter-factuals were run; the ones that found something outnumber the ones that confirmed something. Two instrument defects in the bench. A rotation-order error that left the whole suite green, because the acceptance chain rotates about one axis and its leaf carries the identity. A resolver indexing the mapping by ordinal that answered a neighbouring bone while every positive test stayed green.
An adversarial review then raised twenty-one findings, nineteen real, closed in this branch: three defects in shipped behaviour, eight guards that could not fail, and — sharpest — a guarantee affirmed in a comment the code did not give, committed in the act of closing an instance of that same class.
Measurements
Test floor 2290 → 2357 (2355 on
windows-2025), re-derived FROM THE SUITE at every gate and confirmed independently by the dead-test closure each time. Four corners green locally (Debug/ReleaseSafe × f32/f64),lint,fmt,zig buildclean,bone-ref-counterproofgreen,kinesis-skeleton-demoprinting the hand-computed pose.Two points for arbitration
Transform. Two readings of one frozen sentence, 10 % apart. Reversible in one edit.src/foundation/simd/, which the Scope names: that harness may not importfoundation/math, and a pose bench needsVec3,Quatand aTransform.🤖 Generated with Claude Code