Commit graph

1,245 commits

Author SHA1 Message Date
Meta Vens
d808941b87 cultmath: BoundedLeastSquares sheds redundant checks and scratch arithmetic
Measured with Stryker once Solve was actually mutated (21 survivors), most of
them were code that decided nothing:

- The finite-input loops over A and b duplicated the non-finite test on the
  accumulated A^T b, which is exactly equivalent (floats cannot overflow a
  double product, and NaN/infinity never cancel to a finite value). One check
  on each A^T b entry remains.
- The tolerance scale is the gradient at the box point nearest the origin,
  computed inline; the max with |A^T b| duplicated it whenever the origin is in
  the box, and the scratch vector for it is gone.
- Bound violation is state * gradient, which is zero for a free column; the
  three-way ternary duplicated the free-stationarity check.
- The warm-start clamp is Math.Clamp plus a NaN test.
- Workspace slices come off a cursor (Take) instead of hand-summed offsets.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 09:44:04 +02:00
Meta Vens
4072fce0e7 cultmath: BoundedLeastSquares drops the refinement step it cannot observe
Stryker left the refinement step (removed, negated, or subtracted) unobserved,
and it is unobservable for a reason: refinement with a same-precision residual
does not improve forward error, and Cholesky is backward stable, so the
residual the KKT test reads is already at rounding level. Measured on n=24
problems at cond(A) 1e3..1e6, the solver with and without the step gives the
same iteration counts and the same x error against a Gram-Schmidt QR
reference (both limited by cond(A^T A) * eps).

The refinement pass, its dp buffer and the scratch vector are deleted;
WorkspaceLength returns to 4n^2 + 15n. The doc states where the normal
equations stop being optimal (cost gap below 1e-6 |b|^2 through cond(A) 3e5,
about 2e-5 at 1e6, independent of the scale of A) and that a ridge row beyond
that is the caller's policy.

status is now initialised: the unassigned local made Stryker discard every
mutant inside Solve as a compile error, so 255 mutants were never measured.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 09:29:06 +02:00
Meta Vens
6982adfaaf cultmath: BoundedLeastSquares tests pin iteration counts and the b = 0 start
- n=24 fully free problems at cond 1e2/1e3/1e4, and A x 1e6 with unscaled
  bounds, converge in at most two iterations (a regression to cap-spinning
  fails on the count, not on a clock).
- b = 0 from a far start reaches the optimum instead of the start.
- Warm-start clamp table, free duplicate column gets no step, parallel columns
  at absolute scales 1e3 and 1e-3, near-singular free sets confirm once and
  stop, near-collinear optimum at A x 1e6, and zero allocation on the invalid,
  converged and cap paths (first and second call).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 05:36:50 +02:00
Meta Vens
43035bc827 cultmath: BoundedLeastSquares iterates in double and stops on progress
The working iterate now lives in double inside the workspace (WorkspaceLength
4n^2 + 17n); A, b and the API x stay float and x is converted in at the start
and out at the end. The gradient, the free-stationarity check and the KKT test
all run in the double domain, which removes the float noise floor that made the
free-stationarity re-solve spin to the cap on optimal points (cond >= ~20).

A fixed tolerance is no longer the only stopping rule: after a stationarity
miss or a release, a pass that lowers the double cost by no more than rounding
reports Converged.

The tolerance scale is the larger of the gradient at the origin and at the box
point nearest the origin, so it never depends on the warm start; b = 0 with a
far start is no longer judged converged by its own steepness.

Two clauses were equivalent or unobservable and are deleted: the d <= 0 pivot
test (implied by d <= tol * diag) and the dependent[] exclusion from the
free-stationarity check (the progress rule now terminates it).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 05:36:50 +02:00
Meta Vens
50f26de4a7 cultmath: BoundedLeastSquares factors the normal equations in double
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 03:49:29 +02:00
Meta Vens
44affbb9b6 cultmath: scale-free KKT, InvalidInput contract, free-set refinement
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 03:46:36 +02:00
Meta Vens
8b1a1ffde0 cultmath: near-collinear test uses generators that reproduce the gap on the old solver
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 03:44:57 +02:00
Meta Vens
6f8cb642ff cultmath: InvalidInput status and Soul tests (red on the old solver)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 03:43:32 +02:00
Meta Vens
986711c9d5 cultmath: tests for Stryker survivors (dirty workspace, exact caps, spans, near-duplicates)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 03:19:28 +02:00
Meta Vens
32fb7c1503 cultmath: FISTA reference keeps the random KKT test fast
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 02:52:51 +02:00
Meta Vens
c65777b400 cultmath: stop the projected-gradient reference once it stops moving
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 02:33:51 +02:00
Meta Vens
5382b81036 cultmath: widen fixed-column fixture so no other column blocks
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 02:14:39 +02:00
Meta Vens
eec1e40c25 cultmath: pin that a fixed column is never released
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 01:38:56 +02:00
Meta Vens
60c3d6fb0d cultmath: BoundedLeastSquares primal active-set solver
Dense small bounded LS, zero allocation, caller workspace, C#-only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 01:33:08 +02:00
Meta Vens
de57d8dc9c Merge the Idunn TypeScript runtime into main (route continuity Q6)
The operator ruled Q6 (a) on 2026-09-29: this branch lands on main before
route continuity cut C1 changes it. What lands is the ten 09-28 commits
that StreamPixels pins at 542ddd3, plus the Soul-gated fixes:
- runtime presence and daemon-health details are bounded by UTF-8 bytes
  through one helper, made well formed first, matching the Rust owner's
  len() > 512;
