sots-re/findings/subsystems/value-domain-census.md

7.2 KiB

The value-domain census: a third of the save format has only ever held one value

Owner / date: integrator · 2026-09-09 · host only, no VM Instrument: mars::stream::DomainArchive (sots-engine/src/mars/stream/probe.h), tests/mars_stream/test_domains.cpp, run over the 22-save corpus.

Why this exists

Board row 402 predicted that lane AC's two new saves would break the coverage ratchet: they carry CnRad, tscr = 253, three rt route records, and a StarFleet whose LocID is a TradeSector id — the first fleet in the corpus located anywhere but a star.

The prediction was wrong, and being wrong is the finding. Both saves parse --strict clean, round trip byte-identically, and score 100.0% typed. The ratchet did not move a hair.

The reason is structural, and it is the same shape as the defect the resolver found in lane Z's RNG ledger. CoverageArchive is indexed by shape: it asks "does a field name this item". Lane Z's audit was indexed by firing: it asked "did a draw happen here in a state we ran". Both instruments are sound at what they measure, and both are blind along the axis where the surprises have actually been coming from. A save can carry content nobody has ever modelled and still score 100%, because the novelty lands in a field that was already typed. LocID holding a trade-sector id is still a LocID.

So the value-axis companion asks the other question: what has the corpus ever put in this field?

The number

Over 22 saves:

724 field(s) observed: 490 vary, 234 constant across the whole corpus

234 of 724 typed fields — 32% — have been seen holding exactly one value. We call this format "99.99% typed" and mean it, but for nearly a third of it the typing has never been tested against more than a single observation. tscr was one of those 234 until Commerce Raiding moved it, and every lane that read 252 and inferred a fact about the game was reading a fact about our save set (rule 28, practice 3).

This is rule 23's "thin coverage" with a number on it, and rule 8's eighteen fields generalised: those eighteen split a roster identically because nothing exercised them. There are 234.

Where the constants cluster

Parent Constants Reading
root.Sim.Player 24 incl. 11 in dipstats — no corpus game ever ran diplomacy to a treaty
root.Sim.Sys 19
root.Sim.SvSctOb.EncObj 16 the random-encounter object; no corpus game triggered an encounter
root.CD 13
root.Sim.spymgr.spy 12 see below — this is lane AS's ground
root.Summary.Players.Slot 11
root.CD.CmbR.CRPlSv2.TacReports 9 tactical combat reports never populated
root.Sim.trdmgr.rt 5 trade routes — even with AC's three rt records

Two individually suspicious constants, both non-zero and non-round, i.e. written by something:

  • root.Sim.spymgr.spy.atto = 3728 in all 22 saves.
  • root.Sim.SvSctOb.EncObj.infest.mtrn = 2147483647 (INT32_MAX) — a sentinel, almost certainly "no turn scheduled".
  • root.CD.researchBoostFraction = 0.999215 constant, with researchBoostSpend = 216383.

Direct consequence for the lanes now running

Lane AS (the spy half) is looking for the detection roll gated inline in vslot13. The census says the entire spy sub-object is unexercised: sdet = -1, cbh = 0, cm = 0, cmo = 0, deat = 0, sdo = 0 in every save we own. sdet = -1 is a sentinel in the position a detection result would occupy. That is consistent with the gate never having fired in the corpus, and it means the corpus count for any spy-path predicate is 0 of 22 before AS starts — which is what rule 28 asks a lane to state before it calls a zero a negative.

Lane AG (the gate-indexed audit) gets a corpus-count column it can compute mechanically rather than by hand: for a decoded predicate over save fields, the census already knows which fields never vary, so any predicate resting on one of the 234 has a corpus count of 0 by construction, and its "unread gate" status is not a matter of opinion.

What the instrument is not

It does not know why a field is constant. .Version is constant because we can only read one format version; .GameType because every corpus save is single-player. Those two are whitelisted as explained. The other 234 are unexplained, which is a statement about our reading, not an accusation against the game. A constant field is a coincidence until its writer is found — the census names the candidates; it does not resolve them.

It also folds array elements into one domain per field, deliberately: twenty fleets sharing a LocID kind is the observation we want, not twenty rows.

Ratchet

kVaryingBaseline = 490, on the count of fields with observed variation. Growing the corpus must exercise more of the format, never less; a drop means a save was removed or a shape stopped being reached. Raise it deliberately (rule 27). The constant count is printed but not ratcheted — it is not monotone, since a newly typed body can add constants as easily as it adds variation.

Full list: SOTS_DOMAIN_ALL=1 SOTS_SAVES_DIR=<corpus> mars_stream_test_domains.

Proposed board rows

| the coverage ratchet is blind along the value axis | verify | verified | high | 100% | 2026-09-09 | Board row 402 predicted AC's two saves would break the ratchet. They did not - both score 100.0% typed and round trip byte-identically, because their novelty (LocID at a TradeSector, tscr = 253, three rt records) lands in fields that were already typed. CoverageArchive is indexed by shape, exactly as lane Z's ledger was indexed by firing: both instruments are sound at what they measure and both are blind where the surprises come from. New instrument DomainArchive + test_domains measures the other axis | | 234 of 724 typed fields have only ever held one value | verify | verified | high | 100% | 2026-09-09 | The census over 22 saves: 724 fields observed, 490 vary, 234 constant across the whole corpus - 32%. tscr was one of the 234 until a one-turn tech moved it. Clusters: Sim.Player.dipstats 11 (no corpus game reached a treaty), SvSctOb.EncObj 16 (no corpus game triggered a random encounter), spymgr.spy 12, CmbR.CRPlSv2.TacReports 9 (tactical reports never populated). Individually suspicious because non-zero and non-round: spy.atto = 3728, EncObj.infest.mtrn = INT32_MAX (a sentinel), CD.researchBoostFraction = 0.999215. This is rule 23's thin coverage with a number on it and rule 8's eighteen fields generalised. Ratchet at 490 varying; see findings/subsystems/value-domain-census.md | | the spy sub-object is unexercised: 0 of 22 before lane AS starts | verify | verified | high | 100% | 2026-09-09 | Direct input to lane AS. The census reads spymgr.spy.sdet = -1 in all 22 saves, with cbh/cm/cmo/deat/sdo all 0 - sdet = -1 sits in the position a detection result would occupy and is a sentinel. So the corpus count for any spy-path predicate is 0 of 22 before the lane takes a single measurement, which is exactly the statement rule 28 requires before a zero may be called a negative. Also gives lane AG a mechanically computable corpus-count column: a predicate resting on any of the 234 constants has count 0 by construction |