Commit graph CultLib/packages
Author SHA1 Message Date
Meta Vens
e382bb4e20 Merge variants C0: every reader refuses what C1 will write, by name
C#, TypeScript, Python, Rust and CultMesh refuse an unknown
cultcache.store.* header and a fifth record slot, naming the version or the
key and schemaId. The silent slot-skipping and C#'s ignored header are
deleted, and CultMesh's second record decoder is gone. Shared vectors are in
tests/vectors/document-variants-c0/. v1 stores stay byte-identical. At base,
the single-file commit's read-modify-write rewrote a v2 store as v1, which
lost data; it now refuses.

Soul passed it. Cross-runtime interop was run and passes. Three C#
Caching tests fail on Linux at both base and tip: they are Windows
share-mode tests (a FileShare.Read handle blocking delete/replace), which
cannot pass on Linux.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 03:52:07 +02:00
Meta Vens
6251cbcd37 Merge BP-1: the content plane in cultnet-rs
The Rust runtime gains the reference's content chunk messages on the raw
path, CultMeshCdnArtifactManifest with wire-ordered metadata,
normalize_hash, pack_content, validate_manifest,
answer_content_chunk_request (every bad request is answered, byte
identical to the reference server) and fetch_content (one owner of
receiving-side verification; refuses zero-size floods and unnormalised
hashes before any request). Vectors run both ways between C# and Rust.
The operator ruled Q-BP1 A: chunks travel on the session already held.
This is the explicit Rust content path until an authenticated connector
exists.

Soul passed it after two fix batches. cultnet-rs: content 24, cultnet 53,
rudp 7, lib 110. cargo-mutants on content.rs misses 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 03:31:20 +02:00
Meta Vens
5b538aaf07 Answer chunk requests whose string fields are nil
A C# caller that leaves ChunkHash, MessageId or RecordKey null writes msgpack nil; the
reference reads it back as null, treats it as blank and answers found:false. Rust now
decodes nil as blank in content chunk requests, so those requests are answered with the
reference bytes instead of dropped. Three nil vectors from the reference server are
judged by decoding them in Rust. Record two content-plane limits in the parity scope.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 03:29:41 +02:00
Meta Vens
5ed9dbbcb8 cultnet-ts: stop exporting signIdunnRuntimePresence; document two limits
The function took state and lease digest from the caller, so anyone holding the
authority could sign active without reportHealth or assertWriteLease. It is now
private to the signer. Test: the subpath and root do not expose it. Docs:
waitForWriteLease throws out of publish if health is already non-warming;
MAX_RECENT_WARMING_PROOFS=64 is a recorded limit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 03:26:10 +02:00
Meta Vens
322b7c815a cultnet-ts test: read the signed document, not the SignedRuntimePresence wrapper
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 03:15:40 +02:00
Meta Vens
06d250070a cultnet-ts: add an idunn-runtime subpath export that does not load dgram
`cultnet-ts/idunn-runtime` exposes the signer and loader alone. Web apps import
it instead of the package root, which also carries the Odin publisher and
node:dgram. The root export is unchanged. Test loads both under a stubbed
dgram: the subpath loads, the root fails.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 03:13:37 +02:00
Meta Vens
67b8ae3eda cultnet-ts: freeze the Idunn authority and allow one signer per authority
The loaded authority is deeply frozen (bind, health contract, Expected,
Activation, identities), so a caller cannot steer what the signer signs; the
typed Buffers that could not be frozen are dropped (Expected canonical bytes,
Activation public key kept as hex only for the load-time comparison). A second
createIdunnRuntimeSigner on the same authority throws, so two signers cannot
emit sequence 1 under one runtimeInstanceId.