- the dead readAuthorityRecordBytes is deleted;
- the TypeScript packages require Node >= 20.

Soul confirmed byte-level wire parity with Idunn's Rust owners on real
Rust-written bundles, including the full write-lease flow. cultnet-ts has
90/90 tests on node:24. Capability-floor split authority, forced active
and the http-only route filter are recorded for C1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 01:21:49 +02:00
Meta Vens
0791e1b7d0 Raise the TypeScript packages' Node floor to 20
Some checks failed
CultMesh portability and performance / ubuntu-latest one-command multiplayer onboarding (push) Has been cancelled
CultMesh portability and performance / windows-latest one-command multiplayer onboarding (push) Has been cancelled
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (push) Has been cancelled
CultMesh portability and performance / windows-latest reactive semantics and leases (push) Has been cancelled
The UTF-8 detail bound calls String.prototype.toWellFormed, which
Node 20 introduced. Node 18 reached end of life in April 2025, and CI
runs Node 24. All four TypeScript packages move together so the family
keeps one floor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 01:21:18 +02:00
Meta Vens
a957f07539 cultnet-ts: one UTF-8 byte bound for presence and daemon health detail
signed-daemon-health sliced detail at 512 UTF-16 units while the Rust owner
counts bytes, so 300 x "é" failed shape validation. truncateUtf8Bytes now
serves runtime presence and both daemon-health sites, replacing
truncateRuntimePresenceDetail. It makes the string well formed first, so a
lone surrogate becomes U+FFFD instead of invalid UTF-8 in msgpack. The
unsigned payload is extracted as unsignedDaemonHealthPayload so it can be
tested without a socket.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 01:19:41 +02:00
Meta Vens
8e578ea2ab cultnet-ts: delete unused readAuthorityRecordBytes
Never called; removed with nothing else depending on it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 01:12:15 +02:00
Meta Vens
8b8650221b cultnet-ts: bound runtime presence detail by UTF-8 bytes
The Rust owner refuses detail over 512 UTF-8 bytes; TypeScript counted
UTF-16 units, so 300 x "é" was signed and then refused, and a slice inside a
surrogate pair produced invalid UTF-8. One helper,
truncateRuntimePresenceDetail, now cuts on a code point boundary at 512
bytes and serves both the publisher and validateRuntimePresence.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 01:12:15 +02:00
Meta Vens
542ddd3e58 Bind runtime presence to route challenges 2026-09-28 03:54:21 +02:00
Meta Vens
265f716391 Expose published Idunn presence snapshots 2026-09-28 03:29:01 +02:00
Meta Vens
02f13527eb Use explicit Ajv ESM entrypoint 2026-09-28 02:51:12 +02:00
Meta Vens
564d5bf732 Fix Idunn runtime presence RUDP publication
Some checks failed
CultMesh portability and performance / ubuntu-latest one-command multiplayer onboarding (push) Has been cancelled
CultMesh portability and performance / windows-latest one-command multiplayer onboarding (push) Has been cancelled
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (push) Has been cancelled
CultMesh portability and performance / windows-latest reactive semantics and leases (push) Has been cancelled
2026-09-28 02:11:12 +02:00
Meta Vens
2c5dc3e278 Decode Idunn identity byte vectors 2026-09-28 01:00:52 +02:00
Meta Vens
5dd25718af Read Idunn identity envelope format 2026-09-28 00:52:03 +02:00
Meta Vens
5697802dbf Read inherited Idunn descriptors directly 2026-09-28 00:35:15 +02:00
Meta Vens
71aa615d93 Allow systemd descriptors in PID namespace init 2026-09-28 00:24:02 +02:00
Meta Vens
21dfd83211 Validate systemd descriptor PID across namespaces 2026-09-28 00:10:26 +02:00
Meta Vens
0bedcdeb37 Add TypeScript Idunn runtime presence publisher 2026-09-27 20:55:49 +02:00
Meta Vens
268e0ef17c Merge cellular, smooth-min gradient and the struct mirror into CultMath (Asura cut 2a-i)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 02:24:47 +02:00
Meta Vens
4922438334 cultmath: correct F6 proof arithmetic and chi-square provenance in cellular docs
Soul pass 3 confirmed two documentation defects: the F6 search-radius proof
misstated 3 - 2m + 3m^2 at m=1/2 as 2.5 (actual 2.75; true min on [0,1/2] is
8/3 at m=1/3), and both math.cs and design.md cited a fabricated 203.8
chi-square statistic for float-bit hashing that Soul could not reproduce
(6+ seeds scored 7.7-30.9). Corrected the arithmetic and replaced the
unreproducible measurement with what actually pins integer hashing: the
oracle and mirror tests, and the chi-square test's real scope (the hash as
cellular calls it, not the call site itself). No test or code changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 02:23:55 +02:00
Meta Vens
05ca826091 tests: pin cellular's HLSL mirror against radius-2, 2^24, and tie inputs
EveryMirrorFunctionMatchesCSharpMath's generic random/special-value cases
never happen to hit a point where a radius-2 cell wins (about 3.5e-5 of
points) or an integer point at or past 2^23, so an HLSL mutant of cellular's
search radius, its lower-bound prune, or the removed F2=0 guard could change
cultmath_cellular's real output and still pass this test.

