findings/subsystems/output-term.md is the whole reading: the call chain with real function boundaries, the formula, the closed list of nine values the data files supply (down from an unbounded fear), the advance prediction with its falsification table, and the live result. Corrects strategic-turn-internals.md 3.3 in place. That block had the SHAPE wrong, not just the detail: a system's output is a sum of three terms, and the function it named as the population base-output term is the over-harvest resource demand. Live on VM140, two builds, two species, both hooks in compare mode: GroupOutput 13,105 calls / 0 divergences, ComputeTotalOutput 11,252 / 1 (one ulp), 0 undeclared writes in 24,357 guarded calls. Thirteen distinct system states, and every unexercised branch is listed rather than counted as covered. tools/max_income_predict.py is the other half: it computes lane Y's bankruptcy-limit oracle from colony state and reports 6 of 25 player-records matching exactly, with the misses all AI-owned and the single-system ones short by exactly the 1.1 difficulty income multiplier.
4.8 KiB
4.8 KiB
tracecmp report: trace-ref.jsonl
- build: output-b48d860-20260908T1527Z started: 2026-09-08T15:32:54Z inline_max: 256
- calls: 14853 compared: 14853 diverged: 4957 invalid records: 0 warnings: 0
- coverage: 14853 guarded call(s), 0 undeclared write(s) in 0 call(s); 0 hook(s) unstated, 0 contradicted
| hook | calls | modes | compared | diverged | errors |
|---|---|---|---|---|---|
| Game::ServerSystem::ComputeTotalOutput | 4951 | compare:4951 | 4951 | 6 | 0 |
| Game::ServerSystem::GroupOutput | 9902 | compare:9902 | 9902 | 4951 | 0 |
coverage
| hook | verdict | compared regions | guards | undeclared writes | unmodelled |
|---|---|---|---|---|---|
| Game::ServerSystem::ComputeTotalOutput | partial | - | guard:system | 0 | 4 |
| Game::ServerSystem::GroupOutput | partial | - | guard:system | 0 | 2 |
Game::ServerSystem::ComputeTotalOutput — not checked by this run
- (high) the station count, as for GroupOutput —
oursassumes zero stations, so a system with an imperial population and a station diverges by the station factor [declared input boundary] - (high) the slave population and its xenotech adjustment — the original's slave count is not a plain field: it runs the count through a per-species xenotech factor.
ourstakes the slave term as ZERO, so any system holding slaves diverges. The record logs the raw group-2 population sums so a divergence can be attributed [declared input boundary] - (medium) the capacity surplus the civilian term adds for the owner's own species — the original calls the carrying-capacity helper twice with different out-parameter slots and adds max(0, B - A) to the civilian count;
oursuses the raw civilian population. The surplus is zero except when the colony is at its cap [declared input boundary] - (low) the addiction phase —
oursassumes it is below 3, so ADDICTION_OUTPUT_MOD never applies; the record logs the system's addiction table length so the case is visible
Game::ServerSystem::GroupOutput — not checked by this run
- claims complete coverage: the imperial and civilian output modifiers, the 1.8 factor and the 500000 divisor are literals inside the executable, so nothing about them is assumed from the data files
- (high) the station count that scales the imperial term — the original gets it from a helper that walks the system's fleets and their ships through virtual calls and takes the system in EBX, which a hook cannot call portably.
ourstherefore assumes ZERO stations. A divergence on an imperial row is expected to be exactly the station factor, and the record carriescountand the return, so the factor is MEASURED from the trace rather than fitted [declared input boundary; the trace makes it recoverable] - (medium) the slave row's output modifier, and every value the data files supply — SLAVES_OUTPUT_MOD, the two morale thresholds and their two modifiers, and STATION_BONUS_IMPERIAL_OUTPUT are read out of the live process's globals and logged with every record, so the record says which value drove it [logged as
tuningon every record]
Game::ServerSystem::ComputeTotalOutput: first 6 of 6 divergent call(s)
- call_id 0 (trace-ref.jsonl:4) ret [exact] orig={"t":"f64","v":9248.450318530804} ours={"t":"f64","v":9248.450318530802}
- call_id 7263 (trace-ref.jsonl:7267) ret [exact] orig={"t":"f64","v":9248.450318530804} ours={"t":"f64","v":9248.450318530802}
- call_id 7272 (trace-ref.jsonl:7276) ret [exact] orig={"t":"f64","v":9248.450318530804} ours={"t":"f64","v":9248.450318530802}
- call_id 7293 (trace-ref.jsonl:7297) ret [exact] orig={"t":"f64","v":9248.450318530804} ours={"t":"f64","v":9248.450318530802}
- call_id 7296 (trace-ref.jsonl:7300) ret [exact] orig={"t":"f64","v":9248.450318530804} ours={"t":"f64","v":9248.450318530802}
- call_id 7305 (trace-ref.jsonl:7309) ret [exact] orig={"t":"f64","v":9248.450318530804} ours={"t":"f64","v":9248.450318530802}
Game::ServerSystem::GroupOutput: first 6 of 4951 divergent call(s)
- call_id 2 (trace-ref.jsonl:3) ret [exact] orig={"t":"f64","v":594.0000236034393} ours={"t":"f64","v":594.0000236034392}
- call_id 5 (trace-ref.jsonl:6) ret [exact] orig={"t":"f64","v":617.7600245475769} ours={"t":"f64","v":617.7600245475768}
- call_id 8 (trace-ref.jsonl:9) ret [exact] orig={"t":"f64","v":617.7600245475769} ours={"t":"f64","v":617.7600245475768}
- call_id 11 (trace-ref.jsonl:12) ret [exact] orig={"t":"f64","v":617.7600245475769} ours={"t":"f64","v":617.7600245475768}
- call_id 14 (trace-ref.jsonl:15) ret [exact] orig={"t":"f64","v":617.7600245475769} ours={"t":"f64","v":617.7600245475768}
- call_id 17 (trace-ref.jsonl:18) ret [exact] orig={"t":"f64","v":617.7600245475769} ours={"t":"f64","v":617.7600245475768} other divergent call_ids: [20, 23, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53, 56, 59, 62, 65, 68, 71, 74, 77] …