Tests: stateful set pinned against Rust (warming, active, degraded), lease
accepted for a route-answered warming, refused for another warming and another
target, one signer per authority, frozen authority.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 03:13:28 +02:00
Meta Vens
8072314b7c cultnet-ts: the Idunn signer owns the reported health and warming proofs
The signer holds the last reported health, starting at warming, and gains
reportHealth(state). sign(detail), answerRouteObservation(request) and the Odin
publisher's publish(detail) no longer take a state, so the route answer and the
Odin publish cannot disagree and a fresh signer cannot answer active. Every
warming presence the signer signs becomes a valid write-lease target; the
publisher's recordPublishedWarming is deleted. Tests now assert every committed
stateless vector, dead ones included.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 03:11:48 +02:00
Meta Vens
4ec75eec18 cultnet-ts: regenerate Idunn runtime fixtures with the stateful lease-bound set
Output of the Rust generator (3c9ce38) on Yggdrasil. Every set is re-enrolled, so
all bytes change; adds service-stateful with lease.cc, the two wrong leases and
the warming/active/degraded vectors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 03:10:55 +02:00
Meta Vens
3c9ce3823c cultnet-rs fixture example: add a lease-bound stateful Idunn set
Adds `service-stateful` (streampixels-service shape, state lineage set,
write_lease_required) with a Rust-issued lease.cc, warming/active/degraded
vectors bound to it, and two wrong leases (other warming digest, other target).
Each vector still passes authenticate + correlate before it is written.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 03:05:12 +02:00
Meta Vens
a7eb9967e9 Pin the metadata decode message so the last content.rs mutant is caught
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 02:59:59 +02:00
Meta Vens
36aa8d8791 Rust: drain the header peek so rmp-serde accepts the array
The peek left snapshot slots unread and rmp-serde refused the length
mismatch, so every store fell through to the legacy decode.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 02:34:11 +02:00
Meta Vens
16432d4b1e Fix the BP-1 findings in the cultnet-rs content plane
A hostile manifest of empty chunks no longer becomes a request flood: fetch_content
refuses a zero-size chunk beside other chunks, and settles the content hash and every
chunk hash (normalised, non-empty) before the first request.

Every bad chunk request is now answered found:false in the reference handler's
validation order, never refused at decode. validate_message no longer refuses content
chunk requests or responses, so a blank message id or hash, a prefix-only hash and a
negative size reach answer_content_chunk_request. The answer follows the reference's
double normalisation (handler, then CreateRecordKey), which fixes the prefix-only
hash spelling to ArgumentException on 'chunkHash'. The cs vectors gain nine
reference-server answers (blank and whitespace id, blank, whitespace and prefix-only
hash, negative size, record-key case, blank record key, double prefix) and a
metadata-order manifest; Rust answers all of them byte-identically.

Manifest metadata is Vec<(String, String)> in wire order and refuses a duplicate key,
replacing the BTreeMap that re-sorted keys.

Tests pin the surviving mutants: unusable chunk sizes, the payload-length and
claimed-size halves of the chunk check, stable sort at equal offsets, the normalised
hash and canonical key actually sent, and the kind/mime defaults never trimming.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 02:12:43 +02:00
Meta Vens
795fd1023c cultnet-ts: split Idunn authority from the Odin publisher; answer route challenges locally
idunn-runtime-authority.ts now loads and signs only: no Odin endpoint, no Odin
dependency check, no RUDP. createIdunnRuntimeSigner signs presence and answers
Idunn's route challenge with answerRouteObservation(request, state), a
synchronous local signature in the state the app reports. The RUDP publisher
moves to idunn-odin-presence-publisher.ts for services that declare
odin.verse-rendezvous, and keeps the Expected-endpoint check.