Adds three targeted point cases, reusing CellularAndSminGradTests' own
fixtures so the mirror test does not carry a second copy of the construction:

- the six points from AxisAlignedRadiusTwoOffsets/FindPointRealizingOffset,
  each of which realizes a different axis-aligned radius-2 offset as F1 or
  F2 in the independent 7x7x7 oracle;
- four integer points at 2^24 (both signs), where distinct cells' jittered
  features can round onto the same float32 value, reaching the F2 = 0 guard;
- one exact F1 = F2 tie point, the midpoint of two adjacent cells' feature
  points (bit-exact-equidistant from both by construction, no search
  needed).

Verified against four HLSL mutants applied to shaders/CultMath.hlsl on
scratch copies (not committed) and reverted after each check:
- `dz < 2` instead of `dz <= 2`: killed by the axis-slice point at
  (-56.671272, -67.01973, -79.001).
- `dx` starting at -1 instead of -2: killed by the axis-slice point at
  (62, -77.91331, -79.42451).
- pruning against f1 instead of f2: killed by an axis-slice point and both
  of the 2^24 integer points (3 mismatches).
- the F2 = 0 guard removed (`grad2 = (p - c2) / f2`, unconditional): killed
  by the (2^24, 2^24, 2^24) integer point (NaN vs the guarded zero).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 23:14:10 +02:00
Meta Vens
91596884b0 tests: fix hashing fixtures, false comments, and gradient tolerance
CellularAndSminGradTests.cs changes for cut 2a-i's pass-2 findings:

- FeaturePoint and the two other independent pcg3d/pcg4d call sites now hash
  int3(cell)/int4(int3(cellId), 0) to match production's integer-hashing fix
  (previous commit); the oracle would otherwise diverge from cellular() and
  every comparison test would fail.

- New JitterAndIdPassAChiSquareUniformityTest: jitter.x/y/z and id each pass
  a chi-square uniformity test (16 bins, 15 df, critical value 37.7 at
  alpha=0.001) over 200,000 cells. This is the behavioural pin for the
  hashing fix; reverting to pcg3d(cell)'s float-bit hash fails it.

- Two false comments corrected to the measured facts: the
  NearestFeatureWins test's claim that |component|=2 offsets are "provably
  never the true nearest" is false (their lower bound is 1, not sqrt(3); Soul
  measured 3 F1 wins in 2,000,000 samples), and SearchReachesEveryAxis...'s
  claim of "lower bound is exactly 2" and "zero hits in 3,000,000" is also
  false (lower bound is 1; Soul measured about 1 hit in 28,000).

- PrunedSearchIsBitIdentical... renamed to
  ...BelowTwoToTheTwentyFifth and its comment states that domain explicitly
  (design.md, previous commit).

