Dialogue · Toolbox · definition of done
‹ The toolbox

The definition of done

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.

The one-line test. A GM runs a full werewolf night — deals the roles, opens the day vote, closes yesterday's roll, updates the board — and every seat, human or persona, always knows which card is being talked about. Nothing on this page matters except as a step toward that sentence.

1 · The glossary — one name per thing

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 exhibit — one concept, seven names

The sealed collect, as the codebase names it today (each cell verified in lib/run_room.py):

surfacewhat it sayswhere
the persona's formkind: "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 instancekind: "seal":11534
the human doorsgate · seal · note — three of themUSER_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.

Landed v647Seven names became one, and a test now says so. The form kind, the arm tag, the entry tag and the human door all read 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.

The canon — eight tools

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).

conceptEnglish (canonical)中文form kindarm taghuman doornaming state
diceroll掷骰roll<roll dice/>rollaligned
the spinnerspinner转盘spinner<spin/>spinneraligned — landed v647
cardsdeal发牌deal<deal/>dealaligned — landed v647
the ballotvote投票vote<vote/>votealigned
the sealed collectdeposit密存deposit<deposit>depositaligned — landed v647; <note> is the one surviving tag debt
the countdownclock计时clock<clock/>clockaligned
the public panelboard白板board<board>boardaligned
the private panelpad记事pad<pad>padaligned
Ruled 07-28The canonical name is the English UX name, used on every new surface. Five tools already comply. deal replaces 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.
Ruled 07-28The tags follow the names: <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.
Ruled 07-28Three reserved words — each keeps one meaning. gate retires entirely. The generic is instrument in code and docs, Card on screen(卡片)— the word that used to mean one tool and every tool now means nothing. Legacy uses(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".
Ruled 07-28An address is never translated. Tool names are chosen per language (转盘 and 密存 were chosen, not translated) and card titles are canonical as authored. Layer B may translate a title for display; matching and targeting always run on the authored string. A translated address is a broken address.
LintThe map beats the convention. The alias map lives in exactly one module constant, and the smoketest asserts: every 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.

2 · The contracts — what each tool guarantees

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".

who produces the outcome? CHANCE draws PEOPLE commit STATE holds roll · spinner · deal vote · deposit board · pad · clock the server picks; nobody chooses; a draw is final entries lock on entry; nobody reads early nothing drawn, nothing closes; current value, replaced in place
The first branch of retrieval. One question lands a situation in a family of at most three. Visibility(明·暗·盲)and reveal triggers are orthogonal axes shared across kinds — they appear in no contract, which is exactly what keeps 盲转 findable under the spinner.
toolthe 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.

Every neighbour pair separates on one clause

pairthe separating clause
roll ↔ spinnera number space vs named options — same chance core
spinner ↔ dealthe list never shrinks vs a drawn copy is gone from the deck
vote ↔ deposita 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 ↔ padproves vs remembers — a secret you must later prove unchanged is a deposit, not a pad
board ↔ padeveryone reads vs only you — the audience question, and it is never close
RuleContracts stay pure; scenario mappings live beside them. The manual carries reach-for lines — a quiz question is a vote · rock-paper-scissors is a choice deposit · roles are a deal · turn order is a roll or a spin · the score goes on the board · your own working memory goes on the pad — as examples, never folded into the contract. Contracts are falsifiable; examples are not. Mixing them is how a manual rots.

3 · The thirteen criteria

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).

#criteriontierstatewhere it stands
1Every tool worksevery instrument does what its contract says, end to endcoreT1–T4 · T8–T12 + the board(v642–644)shipped; T5 · T6 · T7 queued
2Every dial, human-reachablea person can set any knob from the sheet, and the sheet makes sense coldcorethe sheets(v634)+ the top bar(v635)built; 「makes sense cold」unmeasured — the exam's job
3Every dial, persona-reachablethe manual teaches name + contract + reach-for; the act call can set every knobcorecapability 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
4Many tools, one beatdeal + poll + close + board in a single turn, or spread across turnscorearming 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-splitthe 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
5Attends to the right card「close that one」lands on the card the room meanscorehandles + {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)
6Restraint is an outcomekind:"none" is scored like any arm; a plain conversation stays barecoreroutine now — the routine found a failure, and the failure is fixedthe 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 MASTERthe 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 FLAT — pooled 66%→69% un-split, at ceiling on split — and s20 cost 6/8→3/8 until the new f clause moved off the FIRST position in its bullet, which put it back to 7/8(the A/B/C: same words, position alone, the 4th sighting of the positional law and the first controlled one). ⚠ 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
7Every act answersarmed / refused / result — the persona reads the outcome of its own movecorethe 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
8Sees its own, never another'sa persona reads its own sealed face; the ladder holds against it as against a humanlivethe 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
9No card strandsevery open card has a path to closed that survives leavers, restarts, silenceliveaudited 07-29the 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
10Two seats, one tablethe human's chips and the persona's note render the same state from one sourcecoredone 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
11Works across languagesaddresses match across 中文/English; display translates, matching neverworldLayer B is live; the never-translate-an-address rule is unenforced
12The exam runs itselfthe battery re-runs on demand with scored outcomes, N>1worlddone 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
13One name per thingglossary + alias map + lint; every new surface uses canon onlycoredone 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

4 · The address ruling(criterion 5, specified)

The title is not a label — it is the address. Failure does not come from how many cards are open; it comes from whether the room can say which one it means. Two untitled deals are unaddressable at two; eight titled cards are trivial at eight.

5 · The build sequence

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.

#stepstateserves
1Multi-instrument act call — several forms, one beat; the double-arm guard goes per-kindshipped 07-284 — §8; s20 3/3, control unmoved
2The 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 openshipped 07-285 · 7 · 8 · 10 — §10; s21 · s22 live, s23 found the pieces-of-a-secret hole
3The uniqueness invariant at arm — a second open card of a kind requires a title, and a DISTINCT one, both doorsshipped 07-285 — 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
4The 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-283 · 13 — §1; battery unmoved, split on and off(the un-split arm A/B'd against v646 at N=8); smoketest 1082
5The exam harness — the battery as a repeatable scored run; restraint a routine metric; leaver/orphan scenarios join itshipped 07-296 · 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
6T7 the door(contract re-scoped first: room membership + the context boundary)→ T6 the floor(the router)→ T5 re-read against everything above1
7Then, its own track: minds:"separate" prototyped in one werewolf room — gated on T7, routed by T6·beyond this page
step 3 finding · owner call owedA second DEAL is impossible today, so §4's own worked example is unreachable. Both deal doors refuse one storey above the invariant —「one deal at a time」(the panel's 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.
step 3 finding · the seam, acceptedAn arm-time refusal is invisible to the SAME turn's speech. The speech call is briefed from the armed list(describe_instrumentsbefore 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.
step 3 · a step-2 debt paid in passingThe panel's「one deal at a time」refusal reached stderr and nobody else — found by s24's first cut, where the host announced a phantom deal(「发好了。一人一张,自己看」)in 2 of 3 runs and doubled down the turn after(「两副牌都在桌上」). Every act answers(criterion 7), so that door now writes an outcome like the other four.
Toolbox · definition of done — opened 2026-07-28 from the owner's five criteria plus the naming and contract review of the same day. Code references point at 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.