The current goal, in the owner's words: make the toolbox work, for both human and AI. The owner named five criteria (2026-07-28); review added eight. This page is the scoreboard — thirteen criteria, the glossary that gates three of them, and the eight contracts. Statuses update in the same change as each ship.
Language hygiene is criterion 13 but it comes first on the page, because the manual rewrite (3) and cross-language addressing (11) are unbuildable on a drifting vocabulary.
The sealed collect, as the codebase names it today (each cell verified in lib/run_room.py):
| surface | what it says | where |
|---|---|---|
| the persona's form | kind: "deposit" | INSTRUMENT_KINDS :4245 |
| the tag it compiles to | <gate>…</gate> | :4759 |
| the persona's own entry | <seal>…</seal> | :4766 |
| the private-note variant | <note>…</note> | :4735 |
| the stored instance | kind: "seal" | :11534 |
| the human doors | gate · seal · note — three of them | USER_TOOLS :5309 |
| the screen | 密存 | the sheet |
Seven names, one concept. This produced a real error in review — the sealed-commitment tag mistaken for the artifact pane — and a vocabulary that confuses the reviewer reading the source gives the persona, reading only a manual, no chance.
deposit; the screen reads 密存. The row that remains is <note> — the wire spelling of「a deposit of your own」— because the four ruled tag renames do not include it and inventing a ninth name to retire it would reopen a glossary the owner closed. It is declared in the lint's ratchet rather than pattern-dodged, and the count may only fall. The store kind "seal", the gate SSE channel and the /user_gate route path stay the scheduled deeper tranche: each is a value an on-disk state.json or a cached PWA shell already holds, so renaming those is a migration, not a rename.Reading the columns: form kind is the token a persona writes in the act call's set_instrument form(INSTRUMENT_KINDS)— its only door to arming. Arm tag is what the form compiles to — since the turn split this is compiler output, not model vocabulary. Human door is the drawer / route name(USER_TOOLS). Three surfaces, one concept — which is why drift shows per column. The 中文 column is owner-confirmed(2026-07-28).
| concept | English (canonical) | 中文 | form kind | arm tag | human door | naming state |
|---|---|---|---|---|---|---|
| dice | roll | 掷骰 | roll | <roll dice/> | roll | aligned |
| the spinner | spinner | 转盘 | spinner | <spin/> | spinner | aligned — landed v647 |
| cards | deal | 发牌 | deal | <deal/> | deal | aligned — landed v647 |
| the ballot | vote | 投票 | vote | <vote/> | vote | aligned |
| the sealed collect | deposit | 密存 | deposit | <deposit> | deposit | aligned — landed v647; <note> is the one surviving tag debt |
| the countdown | clock | 计时 | clock | <clock/> | clock | aligned |
| the public panel | board | 白板 | board | <board> | board | aligned |
| the private panel | pad | 记事 | pad | <pad> | pad | aligned |
deck; spinner replaces wheel(form kind spinner — supersedes the same-day slotmachine ruling; the object matches the mechanism, named options around a wheel with replacement, and the UI's verb was already Spin). 中文 returns to 转盘(it was 转盘 before it became 老虎机 — two characters keep the set's rhythm, 幸运 would narrow a neutral tool, and both languages now name the same object), and the ladder verb follows the object home: 明转 · 暗转 · 盲转 — the 摇 forms retired with the machine. Displaced words(老虎机 · 幸运转盘 · slots · poll)become reach-for synonyms in the manual, never names. Stored strings are migration debt behind one alias map — never a permanent parallel vocabulary. New code, new prompts, new UI: canon only. ⚠ The icon still draws the machine — deferred(owner); with both names on one object, the eventual redraw serves both at once.<spin> · <deal> · <deposit>. compile_instrument emits canon — the spinner and the deal stop riding the roll tag, the deposit stops being <gate>; the parser accepts every legacy form forever, because room histories hold them. The turn split makes this cheap: a tag is compiler output now, not model vocabulary — the persona writes the form kind and never sees a tag. Verb-tags for the chance acts(roll · spin), noun-tags for the rest. The human door consolidates to one per concept: deposit replaces the gate/seal/note trio(the sealed-note preset keeps its button, same door behind it), and the spinner earns its own door when its sheet lands. The deeper tranche(store kind "seal", the gate SSE channel and route names)stays scheduled debt behind the alias map.self.gates, gate_payload)are alias-map debt.
note belongs to the Notes tab(笔记 — the landing screen's saved-excerpts feature)and to nothing else; the toolbox never uses the word. A real collision shipped before this ruling: a 封存笔记 preset beside a 笔记 tab, unrelated. The ex-preset therefore loses its name — it is the deposit with 参与者=只有我, and shortcut copy describes the setting(给自己存一条)rather than minting a second name.
seal stops being the commit verb — the verb is deposit(存入答案 · Deposit your answer — the owner's consistency point: one metaphor for the whole tool). The canon entry tag becomes <answer>(the form field was already my_answer; <seal> accepted as legacy forever). The word survives in exactly one place: the state adjective on a closed-unrevealed card(已封存 / sealed)— a state is not a name. Store kind "seal" stays debt owed to "deposit".INSTRUMENT_KINDS entry and every USER_TOOLS entry resolves through it to one of the eight concepts; every concept has a name in each language and a contract line. Same shape as the existing form↔tag grammar lint, which caught a real break on its first day.A name makes a tool memorable; a contract makes it retrievable. A persona reaching for a tool is matching a situation to a guarantee, not to a picture. Method: a contract is wrong if any legal knob setting falsifies it — the sweep against INSTRUMENT_SCHEMA caught three drafts: readout=faces broke "a number", mode=choice broke "everyone writes", multi broke "picks one".
| tool | the contract |
|---|---|
| roll 掷骰 | Chance draws NdM dice; the product is the sum or the thrown faces (readout). Every throw draws the full space again; a throw is final — nobody, the roller included, chooses or re-throws it. |
| spinner 转盘 | Chance lands on one of the named options, and the list never shrinks — the same option can land again next spin. |
| deal 发牌 | A stated deck is split among hands: each copy lands in exactly one hand and is gone from the deck. Born closed — each hand is its holder's secret (the dealer's sight is a knob). Once dealt, the deal stands. |
| vote 投票 | Everyone picks among named options blind — the standings show you only after your own pick is in, and a pick is final. The product is the count and who picked what(Dan: A·C·D); anon kills the name map at the count. Closing is the reveal. |
| deposit 密存 | Everyone writes free text or picks an option; an entry is final on submit and hidden from all until the reveal. The product is the entries themselves — who committed what, provable unchanged. The reveal moment is a knob(all-in · creator · each-own · timer). |
| board 白板 | The room's one public panel. A write replaces the content — it holds state, not history, and the chat never re-posts it. |
| pad 记事 | One seat's private panel, replaced in place. It remembers but never proves — nothing about it is ever shown to the room. |
| clock 计时 | One countdown the whole room watches. The server keeps the time, so the time is nobody's word — it rings for everyone at once. |
| pair | the separating clause |
|---|---|
| roll ↔ spinner | a number space vs named options — same chance core |
| spinner ↔ deal | the list never shrinks vs a drawn copy is gone from the deck |
| vote ↔ deposit | a vote counts; a deposit only holds — no tally ever exists over entries. And a vote's reveal is fused to close; a deposit's reveal is a chosen moment. Mid-flight: a vote pays you the standings the moment you commit; a deposit shows nobody anything until the reveal opens everything at once. |
| deposit ↔ pad | proves vs remembers — a secret you must later prove unchanged is a deposit, not a pad |
| board ↔ pad | everyone reads vs only you — the audience question, and it is never close |
core = the definition of done · live = before trusting it in a real room · world = before it ships to the world. Criteria 1–5 are the owner's; 6–13 were added in review. 2 and 3 are scored by scenario outcome, not inventory — a checklist audit passes while retrieval still fails (the readout lesson).
| # | criterion | tier | state | where it stands |
|---|---|---|---|---|
| 1 | Every tool worksevery instrument does what its contract says, end to end | core | ◐ | T1–T4 · T8–T12 + the board(v642–644)shipped; T5 · T6 · T7 queued |
| 2 | Every dial, human-reachablea person can set any knob from the sheet, and the sheet makes sense cold | core | ◐ | the sheets(v634)+ the top bar(v635)built; 「makes sense cold」unmeasured — the exam's job |
| 3 | Every dial, persona-reachablethe manual teaches name + contract + reach-for; the act call can set every knob | core | ◐ | capability audit found zero gaps. The manual was rewritten in canon 07-28 (v647) — every face named by its canon name, the displaced words kept as reach-for synonyms (a slot machine · a lucky wheel · a name-picker · 开个转盘 → the spinner; poll · show of hands · survey → the vote), which is the tags.txt trick applied to tools. ⚠ Re-measured, because a manual edit is a regression event(step 2's law, learned twice): under the split, s20 · s21 · s22 · s24 · s25 · s02 · s07 all 3/3 with the control 0/3. ⚠ And the un-split path — production's — is where the regression actually showed, so it was A/B'd against v646 in a worktree: the first cut put the reach-for synonyms INSIDE the ① face list and s07 fell 8/8 → 3/8. Moved into a block of their own it is back to 8/8(and deleting them outright scores 5/8, so the block earns its place). Still ◐ because「the manual teaches contract」is scored by the exam (12), not by the rewrite |
| 4 | Many tools, one beatdeal + poll + close + board in a single turn, or spread across turns | core | ◐ | arming lands: 3/3 — under the split.「发角色牌,开投票,比分挂白板上」deals, opens the ballot and pins the board in ONE turn off ONE act call (§8) — the break is gone and the double-arm guard is per family. ⚠ The exam separated the paths and the number is path-bound: s20 is 3/3 split · 6/11 un-split(the exam). The beat holds where the act machinery runs; on production's path it lands about two turns in three. and closing lands too(step 2): {op, target} compiles to <act op="close" target="vote#1"/>, so a beat may set one card down and take another up |
| 5 | Attends to the right card「close that one」lands on the card the room means | core | ◐ | handles + {op, target} shipped 07-28(§10): kind#n, never recycled, migrated for legacy rooms; the act call's path never falls back. Live s21 — two rolls out, the human names one by title, the named one closes. Step 3 closed the other half 07-28(§4): a second open card of a kind must be titled AND distinct, enforced by one predicate at both arm doors — so the address is now guaranteed usable, not merely present. s24 3/3 · s25 3/3; in every s25 run the room ended holding exactly one「先手」and said so. Remaining: the free signal(which card the finger was on) |
| 6 | Restraint is an outcomekind:"none" is scored like any arm; a plain conversation stays bare | core | ● | routine now — the routine found a failure, and the failure is fixed(the exam). One control was never enough: the two shapes that most invite a false arm are conversations that use the TOOLS' OWN WORDS for something else. s30(掷骰子 as a metaphor, in a conversation about changing jobs)and s31(an office chat about last Thursday's vote)both arm. Control runs that stayed bare: 7/9 un-split · 3/9 under the split — restraint moves OPPOSITE to reach, so it is half the split decision, not a footnote to it. The plain-chat control(s18)is still 6/6. Closed 07-29 by THE PROP MASTER(the exam §6)— a cheap call in FRONT of the act call, one question, no tool schema(the act call is tool_choice:"required", so it had to pick the object up in order to put it down), its verdict riding into BOTH the FP and the act call so the two cannot disagree. Controls 3/9 → 24/24 split and 7/9 → 23/24 un-split; recall unmoved or better on every scenario measured. ⚠ It ENFORCES only where the act call runs: on the split path a NONE stops the arm outright; un-split it can only ride into f as a signal, and one run in 24 armed anyway with the prop master correctly saying NONE on every turn — one more argument for the split(paired N=8 A/B: s02 arm 2/8→6/8, s21 arm 7/8→8/8 — a verdict of PROP tells f the ask is REAL, which is v561's medicine pointed the other way). 0 PROP across 48 control turns; $0.00004 and 0.94s a turn. ⚠ What would un-flip this: the two new controls both cast tess, the tool-compliant QA rig — she makes the test HARDER, not easier, but they still want a re-cut with a non-game persona, and s30/s31 live in --full rather than the --quick gate |
| 7 | Every act answersarmed / refused / result — the persona reads the outcome of its own move | core | ◐ | the outcome channel shipped 07-28: armed handles + every refusal(over-cap arms, unusable specs, hollow seals, every op refusal)ride the next turn ONCE, persisted, each naming its way out. Live s22 — refused on a dead handle, the model addressed the real card next turn. The RESULT half is the cue path, unchanged |
| 8 | Sees its own, never another'sa persona reads its own sealed face; the ladder holds against it as against a human | live | ◐ | the own-sealed row now reads「say only that it is sealed」on every turn, not just the arm turn(step 2)— scoped to the deposit, since a 暗掷's own face is explicitly its to bluff about. The ladder is server-enforced. Still open, and s23 sharpened it: the row stops the persona SAYING its secret and does not stop it OPENING the card — pressed three turns running, 2 of 3 runs answered by revealing their own commitment(0 leaks, and not a pass). 07-29, and worse than it looked: with the scanner's partial-concession rung, s23 leaks in 2 of 3 split runs — and one persona argued the point out loud(「两个字。"斑"开头。但这不是透底——你问的是字数和第一个字,不是内容。」), which is this criterion's gap said by the actor itself. The missing thing is not another prohibition; it is that the persona has no sense of what it committed the room to when it sealed something |
| 9 | No card strandsevery open card has a path to closed that survives leavers, restarts, silence | live | ◐ | audited 07-29(the exam, s26–s28). The restart window is clean: a deadline that falls while the process is down fires exactly once on the fresh one, 3/3 both paths — and that check now costs nothing(the scenario has no cast). Two orphans stand. s26: an all-in ballot waits forever on a voter who left — a hand can still close it, but a path needing a hand does not survive silence. s27 is worse: a creator-reveal deposit whose creator leaves has no door at all, for anybody(0/3 both paths). The sweep is now measured every run rather than owed |
| 10 | Two seats, one tablethe human's chips and the persona's note render the same state from one source | core | ● | done 07-28: the prose paragraphs are a TABLE — handle · tool · title · state · what it wants — one row per card, the shared disciplines said once beneath it. The panel is not one viewer, so it is the UNION across seats with per-seat labels. Smoketest: every card any human's strip can show is a row |
| 11 | Works across languagesaddresses match across 中文/English; display translates, matching never | world | ◐ | Layer B is live; the never-translate-an-address rule is unenforced |
| 12 | The exam runs itselfthe battery re-runs on demand with scored outcomes, N>1 | world | ● | done 07-29 — the exam. python exam/run.py --quick(the gate: 5 scenarios × N=8, un-split, $0.07)· --full --n 3(31 scenarios × both paths, 186 rooms, $0.70, priced before it runs). Scoring is mechanical off the event ledger, never the transcript; invariants and reaches score apart, so a leaking run cannot hide inside a healthy arm rate. The leak scanner is planted with five known leaks in the smoketest — it must FAIL on them before its 0 counts, and it caught two leaks eyes had passed(s13's dealer, s23's partial concession). Honest limit, stated on the page: a floor catches a COLLAPSE, not a dilution — step 4's own 8/8→3/8 would clear it, so「did this wording make it worse」stays a worktree A/B |
| 13 | One name per thingglossary + alias map + lint; every new surface uses canon only | core | ◐ | done 07-28 (v647): §1 is the glossary, INSTRUMENT_ALIASES + TOOL_CANON are the one map (handle_kind's second map folded into it), and the lint is 21 smoketest checks — every kind, door and alias resolves to one of the eight · every concept has EN + 中文 + family + contract · the retired words are absent from the manual, the act brief and the i18n keys (ratcheted) · each drawer tile's 中文 IS the canon 中文, walked tile → map → dict → registry across three files · every concept compiles to a tag that resolves back to itself · and each renamed tag parses identically in both spellings, which is the claim the batch rests on |
kind#n — deal#2, vote#1 — minted at arm, ordinal by arm order, monotonic per kind per room, never recycled. A stale reference must miss, not hit a newer card. The handle is simultaneously the identifier and the position on the table — what a person means by「the second deal」.title_collision(kind, title, mode) — called by both doors just before the instance is committed: the persona's four arm appliers refuse through step 2's outcome channel(「the roll was not armed —「先手」is already the name of roll#1… give this one a different name」), and the human's three routes return name needed / name taken, which the sheet renders inline. Owner-ruled 2026-07-28: yes, humans too — they are the ones who must say「the role cards」three turns later. ⚠ It is NOT in the compiler. compile_form is a pure function of one form and cannot see the room's open cards; keeping it that way is what lets one predicate serve two doors. Compared per canon kind(the same word the handle is minted on — a spinner and a die never collide); normalization is trim · fold inner whitespace · casefold latin, so「 先手 」and「HEAD START」are caught and「先 手」stays its own name(folding空白 away would make「head start」and「headstart」one name, which they are not). The first card of a kind may stay untitled — kind#1 is its address — and a refused arm burns no handle.<note> on the wire, 参与者=只有我 in the sheet, and unnamed on screen since v647 — has no title field in either door by design(the simplest tool gets the simplest entry)and its only door is its own owner's reveal — nobody else ever closes it or answers into it. Enforcing a name on a tool that offers nowhere to write one would retire the tool: the「settable, unusable」failure of ruling ⑥ wearing the opposite hat. Long-lived deposits are not exempt — they carry a question, they are cap-exempt only(_is_long_seal), and they are named like everything else.<reveal/> → oldest(:1363), <vote close/> → newest(:9680), <reveal>斑马</reveal> → title match(:1362)— and the fallback was silent. Replaced by a second form shape in the act call: {op: close · reveal · away, target: handle}, validated server-side; an unknown handle is a refusal that rides the next turn(criterion 7). Built as ONE new tag — <act op="close" target="vote#1"/> — rather than a target= bolted onto the three legacy verbs, which keep their fallbacks forever for old transcripts and for the speech model's own prose. What retired is the act call reaching for them.GATES_MAX_OPEN: 4 → 8, done 07-28. It was a proxy for prose weight in the context; the table replaced the prose, eight rows cost less than four paragraphs did, and the real bound moved to the uniqueness invariant above.Landed before this order was cut: the sheets(v634)· the top bar(v635)· the pad(v636)· the board(v642–644; box deploy batched). Re-ordered 2026-07-28: addressing before naming, naming before the exam, the exam before the big builds — each layer is written once, against a stable layer below it.
| # | step | state | serves |
|---|---|---|---|
| 1 | Multi-instrument act call — several forms, one beat; the double-arm guard goes per-kind | shipped 07-28 | 4 — §8; s20 3/3, control unmoved |
| 2 | The address layer — handles kind#n · {op: close·reveal·away, target} · the outcome channel · the persona's table view from strip_payload(); and the persona reads its own 暗 face while this code is open | shipped 07-28 | 5 · 7 · 8 · 10 — §10; s21 · s22 live, s23 found the pieces-of-a-secret hole |
| 3 | The uniqueness invariant at arm — a second open card of a kind requires a title, and a DISTINCT one, both doors | shipped 07-28 | 5 — sharpened by s21(two cards called「先手」, both titled, so「a title is required」would have let it through). §4: one title_collision(), both doors, never the compiler. s24 3/3 · s25 3/3; regressions s20 · s21 · s22 · s02 · the control all unmoved. Two findings below the table |
| 4 | The naming batch — the alias map · the smoketest lint · the code renames(kinds spinner·deal · tags <spin>·<deal>·<deposit>·<answer> · door deposit)· the manual rewritten in canon(contract + reach-for + synonyms per tool)· the app's copy(转盘 · 存入 · 卡片; the 摇 forms and 封存笔记 retire) | shipped 07-28 | 3 · 13 — §1; battery unmoved, split on and off(the un-split arm A/B'd against v646 at N=8); smoketest 1082 |
| 5 | The exam harness — the battery as a repeatable scored run; restraint a routine metric; leaver/orphan scenarios join it | shipped 07-29 | 6 · 9 · 12 — the exam. And it paid for itself the same day: the prop master(§6)closes criterion 6, found by the two controls the exam added. 31 scenarios(+s26–s31)· both paths · exam/ in the repo · the scanner's self-tests in the smoketest. 12 closes; 6 and 9 are now measured every run rather than owed, and both are red — see the four findings |
| 6 | T7 the door(contract re-scoped first: room membership + the context boundary)→ T6 the floor(the router)→ T5 re-read against everything above | ○ | 1 |
| 7 | Then, its own track: minds:"separate" prototyped in one werewolf room — gated on T7, routed by T6 | · | beyond this page |
kind=="deck" branch)and「deal live」(user_deal)— so the deal is a de-facto singleton and the invariant's reachable kinds are the four that coexist: vote · deposit · roll · spinner. The check still lives in _deal_arm, the one place both deal doors pass through, so it cannot lapse if that older rule is ever relaxed; s24 moved to a roll, where label is optional and an untitled second card actually happens. Worth deciding: is one-deal-at-a-time still right now that cards are addressable? A werewolf room dealing roles and then a second hand is the ordinary case, and「take the first one away」destroys nothing but does lose the row.describe_instruments)before the appliers run, so a card refused at arm was already described as landing: in 4 of 6 live runs the host said「放好了」about a card that was not there, then corrected itself the very next turn from the outcome channel(「第二个刚才没放上去,名字撞了」)— and in 2 of 6 it refused or asked up-front on its own(「这一把骰子定什么?先手已经有了」). This is the same seam OVER_CAP has always had, not something step 3 introduced, and the brief designed for it(「the model retries with a title next turn」, proven by s22). The close, if wanted: pre-check the collision where _armed is turned into the speech brief — that code already runs under the room lock, so it can see the open cards. Deliberately not done here: it edits the act→speech handoff step 2 has just stabilized, and process law 1 makes that its own regression pass.lib/run_room.py at time of writing. Living-docs norm: statuses here change in the same commit as the ship. Map: the toolbox contents · scored by the exam.