- GradientTolerance split into SminGradientTolerance (2.5e-4f) and
  CellularGradientTolerance (1.3e-3f): the two functions have different
  curvature (smin_grad is exactly quadratic in a band; cellular's F1/F2 are
  real distance fields), so a shared 3e-4 tolerance carried almost no
  headroom over either's actual measured noise (re-measured here at 5.3e-4
  for smin's old eps, 2.0e-4/2.6e-4 for cellular), not the "~6e-5" the old
  comment claimed. smin_grad's eps moves from 0.001f to 0.01f (no truncation
  cost there; dilutes float noise 8x) with a matching widened seam-exclusion
  band so p +/- eps cannot step across the seam the check at p already
  avoided. cellular's eps stays at 0.0005f (a small sweep found it already
  near the truncation/noise sweet spot for a real distance field). Both new
  tolerances keep >5x headroom over their own measured worst case and both
  still die under a 1.01x-scale mutant on the returned gradient — smin down
  to 1.0005x, cellular (M9/M16-style, either gradient) down to 1.002x.

- GradientMatchesCentralDifferencesAwayFromTheF1EqualsF2Seam also excludes a
  band around F2 = F3 now, not just F1 = F2: the second-nearest feature's
  identity swaps there too, a genuine kink the old test could silently hit
  (an unfiltered large sample found a 0.78 edge error this way, swamping the
  ~2e-4 the filtered test actually sees).

- AxisAlignedRadiusTwoOffsets and FindPointRealizingOffset are extracted from
  SearchReachesEveryAxisAlignedRadiusTwoSlice as internal fixtures so
  HlslSourceCompatibilityTests (next commit) can reuse the same construction
  instead of a second copy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 23:13:50 +02:00
Meta Vens
cb94b74ae3 docs: correct F6 proof, integer hashing, and the prune's precision domain
design.md follow-ups matching the math.cs/HLSL fix in the previous commit:

- The F6 "5x5x5 is exact" argument is rewritten with the corrected near-side
  neighbour bound (choose the axis closest to its own boundary, not a fixed
  axis), the m <= 1/2 derivation, and the counterexample that showed the old
  argument wrong. Same text as the math.cs comment, so the two do not drift.

- "Integer hashing" now states that cellular hashes the winning cell's
  integer coordinate, not its float32 bit pattern, and records the measured
  chi-square evidence (203.8 over 1,000,000 cells, 15 df, critical value
  37.7) for why the old bit-pattern hash failed a uniformity check.

- "Precision domain" gets a new paragraph: the lower-bound prune's bit
  identity to the unpruned 5x5x5 search holds for |p| < 2^25, not
  unconditionally. Above that, cell + offset itself starts to round in
  float32, so the pruned and unpruned loops can visit different actual
  neighbours at the same nominal offset (Soul, cut 2a-i: 180 of 1,100,000
  points differ at |p| ~= 3.4e7). This is a float32 domain limit on the
  equivalence proof, not a bug in the prune; the committed test's [-50, 50]^3
  sweep is comfortably inside it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 23:13:25 +02:00
Meta Vens
9207a59280 cellular: hash the integer cell coordinate, correct the F6 near-side proof
Soul (cut 2a-i, pass 2) found two defects in cellular's own math.cs source
and its HLSL mirror:

- cellular hashed pcg3d/pcg4d over the FLOAT32 BIT PATTERN of each
  integer-valued cell coordinate (pcg3d(neighbor), pcg4d(float4(cellId, 0)))
  instead of the integer coordinate itself. Two adjacent cells' bit patterns
  share more structure (same exponent, mantissa one increment apart) than
  the integers do going into pcg3d's own mixing, and that structure survived
  into the jitter: measured chi-square 203.8 for jitter.x over 1,000,000
  cells against 15 degrees of freedom (critical value 37.7). Both call sites
  now hash int3(neighbor)/int4(int3(cellId), 0) directly, in C# and in the
  HLSL mirror (both shaders/CultMath.hlsl and the Unity copy, byte-identical
  as before). This changes cellular's noise output — nothing has shipped, so
  that is acceptable, but it is a real behavioural change, not a comment fix.

- The F6 exactness proof's "near-side neighbour" bound was wrong as written:
  picking the near-side neighbour on a FIXED axis is not always within
  sqrt(3) of the query (counterexample: u = (0.5, 0, 0) with the x-neighbour
  jittered to (-1, 1-2^-24, 1-2^-24) gives ~2.06, past the radius-2
  exclusion). The comment is rewritten to the correct argument: choose the
  axis whose u_i sits closest to a cell boundary (m = min(u_i, 1-u_i) <=
  1/2 on that axis), and the near-side neighbour on THAT axis has squared
  distance at most (1+m)^2 + 2*(1-m)^2 = 3 - 2m + 3m^2 <= 3 on [0, 1/2],
  strictly below 3 since the jitter never reaches the boundary. Margin
  2 - sqrt(3) ~= 0.268 (Soul's adversarial check: worst in-box F2 is exactly
  sqrt(3), only in the unattainable corner limit).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 23:13:09 +02:00
