ops-roster-01 · people & dose roster · round 1

The People

When this round began the city had 79 registered assets, 80 knowledge cards and no layer for the people who live in it: who sleeps where, for how long, how much of a dose allowance each person has left, how EVA hours are shared. This page is five ledgers that ask those questions of the cards as written - a bed and workstation census, a dose map, three quota lines printed side by side, a machine-readable roster, and two stress tests - with every expectation registered before the first card was opened, every number traced to a card commit or a run, and every number the cards do not hold left as null, never estimated.

People on the only card that counts them
11585 undercity + 30 village · derived, not self-reported
Beds on the cards
35+2 temporary · 80 people with no card bed
Positions with a dose number
7 / 275 village floors from Run B / Run E (void systematic bounded ±17% on bunks) + RAD surface
Pre-registered gates
34 / 34green · 26 hit · 2 miss · 4 not closable or declared
01

What it is

A dossier with no measurement of its own. Every number here is a citation - a city card at a named commit, the village's berth file, the life-support dossier's staffing script, the RAD instrument's published surface dose - or arithmetic on citations. What it adds is the arithmetic nobody had done across cards, and the blanks nobody had printed.

The village had just written its stay limit as policy - a floor per cabin class from the glass works' Run B, a formula that turns a floor and an EVA share into sols, and a set of occupancy weights it calls occupancy v1. That covers one living area. The undercity quarter, the surface duty cabins, the airlock hall, the deep laboratory: none has a dose floor a person could be assigned to, and no card in the city says how many people each unit needs. The census found ten cards that state their own crew - eight of them say "unmanned" - and one card, the life-support ledger, that states the city's population by a billet method: 115 people, 85 sleeping in the undercity, 30 in the village. The undercity's only residential card describes five single cabins.

So the roster is mostly empty on purpose. Of 115 people, 87 carry a dose of null: 85 because the 30 m rock layer has a shielding factor on its cards but no absolute floor, 2 because the village's corner cabin has no Run B number. The board built from it draws those people as empty frames. The rule from the first commit is that a blank is a finding, and filling it would be the error.

Qualifier, 2026-09-21; bounded 2026-09-24. The in-village floors this page cites - 27.0 at the standard bunks, 28.3 at the end bunks after the vestibule works, 35.2 at the new living position (Run E), 30.5 in the airlock box after the works - and every stay length, per-sol dose and remaining allowance derived from them come from a transport model that is not the village's declared geometry: since Run B (and through Runs D and E) the hull was built as a whole cylinder, leaving about 50 m³ of cabin air under the floor where the village declares native ground. The glass works found and reported it themselves, then ran the corrected geometry (half shell on native soil, eight paired seeds per class): at the bunks the systematic lies between −17% and +15% at 95%, direction unresolved - which rules out a large effect and is not to be read as no effect. The living and airlock receptors were reported, not judged. The numbers are unchanged and carry the mark runB_void_under_floor in roster.json (since 1.1.0; its wording updated in 1.3.0, the earlier "direction and size unknown" kept in text_history as retired), a violet cap on the board, and - on the scenario bars only - a thin violet span showing what ±17% on the sleeping hours alone would do. The RAD surface figures (234 mSv/yr, 0.66 mSv/sol) do not pass through that model and carry no mark.

5 ledgers34 gates32 pre-registered expectations 26 hit · 2 miss · 4 not closable / declared1 roster.json1 board part
The roster and quota board standing in front of the compute centre by day: three bands of bars and a 23 by 5 grid of person tiles, most of them empty frames
The board in the city, daytime (roster 1.3.0: violet caps and tile edges mark numbers that rest on the model with a void under the floor; the thin violet span on each scenario bar is the ±17% bunk bound). Top: dose floors per position, surface in red at full width. Middle: 115 person tiles, 28 filled. Bottom: sols to the 20 mSv line for four EVA shares; the red tick is one Earth year, and no bar reaches it.
The same board at night, its bars and tiles emissive against the stars, the compute centre's screen glow behind
Night. Emissive bars only; the empty frames stay dark because a null is not a number.
02

How it works

Five ledgers in the order each feeds the next. Each script carries its gates in its docstring and refuses to write a result if any gate is red; the runner exits non-zero on any red and on a roster that is not byte-identical on a second run.

