CultWitnessContractPin, CultWitnessArtifactEntry and CultWitnessTimingEntry get
an id slot. The JSON schema contract and the Python schema copy gain an optional
id per element, and the Python and TypeScript fixtures carry and assert ids.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
[CultElementId(nameof(Member))] declares an id derived from that member's
invariant text, never random, so content-addressed elements keep encoding to the
same bytes. CultMeshCdnChunkRef derives its id from Offset; PackArtifact sets
it, the Rust pack_content writes it, and the committed content vectors are
re-cut once so both runtimes carry it. Engine tests drive Problems and Assign
directly (InternalsVisibleTo the Caching tests).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reconcile's withdrawal sent an untyped error with no subscription id and left the
peer holding the rows the subscription had delivered. It now sends removals for
those rows, then a variant_unsupported error whose details carry
field "subscriptionId" and the id (the existing details slots; no new wire key).
The subscribe and per-change paths use the same code. The error schema and the
Rust CultNetErrorCode accept variant_unsupported, so a Rust peer decodes it
instead of refusing an unknown code.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The four hard-coded sets become flags, with @file response-file splicing.
A consumer keeps its own .args file instead of committing a patch against
the example. fixture.args carries CultLib own sets; the README documents
the flags. Committed fixture bytes are unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
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>
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>
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>
`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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>