Meta Vens
e739ff1e52 tests: kill a single-slice loop-bound gap in cellular's 5x5x5 search
Stryker (mtp, --since 29e50ad3) found 261 survivors / 24 timeouts across the
whole repo; triaging the ones in this fix batch's own new code (cellular's
loop, its prune, cellular_unit) surfaced one real gap disguised as
collateral timeout noise from a batched infinite-loop mutant: dz < 2 (and
the dy/dx equivalents), silently dropping the +2 layer on one axis alone,
passed every existing random-sampled test. That is not the sweeps being
weak: the exactness proof's own margin (F1, F2 always < sqrt(3), radius-2
cells' lower bound is exactly 2) makes a genuine win from any single
|offset component| = 2 cell astronomically rare over uniformly random
points (measured zero hits in 3,000,000 samples).

SearchReachesEveryAxisAlignedRadiusTwoSlice constructs a real example for
each of the six axis-aligned radius-2 offsets directly: for a candidate
integer cell, build the query point that is the closest point of that
cell's own unit cube to the target neighbour's actual jittered feature.
That construction finds a hit for every offset within a couple thousand
candidate cells, deterministically. This kills dz < 2 / dy < 2 / dx < 2 (all
manually confirmed).

Also manually confirmed via Stryker's own reported mutants: the dz--/dy--/
dx-- (increment-to-decrement) mutants are genuine infinite loops, correctly
caught as timeouts. The f2 >= 0.0f mutant (disabling the F2 = 0 guard) is
killed by the existing NoNaNAtIntegerPointsNear2To24 test. The remaining
survivors/timeouts in the new code (the lower-bound prune's >>= vs >
comparison, its arithmetic terms, and cellular_unit's >> vs >>> shift on an
already-unsigned operand) are equivalent mutants: since the per-axis lower
bound is always 0 or 1 by construction, and pruning is a proven-safe
optimization that never changes cellular's output, no mutation confined to
"when does the prune trigger" can be observed by any value-based test.
Verified by hand: disabling pruning entirely reproduces the unpruned
reference bit for bit, as it must.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 21:28:36 +02:00
Meta Vens
4163001c91 Merge surface nets and face-weighted normals into GameCult.Geometry
Asura Cut 2 (GameCult/Asura docs/cut-map.md). CultGeometrySurfaceNets.Extract
emits a welded quad mesh with each quad named by the grid edge it crosses,
(cell, axis), in strictly ascending order; orientation comes from the inside
flag and a per-axis handedness table, one authority. Face-weighted normals
are area-weighted via quad diagonals. Non-finite inputs are rejected.
Stryker is scoped to GameCult.Geometry.

Closed after three Soul passes; byte-identical behaviour across the
closing refactor on 31 fixtures. The 7 GeometryDocumentTests failures
predate this merge (e420410, registry check D11) and are tracked
separately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 21:05:58 +02:00
Meta Vens
3b1ea71eef docs: correct cellular/smin_grad design.md to match fix batch 1
- smin_grad is Quilez's quadratic-polynomial smin, not cubic.
- cellular's search radius is 5x5x5 (F6), not 3x3x3; adds the exactness
  proof and the pruning claim.
- id source is now pcg4d over the winning cell, not the pcg3d hash that
  produced its jitter (F4).
- F2 = 0 is documented as reachable (past |p| of about 2^23), not
  unreachable; removes the false "unreachable" claim and adds a "Precision
  domain" note stating the float32 feature-spacing/tie behaviour honestly
  (F3, minor finding).
- The struct-mirror comparison note now says it also matches field name and
  type in declaration order, not just position (F2).

No entry added to THIRD-PARTY-NOTICES for the Quilez/Worley formulas: per
Self's standing ruling, a published formula is not ported code.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:40:43 +02:00
Meta Vens
f6fb589ac3 Fix batch 1 (cellular/smin_grad): exact search, decorrelated id, finite F2=0
Cut 2a-i fix batch 1, findings F1, F3, F4, F5, F6, F7 (Soul falsification of
the first landing):

- cellular now searches a provably exact 5x5x5 neighbourhood (radius 2), not
  3x3x3: 3x3x3 missed the true F2 for 9 of 400,000 sampled points. The
  comment in math.cs proves 5x5x5 always contains the true F1 and F2 (both
  are always < sqrt(3), every excluded cell is always > 2). A cheap
  position-only lower-bound prune skips the pcg3d/jitter/distance work for
  cells that provably cannot beat the running F2, without changing any
  output bit (F6).
