A cyberpunk city that runs by itself: 2,000 residents on a 256 x 192 map, living under owners. The end state is a place a player can go break, take over, or simply exist in, with a lot of little stories along the way. Corps own the Vat Farms, Street Markets and Blocks, gangs hold the Sump, the Precinct's guards answer to whoever bribes the captain, and districts decay as the money moves. Agents are utility-driven over a GOAP planner; off screen they live on cheaper tiers but still commit crimes and have a biography you can read. The sim is the product; the player comes after it stands on its own. See docs/VISION.md.
The full v1 build spec lives in docs/SPEC.md.
citysim— headless simulation librarycitysim-app— macroquad renderer and debug UIcitysim-cli— headless runner and calibration
cargo run -p citysim-cli -- run --days 30 --seed 42 --report
cargo run -p citysim-app
citysim-cli run flags: --days N --seed S (required), --report (daily CSV on stdout), --events (events on stderr), --population N (override [world] population), --map FILE (override the map), --lever "day=D:name=value" (repeatable, e.g. day=30:law_posture=crackdown, law_posture=auto releases the pin), --save-at T, --load FILE, --force-lod full|coarse|stat, --diag (with --events: a daily line per Street Market and per corp), --v1-profile (the 300-resident v1 city).
citysim-cli shadow (an observation tool) follows individual residents at full detail and writes a readable diary per agent: shadow --seed S --days N [--start-day D] [--pick gang_member,gang_leader,ripperdoc,homeless,ceo,exec,guard,worker,runner,purist,reporter,child,dealer,cook,club_staff,fighter,fabber,sweeper,worker_friday] [--agent INDEX]... [--count K] [--no-pin] --out DIR (--list prints the candidate count per archetype on that seed and day, how many are free, a child as unpinnable, and whether the start day is the worker_friday weekday, the one before [leisure] collect_weekday; picks are free agents first, a jailed pick only as a fallback and kept Full inside the Jail; a dealer is anyone in a deal_log row or registered at a Bar, a runner needs a JackedIn in the 7 days before the start day; --no-pin observes without pinning so the diary shows Statistical and held stretches). The world runs normally to --start-day, the picks are chosen by a seeded shuffle and pinned to Full LOD (Brain.pinned), and DIR/<archetype>_<index>.md (plus a .jsonl of the same entries) records, per sim day, every goal choice with its score and the top runners-up, plans and actions with places and durations, a needs snapshot every two hours (hunger to fun), every purse flow with a guessed kind, social moves, gossip told and heard and gossip told about the agent, the Unwind pick with its rung and venue, HangOuts with who was there, venue payments by the sim's own flow kind (leisure, gambling, tribute), cell settlements, Statistical GangWork hits, off-screen holes, memories, edge changes, grudges and Hunts, events naming the agent, and a daily and whole-run summary (fun hours, coins on leisure vs food vs rent, HangOuts and known contacts, tribute, the Statistical stand-in). A plain run is unchanged; the only sim hook is World::shadow_notes, a log that stays None unless the tool turns it on.
Levers: release_reserve, tax_rate, sentence_mult, guard_count, immigration_per_week, dole_per_day, law_posture, and since M11 city_rent=1/2/4 (rent on city Blocks, Sump/Mid/Spire), rent_cap=3 or rent_cap=none, no_city_evictions=1, breakup=<corp slot>. God levers (out of the rules, for the god suites): kill_leader, kill_gang, seize_gang, jail_gang=<gang>:<days>, fund_gang=<gang>:<coins>, fire_guards, treasury=<coins>, and for corps (by seeding slot 0-7; the CSV column corpN is slot N-1) fund_corp=<slot>:<coins>, bankrupt_corp=<slot>, seize_corp=<slot>:city|gang<i>|<slot>, kill_exec=<slot>, kill_staff=<slot>, strike=<slot>, corp_order=<slot>:<Order>[:<Niche>]:<days>, wipe_corps=1. Nationalise and Subsidise are app buttons (building and corp panels).
M12 levers (districts by index 0-7: Spire, Civic, Vats, Mid West, Mid East, Sump West, Sump Central, Sump East): guard_weight=<d>:<0..5> (0 withdraws the law), stance=<d>:patrol|sweep|cordon|withdrawn|crackdown<gang>|auto, sanitation=<count> (city sweepers), sanitation_weight=<d>:<0..5>, curfew=<d>:on|off (Vagrancy x2, submission up, happiness -0.05), riot_response=contain|disperse|crush|auto. M12 god levers: riot=<d> (a riot at the next muster hour, unrest ignored), litter=<d>:<0..1> (every street tile), split_gang=<gang> (the split rule minus the roll), derelict=<building index>, buy_building=city|gang<i>|corp<slot>|<agent index>:<building index>:<price>.
M13 levers: stims_legal=on|off (Markets restock and sell Stims; dealing stays a crime but nothing is confiscated), asset_tax=<class>:<rate> (upkeep x 1 + rate for moto|car|truck|flyer|implant|robot|pack|bridge, rate 0..5), impound=on|off (the city impounds vehicles with unpaid upkeep; on by default). M13 god levers: grant_asset=<agent index>:<kind>:<tier> (kinds moto|car|truck|flyer|arms|legs|nerves|eyes|skin|robot|pack|bridge, free, no import), wreck=<asset index>, chrome_everyone=<tier> (every adult gets Arms and Nerves of that tier, filled slots kept), flood_stims=<d>:<n> (n doses into the Hideout stock of every gang dealing in, holding or hiding in district d), brick=<corp slot>|agent:<index> (every implant that lender financed is bricked and its loan called), chase=<agent index> (the agent's next trip counts as a chase for the crash roll).
M14 levers (the Virt plane, an abstract second board of nodes laid over the buildings; every run is one seeded dice contest per guarded node): city_ice=<0..3> (the Treasury's and the Precinct's ICE, installed or lowered one tier at a time, the upkeep the Treasury's), data_tax=<0..1> (an extra share of every Data sale to the Treasury, out of the seller's take), hack_sentence=intrusion|data_theft:<days> (replaces the base sentence; the sentence multiplier still applies). M14 god levers: wipe_data=<building index>, set_tech=<corp slot>:<chrome|deck|industry>:<tier>, grant_data=<corp slot>|gang<i>:<track>:<units>, grant_deck=<agent index>:<tier> (free), set_ice=<building index>:<0..3> (the city's own, free), fry=<agent index> (a jacked-in agent is fried at once, at command time, a lost contest at its next contested node with no save; the kill roll uses p_flatline only at ICE 3, else p_fry_kill), run_now=<agent index>:<building index|corp<slot>>:<data|wipe|ledger|door> (pins a run for an agent with a deck; the contest rules apply), and the god suites' plurals wipe_corp_data=<corp slot> (every node the corp owns), grant_decks=<district>:<n>:<tier> (the n best hackers living in the district without a deck), set_corp_ice=<corp slot>:<0..3> (every node the corp owns, the city's own, free). In the app the City panel's Virt section has the city ICE and data tax sliders and the two hack sentences.
M15 levers (the word: deeds as records passed between agents, Feeds as buildings that turn the day's records into stories with a slant): press_licence=on|off (off: only the city's Civic Wire publishes; private Feeds keep their staff and lose their ads), news_tax=<0..1> (an extra share of every ad and Spin payment to a Feed, paid to the Treasury), censor=gang<i>|corp<slot>|law[:off] (the Civic Wire drops every story about the faction until lifted). M15 god levers: plant_rumour=<agent>:<deed>:<object agent|none>:<district>:<reach>, set_reputation=<agent>:<dread|standing|honour|heat>:<v>:<days>, grant_skill=<agent>:<skill>:<v> and grant_skill=dregs<n>:<skill>:<v>:suit, set_creed=<gang i>:purist|none, declare_vendetta=<faction>:<faction>:<w> (factions gang<i>, corp<slot>, law: the feud is declared at weight w and held open for [grudges] declared_days), kill_friend=<agent|leader<g>>:<agent|member<g>> (the first's highest-affinity Friend is killed by the second, member<g> being gang g's best fighter, with one witness), hunt=<agent>:<agent> (a 1.0 grudge and a Hunt at once). In the app the City panel's Word section has the press licence, the news tax and the censor list.
L2 levers (the living city): leisure_tax=<f> (an extra tax on every venue entry, meal and house take), ban_gambling=on|off (Dens close, FightPit bets stop), public_works=on|off (the Treasury band's hiring arm), export=on|off (the World account buys Food, Parts and Data). L2 god levers: open_venue=<kind>:<district>:<city|corp<slot>|gang<i>> (a venue or Fab on the Lot nearest the district's centre, else a refitted derelict, free; a gang owner makes a front), close_leisure=<district>:<days> (every venue in the district closed), set_fun=<district>:<v>, export_price=<food|parts|data>:<n>, faction_strike=<gang i>:<district>:<days> (the gang's order pinned to Contest and the district touched by its violence off screen), hire_all=<kind> (every open vacancy at that kind filled now). The curfew lever now also closes the venues of its district from 22:00 to 06:00.
M16a levers (the contract board: a contract is a record with a price, matched by a score and resolved by one seeded dice roll between fictional agents; a Fixer is a building whose owner keeps the record book): fixer_licence=on|off (off: every Fixer office's heat is floored at 0.5, so the law busts it at warrant_heat sooner, and no agent registers a new one), accessory_mult=<f> (the Conspiracy sentence as a multiple of Murder's, over [law] accessory_mult), public_bounties=on|off (on: the law posts a public Locate on every wanted suspect after one day instead of [contracts] law_bounty_days). M16a god levers: post_contract=<buyer>:<hit|beat|guard|locate>:<agent i|b<building i>>:<price>:<0|1>:<days> (buyer an agent index, corp<slot>, gang<i> or city; price 0 = quoted, days 0 = the kind's; 1 brokers it with the open Fixer nearest the target), take_contract=<id>:<agent i>. In the app the City panel's Contracts section has the three levers.
M16a gives the city a contract board (docs/M16_CONTRACTS.md; always on): a contract is a record in World::contracts with a buyer (an agent, a gang, a corp or the city), a kind (Hit, Beat, Guard, Locate), a target, a price held in escrow (inside total_coins, so the coin identity covers it) and a deadline, matched daily to a taker by a utility score and resolved by one seeded fight roll when a body works it (LIVE) or one ledger draw when nobody is on screen (LEDGER). A Fixer (BuildingKind::Fixer, letter X; one seeded in Mid East, more by NPC Register under [fixers] fixers_per_pop) brokers records for its regulars (agents who Network there, a scripted Contract goal for the jobless with a skill to sell) at fixer_cut; a direct record goes to the buyer's own edges, gang and staff. A gang takes a job as a mission on the raid machinery (Order::Job, a crew marching to the target's door), a weak gun recruits a squad, a strike in a district a faction holds pays a political cost (called off past it: StrikeDeclined; the holder may take the job over: SoldOut). The law sees a contract killing as a Murder: a guard who interrogates an arrested taker, or a witness of the strike, who holds the Hired deed files Conspiracy against the placing agent (Accessory); a Fixer named to the law warms (heat) and is closed at warrant_heat (FixerBusted) unless its owner bribes the captain. The law and the corps post Locate bounties paid per sighting (BountyPaid), and a city guard may be bought (GuardTaken). In the app, C draws the contracts overlay (each Fixer sized by its book, a crosshair on each live mission's door) and Shift+C opens the Board panel (every record, or what the selected agent can see: the M18 quest log); the Building panel of a Fixer, the Inspector, the Mission panel and the City panel show the records too. Screenshot flags: --overlay contracts, --board, --select-mission (the Mission panel of the first live contract mission).
The CSV since M16a adds, before flow_import_out: contracts_open, contracts_posted, contracts_fulfilled, contracts_failed, contracts_expired, contracts_cancelled, reneged, per kind k_{hit,beat,guard,locate}_posted, k_…_done, hits_done, hits_squad, hits_solo_weak, strikes_declined_pol, sold_out, contract_murders, contract_cleared, contract_holes, contract_hole_wrong, accessory, interrogations, interrogations_won, bounties_paid, guards_on_take, fixer_runs, regulars, live_parties, live_queued, escrow_held, escrow_leak, escrow_stuck, per Fixer slot f{0..3}_heat and f{0..3}_income, then flow_escrow, flow_payout, flow_fixer_cut, contracts_refused_full. tools/analyze_run.py's M16a section reads them.
citysim-app flags: --seed, --load, --map FILE, --fps, and the screenshot helpers --start-tick T, --screenshot out.png (frames the whole map unless something is selected), --fit, --select INDEX, --select-kind Hideout (a Rust BuildingKind name: Market is shown as Street Market, Jail as Precinct), --select-name "First Last", --tab story, --tab corp (with --select-kind: open the panel of the corp owning the first building of that kind), --no-autobind. The City panel shows tier counts, open holes and the sim rate, one row per corp with a share bar per niche, the three classes (count, happiness, loyalty, submission, unrest) and the M11 levers (city rent per tier, rent cap, no city evictions); the event log has an "Unattributed" toggle that lists open holes and binds one on click. Clicking a corp (City panel, a building's owner, an inspector's employer) opens the Corp panel: niches with price level, share and monopoly flag, governance, exec, treasury and a 14-day cashflow sparkline, the bankruptcy countdown, the order and its trace, buildings, employees, contracts, Subsidise and Break up. Building outlines take the owner's colour (a corp's, a gang's, white for an agent, grey for the city); Lots are dashed. M14: N draws the Virt plane over the ghosted city (nodes coloured by ICE, Data glyphs, links, live runs as dots; a click selects a node or a run), the Node, Run and raid (Mission) panels, and the screenshot flags --overlay virt, --select-node <index>, --select-runner (the first jacked-in agent and its run), --select-raid (the first streamed raid).
With B the app draws the eight districts (borders, a fill in the controller's colour, Contested hatched) and a click on a street tile opens the District panel (aggregates, control and its presences, the law's allocation, stance and trace, litter, unrest and riot state, the street, the district levers); L draws the litter heat. The City panel lists the districts (controller, guards, unrest, litter, crime) under the sanitation and riot-response levers. citysim-app also takes --districts, --litter and --select-district <index> for screenshots.
The CSV (--report) since M11 adds rent and evictions (evictions, rent_paid, rent_short, housed), the money ledger per flow (flow_food, flow_drink, flow_wages, flow_rent, flow_upkeep, flow_wholesale, flow_overflow, flow_restock, flow_contract, flow_tax, flow_dole, flow_other), wealth (wallets, wallet_gini, wallet_top10), each seeded corp's corpN_treasury, corpN_order (slots 1-8, 0,- once dissolved), acquisitions, bankruptcies, monopolies, foundings, incorporations, strikes, and the classes (unrest_corp, unrest_street, unrest_dreg, class_corp, class_street, class_dreg, happiness_street), all before ticks_per_sec. M12 adds, per district (columns d1_ to d8_, d1 = Spire; the levers number districts 0-7), d{i}_coverage, d{i}_control (0 Contested, 1 City, 2 Gang, 3 Corp), d{i}_litter (share of street tiles visibly littered), d{i}_unrest, d{i}_crime, d{i}_guards, then dregs, hotel_nights, squatters, derelicts, vagrancy, riots, crossfire, gangs, also before ticks_per_sec.
M13 gives the city's people things. An asset is an entity with an owner (an agent, a gang, a corp or the city), an upkeep, a condition and maybe a finance plan; it can be repossessed (towed, bricked, taken), impounded, stolen, fenced, chopped for Parts, looted from a corpse and inherited. Vehicles (motorcycle, car, truck, flyer) change how long a step takes (road, ground, farmland), haul six batches on a Vat Farm truck, crash at the end of a trip (worse in a chase; reflex is the save) and are stolen from the street; corps run truck and patrol-car fleets and buy their execs flyers, gangs buy bikes for members. Chrome (Arms, Legs, Nerves, Eyes, Skin) is bought and installed at a Ripperdoc (Clinic), changes fights, sight and stealth, and costs sanity: low sanity rolls a berserk episode the law hunts; Therapy treats it; scavs strip and rip corpses, and gangs under the Harvest order abduct the chromed. Stims are cooked by gangs and dealt at Bars (illegal by default, the stims_legal lever lets Markets sell them), with addiction, withdrawal, overdose and Detox. The security robot is posted by a corp's Secure order, defends in brawls and detains thieves. Garages and Clinics are a ninth corp's niche (Tech) and agents can found them.
The CSV since M13 adds, before ticks_per_sec: vehicles_moto, vehicles_car, vehicles_truck, vehicles_flyer, truck_hauls, walk_hauls, commute_tpt_walk, commute_tpt_drive, chrome_installs, chrome_agents, mean_sanity, episodes, hooked, stims_dealt, stims_legal, dealing_reports, repos, impounds, crashes, crash_deaths, vehicle_thefts, chops, abductions, stripped, robots, flow_asset, flow_asset_upkeep, flow_finance, flow_import, flow_stims, flow_parts, flow_treatment, overdoses, harvests, stripped_window, gang_income, gang_income_dealing, episodes_by_law, treatments, detoxes, and the ninth corp slot (corp9_treasury, corp9_order). The app's assets overlay heats the districts by hooked adults (A per the plan, K where A pans the camera: the UI phase decides).
M14 gives the city knowledge as property, as a board game laid over the map: the Virt plane is a second board of abstract nodes (a Public node per district, a node per faction building, Lab and Hideout, a Ledger per corp and the Treasury) joined by links of a tier (Street and Access tier 1, Trunk tier 2). An agent with a deck (a tier-1 to tier-3 asset bought at a Clinic or Security Office) sits in a chair (home, Hideout, its Lab, or a Bar or Hotel terminal for a fee) and plays a run against a target node: every guarded node on the route is one seeded dice contest, deck tier plus the hacking skill against the node's ICE tier (0-3), exactly as a brawl or a lock is rolled; a lost contest bounces the run and may fry the runner (reflex saves), flatline them at ICE 3, or trace them to the chair, where the law cuffs a corp's or the city's intruder and a gang takes the grudge. A run steals or wipes Data, robs a Ledger, opens a building's doors before a gang's raid, or turns a posted robot; its effects are timed flags on buildings and units moved between stores. Data comes in three tracks (Chrome, Deck, Industry), made by Researchers at Labs from the shift ledger and sold to corps that research the track. Each corp holds a tech tree (tier 1-3 per track): tiers 2-3 cost Data a day to hold, lapse after seven short days or at once when a wipe leaves no backup, and gate what its sellers sell; an asset runs at min(its tier, its maker's tier), so a corp that loses Chrome 3 greys every tier-3 implant it sold. Corps buy and raise ICE under Secure and on the node robbed last week, lower it under Hunker; gangs run VirtRaid and the door prelude; corps run VirtRaid and Research. Cameras feed the first faction database. In the app, N toggles the overlay.
The CSV since M14 adds, before ticks_per_sec: nodes, labs, decks, runs, runs_ok, data_made, data_stolen, data_wiped, data_sold, ledger_hacks, doors_hacked, traced, fried, flatlined, hack_arrests, ice_mean_corp, ice_spend, runs_bounced, runs_captured, runs_dumped, hack_arrests_chair, robots_turned, blinded, cameras, sightings, ice_raised, ice_lowered, tech_gained, tech_lost, research_spent, data_held, then per corp slot corp{i}_tier_chrome, corp{i}_tier_deck, corp{i}_tier_industry, corp{i}_data, then flow_data, flow_hack, flow_ice_upkeep, flow_research, flow_terminal.
M15 gives the city knowledge as talk. A deed (killed, assaulted, robbed, extorted, stripped, arrested, married, evicted, struck, raided, founded, avenged, betrayed, repaid, poached) is a record that travels between agents: a witness holds it first-hand, a rumour is a heard copy one hop further (at Chat, at a Bar's Drink, when a hunter asks around, and to the dead's kin), with a confidence that falls per hop and a chance per hop of naming the wrong actor (the actor's gang, or the speaker's Enemy). Off screen each district keeps a rumour pool that decays, leaks to the next district and is heard once a day by its Statistical residents. Every midnight reputation is rebuilt from what is known, never from what happened: dread, standing, honour, heat and known_by for every adult and faction, and a faction × faction regard matrix; the gang brain reads fear, the law reads heat, the Security clients and sellers read honour. A wrong learnt becomes a grudge (a robbery of the agent itself, a killing of kin or a friend, a beating seen by kin), which decays, passes to spouse and adult children, and adds up between factions into a vendetta that Retaliate and Lobby read. A heavy grudge becomes the Hunt: ask around at the target's Bar (one move, which a friend of the target may answer with a lie), stake out where the target is known to be, and strike, lethally on a kin killing; the street does not report a revenge it supports, and the avenged's kin may hunt the avenger in turn (the revenge chain). Every adult has one primary social skill (persuasion, intimidation, deception, knowledge; about 5 % reach 0.8), and every social move (an extortion, a poach, an AskAround) is one seeded dice roll through moves::resolve, the fifth dice rule. Skills make competence (a corp's or the Law's multiplier on what it produces and notices), and a corp fills a skilled vacancy by poaching a rival's best. A Purist creed (The Unplugged) refuses chrome. Feeds (the Civic Wire and Nutrix Now at seed, foundable) turn the day's most newsworthy deeds into stories with a slant, carried by ads; a corp in Spin pays a Feed to plant a true misdeed of a rival its people know and to bury stories about itself. In the app, J toggles the word overlay (pools, Feeds, vendettas); the inspector has Known and Heard tabs and the corp, gang and city panels a Word section.
Life pass L2 makes it the living city: wages become the economy and the dole the floor under them, fun gives money a purpose, the faction layer reaches the Statistical tier, and every tier has a budget it keeps. Seven building kinds stand at seed on vacant Lots and can be founded (Register) or refitted from a derelict Block at half cost: the Club (Hosts; the strongest on shift works the door and turns away the hot-headed), the Braindance Parlour (Arcade), the Noodle Bar (Cooks; a meal at the Market's price plus one, restocked from the nearest Market), the Fight Pit (Fighters; a nightly bout at 23:00, no death, bets on the winner), the Gambling Den (Croupiers; a stake against the house edge), the Spire Lounge (Concierges; refuses Dregs and the badly dressed) and the corps-only Parts Fab (Fabbers make Parts the Clinics and Garages buy, a Tech corp's own Fab first, then the Recycler's scrap from scavenging, then a rival's Fab). Markets staff 12 and Bars 5. Every adult has a fun need that decays (faster for the rich) and the Unwind goal satisfies it on a cost ladder: a free HangOut at a street spot (a Bar, Market, Club or Noodle Bar door, the gang's Hideout door, a Sump fire barrel) chosen by where the agent's known contacts are expected to be, a meal out, an Arcade, a drink, the pit, the den, a Club, the Lounge; Statistical agents spend their evening at 21:00 in one pass. Gangs open Fight Pits and Dens as fronts in districts they hold (their dealers deal inside, a Crackdown closes them), and the leader Collects the week's tribute and takes once a week, paying the members in equal shares (Flow::Tribute); a Purist gang Preaches at the busiest spot; ordinary members walk away from a gang (daily, more often when employed, unpaid, settled, older or lately jailed, rarely in a feud), so rosters turn over below the cap. The Treasury keeps a budget band: above it the city posts public-works sweeper jobs at a wage over the dole, then scales corp upkeep down; below it the reverse. Held prisoners are Statistical in the cells (fed, decayed by the hour, meeting arrivals), a body only on the walk out; each forced LOD class has a quota inside max_coarse (25 bodies per gang in rank order, 40 at a raid muster, 12 private guards). The faction layer reaches the off-screen tier: an on-screen ledger counts what each source (a gang's order, a vendetta, a riot, an episode) does to bodies per district and victim class, and every midnight each Statistical adult in a touched district rolls once against that rate shrunk toward a per-class prior; the hole names the acting faction and the binder only binds its members (or Unknown). An export hook (the World account, off by default) is M17's to fill.
The CSV since M15 adds, before ticks_per_sec: rumours_heard, second_hand_share, known_by_killers_median, distorted, grudges, grudges_inherited, hunts, hunts_active, avenged, revenge_kills, chain_max, vendettas_open, stories, planted, buried, poached, talent_lost, skill_rare_share, extort_success, rep_flips, rumour_hops_max, pool_reach, contradicted, silenced, hunts_failed, hunts_abandoned, guard_body, expelled, contracts_lost_honour, extort_tries, then per gang slot g{i}_dread, g{i}_heat, per corp slot c{i}_honour, c{i}_standing, c{i}_competence, then law_competence, flow_ads, flow_plant.
The CSV since L2 adds, before ticks_per_sec: flow_leisure, flow_gamble, flow_gamble_win, flow_tribute, flow_export, flow_public_works, wage_dole_ratio, employed_share, per leisure kind venues_{club,arcade,noodle_bar,fight_pit,den,lounge} and visits_{…}, fab_parts, scrap_parts, parts_imported, works_jobs, upkeep_mult, outside_inbound, outside_minted, fun_mean, fun_satisfied_share, hangouts, hangout_contacts_mean, fronts, collected, preached, per district d{i}_street_density, fv_killed, fv_assaulted, fv_robbed, fv_abducted, fv_bound, fv_unknown, fv_capped, fv_bound_wrong, kill_rate_body, kill_rate_stat, kill_rate_body_civ, kill_rate_stat_civ (the living columns), then tier_held, tier_held_body, held_fed, gang_bodies, gang_stat, stat_extorts, stat_claims, stat_deals, aborts, aborts_scavenge, aborts_sleep, aborts_checkin, aborts_seat, rough_sleeps, scavenge_dry, flow_street_dice (the LOD budget and the churn).
Display names differ from code names: Block (Home), Vat Farm (Farm), Street Market (Market), Precinct (Jail), Civic Hall (Hall), Recycler (Cemetery), Reserve Depot (Warehouse); money is shown as ¢. The CSV columns and the Rust identifiers keep the v1 names.
| Milestone | Scope | Spec |
|---|---|---|
| M0-M7 | v1: one gang, economy, law, social graph, demography | SPEC |
| M8 Factions | two gangs with a brain: orders, turf, raids, sacks | M8 |
| M9 The law | the guards as a faction: captain, postures, breakouts, bribery | M9 |
| M10 Scale (done) | 2,000 residents, five zones, Statistical tier, holes and the binder, Life | M10 |
| M11 Ownership (done) | theme labels, owners and purses, rent and eviction, eight corps with a brain, founding, classes | M11 |
| M12 Districts (done) | eight districts with owners and moods, per-district law and stances, litter and sweepers, Hotels, derelicts and squats, riots, crossfire, splits, corp raids | M12 |
| M13 Assets (done) | vehicles, chrome at the Ripperdoc, stims and dealing, the security robot, loot and the Tech niche | M13 |
| M14 Data and Virt (done) | a second plane, decks, ICE, Data as a resource, the tech tree, cameras and the faction database | M14 |
| M15 Word and blood (done) | rumours and pools, reputation, grudges and vendettas, the Hunt, social skills and moves, competence and poaching, the Purist creed, Feeds and Spin | M15 |
| Life L2 The living city (done) | wages over the dole, seven venue kinds and the Parts Fab, fun and HangOut, fronts and Collect, the budget band, held prisoners and LOD quotas, faction violence off screen | L2 |
| M16 Contracts | hits, fixers, leverage moves, the quest board | post-M14 |
| M17 The outside world | megacorp, syndicate and state parents at ledger LOD | post-M14 |
| M18 The player | one more pinned agent, dialogue as a renderer over social state | post-M14 |
The tiers, what each asserts and when to run it: docs/TESTING.md. Asserts catch broken; behaviour judges alive; numbers are reports, not gates.
# every change (~1.5 min after compile): unit tests, determinism, saves, the person probes and core_sanity
# (seeds 42-44 x 120 days: coin identity, collapse bounds, mechanism existence, the ticks/s floor)
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace --release
# every merge, one at a time: the year (seed 42 x 365 days, ~2 min), the behaviour tier (the archetypes'
# diary metrics, ~30 s) and the 17 god scenarios (~2 min)
cargo test --release -p citysim --test core -- --ignored core_year --nocapture
cargo test --release -p citysim-cli -- --ignored behaviour --nocapture
cargo test --release -p citysim --test god --test god_corps --test god_districts -- --ignored --nocapture
# every milestone or nightly: Full-vs-Statistical parity, and the diaries for a critic's read
cargo test --release -p citysim --test lod -- --ignored --nocapture
cargo run --release -p citysim-cli -- shadow --assert --out shadow_out
# M12 unit tests: districts, the law in districts, litter, the street, riots and splits
cargo test --release -p citysim --test districts --test street --test factions
# M11 unit tests: ownership and rent, the corp brain, founding and classes
cargo test --release -p citysim --test ownership --test corps --test classes
# M14 unit tests: the plane, runs and contests, Data and the market, the tech tree, orders, levers
cargo test --release -p citysim --test virt --test runs --test data --test tech --test orders_virt
# M15 unit tests: rumours and pools, reputation, grudges and the Hunt, moves, news
cargo test --release -p citysim --test gossip --test reputation --test grudges --test moves --test news
# L2 unit tests: jobs and venues, leisure, the LOD budget, faction violence, the export hook
cargo test --release -p citysim --test jobs --test leisure --test lod_budget --test fviolence --test outside
# criterion bench (tick_300_agents, tick_2000_agents)
cargo bench -p citysim --bench tick
# regenerate the Statistical tier's table (assets/stat_table.toml): calibrate v2, 500 agents, gangless, 3 seeds
cargo run --release -p citysim-cli -- calibrate
# debug run with the event log (events go to stderr)
cargo run --release -p citysim-cli -- run --days 120 --seed 42 --report --events > run.csv 2> events.tsv
Stdlib-only Python scripts in tools/ (see tools/README.md):
python tools/analyze_run.py run.csv events.tsv # one run: economy, law, holes, ownership, corps, districts, assets, Virt, throughput, flags
python tools/compare_runs.py a.csv b.csv # per-column means of two runs
assets/gen_map.py regenerates assets/map.txt (write bytes, LF line endings).