2026-10-09 (Dylan): "The most important thing is emergent story. For that we need people that appear to act like people." The suite was rebuilt around that: a fast core that catches a broken city, a behaviour tier that judges whether people act like people, and a short list of god scenarios. Everything else that used to be asserted (calibration bands, multi-seed trajectories, numbers pinned to an older city) is gone.
The doctrine, in three lines:
- Asserts catch broken: a panic, nondeterminism, a coin made or lost, a collapse, a mechanism that never fires.
- Behaviour judges alive: the archetypes' diaries and the person probes are the primary judge.
- Numbers are reports, not gates:
tools/analyze_run.pyprints them fromcitysim-cli run --report.
| tier | what | where | asserts | wall time (this box, release, alone) |
|---|---|---|---|---|
| unit | every hand-built test (one mechanism, a small or seeded world) | citysim/tests/*.rs, citysim-cli |
the mechanism does what it says | ~40 s summed over the binaries (all non-ignored but core_sanity) |
| core | core_sanity (non-ignored) |
citysim/tests/core.rs |
seeds 42-44 x 120 days in threads, one collector: coin identity and escrow_leak every day, collapse bounds, 54 mechanism-existence bullets; ticks/s printed |
~30 s |
| core | core_throughput (ignored) |
citysim/tests/core.rs |
seed 42 alone x 30 days: the 4,000 ticks/s floor (release). Run it alone on a quiet box: parallel builds make the reading meaningless | ~10 s |
| core | core_year (#[ignore]) |
citysim/tests/core.rs |
seed 42 x 365 days, the collapse bounds per 30-day window | ~100 s |
| core | core_20k (#[ignore]) |
citysim/tests/core.rs |
the 20k profile (Scale S4): the generated 20,000-resident city (gen:20000, scaled_to(20_000)), seed 42 x 30 days twice in parallel: the collapse bounds per capita, an employment collapse floor, the coin identity, determinism (the two runs' save hashes); J24 and the per-capita readings printed as FINDINGs; ticks/s printed against its own floor (140) |
~5 min |
| core | determinism and saves | determinism.rs, save.rs |
same seed same hash at a day boundary; a mid-day save runs on byte for byte (plus the hunt, guard and contract rebuilds) | seconds |
| behaviour | shadow --assert / test_behaviour_tier (#[ignore]) |
citysim-cli/src/shadow.rs |
135 per-archetype bounds on the diary metrics (days 19 and 90, seeds 42-44 pooled, up to 30 agents per archetype and seed, each archetype in its own clone), calibrated on the noise of seven config butterflies | ~80 s, ~3 GB |
| behaviour | person probes | citysim/tests/person.rs |
one pinned agent, one stimulus, what a person would do | ~2 s |
| god | 20 scenarios + 3 controls (#[ignore]) |
god.rs, god_corps.rs, god_districts.rs |
the world reacted to the shock (or the command applied), 60 days | ~55 s |
| nightly | Full-vs-Statistical parity (#[ignore]) |
lod.rs::test_full_vs_statistical_within_15pct |
the Statistical tier's rates within 15 % of Full (and the diet rates) | ~3.5 min (the 2,000 city since the hunger-as-sickness P5 port of the recalibration) |
Measured 2026-10-09 (after the off switches retired): cargo test --workspace --release (every non-ignored test, core_sanity included) ran in 1 min 17 s after compile (653 passed, 19 ignored).
- Every change (the per-change loop, ~1.5 min after compile):
cargo fmt --all -- --check cargo clippy --workspace --all-targets -- -D warnings cargo test --workspace --release - Every merge (the loop plus the year, behaviour and god, ~4 min more; run them one at a time, each spawns its own threads):
cargo test --release -p citysim --test core -- --ignored core_year --nocapture cargo test --release -p citysim-cli -- --ignored behaviour --nocapture # or: citysim-cli shadow --assert cargo test --release -p citysim --test god --test god_corps --test god_districts -- --ignored --nocapture - Every milestone (or nightly): the merge set, the parity test, and a human (or model critic) read of the diaries:
plus a shadow pass in the style of
cargo test --release -p citysim --test lod -- --ignored --nocapture cargo test --release -p citysim --test core -- --ignored core_20k --nocapture # the 20k profile, alone (~5 min) citysim-cli shadow --assert --out shadow_out # keeps the diaries under shadow_out/d19, shadow_out/d90docs/SHADOW_V2.md(critics read one diary per archetype and write what would make it real). That read is the final judge of "alive"; the bounds only catch regressions between reads. - Profiling (Scale S1,
citysim/src/prof.rs): build with--features profandrunprints aPROFline per timed site (seconds, share of the tick, calls) at the end;--prof-from DAYzeroes it at that day's start, so the profile is the run's tail. Off (the default build) the timers compile to nothing. A scaled city for measurement:run --population N --scaled [--rph R] [--map FILE](Config::scaled_to).cargo build --release -p citysim-cli --features prof target/release/citysim-cli run --days 30 --report --population 20000 --scaled --rph 50 --map <map> --prof-from 25 > run.csv 2> prof.txt - Big-map paths (Scale S3a):
cargo test --release -p citysim --test big_island -- --ignored --nocaptureruns the synthetic 3,000 × 1,875 island (CITYSIM_BENCH_POP,_DAYS,_FULLfor the population, days andmax_full) and prints ticks/s, the path queries' latency (p50/p90/p99, Manhattan-only against the landmarks), the pathing state's bytes and the peak working set;paths.rs::measure_flow_field_vs_big_path(ignored) prints a flow field's build against an A* query by map size.
core_sanity runs the shipped config (Config::load()) on seeds 42, 43 and 44 for 120 days, each in its own thread, and walks every event by id cursor once an hour into one collector per seed. It fails on:
- the coin identity (
econ::identity:total_coins + Σ outside treasuries − minted) moving on any day; - escrow (M16a):
escrow_leak(ΣContract.escrow−World::escrow_held) non-zero on any day (also judged per window incore_year); - collapse: population outside 1,333..=2,667 on any day; starvation over 200 in 120 days; Assault + Murder events over 42.7 a day; the Treasury below 0 from day 30; the Jail over capacity; every Market empty three days running; fewer than 4 seeded corps alive at the end; any prisoner held at the Statistical tier starving;
- a mechanism that never fired on any of the three seeds (the list below);
- hunger as sickness (on with the flip, always on since P10): starvation over 20 per 120 days (Dylan: a broken free-food floor), and five more bullets (a synth meal, a bin meal, a bin shakedown, a malnourished agent, a sick day). J25 counts adults sleeping on the street (Dylan, 2026-10-10: a squat, a cot or a Hotel bed is a rung); the Dregs and their split print beside it;
- ticks/s under 4,000 on seed 42 (release only). The core binary holds one non-ignored test and cargo runs test binaries one at a time, so the seed runs alone but for its two sibling threads (it reads ~6,000 there; ~10,700 truly alone and idle).
The 20k profile (core_20k, Scale S4, docs/SCALE_20K.md § S4): the same bounds per capita, the counts (population range, starvation, assaults a day, corps alive) times Config::per_capita (10 at 20,000; 1 in the shipped city), the Jail's capacity read from the fitted world. Employment is asserted as a collapse floor, not a calibration band: employed >= 0.40 of free adults from day 30 (S3b's collapsed 20k city read 0.17). J24's 0.5 is printed beside it as a FINDING (the 20k city reads ~0.50 on day 30, the 2,000 city ~0.53: a calibration gap, written up in SCALE_20K.md). Determinism: seed 42 runs twice in parallel threads and the closing save hashes must match. Ticks/s is printed against TPS_FLOOR_20K (140: 189 measured with the two runs in parallel after S4 perf, less ~25 %), never asserted (the box is noisy). The mechanism bullets are core_sanity's, not repeated here. M17 phase 1: the profile runs on an island of Ambient flats ([ambient] flats_per_slot 50 on gen20k) and asserts an Ambient sale (ambient_food_bought + ambient_visits_made > 0) on every day from day 2, the book's one existence bullet (the 2k city has no flats); the book's day (units and visits wanted and bought, coins paid, their share of wages) prints as FINDING lines.
The Ambient tier (citysim/tests/ambient.rs, Scale S5, docs/SCALE_20K.md § S5; unit tier, ~7 s): the shipped city has no Ambient flats (no columns, nothing in the save); a flat gives the same person in any order and a day later; a materialised household is housed, Statistical and paid from the World (the coin identity holds); the labour pool's daily cap; the midnight job search hires from the pool when the city is short; immigration draws Ambient households before the map edge; a save resumes byte for byte; and the parity-style check: a cohort materialised on day 20 is printed against the born-simulated adults of the same class on day 20 and day 30 (needs, mood, wallet median, contacts median, employed share), loosely asserted (a broken materialisation, not a calibration).
The bounds are the v1 sanity trio scaled to 2,000 residents (M10 plan D38), unchanged since M8. core_year runs the same bounds over each 30-day window of a 365-day seed-42 run, the Assault bound at 1.5 x (a 30-day window is noisier than 120 days; seed 42 peaks at 36.3).
The mechanism bullets (61, one per milestone's core mechanism, measured on 42-47; each fired on 4-6 of the 6 seeds): a Marriage, a GangJoin, a Birth, a Burial (M5, M6); a raid resolved, a Home flipped (M8); a Jailbreak, a day in Crackdown (M9); a hole bound (M10); an eviction, an NPC founding, a hostile takeover, a Strike (M11); a Riot, a gang Split, a Squat (M12); a Crash, a chrome Install, a Dealing report (M13); a run, a Data sale, a Flatline (M14); a Feed Story, a Hunt, a Vendetta, a Purist expulsion (M15); a World export, a Collect, a Bout, a gang front, a Mission meal (L2, the Real economy); a BountyPaid, a city guard on the take (M16a phase 3); a Guard fulfilled, a Locate fulfilled, a Fixer in business (open, a record brokered there within 7 days), a Reneged (M16a phase 5, each on 6 of 6; the Hit chain fired on 0 of 6 and is unit-tested: core.rs's comment lists the tests); a protection paid, a blackmail made, a debt called in, a report withdrawn under a threat, a coerced job, a fake job, a Parley (M16b phase 1, leverage; the rare ones, a lapse, a Fraud filed, a poach by threat, a Persuade, a coerced job done, are printed and unit-tested in leverage.rs); an Abduct posted, a Steal fulfilled, a heist run, a Spin posted (M16b phase 2; a hostage taken, a Rescue, an Escort, a Deliver fulfilled, a fabrication planted or exposed are printed and unit-tested in captives.rs); a downed agent, a bleed-out, a stabilisation, a rescue by kin, a downed agent finished off, a Trauma subscription (M16b phase 3; a rescue by a gang-mate, a Trauma call and a TraumaSave are printed and unit-tested in wounded.rs); a board vote, a campaign move, a VoteSwung, a tag planted, a tag found, a scan, a day a faction is distracted (M16b phase 4; a Purist Patrol scan, a board Persuade and a Vote record are unit-tested in tags.rs and governance.rs, the Patrol printed; and the run fails on a day a faction's alertness ÷ pool sits under alert_floor). M17 phase 2: a day with a remittance home and a Subsidised (each on 6 of 6; the parents' failure flag, the Embargo, the tariff and the pre-M17 migration are unit-tested in parents.rs). M17 phase 3: an OutsidersArrived and a StateGrant (6 of 6 each; a Retake, a buy-back, a FoundBranch, a refound, a tour's end and a settling are unit-tested in bodies.rs, a ParentFounded and an Abandoned printed; the phase's BrothersArrived bullet went with phase 4b). M17 phase 4b: a Succession (6 of 6: an acting leader for a jailed one, or a promotion after a death; a CivilWar on 3 of 6, its endings and the brothers' sack-gutting trigger, 1 of 6, are unit-tested in succession.rs). M17 phase 5: none (a mission abroad, a promotion, a Blackwall breach and a trace fired on 0 of 6 and are unit-tested in abroad.rs; names.rs asserts that every name the config uses resolves to a row, the rename hazard). M17 phase 4: a day with scrip paid, a day with scrip converted, a DataShipped and a Sanctioned (6 of 6 each; an uplink run, a Campaign, a parent's death and a scrip wipe fired on 0 of 6 and are unit-tested in weapons.rs, with god_kill_militech and its control). With [transit] enabled (the shipped default since transit phase 3) two more, transit_mechanisms: an El ride and a fare paid to the city (every day on 42-44), and the ride sanity fails the run: a filed ride with its body on none of its legs, or a body aboard past its arrival.
- Run
core_sanity --nocapture: it prints every event kind's count and every mark per seed. - If the mechanism is an event kind, add
("a Thing", k(EventKind::Thing))tomechanisms()incore.rswith the 42-44 counts in a comment. If it needs more than a kind (an event's text, a day-end state, aDayRowcolumn), add a mark inrun_seed(Some("my mark")in the event match, orday_mark(...)at the day's end) andm("my mark"). - Rare mechanisms get a unit test instead. If it fires on 2 or fewer of 6 seeds (42-47) in 120 days, do not add a bullet (it will flip with every unrelated change). Write a unit test in a seeded world that sets up the preconditions and calls the system directly. Examples:
tech.rs::test_research_held_builds_a_lab_in_the_focus(a Lab under Research, 2 of 6 seeds),camp.rs::test_unfed_child_taken_at_take_days_before_death(a child taken, 0 of 6), the contract posting tests incontracts.rs(1 of 6). A bullet that turns out rare is demoted the same way and keeps its per-seed counts as a printedFINDINGline (core.rs::printed_mechanisms): a Riot and a gang front (hunger as sickness P5i, 2026-10-10: both read [0, 0, 0] on seeds 42-44 under a one-step butterfly with the change off, and fired under other butterflies with it on), nowdistricts.rs::test_riot_fires_at_streak_and_picks_target(the Riot event at the streak, none a midnight short) andleisure.rs::test_front_built_under_expand_in_held_districtwith its negativetest_no_front_without_expand_or_treasury; and a Jailbreak (the flipped city after the recalibration: [0, 0, 0] on 42-44, [0, 1, 1] under a butterfly), nowfactions.rs::test_breach_with_no_guards_frees_the_boss_first_and_reopens_the_warrantwith its negativestest_breach_with_nobody_inside_fizzlesandtest_breach_against_three_guards_fails_and_shocks. And a day in Crackdown (the Hit market P2, 2026-10-11: days held over 42-47 [4, 9, 0, 1, 0, 0] on the flipped main, [0, 0, 0, 4, 6, 0] with P2), nowlaw_faction.rs::test_posture_patrol_at_rest_and_crackdown_under_pressure. And an organic Hit fulfilled (the Hit market P4, 2026-10-11:hm_hits_doneover 42-47 [1, 1, 1, 0, 1, 0] onfe60d37, [1, 1, 1, 0, 0, 0] with P4's D8 fix, 2 of 6 before the P2 fix round; one a seed at most), never a bullet:hitmarket.rs(the price, the pooled deposit, credit, stiffing),person.rs::probe_widow_commissions_a_hit_at_the_fixerandgod_kill_ten_fathers_day_10carry it.tools/analyze_run.py's "Hit market" section prints the organic records' story list from an events file.
Never add a band. If a number matters, print it in run --report and read it with tools/analyze_run.py.
citysim-cli shadow --assert [--out DIR] (or test_behaviour_tier) runs the V2 shadow pass's pinned windows, 7 days from day 19 (a Friday) with every archetype and 7 days from day 90 with the gang archetypes, on seeds 42, 43 and 44. Each window's world is run to its start day once per seed; then each archetype gets its own clone of it with up to 30 of its free candidates pinned at Full (every candidate in a small pool), all in parallel threads. A row pools the archetype's agents over the three seeds:
- a share (
worked,paid/wkd,waged_wd,bouts_at) is pooled over every shift or bout, and judged only over 20 or more of them; - every other metric is a per-agent rate, averaged as a 10 % trimmed mean over the agents alive a whole day of the window (one agent's 6 h commute or week at energy 0 does not carry a row; a regression most of them share does);
- a row is judged only over 5 or more agents (never a smaller sample than the old 5 picks); under a floor it is a SKIP, not a failure.
The first 5 of each archetype's sample write their diaries (--out DIR: d19/, d90/ for seed 42, s43_d19/ and so on): the diaries are the human-readable judge and are not what the numbers are read from. Some archetypes are diaries only, outside the windows and CHECKS: fixer, gun, sick and buyer (the Hit market P4: a want's holder or the placing agent of an organic Hit or Beat live or posted in the last 7 days; shadow --pick buyer, or --agent <id> from hit_probe::hit_market_report's story lines, which print each record's buyer and target indices). The table prints the pooled row and each seed's row (cook@43) so a failure shows which city carried it.
The metrics:
| metric | what |
|---|---|
walk_h/d |
travel-class hours a day |
sleep_h/d |
sleep-class hours a day (printed) |
longest_h |
the mean over agent-nights (noon to noon) of the night's longest sleep block |
e0_h/wk |
hours at energy 0 per agent-week |
work_h/d |
work-class hours a day |
worked |
whole workday shifts (free) with half the shift at work-class steps (printed) |
paid/wkd |
worked shifts paid a wage by two hours after the end |
waged_wd |
whole workday shifts (free) with a wage |
hang/wk |
HangOut steps per agent-week (printed) |
known/wk |
distinct known contacts met at a HangOut per agent-week |
refund/wk |
purchases refunded (a started buy undone) per agent-week |
abort/d |
Work goals dropped before a single work-class minute (a commute turned back) per agent-day |
bouts_at |
bouts with the fighter inside the pit, of the bouts naming her |
starve_h/wk |
free hours at hunger < starving_hunger per agent-week (printed, no bound yet) |
synth% |
synth and Mission meals of all meals, pooled (printed) |
dive/wk |
bin meals per agent-week (printed) |
The bounds are calibrated on the noise, not on one reading (last re-calibrated 2026-10-11 on a6887f1, the shipped stat_violence_mult 1.0 and max_coarse 150 at the centre). CHECKS in shadow.rs holds each row's bound and the range the row read on a calibration ensemble: the tier on main and on seven config butterflies ([lod] stat_violence_mult 0.98, 0.99, 1.01, 1.02, 1.03 and max_coarse 149, 151 in a copy of assets/ passed as CITYSIM_ASSETS: the same rules, a city that diverges from day 1, so every pick is someone else's week). Each bound sits a cushion outside that range: walking + max(1 h, 15 %), the longest nightly block − 0.5 h, energy 0 + 2 h a week, known contacts met × 0.6, commute aborts + 0.05 a day, paid of worked and waged workdays − 0.1, the reporter's work hours × 0.5; refunds ≤ 1 a week and the fighter in the pit for ≥ 3 bouts in 4 are absolute. A FAIL line prints the calibration range beside the value. The d90 runner rows are not judged (3-10 agents over three seeds, an archetype defined by a run in the last 7 days: they moved more under the butterflies than any injected regression moved anything). They catch a regression in "acts like a person"; they are not targets.
Why (measured 2026-10-09, d11e397; the numbers are in the commit message): the 5-pick tier, its bounds re-derived on main by its own rules, flipped 1-4 rows on every one of the seven butterflies (as shipped, with its accumulated margins, 0-3 on six of seven) and 2-6 on the session's earlier commits, every flip a pick change or one city's gang or Law trajectory (the guards patrol or stand post as a city, the Purists of one seed sleep 2.5 h a day). Three causes: five people are a sample of five; one seed is one gang trajectory; and the archetypes shared one world, so a pick change in one moved the city under all the others. The pooled tier, calibrated on main and four butterflies, flipped no row on three held-out butterflies or on be7e4d3 (before the trades), and one on 7a0f003 (before Jobs P2's 100 Lots: the ripperdocs walk 5.6 h a day on all three seeds there against 3.6-4.5 today, a real change). On M16a phase 4 (2935f18) the 5-pick tier failed two d90 rows on a jailed runner pick; the pooled tier passes. Injected regressions: a role that never chooses Eat (the sweepers) fails KnownMetWeek (3.2 against a range of 11.7-14.0, all three seeds); breaking the commute latch (L2 shadow fix 1) fails d90 homeless CommuteAbortsDay (0.11 against 0.00-0.03); undoing the committed Sleep (fix 3) is not caught (the longest block drops ~0.2 h on three archetypes, inside the noise; the 5-pick tier caught the Eat one on its sweeper rows, missed the latch, and failed the Sleep one only on two d90 gang-leader rows that also flip on butterflies).
When the tier fails: read the row's per-seed lines, then the diaries (--out). If the city really changed and the change is wanted, re-calibrate: run shadow --assert on the new main and on the seven butterflies (eight runs of ~1.5 min), take each row's lowest and highest pooled value over the eight, apply the cushions above (a Min rounded down and a Max up, to 0.01 for aborts and shares, else 0.1), and record the old and new ranges in the commit message. Re-calibrate from one run only when a single row moved for a known reason, widening that row's range by the new reading. A stat table regeneration or a recalibration that does not change how people behave should pass without one; that is the point.
citysim/tests/person.rs: the shipped seed-42 city, one agent pinned at Full LOD, one stimulus, and an assert on what a person would do. The always-on probes (all pass; the Jobs probes that need the wages switches are #[ignore]d until the flip):
- hungry, broke, an empty larder at 15:00: paid at 18:00, buys food before midnight's rent;
- married, across town at 20:00, tired, no Hotel bed affordable (2026-10-11: an affordable away Hotel is L2's designed bed): at home at 03:00;
- an Enemy standing at the nearer HangOut spot: the other spot;
- at the counter at 17:45, a little hungry: stays to 18:00 and is paid;
- an immigrant with no coins and no food: eats or is hired within five days, alive;
- a guard starting a twelve-hour shift hungry: eats on shift, never at hunger 0;
- a Night Porter on shift at 02:00 at the Hotel, paid by 08:00;
- M16a: a gun alone on a Fixer's books never takes a Hit on his own wife (the control: he takes the same Hit on a stranger);
- M16a: the buyer of a fulfilled Hit nobody else knew of is never charged before somebody holds the
Hireddeed (30 days); - M16a: a broke jobless hand with a skill a Fixer can sell networks at the Fixer within a week.
- The Hit market (the H7 fix un-ignored the first): a buyer never tells his own Hit (
probe_buyer_keeps_his_own_hit_quiet); a widow whose husband was killed by a stronger stranger, 40 coins and a sharing brother, walks to the Fixer and commissions a Hit on him within 7 days (control: a stronger, braver widow hunts him herself and buys nothing); a lawless suspect buys a Beat on the known witness to his assault (control: an unknown witness is never targeted). - Hunger as sickness P1 (each failed on d771073 before its fix,
[life] hunger_fixes): starving with a meal's coins or a coin short, eat before spending or sleeping (G1); lawful and broke beside a stocked Mission, eat there within 6 h (G2); lawless and starving with three meals' coins, buy, never steal (G3); a Beg with no giver is a dry hour, not a failure (G4, mechanism-level). - Hunger as sickness (
tests/sickness.rs): a broke hungry adult with no Mission dives a stocked bin within 6 h; a diagnosed patient with the coins is treated at a Clinic within two days. - Transit (
probe_el_*, docs/TRANSIT.md § 7, the El on): a commuter with the fare rides and arrives sooner than the walk would, and on time; a broke lawful worker walks; a broke lawless one jumps the gate and rides; a timid worker set out at 22:00 from the worst district never boards (the brave one would); with every line cut, the next workday the worker walks to work.
Gap probes (#[ignore]d, for the behaviour round): none open (probe_buyer_keeps_his_own_hit_quiet, docs/GOD_SCENARIOS_V8.md gap 5, passes since the Hit market's H7).
Copy a probe: build World::new(42, Config::load()), run to the hour you need, pick an agent with the helpers (day_workers_at_work, the civilian filters), pin it, set the stimulus through components (Needs, Wallet, Inventory, Position, edges), run with run_to (it re-pins every tick and lets you watch each tick), then assert the outcome a person would pick. Read the outcome from the agent's own state (its step, its wallet, its building), never from a city-wide count. If the probe exposes a real gap, mark it #[ignore] with the gap in a comment and list it for the behaviour round; do not fix behaviour in the test change.
The 13 kept, one per reaction class, seed 42, 60 days (the shock at day 45, or day 10/20 in the v5/v6/v7 harnesses), each against its unshocked control: god_decapitate_gang (succession), god_kill_gang (a gang wiped), god_city_takeover_by_force (unlimited resources), god_bankrupt_city (the city's money), god_crackdown_forever (the law pinned), god_chrome_everyone_2 (chrome and violence), god_wipe_zetatech_day_20 (the Virt plane and the tech tree), god_kill_friend_of_leaders_day_10 (grudges, Hunts, vendettas), god_export_10x (the outside world's demand), god_kill_exec (corp succession), god_wipe_corps (the corps' money), god_riot_sump_west (a district riot), god_evict_sump_rent_10 (housing); M16a's four (v8, docs/GOD_SCENARIOS_V8.md): god_hit_each_exec_day_10 (a Hit on every exec), god_hit_hollow_leader_from_arasaka (a corp's Hit on a gang's leader), god_fixer_licence_off (the board's lever), god_law_locate_on_gang_leader (a public bounty), which assert only that the commands applied; hunger as sickness's one (v9, docs/GOD_SCENARIOS_V9.md): god_famine_synth (every shelf emptied each morning for 15 days: starvation within the control's + 5, the bins feed more); the Hit market's one (v9): god_kill_ten_fathers_day_10 (ten household heads killed on day 10, each by a named stranger stronger than his widow: at least one widow or adult child places a Hit within 30 days; the control, the same city unshocked from day 10, prints the same houses; 40 days). The other 40 were near-duplicates of these (the same lever class, or a lever with its own unit test). Transit's one (v10, docs/GOD_SCENARIOS_V10.md): god_line_cut (the Vat Line cut for 10 days at day 20 of 45: no ride on it during the cut, its riders' commute cost up, fares down, rides back after). M16b's four (v11, docs/GOD_SCENARIOS_V11.md, on the v8 harness): god_hostage_captain_spouse_release (a hostage over the captain demanding a convict out), god_subscribe_and_chrome_everyone_3 (Trauma subscriptions under a tier-3 chrome wave), god_call_vote_habitat_after_coerce (two coerced board votes and a called vote), god_parley_strongest_gangs_in_vendetta (a vendetta between the two strongest gangs), each asserting the commands applied and a named reaction. M17's one (god.rs, its own harness): god_ambient_boom (the shipped map with 4 Ambient flats a slot, the Ambient appetite pinned at 3.0 on day 10: the pin applied and the book bought more than the control; vacancies, employed, layoffs, foundings, prices and coins printed). The M17 phase 1 unit tests are citysim/tests/outside.rs. M17 phase 3's three (the same file): god_raze_arasaka (every Arasaka building given to Ninefold on day 10: the parent reacts within 30 days; printed: a branch owns a building again), god_decapitate_twice (The Hollow's leader killed on days 20 and 35: since phase 4b the gang answers each fall itself within two days, a promotion, a civil war or its end; the brothers printed), god_murder_wave (25 residents killed on day 10: a StateGrant within 14 days). M17 phase 4b's one: god_decapitate_close_succession (The Hollow's leader killed on day 20 with two near-equal lieutenants: a CivilWar, members taking sides, and the war settled within war_days). M17 phase 5's one: god_abroad (four of Ninefold's members on Arasaka Global's depot on day 20, promote_p 1: the job settled within abroad_days, and someone the crew met abroad arrives with an edge to it within promote_delay_days). Write-ups: docs/GOD_SCENARIOS_V1..V11.md. M16b's calibration findings against the spec's bands are printed in docs/M16_CONTRACTS.md ("Implemented: deviations (M16b)", phase 5).
tests/scenario.rs (4,746 lines: 12 milestone gates re-running the same city ~50 times, ~50 calibration and trajectory bullets, the pinned murders/starvation/FNV constants, the majority and six-seed devices, eight copies of the sanity trio and the floor, the M1/M0/M5/M6 short scenarios), tests/scratch_probe.rs (33 ignored print-only probes), the 13 legacy-save loaders and the v1-profile bit-identical save in save.rs, four of the five lod.rs ignored tests (kitted parity, leisure parity, faction-violence parity, the stat-policy bench) and its non-ignored throughput copy, scale::test_tick_2000_median_under_250us, outside::probe_coin_census, 40 god scenarios.
The flip (2026-10-10): the shipped default is the flipped city: wages from revenue, no dole, hunger as sickness, the shanties, with class pay and the hunger fixes ([life] hunger_fixes, items by CITYSIM_HFIX_OFF). P10 (2026-10-11, docs/P10_POLICY.md) retired the flip's switches: [economy2] wages and no_safety_net became saved policy levers seeded from [policy] (the levy, the dole, public works, school meals, the Reserve release; --lever day=D:levy=on, safety_net=on, dole_per_day=N, ...), [sickness] enabled and [street] shanties are gone (always on), [demography] school_meals moved to [policy]. The old default, the dole city, is the policy preset Config::dole_city() (citysim-cli run --dole-city): the levy and the whole net from day 0, now with hunger as sickness and the shanties. The unit-test files (all but core.rs, lod.rs, person.rs, policy.rs and the god scenarios) build their cities with Config::load().dole_city(), so they pin the mechanisms they were written for; core_sanity, core_year (J24/J25 asserted on the no-net city), the parity test, the person probes, the god scenarios and the behaviour tier run the new default, whose bounds were re-derived on it (eight calibration runs, the cushions below). Every feature is always on except the work in flight: [life] violence_fixes with its vf_* items and CITYSIM_VFIX_OFF, and [hitmarket] enabled (A/B, CITYSIM_HITMARKET_OFF); the part keys [sickness] diet, bins, cancer stay (no test turns them off). Policy is not a switch: it is world state a lever moves. Gone: --virt-off, --word-off, --life-off, --l2-off, --contracts-off, --econ-off (and shadow --life-off), Config::{virt_off, word_off, living_off, econ_off, contracts_off, with_leisure}, the enabled master of [life], [living], [jobs], [leisure], [budget], [fviolence], [gossip], [hunt], [moves], [competence], [news], [virt], [contracts], [economy2] (and its market), [world_market], [charity], [camp], [treasury] and [export] (with L2's flat-price export hook the market replaced), [lod] budget, [gossip] legacy_second_hand, [life] l2_fixes with its six sub-switches and CITYSIM_L2FIX_OFF, every off branch behind them, their identity tests, and the legacy-save migrations (World::migrate_legacy, the per-system migrate hooks, the RivalHideout alias): a save from before format 4 no longer loads. v1_profile (the 300-resident unit-test city) and calibration_city now run the life pass, the plane, the word, the living city, contracts and the World market; they keep only their own cuts (the v1 map and economy; gangless, rent-free, no riots or assets), start under their policy (v1_profile the dole city's less school meals, calibration_city the configured [policy]), and follow the shipped default for [life] violence_fixes. calibrate builds assets/stat_table.toml in that city (since hunger as sickness P5, the recalibration's: the shipped city at [world] population, corps, rent, tiered wallets and derelicts kept, gangless, riot- and asset-free; the income path, p_desperate and, with the bins on, p_dive learned in every city): regenerate it (cargo run --release -p citysim-cli -- calibrate, deterministic, ~2 min) when the Full tier's hourly behaviour moves, then re-run core_sanity, core_year and the behaviour tier on it. The planner budget scales in the calibration city (hunger as sickness P5h, a harness change): calibration_city(n) (so calibrate and the parity test) multiplies [brain] plan_budget_per_tick and plan_expansion_budget_per_tick by n / 500 (Config::PLAN_BUDGET_REF_AGENTS; never below the shipped values). The budgets were tuned for a 500-agent Full city; at 2,000 all-Full on budget 12 the queue held ~480 entries and 12 % of shift time was a Work goal waiting on a plan, which the table then learned as missed shifts (1.1 % at 4x). lod::test_calibration_city_scales_the_planner_budget pins the rule. The shipped mixed-LOD city keeps the shipped budget: lod::finding_planner_queue_shipped_city prints its queue (max, mean, stale drops, Full agents) on seed 42 as a finding, not an assert (2026-10-10: max 4, mean 0.1, 0 drops, at most 50 Full agents; [lod] max_full caps the on-screen crowd).