- F2 = 0 now gets the same degenerate-gradient guard F1 = 0 already had,
  fixing a NaN that was reachable past |p| of about 2^23 (F3).
- id now comes from pcg4d over the winning cell alone, never from the pcg3d
  hash that produced that cell's jitter, so id and jitter are no longer
  deterministic functions of each other (F4).
- cellular_unit maps the top 24 bits instead of a plain divide-by-2^32, so
  the mapping is always inside [0, 1); the old formula rounded
  0xFFFFFF80..0xFFFFFFFF up to exactly 1.0f (F7).
- Central-difference tolerances tightened from 0.02 to 3e-4 (measured float
  noise at the committed eps is about 6e-5), and cellular's own gradient
  test now differentiates cellular's RETURNED .w values instead of a
  duplicate FindF1F2 search, so a mutant that corrupts only the returned
  gradient can't hide behind an independently-computed value (F1, F5).
- The struct-mirror comparison in HlslSourceCompatibilityTests now checks
  field name and declared type in declaration order, not just position, so
  a reordered-and-relabelled mirror struct (H2) or a renamed/retyped one
  (H3) both fail the walk instead of surviving on values alone (F2).
- FindF1F2 widened to a true independent 7x7x7 brute-force oracle; new tests
  pin production's exact agreement with it, the pruned search's bit-for-bit
  equivalence to an unpruned 5x5x5 reference, no NaN at 2^24, and id's
  correlation with jitter under 0.05.
- Comment/doc fix: smin_grad is Quilez's quadratic-polynomial smin, not
  cubic.

Both shaders/CultMath.hlsl and unity/.../Shaders/CultMath.hlsl stay
byte-identical.

Manually verified mutants M9 (grad1 x 1.01), M16 (grad2 x 1.01), and M18 (id
from hash.y) all die against these tests; H2/H3 struct mutants die via the
new comparator tests. Local dotnet test: 191/191 passing. FXC cs_5_0 compile
of a scratch kernel using both cultmath_smin_grad and cultmath_cellular
succeeds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:40:28 +02:00
Meta Vens
d64de1b69d fix: reject non-finite isoValue and origin in Extract
samples and cellSize were validated but isoValue and origin were not:
iso = NaN or +inf silently returned an empty mesh, and origin = NaN gave
NaN positions. Extend Extract's existing top-of-method validation authority
to reject non-finite isoValue and non-finite origin components with
ArgumentException, matching the existing style, with tests for each
component.

Overflow at extreme magnitudes (cellSize ~3e38, or finite samples whose
interpolation overflows to inf) is a separate, float-domain limit rather
than a validated-input error; documented in the XML doc and provenance
notes, deliberately not range-checked.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:31:44 +02:00
Meta Vens
bef3d3eba3 refactor: replace TryGetOtherEndpoint's dead bool with a structural loop bound
TryGetOtherEndpoint's bool return was dead control flow: when a mutant
flipped its `return false` guard to `return true`, `other` stayed a clone
of `coord` (only one component would have been incremented on the true
path that never runs for that guard), so the caller compared a sample
against itself and behaved identically either way. That produced the
equivalent survivors at Extract's :92 continue and the guard's :198
return.