1 · census

Beds and workstations from the cards, nothing from geometry. Two regex passes over 80 cards produced 829 candidate sentences, 86 with a number next to a people-word, each read by a person. Residential beds: the village's 30 (24 in standard cabins, 4 in the two end cabins, 2 in the corner cabin - the berth file is the source of truth and a gate demands it counts to 30) plus the quarter's five single cabins - 35, with 2 temporary and 5 non-residential (a show-home bed, four Starship sleep pods). The clinic has a module with a scanner couch and no card, so it is a gap, not a bed. Ten cards state a crew, eight of them "unmanned"; the fire station says zero full-time and sixteen part-time drawn from the population. Capacities that are not people - a 12-person lift cab, "3-7 crew (inferred)" on the CZ-10B, 15 cinema seats, 12 seats at the commons tables - are printed in their own column and never summed. Expectation E1.1 (40-70 beds) missed at 35, and the miss stands.

2 · dose map

27 positions, 7 with a number. The village's four Run B receptors (standard bunk 27.0 mSv/yr; end bunk 31.9 and airlock box 93.7, both before the vestibule works; the old living position 52.8, retired by the village's table v2 - all lower bounds, seeds and sigma beside them), the new living position at crown depth from Run E (35.2, two seeds, about 30% per receptor and seed - pending when this page was first written, adopted by the village's stay table v2 on 2026-09-21), the RAD surface 234 mSv/yr and its per-sol EVA form 0.66 mSv. Since the village's stay table v3 (2026-09-24) the end bunks and the airlock box are carried at their values after the vestibule works, 28.3 and 30.5 - the Run B floor times the 95% upper bound of the Run E before/after ratio, a cross-model product the village itself calls an upper bound of an upper bound; the values before the works, 31.9 and 93.7, are printed beside them in the ledger. Eleven undercity positions and the deep laboratory have shielding factors on their cards - 5000 g/cm², 52 hadronic e-folds, muons ×0.03, ×10⁻⁶ at 3 km - but no absolute floor: the 0.3 mSv/yr muon floor the surface station quotes is marked by the village's own berm card as a self-set order of magnitude, so the map does not use it. Every surface building interior is "no basis". A known-answer gate checks that 234 ÷ 365.25 × 1.0275 returns 0.658, within 1% of the 0.66 the policy uses; an ablation removes each village floor in turn and requires the blank share only to rise.

3 · quota

Three lines, printed side by side, never merged. The city's 20 mSv per Earth year indoors; the village's own 50 mSv per Mars year; and the occupational limits by version - NASA-STD-3001 Vol. 1 Rev. C, 600 mSv career for every age and sex, and ICRP Publication 103, 20 mSv/yr averaged over five years with no year above 50. An independent rewrite of the village formula reproduces its table exactly, twice: the bunk-only row (263 / 228 / 201 sols to 20 mSv at 0 / 0.5 / 1 h EVA) and the occupancy-weighted row of the current table v2, which uses the Run E living floor (218 / 194 / 175). The earlier weighted row, 160 / 148 / 137, was built on the old living position and was retired by the village on 2026-09-21; its reproduction is kept in the ledger as history. Per sol, the village line is 1.33× looser than the city's, so the village's sentence that its own line is "stricter than 20" holds only in its zero-EVA indoor conversion. The career line binds nobody who stays under thirty years: at occupancy v1 with half an hour of EVA it is spent in 5,830 sols, 16.4 Earth years (the earlier 4,431 sols rested on the retired living floor); living on the surface, 2.56 years. The ICRP five-year average cannot be met in the village under any schedule, since the bunk floor alone is 27 and the living floor 35.

4 · roster

roster.json 1.3.0: 115 people, 76% null. The life-support dossier's 22 billet rows expand into individuals; villagers take berths in the order the berth file lists them; EVA hours are the only per-person number any card gives - the life-support duty split puts a villager at 2.6 h/sol and an undercity resident at 0.3. The dose per sol follows occupancy v1 exactly (8.0 h asleep at the bunk floor, the rest awake at the living floor, EVA at 0.66/24.65), and a person whose sleep or awake floor is null gets a null dose with a gap field naming what is missing and whose card could fill it. A conservation gate sums the roster two ways (per person, per location × person-hours) and demands agreement to 10⁻⁶; a known-answer gate demands 194 sols for the standard bunk at X = 0.5 under table v2 (and still 148 under the retired v1, as history); a schema gate validates the file; the runner checks determinism. What scheduling can change is X alone: from 0.5 h to zero buys 12.5% more stay. The floor is geometry's.

5 · stress

Full village; one measured solar particle event. Dose is not a shared resource, so filling all 30 beds changes no one's floor - it changes the EVA share, because the life-support card does give a city-wide EVA demand, 104 person-hours per sol, 78 of them the village's: at 24 occupants that is 3.3 h each and 121 sols to the line, at 32 it is 2.4 h and 136. The only SEP this dossier will cite is the one RAD measured on the surface: 10-11 September 2017, 418 µGy in silicon, peak rate 2-3× the GCR background. Converted with a declared silicon-to-water factor of 1.3 and a quality factor scanned from 1.0 to 1.5 - not chosen - that is 0.54-0.82 mSv, under one sol of ordinary surface dose, 2.7-4.1% of the 20 mSv line. The asymmetric conclusion registered in advance: this rules out "a RAD-class event triggers a rotation"; it does not license "SEP is safe", because the larger historical events were never measured on Mars and the attenuation under 2 m of soil or 30 m of rock has no run.

What the board draws

Run B floor, lower bound (village)
RAD measured (surface 234)
no basis / shielding factor only / pending Run E
villager, standard bunk (24)
villager, end bunk (4)
villager, corner bunk, no number (2)
undercity, one of the five carded cabins
undercity, no card bed (80)
sols to 20 mSv · green tick = 20 mSv/yr line
red tick = one Earth year (355.5 sols)
violet cap / tile edge = value rests on the model with a void under the floor (bunk systematic within ±17%, direction unresolved)
thin violet span on a scenario bar = sols to 20 mSv if the bunk floor alone moved by ±17%; a sensitivity, not an error bar
Wide view: the freestanding board in front of the compute centre's glass wall, the storage farm and fusion radiator to the left
Where it stood for the smoke test (taken 09-13, before the qualifier marks): 14 m in front of the compute centre. Whether it lives there, in the quarter's commons, or nowhere is the hosts' call; the part is offered, not placed.
03

The ledger

Every headline number on this page, the state it is in, and the script that produced it. Scripts live in the dossier mars-roster/ledger/ and write mars-roster/ledger/out/; the pre-registration is the dossier's first commit.

ClaimValueStateProduced by
Registered assets / unit modules / cards79 / 78 / 80 on 09-13 · 83 / 82 / 84 on 09-21measured · brief said 82, not reconciledmars-roster/ledger/01_census.py
Residential beds on the cards35 (+2 temp, +5 non-res.)census · E1.1 miss (40-70)01_census.py · hab-village-01 plaza · hab-quarter-01 cabin
Village beds vs berth file30 = 24 std + 4 end + 2 cornerknown-answer gate G1.301_census.py · mars-village berths.json v5d
City population115 = 85 + 30derived by another card, not self-reportedres-eclss-01 screen · mars-eclss calc/L0_population.py
Cards stating their own crew10 of 79 (8 unmanned)census01_census.py
Units whose crew count is a gap42 on 09-13 · 45 since 09-21 (four new units; ops-drill-01 states a drill count, so three are gaps - the 46 printed here on 09-21 was read before that row was added and is withdrawn)declared gap · per-unit list in json01_census.py
People without a card bed80census · 85 vs 5 cabins01_census.py · 04_roster.py
Dose-map positions with a number7 of 27 (6 on 09-13; Run E landed)map · E2.1 hit02_dose_map.py
Village floors: std / end after works / living(new) / airlock after works27.0 / 28.3 / 35.2 / 30.5 mSv/yrruled · Run B lower bounds; after-works rows = Run B × Run E ratio 95% upper bound · void systematic ±17% on bunks, direction unresolvedhab-village-01 plaza card 0284870 (table v3) · Run B b2077f4 · Run E 7ca30ae · glass ledger 30 63a57b8
Same, before the vestibule works; old living position31.9 (end) · 93.7 (airlock) · 52.8 (old living)ruled · kept in the ledger beside the current values; the old living position was retired by table v202_dose_map.py value_pre_works
Village table v3 reproduced251/219/194 end · 233/205/183 airlockcross-implementation gate G3.7 · to 50 mSv within ±1 sol03_quota.py
Surface dose; EVA per sol234 mSv/yr · 0.66 mSv/solmeasured · RADsci-rad-01 rack card (RAD anchor)
234/365.25 × 1.0275 vs 0.660.658 · 0.26%known-answer gate G2.202_dose_map.py
Undercity absolute floornullnot closable · missing rock γ background and a measured muon floor02_dose_map.py · hab-quarter-01 · sci-rad-03
Deep-lab absolute floornullnot closable · µ ×10⁻⁶ only02_dose_map.py · sci-deeplab-01 · sci-rad-04
Village table reproduced263/228/201 · 218/194/175 (table v2)cross-implementation gates G3.1, G3.6 · Run B qualifier03_quota.py
City line vs village line, per sol0.0563 vs 0.0748 mSv · 1.33×account03_quota.py
Career 600 mSv, occupancy v1, X = 0.55,830 sol · 16.4 yraccount · NASA-STD-3001 Rev. C · Run B qualifier03_quota.py
Career 600 mSv on the surface2.56 yraccount03_quota.py
ICRP 103 five-year average in the villagenot satisfiableasymmetric · 27 > 20 before EVA03_quota.py
Roster people / with a number / null share115 / 28 / 76% · all 28 qualifiedroster.json 1.3.0 · 1.3.0 changes only the four end-bunk residents' sleeping term (G4.11); earlier steps G4.8, G4.904_roster.py
Conservation, two summation routes4.2479 = 4.2479 mSv/solgate G4.1 · first version red on rounding04_roster.py
Villager EVA share, life-support split2.6 h/solcited · not in the village's v1 tablemars-eclss L0 duty_split · 04_roster.py
Std bunk to 20 mSv at X = 2.6132 sol (end bunk 131, after the works)account · v1 structure, fourth column · Run B qualifier04_roster.py · 05_stress.py
Std bunk, X = 0.5, bunk floor ±17%187 – 203 sol (point 194)sensitivity · glass ledger 30 bound on the sleep term only (gate G4.12)04_roster.py
Stay bought by X 0.5 → 0194 → 218 sol · +12.5%account · E6.4 · Run B qualifier04_roster.py
City EVA demand; village share104 · 78 person-h/solcitedres-eclss-01 screen card
Full village 24 → 32 occupantsX 3.3 → 2.4 h · 121 → 136 solaccount · floors unchanged (gate G5.4) · Run B qualifier05_stress.py
RAD SEP of Sept 2017, surface418 µGy (Si)literature · Zeitlin et al. 2018 GRL05_stress.py constants
Same event as tissue dose equivalent0.54-0.82 mSv · 2.7-4.1% of 20declared conversion (1.3 × Q 1.0-1.5) · E5.1 miss on the letter05_stress.py
SEP under soil or rock≤ surface valuebound only · no run05_stress.py
Board geometry5,592 tris · 2.52 × 2.45 × 0.60 m · minY 0measured · validate_unitviewer/units/roster-board.js
In-city smoke testscale 1 · 57 assets · 0 console errors · people layer reads 115 / 30 / 85 / 80measured · ?colony=1&debug=1viewer/index.html
04

What broke

Twenty-two expectations were written before the first card was opened, five more before version 1.2.0 and five before 1.3.0 were computed. Two missed, four could not be decided, two gates went red on their first version, and a week later a source reported a defect in its own model. These are the ones worth reading.

I expected 40-70 beds. The cards hold 35.

The pre-registration guessed one to four beds per undercity cabin and up to fifteen temporary bunks. The quarter's card says five single cabins; the only temporary bunks are two in the village and four sleep pods in a landed Starship that is a museum piece. The band stays where it was written; the miss is the finding: the city's residential capacity on paper is a third of its stated population.

The undercity floor cannot be closed, and I had expected a number under 5.

The quarter's card gives a shielding factor and the phrase "about Earth background"; the sentinel column gives 52 hadronic e-folds and muons ×0.03; the surface station's 0.3 mSv/yr muon floor is, on the village's own berm card, a self-set order of magnitude with no measurement. Eighty-five people therefore carry a null dose. Writing 0.3 would have filled the roster and hidden the gap.

Two cards disagree about the propellant plant and the roster does not rule.

The ISRU card describes a 480-sol unmanned production period; the life-support staffing script bills it six people on continuous shifts, housed in the undercity. Both are cited in the same census row with "registered, not ruled". A roster that picked one would be manufacturing a fact.

The conservation gate went red on its first run, and the model was right.

Summing per-person doses after rounding to five decimals and comparing with a per-location sum missed by 2×10⁻⁴ over 28 people. The gate now sums unrounded components; the band (10⁻⁶) did not move, and the reason is in the script. A second gate - "a person with zero shares gets zero dose" - had been written so it could not fail; it now also requires a non-zero share to give a non-zero dose.

The village's occupancy table stops at 1 h of EVA. The life-support split gives its residents 2.6.

Not a contradiction in any one card, but a cross-ledger difference nobody had multiplied out: at 2.6 h/sol a standard-bunk villager reaches 20 mSv in 132 sols, not 194. The roster prints it as a fourth column inside the v1 structure and sends the difference to both holders.

My scorer marked a hit as a miss by reading the wrong round's inputs.

The first run of version 1.3.0 reported three misses where there had been two. The new one was a round-2 expectation - "the end bunk reaches the line 3 to 8 sols sooner than the standard bunk" - which had been a hit on 1.2.0 and was now being re-scored live against 1.3.0, where the vestibule works had moved the end bunk. The dossier's own convention is that a round's expectations are scored on that round's inputs; the scorer now reads the archived 1.2.0 file, and the verdict is again the one it earned when it was written.

Five of my seven numbers rest on a model with a hole under the floor.

Not this dossier's error and not its finding - the glass works reported on 2026-09-21 that since Run B their transport model built the village hull as a whole cylinder with a void beneath the floor plate. But a dossier made of citations inherits its sources' defects whole. The roster went to 1.1.0 with a qualifier on every value derived from those floors, a propagation gate in each ledger that goes red if a Run B number lacks the mark or a RAD number carries it, and a gate that requires 1.1.0's values to equal 1.0.0's: a qualifier is not a correction, and nothing was re-estimated. Three days later the glass works ran the corrected geometry and bounded the bunk systematic at ±17%; the mark kept its name and changed its words.

The village's table moved while this round was being written, and a gate caught my sentence about it.

Between two runs of the same script on 2026-09-21 the roster's hash changed: the village had committed its stay table v2, replacing the living floor 52.8 with Run E's 35.2 and retiring 160 / 148 / 137. A second pre-registration was written before anything was recomputed; it said the two corner-cabin residents would be byte-identical across versions. They are not - their total is null, but their awake-hours term is a number and it moved with the floor. The gate went red on my own wording; the gate's statement was corrected to the true mechanism, not loosened.

The SEP share came out at 4.1% against a registered 2-4%, and is judged a miss.

The upper edge of the declared conversion (silicon-to-water 1.3, Q 1.5) gives 0.815 mSv, 4.08% of 20. The letter of the expectation says 4%. It is scored as a miss rather than rounded into a hit, because a band that is stretched after the fact is not a band.

05

Try it

Data interface for the engine's "where people are" layer

roster.json
  schema        "ops-roster-01/roster"        version "1.3.0"        occupancy "v1"
  qualifiers[]  { id, date, revised, text, text_en, bound{interval_pct, verdict, applies_to_receptors, ...}, text_history[], source, applies_to[], not_applied_to[], effect }
  locations[]   { id, unit, status: ruled|measured|card_shielding|pending|none, floor_msv_per_earth_yr|null, beds|null, qualifier|null }
  people[]      { id p001..p115, home: undercity|village, billet, sleep_location, awake_location,
                  eva_h_per_sol, dose_msv_per_sol|null, components{habitat_sleep, habitat_awake, eva},
                  remaining{city20_sol, village50_sol, nasa600_sol}|nulls, rotation_sol|null, gap|null, qualifier|null }
  scenarios[]   { id, eva_h_per_sol, city20_sol_std, city20_sol_end, void_bound_city20_sol_std[lo, hi], qualifier, source }
  totals        { n_people, n_with_number, null_share, eva_person_h_per_sol, people_without_card_bed, ... }
  gates         { name: true|false }          lineage { supersedes|null }