Deleted: the TS capability-minimum refusal (the Rust correlation records the
typed disagreement), latestPresenceDocument, the forced "active", and the
publisher's redundant health-contract and bind checks. Route validation now
accepts http, tcp and rudp as Rust does; the machine-id falls back to
/var/lib/dbus/machine-id. Tests run against the Rust-written fixtures.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 02:05:14 +02:00
Meta Vens
6b107c1adc cultnet-ts: Rust-generated Idunn runtime fixtures
Expected, Activation, identities and signed presence vectors from the cultnet-rs
example; the README says how to regenerate them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 02:05:14 +02:00
Meta Vens
95f04c1efb Rust: name the record and the version when a store is refused
PersistedRecord refuses a fifth slot naming its key and schema id, and an
unknown cultcache.store.* header no longer falls through to the legacy
envelope decode that hid the cause.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 02:04:34 +02:00
Meta Vens
c8777fe08d Python: refuse unknown store headers and extra record slots by name
Delete the raw_record[:4] slice and the legacy-envelope fallthrough for
unknown cultcache.store.* headers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 02:04:34 +02:00
Meta Vens
569d668141 TypeScript: refuse unknown store headers and extra record slots by name
The store reader and the inspector share one header and record-slot rule;
an unknown cultcache.store.* header no longer falls through to the legacy
envelope decoder.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 02:04:34 +02:00
Meta Vens
9ba6ac4a2e cultnet-rs fixture example: name an Odin port a dev machine does not run
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 01:44:04 +02:00
Meta Vens
f5c597a81d cultnet-rs fixture example: remove the store lock files it leaves
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 01:40:30 +02:00
Meta Vens
0e7d2204cd cultnet-rs: example generating Rust-owned Idunn runtime fixtures
Real enrolled identities, a validated Expected, an Activation issued by
IdunnRuntimeActivationLaunch, bundle files through the CultCache store, and
runtime-presence vectors signed by the Rust signers and run through
authenticate/correlate before they are written. cultnet-ts tests will commit
the output.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 01:32:57 +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
11485fb491 Pin the content plane against the reference's bytes
content-vectors.cs-written.json is written by CultMeshContentVectorTests on
Linux, and includes the answers CultMeshLegacyRudpContentServer gives, so the
failure spellings are the reference's. tests/content.rs judges it. The
rs-written mirror follows in the next commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 00:49:58 +02:00
Meta Vens
29c3e4c2b5 Add the content plane to cultnet-rs: chunk messages, pack, answer, verified fetch
ContentChunkRequest/Response join CultNetMessage on the raw path, byte
identical to the reference's field order. content.rs owns normalize_hash,
pack_content, validate_manifest, answer_content_chunk_request and
fetch_content, mirroring CultMeshCdn and CultMeshLegacyRudpContentServer.
The C# vector test is added here; the vector files and Rust tests follow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 00:48:13 +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
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
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
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
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
Meta Vens
77c35808f5 Fix a build break: bare Random resolved to CultMath's own xorshift32
Inside namespace CultMath.Tests, unqualified `Random` resolves to the
enclosing CultMath namespace's own Random struct (design.md: "Random is
CultMath's own xorshift32") before `using System;` is even considered, so
`new Random(seed)` in the new test file was constructing CultMath.Random,
which has no NextSingle(). HlslSourceCompatibilityTests.cs already works
around this the same way (`new System.Random(...)`); CellularAndSminGradTests
now qualifies every use the same way.

Found by the second ygg-verify.sh build/test run on e613ed6.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 19:23:54 +02:00
Meta Vens
e613ed6277 Fix a build break: float3.zero shadowed by math's own float3(...) method
Inside the static math class, the unqualified name `float3` resolves to
math's own `float3(float,float,float)` constructor-function first, not the
CultMath.float3 type, so `float3.zero` fails to compile there (CS0119). No
prior code in math.cs used the Type.zero static-field spelling from inside
math itself for this reason. Switched cellular's zero vectors to
`new float3(0.0f, 0.0f, 0.0f)`, which is unambiguous under `new`.

Found by the first ygg-verify.sh build/test run on 3eed434.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 19:21:37 +02:00
Meta Vens
3eed434b31 Test smin_grad and cellular's invariants behaviourally
CellularAndSminGradTests pins the rules the cut map calls out, each against a
mutant it must kill:

- smin's value never exceeds the ordinary minimum, and matches min with that
  input's own gradient once |a.w - b.w| exceeds k on either side.
- smin_grad's gradient matches central differences of its own .w at random
  seeded points (the formula is exact, not an approximation, so no exclusion
  band is needed away from the |a.w-b.w|=k seam itself).
- A direct case falsifies blending the gradient with a constant 0.5 or with
  (1 - h) instead of h.
- cellular's F1 never exceeds F2, matched against an independent brute-force
  Worley search that shares only the public pcg3d hash, not cellular's control
  flow.
- The true nearest feature wins even when an independent search finds it in
  the (1, 1, 1) neighbour offset, the last one cellular's own loop order
  visits, ruling out a "first found" or early-exit mutant.
- The gradient at a feature point is finite zero, not NaN.
- Both nearest and edge gradients match central differences away from a band
  around the F1 = F2 set, where the nearest and second-nearest identity swap
  discontinuously.
- id is constant across small moves that stay in the same feature's region and
  differs from a distant cell's id.

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