Bound the edge loop with the same condition (coord[axis] < cellCounts[axis])
directly, computing `other` inline once the bound holds. One path instead
of two that happened to agree; the helper is gone, and so is the `continue`
that used to depend on it. Behaviour is unchanged: verified with a
before/after run of the existing suite plus a temporary harness comparing
Extract's Positions/Quads/QuadEdges against ca220a5's code on sphere,
non-cubic, open-plane (each axis), checkerboard, and minimal 2x2x2
fixtures - all byte-for-byte identical.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:30:19 +02:00
Meta Vens
b4db5cb6ed test: cover CultGeometryGridEdge.Equals(object) behaviourally
Mutating obj is CultGeometryGridEdge other && Equals(other) to && true
passed the suite: nothing exercised object.Equals directly with a mismatched
X/Y/Z/Axis, null, or an unrelated type. Add a behavioural test that checks
each component independently, rejects null and other types, and agrees with
the typed Equals and GetHashCode.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:29:00 +02:00
Meta Vens
15d172df3f docs: state the actual quad-winding rule in SURFACE-NETS-PROVENANCE.md
The provenance doc still described the deleted geometric winding rule
(diagonal cross product must have a positive dot product with the
inside->outside direction) as the spec. That rule was replaced by the
inside-flag times per-axis handedness table (OrientationSign: +1, -1, +1;
axis 1's perpendicular pair (X, Z) is left-handed) when the geometric
fallback was deleted. State that as the actual rule, with the
outward-cross-product property following only on non-degenerate quads.
Also record that non-finite isoValue/origin are rejected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:28:21 +02:00
Meta Vens
396b2996a5 docs: correct the mutation-testing.md false-survivor claim on OrientationSign
The `==`->`!=` and `< 0`->`> 0` mutants on Extract's orientation check are
both Killed. The one survivor, `OrientationSign[axis] <= 0`, is an
equivalent mutant, not a coverage gap: OrientationSign only ever holds ±1,
so `< 0` and `<= 0` agree on every value it can produce. No vstest
coverage-based false survivor has actually been confirmed for
GameCult.Geometry; remove the withdrawn claim and keep the correct notes
about full-SHA --since, safe.directory, and config-change scope fan-out.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:27:43 +02:00
Meta Vens
ca220a5d35 Record a confirmed vstest false survivor on the new orientation authority
Stryker's diff-scoped run against this cut reported CultGeometrySurfaceNets
.Extract's `if (inside == (OrientationSign[axis] < 0))` - the sole authority
for quad winding after F4's fix deleted the geometric cross-product fallback
- as a Survived Equality mutation (== -> !=). Hand-mutating it and running
dotnet test directly failed 4 of 38 SurfaceNetsTests (the three per-axis
corner-order tests and the outward-winding test), confirming it is a real
vstest coverage-based test-selection miss, not a genuine gap. Recorded this
as a second confirmed case alongside the mtp/static-readonly-initializer
caveat, per this cut's verification requirement to hand-confirm any survivor
that looks like it must be covered.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 19:58:54 +02:00
Meta Vens
1add078eea Delete cellular's unreachable F2=0 guard instead of leaving it undefended
Stryker (scoped to this cut's diff) survived two mutants on the grad2 ternary
in math.cellular: forcing the true branch unconditionally, and loosening
f2 > 0.0f to f2 >= 0.0f. Both are real findings about the code, not the
tests: F2 = 0 needs two distinct integer cells' pcg3d-jittered feature points
to land on the exact float32 value in every component, which the jitter's
~24-bit float precision makes unreachable for any p this function is
actually called with. The spec only asks for the degenerate guard at F1 = 0;
F2's copy of it was added for symmetry and had no reachable case to defend.

Deleted the guard (grad2 is now a bare (p - c2) / f2) in math.cs and both
HLSL mirrors, and corrected design.md's invariant-8 section, which had
overstated this as symmetric with F1's guard.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 19:47:36 +02:00
Meta Vens
13a11ed64a Correct F2's vstest survivor triage in mutation-testing.md
The three TryGetOtherEndpoint survivors were triaged as a vstest coverage
false-survivor, mirroring the confirmed mtp/static-readonly caveat above.
That triage was wrong: the actual mutant is `return false` -> `return true`
inside each per-axis guard, which is equivalent (it makes the method report
an edge going nowhere, so the caller compares a sample against itself and
skips it exactly as it would with a correct `false`). Rewrote the paragraph
to say so, and removed the unsupported claim that this project has a
confirmed vstest false-survivor case. Also recorded, per this cut's
verification requirements, that `--since` needs a full commit SHA and that
`safe.directory` must be set in a container.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 19:30:29 +02:00
Meta Vens
6a4b0ed21f Add tests for surface-nets F1/F3/F4/F6/F7 mutation findings
- Fixed An_extremely_small_edge_delta_falls_back_to_the_edge_midpoint (renamed
  A_tiny_but_normal_edge_delta_...): it pinned the wrong value (1/6) for the
  fallback that F3's production commit deleted; now asserts the exact
  interpolated value, hand-derived in double precision independently of
  Interpolate. Added a genuinely subnormal-delta sibling test.

- Added two more vertex-placement fixtures (crossing counts 4 and 6) to kill
  the sum/count -> sum/3 mutant, each with a hand-derived expected mean.
  Counts of 1 and 2 from the finding are not used: a single cell's 12 edges
  form the cube graph Q3 (3-regular, 8 vertices), whose edge-cut size for any
  corner sign pattern is never 1 or 2 for any nonzero cut (minimum nonzero
  cut is 3, already covered by the existing symmetric/unequal fixtures) -
  see the fork note in this cut's report.

- Added three corner-order pinning tests (one per axis), each asserting the
  exact welded vertex at each of Quads[0..3] for a chosen edge against a
  hand-computed position, including the axis-1 v1/v3 swap that F4's fixed
  OrientationSign now performs. These kill the axis-0-mirrored, c0/c2-swapped
  and equivalent per-axis corner-order mutants.

- Added four degenerate-quad orientation tests (sample exactly at iso,
  large-origin/tiny-cellSize sphere, octahedron at iso, tiny-tolerance
  sphere) asserting every shared undirected edge is traversed in opposite
  directions (net zero), via a new AssertEdgeDirectionsBalance helper that
  also covers the 4-use case. Restricted the existing
  Every_quad_winds_outward_from_inside_to_outside test's geometric check to
  non-degenerate (non-zero cross product) quads, per spec.

- Added Grid_edge_inequality_is_the_negation_of_equality (F6: != is compiler-
  mandated alongside ==, and needed its own direct test) and
  A_non_finite_sample_is_rejected (F7: NaN and +-Infinity samples).

All 38 SurfaceNetsTests pass; the 7 pre-existing GeometryDocumentTests
failures from CultLib commit e420410 are untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 19:30:14 +02:00
Meta Vens
967142e413 Fix surface-nets mutation findings F1/F3/F4/F7/F8 (production code)
F1: Extract's locals (position, v0..v3) were assigned through pattern-match
and short-circuited out-var chains whose definite-assignment analysis breaks
under a single mutated operator, so Stryker's Safe Mode dropped all 98
mutants in Extract as compile errors. Restructured to plain null checks and
a for-loop over a uint[4] so every local is unconditionally assigned; no
mutation of a boolean operator can make it fail to compile.

F3: deleted the `<= 1e-20f` fallback in Interpolate. A straddling edge always
has delta > 0 (.NET preserves subnormals), so the guard only ever replaced a
correct interpolated value with a wrong 0.5 midpoint.

F4: deleted the per-quad geometric cross-product winding test. Orientation
now comes only from the topological inside/outside flag plus a fixed
per-axis handedness sign (OrientationSign; axis 1's (X, Z) perpendicular
pairing is the one left-handed case, X x Z == -Y). This is now determined
purely by which endpoint of the crossing edge is inside and the axis, never
by quad geometry, so degenerate (zero-area) quads orient consistently with
their neighbours instead of falling back to an unstable dot-product sign.

F7: Extract now rejects any non-finite sample (NaN or +-Infinity) with
ArgumentException, in the same style as the existing shape/cellSize checks.

F8: collapsed three duplicated (edge, axis) mappings - TryGetOtherEndpoint's
per-axis switch, the three Axis*EdgeStarts tables plus the axis==0?1:0
ternaries in AccumulateAxisEdges, and TryGetQuadCells' three axis-specific
corner blocks - into one PerpAxes/PerimeterLoop table pair, used by all
three. Replaced the three duplicated `<= isoValue` inside tests with one
IsInside predicate. Deleted the per-axis trailing `InRange(x, cellsX)` /
`InRange(y, cellsY)` / `InRange(z, cellsZ)` checks (provably always true,
since TryGetOtherEndpoint already guarantees the edge's own-axis coordinate
indexes a valid cell) and the explicit TryGetValue+throw invariant guard
(the four cells around a crossing edge are proven to always have a vertex;
a direct dictionary index now fails loudly via KeyNotFoundException if that
invariant is ever actually violated, with no reachable branch to maintain).

F6: deleted CultGeometryGridEdge's <, >, <=, >= operators (CultGeometryQuadMesh.cs).
Nothing in production or tests used them; ordering goes through CompareTo.
!= is kept only because C# requires it whenever == is defined (CS0216).

Builds clean; does not touch CultGeometryIsoSurface.cs, CultGeometryDocuments.cs,
or ISOSURFACE-PROVENANCE.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 19:29:45 +02:00
Meta Vens
08b0dced53 Fix flaky central-difference assertions: use absolute tolerance, not rounding
dotnet test found two failures where a fixed-decimal-place Assert.Equal
snapped two numbers that already agreed to within about 2e-5 to different
rounded digits, because their true difference straddled a rounding boundary
(0.824987829 vs 0.825006127 -> 0.82 vs 0.83). Both were central-difference
checks (smin_grad's own invariant-8 property test, and cellular's gradient-
matches-central-differences test), not a correctness bug in the primitives.

AssertWithinTolerance replaces the precision-based assertion for both central-
difference comparisons with an explicit absolute-difference check, which
states the actual claim (agreement within the finite-difference step's own
truncation error) without the rounding-boundary artifact. Also dropped an
unused local function (F2At) the build flagged.

Found by the third ygg-verify.sh test run on 77c3580.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 19:28:36 +02:00