← The toolbox — contents

The kits-era sweep

Opened 2026-08-07 on the owner's report: 「we've made lots of changes in order to make tools and kits work — the system is not as stable as before the kits era」. Twelve symptoms were named; this page is the ledger for all of them plus whatever the sweep turns up. One case per row, each carrying the symptom as reported, the root cause once proved, and the fix. A case is not closed on a plausible story — it closes on a reproduction that goes red and then green.

The sweep's law. The kits era added state that outlives a turn — a kit on the table, a running flow, a board, a probe, an offer card. Everything on this page is a place where that state and the room's ordinary machinery disagree about who owns what. Fix the disagreement, not the symptom.

The board

#Symptom, as reportedStatusRoot cause
1Can't dismount cleanly — the kit's invoked tools are left on the tablefixedA dismount cleared kit and nothing else — every OPEN instrument stayed live (§A)
2Can't dismount cleanly — still in kit mode; the persona thinks the kit session is still runningfixedMounting a kit OVER another never handed over: the old engine stayed armed (§B)
3Bubble flakiness — after a refresh, bubbles are gonefixedReplay is faithful; the one real loss was the record wipe at first open (§F)
4Ghost thinking bubble — appears and never lands as a messagefixedThe egg was raised on four paths the drain then refuses (§C)
5Uninvited thinking bubblesfixedSame seam as §C — the device raised an egg over a blocked drain
6Missing thinking bubblesfixedThe utility kit's turn returned before the only line that raises it (§D)
7No response of any kind to a user input the room wasn't designed forfixedTwo seams: §C's silent refusal and §E's front-door loop
8Dead loopfixedThe interrogation's confirm gate was an exact-match word list (§E)
9Token bleedingfixed§E spent a model call per lap; and the whole engine was outside the call fuse (§I)
10The board doesn't auto-refresh when its content updatesexplainedThe board does refresh live — but §E meant it had no content to update (§G)
11Tools are shared across kits — find any kit-specific ones; unify the visual languagefixedNo kit-specific TOOLS — but two kit-local NAMES rendered no glyph at all (§H)
12Unstable bubbles — refresh the browser and the bubbles disappearfixedSame as #3
13found in the sweep — a kit mounted before the room's first open is erased from the recordfixedRoom.open() called events.clear() (§F)
14found in the sweep — the interrogation's model calls answer to no meterfixedIts pump ran outside the call fuse — the app's one hard stop (§I)
15found in the sweep — a handover left the old kit's ballot standing, freezing the new kit's enginefixedA gap in §B's own first cut — the sweep runs on a replace too
16found in the sweep — one line after a dismount bought three panel turnsfixedThe take-off was the one kit route that never scanned the wake door (§J)
17found in the sweep — a provider retry, the presence gate's own note and a private card look each bought a panel turnfixedThe wake door's catch-all arm was a default, not a decision (§K)
18found answering the owner's questionmental 大话骰: a cartridge below its player floor mounted anyway and the persona could only narrate the gamefixedA「classic」tier the design does not have, reachable by any solo player (§L)
19the owner's design, not a defectTHE FREEZE: the open world stops while the mini-game runsbuiltA kit shuts the drawer, suspends what is standing, and hands it back untouched (§M)
20the owner's question, not a defect — should the two engines be merged behind one abstraction?answeredMerge the lifecycle and the contract; never the language — the narrowness IS the guarantee (§N)
21found by the live run — a dismount mid-turn was undone by the turn: the engine and its board came straight backfixedAn unconditional state write-back at the end of an off-lock turn (§O)
22found by the live run — the freeze was invisible to the client: a suspended card looked live and a paused clock kept counting to zerofixedfrozen reached neither the gate payload nor the clock's (§O)
23found by the sim's first run — one answer woke every seat, and a twelve-answer sitting ended at onefixedThe router was asked for thread ids, and the world obeyed all of them (§P)

What changed

§The fixWhere
AA dismount sweeps the table: every open instrument is called off, the clock comes down, the board clears, an undealt hand goes back in the box. Every capsule and settled card stays. One door for all four take-off paths._kit_sweep_table
BA replace is a handover: whatever engine is running stops — through the lawful abort, so an aborted game still publishes its pile — before the next kit starts._kit_engines_off
CThe egg has a door. turn_will_run() asks the drain's own refusals BEFORE the promise is made, and the worker lowers the egg on every silent refusal — with mute:false, so it doesn't gag the next turn.turn_will_run · _worker · the egg handler
DThe interrogation raises the egg for the whole room, not just for whoever typed.the probe branch of drain_turn
EThe front door reads meaning, not equality — a line that agrees and proposes no change is a go — and two brakes make a loop impossible: an unchanged re-reading starts the run, and so does the third read-back._is_go · _on_readback
FThe first open drops the prior opening only. Everything else recorded before it is somebody's record and stays._drop_prior_open
HOne object, one name: the client resolves synonyms so an alias can never render blank, the three kit files are corrected to the canonical names, and a lint over every kit on disk keeps them there.TOOL_ALIAS · the kit files · the smoketest
LA cartridge below its player floor is REFUSED at the door — no「classic」tier to fall into — and the shelf greys the row with the reason before the tap. One copy of the floor arithmetic serves the door, the device and the shelf.kit_floor_miss · kitFloorWhy
POne message wakes exactly one seat, however many the router names — and the sim harness stopped reading half a conversation, returning mid-think, and accepting zero as「exactly one」._on_route · exam/live_sim.py
OThe lifecycle driven over the REAL routes for every kit — 64 checks. frozen now reaches the client on both payloads, and a turn landing after a dismount no longer resurrects the engine it was holding.exam/kit_lifecycle_live.py · _life_payload · clock_state · _probe_emit
NThe five axes as one contract both engines are driven through — plus engine(), the「is any engine running?」question nothing could ask before. Merge the lifecycle, never the language.exam/kit_contract.py · engine()
MThe open world freezes while a kit plays: the drawer shuts, empty cards clear, everything holding a commitment is suspended and handed back with its deadline moved forward by the time it was held. Plus a 24h self-sweep for empty cards.freeze_table · thaw_table · stale_card_sweep · _frozen_stop
IThe interrogation's pump answers to the call fuse like every other model call the room makes. Refused → it stops, leaves the call pending, and resumes when a human acts._probe_pump

Sixteen pins in lib/smoketest.py hold all of it. Each was watched go red against the old code before being called done — including the §H lint, whose first cut passed vacuously because it scoped itself to the shelf and both offending kits were withdrawn.

The mental image — what the kits era actually changed

The owner's question, 2026-08-08: 「Before, a world event triggers a wake — human message, tool mount, tool state change, a long silence — the general form is still tic-toc. After, in kit mode the device takes over the wake?」 Close, with one thing inverted, and the inversion is the whole reason this page exists.

The wake machinery did not change. A source pushes an item into the turn queue, something signals the drain worker, and the worker takes the whole queue as ONE turn. That is the tic-toc, and it is identical before and after. What the kits era changed is who is allowed to push, and who decides what the turn is for once it drains.

THE SOURCES a human line · a tap a reaction the world door(wake_scan) a device moment a dead-man timer · a settle turn_queue one batch many in, one out THE DRAIN WORKER takes the WHOLE queue → ONE turn …and WHO owns that turn is the thing a kit changes — see the ladder below the turn's own events can push again — the loop that is new
The machine is the same one it always was. The two coral sources, and the dashed return path, are what the kits era added.

「Kit mode」 is a ladder of three

RungWhat is on diskWho owns the turnWhat can wake the room
no kitthe personaa human · the world door
a cartridge
```flow
every play kit
a rulebook + a programthe server deals, counts the ballot, applies the elimination, checks the END. The persona is asked only for moments — facts and obligations, never wording+ its own: dead-man timers, a settle, 「everyone has described」, after-speech
a utility kit
```probe
a rulebook + an enginethe engine runs everything — which seat speaks, what it is asked — and makes its own model calls. The whole ordinary turn(prop master, floor producer, act call, tool manual, dispatch)is OFF+ its own beats

So the device never「takes over the wake」. It becomes one more source pushing into the same queue, beside humans and the world door — not a replacement for either.

corrected An earlier cut of this page had a fourth rung — 「a classic kit: 大話骰 as prose, the persona has simply read a rulebook」. That was wrong, and the owner caught it: 「in my design there is no kit played from persona's memory. All kits are in the form of a cartridge. There should be no mental liar's dice.」 The shelf agrees — every live play kit carries a flow block. What I had found was not a design rung at all but a fallback the runtime could drop into, and finding it that way is the whole argument for writing the model down: a code path read as a design becomes a design. It is now case 18, below, and it is closed.

The difference that actually matters

before Every wake traced back to a person. Somebody typed, tapped or reacted. The room was purely reactive: it could not spend a cent on its own.
after The room can wake itself, in a loop. A timer fires. A ballot settles. A phase completes. An engine has a pending call. Nobody did anything — and a model call happens, which produces events, which can wake it again.

The toolbox(v635–v642)introduced the first self-wakes, but each was a one-shot tied to an object a person had placed: a clock rings once. The kits era turned self-waking from an EVENT into a PROCESS — a cartridge advances on its own until the game ends, and a utility kit runs a whole sitting.

This was already felt once. The commits of 2026-08-05 are literally feat(bleed): the presence gate — the room stops talking when nobody is there and fix(bleed): a ruling is not a pulse, and the fuse must outlive the process. Three brakes exist for exactly this reason:

And that is why this page is a page. Every one of the seventeen cases below lives in the self-waking territory, and most of them are the brakes not reaching it. The interrogation shipped on 08-07 with its model calls entirely outside the fuse built on 08-05(§I). The front-door loop was the room asking itself the same question forever, no human needed(§E). The ghost bubble was a self-wake promising words the drain then silently refused(§C). The dismount cascade was the room's own events waking it twice more(§J). None of them could have happened in the reactive world.

The cases

§AA dismount takes the rulebook off and leaves the furniture running
What happens

Taking a kit off the table clears self.kit, aborts a running cartridge and (since 2026-08-07) clears the interrogation's engine. It does not touch the instruments the kit armed. An open ballot, an open roll, an open deposit box and a running clock all survive the dismount — pinned to the top bar, still counting, still holding their dead-man timers.

Evidence

13 rooms in rooms-dev/ carry a dismounted kit and a still-open instrument. Three sampled: a roll and a deal (…073015-37d3), a roll (…085022-2a5d), a deposit (…064601-a727).

Why it also feeds #7 and #9

An open ballot with blocking=host makes _blocking_open() true forever, and that predicate silently refuses every drain (§C). So a leftover ballot from a kit nobody is playing any more can mute the room permanently — and its lifecycle timer keeps waking the room to narrate a game that is over.

The prior ruling this reverses

_unload_kit_core's own comment says a play kit's objects「are the room's objects now, and a rulebook leaving is not a reason to sweep the table」. That reading produced the report. An open instrument is not an object — it is an unfinished obligation. The fix sweeps what is still OPEN and leaves every stream record standing.

§BMounting a kit over another is not a handover
What happens

load_kit on a table that already holds a kit sets self.kit to the new slug and clears the standing offer. It never clears self.probe (the interrogation engine) or self.game (the device). Both survive into the new kit's life.

Evidence — a repro, not a reading
mount 诘问, then mount 谁是卧底 over it: kit : 'who-is-spy' ← the table says the game probe.on : True ← the interrogation is still running _flow_kit : True ← and the device is armed too _probe_on : True ← so it intercepts EVERY turn

The interrogation's intercept sits at the top of the drain and keys on probe.on alone, with no check that its kit is still on the table. So every line goes to an engine whose rulebook left, while the room shows a different game — which is exactly「the persona thinks that kit session is still running」.

§CThe thinking egg is raised on four paths the drain then refuses
What happens

The device's kick raises the egg for everyone whenever a turn is queued. The drain then has four gates that return nothing — no reply, no error, no egg-down — and the worker treats that as「queue empty」and goes back to sleep:

The egg breathes forever. Seen live at 127 seconds on a mounted room whose beat the drain had refused.

Why it is #4, #5 and #7 at once

Ghost (it never lands) · uninvited (nobody asked for that beat) · and no-response (the user's own line is the thing being silently refused).

§DThe interrogation returns before the line that raises the egg

The one call that publishes responding inside a turn sits far below the utility kit's intercept, so an interrogation turn never publishes it. The person who typed sees an egg only because their own client raises one optimistically; every other member of the room sees nothing at all while the engine makes its calls.

§EThe interrogation's front door loops forever on a natural「yes」
What happens

The chair reads your claim back and waits. The confirm gate matches your reply against an exact whole-string list of 18 short words. Anything else is filed as a correction — which re-reads the same claim back and returns to the same gate.

Evidence — a live room, two turns
pitch : "Group chat apps should replace email at work entirely." readback : 你主张群聊应该完全取代工作中的电子邮件… me : "Yes, that is what I mean." ← filed as a CORRECTION readback : 你主张群聊应该完全取代工作中的电子邮件… ← identical phase : confirm (unchanged) · claim entries: 2 (identical) · threads: 0

"yes" passes. "Yes, that is what I mean." does not. "对" passes; "对,就是这个意思" does not. There is no loop counter and no escape: the run cannot start until the pitcher happens to type one of eighteen bare tokens, and every lap costs a model call. This is the dead loop and the token bleed in one seam.

§FThe first open erases everything recorded before it

Room.open() calls self.events.clear() before writing its own opening event. Anything recorded between a room being created and its first open is destroyed — proved by mounting a kit on a freshly-created room: the kit_load record was gone from the log while kit and probe survived in state, so the room ran the interrogation with no capsule saying it had ever been mounted. This is the one real instance of「bubbles gone after refresh」the sweep found: the bubble was live, the record was not.

What was ruled out for #3 / #12

Replay itself is faithful. Across three real interrogation rooms every recorded line comes back: 45/45, 38/38, 19/19 persona bubbles and 19/19, 24/24, 13/13 human lines. A live room re-rendered identically before and after a browser refresh (47 nodes, same tally). The renderer handles every turn kind replay can emit.

§GThe board — it does refresh; §E is why it looked like it didn't

Checked, then re-checked after §E was fixed. With the front door unblocked the run reached its first sweep, and the board went from its setup row to two live rows in the same turn, over SSE, with no refresh — each row carrying its seat, its colour, its anchor into the transcript and its move stamp:

ROOM_BOARD.rows, read out of the live client: { note: true, line: "**群聊取代工作邮件**" } { icon: "⏳", name: "Tess", seat: "tess", lid: "9", moved: 1, line: "群聊如何保证特定指令…" } { icon: "⏳", name: "Lincoln", seat: "abraham-lincoln", lid: "10", moved: 2, line: "群聊如何确保正式政策声明…" } { note: true, line: "还剩 12" }

The reading: the board was not failing to refresh — it had nothing to refresh to. §E held every interrogation at the confirm gate, so the only board the owner ever saw was the setup row, re-written identically on every lap. A stuck board and a stuck run look the same from the outside. Left open on the ledger as explained rather than fixed: if it recurs on a room whose run is demonstrably moving, it is a different defect and deserves its own case.

§JThe take-off was the one kit route that never read the wake door
What happens

The standing law is written on the react route: every route that records a world event scans the wake door and, if it cued, wakes. Mounting by the menu did it. Mounting by the offer card did it. Taking a kit off did not. So the kit leaving, the board coming down and every card being called off all sat unread in the ledger — until the next human line, where they cued off the back of it, and that cue's own turn cued again.

Measured live, before
DELETE /kit → 0 turns "So anyway, what did you both think of the weather today?" 16:44:46 say → the weather answer ← asked for 16:44:50 say → "the room is only registering a world event" ← not 16:44:58 say → "the room is winding down with small talk" ← not one line · three panel turns · three prop calls · three stagings
…and after
DELETE /kit → 1 turn, at once, on topic: 「桌上那张纸撤了。你最开始想琢磨的那件事,是什么?」 next human line → 1 turn

Better on both counts: it costs less, and the room remarks on the dismount when it happens rather than thirty seconds later, attached to a question about the weather.

§POne message, one seat — the law the router was breaking
How it surfaced

The owner asked for live sims across all five kits. 诘问 — the newest engine, and the one every defect on this page came out of — had never had one. Its first run found this in ninety seconds.

The law, written at the top of its own module
⚠ ONE LAW ABOVE THE REST: a seat speaks only when its own thread gets an answer. Nothing else makes a seat speak. It is what keeps N seats from firing N times on one message, and it is what lets the pitcher set their own pace by choosing which question to pick up.

The code contradicted it. An untargeted line goes through a route call whose schema asked for 「the thread ids this message actually answers」 — plural — and the handler armed a ruling for every one.

Measured, live
the pitcher answered ONE seat, and said so in the line: 「针对Moritz:…」 → BOTH seats were ruled → the other declared itself satisfied — 「我信服了」 — on an answer never addressed to it → both threads closed → a sitting with a budget of TWELVE ended at spent = 1

One message collapsed the entire run. The world takes the first match and only it now; a seat with more to ask presses on its own next beat, which is the design and costs nothing. The route's ask says what the world will do, because asking a mind for a list the world discards is how the two halves drift apart.

and the test was pinning it The dry run asserted 「…and wakes only the seats it actually answered」 — two seats for a two-id route, the exact thing the law forbids. Rewritten to the law, plus its missing half: the thread the world did NOT take is still open, free to press later. That is the third test this sweep found pinning behaviour its own module forbids.
And the harness was hiding it. Three defects in the sim itself, each quietly weakening every run in the file: transcript() dropped the human half of every conversation(it expected a dict, the field is a string), so the UX judge has always read one-sided; wait_quiet returned mid-think, so every「what landed after I spoke」check read an empty window; and this very check was written <= 1, which zero satisfies. Fixed in that order — and only then did the law violation become visible.
§OThe live run — 64 checks, five kits, and two things nothing else could catch
The owner's question

「Have you run some sims to test if now all 5 kits work?」 The honest answer was no, and the gap was specific: every change of this sweep lands on the mount/dismount path, and the dry runs go straight to the Room object without touching a route. 590-odd scripted checks were green with not one cartridge ever having been mounted live.

exam/kit_lifecycle_live.py is that gap closed — the lifecycle driven over the same HTTP routes a phone hits, on the dev server, with three real humans in the room, for every kit on the shelf. 64 checks. It found two defects in code shipped hours earlier, and neither was reachable from a unit test.

1 · The freeze was invisible to the client

The server refused every door correctly — and said nothing about it on the wire. frozen reached neither gate_payload nor clock_state, so a suspended card arrived looking exactly like a live one, and a paused clock went on counting down to a deadline that would never fire, hit zero, and sat there. The server was right and the screen lied.

Both now ride the one dict every payload already spreads — the same seam the card's address uses, so no kind can reach a client without it and no future kind can forget it. Three unit tests missed this because they read the room's own dict rather than the payload a client receives.

2 · A dismount mid-turn was undone by the turn
12:02:29 kit_load 12:02:32 kit_unload ← the human dismounts 12:02:33 probe ← the engine runs anyway, one second later 12:02:33 speak tess ← the chair speaks again 12:02:33 board_set ← and the board is re-established on a cleared table probe.on = True, with no kit on the table — the room could not be handed back at all

A probe turn runs off-lock because it makes its own model calls, so a dismount can land in the middle of one. The take-off cleared the engine; the turn's unconditional write-back then put it straight back from the run object it was already holding.

The law: the last writer does not win, the OWNER does. The in-flight turn still speaks — its words were paid for and are real — but its state and its board go nowhere. Off is off.
why nothing else found it Three unit suites unload a kit with nothing in flight. The race needs a real turn, taking real time, with a real HTTP dismount landing inside it. This is the argument for the live harness existing at all: the scripted benches prove the logic, and only the wire proves the timing.
§NThe kit contract — five axes, both engines, one harness
The question

The owner, having settled that both families are one animal: 「Do you recommend merging them with high abstraction, so we have less class and fix on fix all?」 The measurement answers it:

the two flow vocabularies: 62 terms · 21 terms · SHARED: 2("question", "rule") the lifecycle: 11 functions, both engines already go through all of them
The recommendation, and the half that is counter-intuitive

Merge the lifecycle. Do not merge the language. A cartridge's safety comes entirely from its vocabulary being closed — the validator can say 「did=X」 is not one of DID. Union the two and you get ~81 terms of which each kit uses a quarter, and the validator can no longer reject anything. The narrowness is the guarantee: a higher abstraction over the flow would destroy the very thing it was meant to spread.

LayerVerdictWhy
the lifecyclemerge — donemount · dismount · freeze · thaw · floor · fuse · presence · wake · exits. 11 shared functions
the predicatemerge — doneengine() / engines_running(): the question「is ANY engine running?」that no caller and no test could ask before — and whose absence was §B
the contractmerge — doneexam/kit_contract.py: the five axes, both engines, one file, gated in the smoketest
the flow vocabularyno2 shared terms out of 81; the union cannot be validated
the executionnothe interrogation makes 2–12 direct calls to produce one turn's questions; a cartridge queues moments through the ordinary drain. Forcing one onto the other is slower and dearer
Share the lifecycle, not the language. Abstract when a kit starts, stops, freezes, spends and exits — never what it is about. And note which fork actually caused the damage: not one defect in this sweep came from the two engines speaking different languages. Every one came from the lifecycle being implemented twice.
The five axes, as the harness asserts them
AxisHow each engine is held to it
0one table, one enginemount, hand over, hand back, take off — engines_running() is never longer than one
1constrainedcartridge: phases declared, and a missing one is refused · utility: every front door terminates even when the human never types a bare「yes」
2exiteach has its own END; the four house exits are proved present and proved to funnel through one take-off
3code matha mind answering nonsense(budget 9999, spend −5, verdict「banana」)cannot move a counter or enter the closed vocabulary
4surfaceboth put state on the board — a mini-game with no HUD is played blind
5never hangsa mind answering nothing still settles; and a wait with no dead man is a schema error
watched go red The harness was run against both real bugs re-introduced. The front-door loop and the double-armed handover each turned it red, and green again on restore. ⚠ Its first cut passed the loop — the stub mind answered clear: True, so the run went straight to the sweep and the phase that loops was never entered. A harness that cannot reach the failure is not a harness, and that is the third time this sweep a check has passed vacuously(the shelf-scoped tool lint, the roster-wiped drawer check, and this).
§MThe freeze — the open world stops while the mini-game runs
The owner's model, in full

「Our app is like an open-world ARPG — the user can talk to any persona at any time, no restrictions, no order required. When we enter kit mode it's like starting a mini-game inside it: the player walked into a pub and started a gamble game with the owner. Now the flow is taken over by that mini-game, it's clear what can be said and done at what moment, and there's a clear exit condition. The other states in the open world freeze until the linear gameplay ends.」

Scored against the code, before
The specBefore
a clear exit✓ heldfive doors, all through one place; two of them the kit's own
clear what can be said◐ partlyheld by a utility kit; deliberately loose on a cartridge — a board game at a party doesn't stop the table talking
clear what can be done✗ not at allthe drawer never narrowed. Mid-谁是卧底 a player could arm their own ballot, roll their own dice, deal their own cards
What was built
the flaw the owner found An earlier cut of this swept the table on a mount, and one question killed it: 「if Amy mounts a kit when Bob's tool is still ongoing, the kit asks if Amy wants to sweep the table?」 Under that design Amy authorises destroying Bob's content — the one person with no stake in it and every reason to tap straight through. Confirming an incidental discard is worse than doing it silently: it launders it into a decision. Nothing is destroyed now, so nobody is asked to decide for anyone else.
And a freeze is not three-quarters of a freeze. Stopping the taps and leaving the clocks running means a card from before the game reveals itself mid-game. Stopping the clocks without moving the deadlines is worse — _life_arm's own rule is that a deadline already past fires on a one-second fuse, so the instant the kit came off every frozen card would fire at once. Three parts, and the third is the one that is easy to forget:
① the settle doors refuse a frozen card · ② its deadline stops · ③ on thaw the deadline moves forward by however long it was held.
§LMental 大话骰 — a tier the design does not have
How it surfaced

Not from a symptom. The owner read the mental-model section above and said: 「in my design there is no kit played from persona's memory. All kits are in the form of a cartridge. There should be no mental liar's dice (flaky). I don't know where you got the impression that there are mental kits.」 The shelf backs that up completely — every live play kit carries a flow block:

liars-dice · quiz-night · twenty-questions · who-is-spy CARTRIDGE interrogation probe (a utility engine) two-truths-one-lie no program — and withdrawn
…and yet the tier was reachable

_game_boot declines a cartridge whose player floor the room misses, and the kit mounted anyway — into what the code called「the second tier, the classic path, whole」. What that state actually is: the rulebook sits in the persona's context, _flow_kit() stays true so the act call is gated OFF, and the device is not running. The persona cannot deal a real card or open a real ballot. It can only describe a game. That is mental 大话骰 exactly — not a broken game, a narrated one.

And it was the ordinary case, not an edge

The floor counts HUMANS(the device arms and narrates through a persona seat, so a persona hosts and never plays). Measured across the shelf:

kit wants humans device result liars-dice 2-6 1 floor:1/2-6 the persona narrates it who-is-spy 3-9 1 floor:1/3-9 who-is-spy 3-9 2 floor:2/3-9

One person playing with AI personas — surely the commonest way this app is used — could never get anything else out of those two kits.

And the guard built for exactly this was switched off

The hand-build suppressor exists to stop a persona laying a shelf game's table by hand. Its condition is not (_sup and not self.kit) — it only fires when no kit is mounted. With a declined cartridge on the table self.kit is set, so it returned immediately. The only thing left standing between the room and an improvised game was a clause in the mount cue(「this game cannot run here, so say so plainly」)— and this project's own ledger has the rule for that: a clause is a request, not a guarantee.

The fix (owner's ruling, 2026-08-08)
and two tests were lying 「under the floor the device declines and hands the kit back to the classic path」 and 「two seats → the cartridge DECLINES and the kit runs classic」 both pinned the behaviour the design forbids. A third — the wake door's kit-landing cue — had been reaching that path for a different reason: its fixture seated one human against a three-player cartridge, so it had only ever tested the declined case. All three are rewritten against the real rule, and the assertions came out stronger.
§KThe wake door's catch-all was a default, not a decision
What happens

The wake door sorts every event the room records into three piles: silent, covered elsewhere, or cue the panel. Cueing means a whole turn. Sixteen event types have bespoke words written for them; everything else falls through to one line:

lines.append(f"{ty} — recorded")

— an internal identifier, handed to a model, bought with a turn. That arm is right for a real world change nobody has written words for yet. It was catching eight things that are not world changes at all:

EventWhat it actually is
model_emptythe provider returned nothing and the room retried
room_quietthe presence gate's own note — and it fires precisely when nobody is there to read the turn it was buying
promise_checkthe prop master's internal flash question
ghost_whothe harness dropping a line from a non-cast speaker
deal_seenone person privately looked at their own card — cueing it also says out loud that they did
dispatch · artifactboth have their own card and ride the turn that produced them
setting_changean archive, a trash, a rename — not table news
The fix, and the shape of it

Those eight are silent now. But the real repair is that the catch-all is a ratchet rather than a default: the types that still cue through it are pinned by name(26 at the sweep; 23 since 2026-08-23, when join · leave · member_leave were ruled COVERED — a newcomer walks in without a greeting and rides the next turn's capsule; room 8db0 had bought a second turn for an arrival already played), so a new event type joining them fails the smoketest until somebody decides which pile it belongs in. A default that silently spends money is how all eight got there.

§IThe interrogation's model calls answered to no meter

The call fuse is the app's hard stop — one meter over every model call a room makes, reset by any human action, and its own comment claims it 「makes runaway impossible, including runaways from bugs not yet written」. The interrogation's pump was outside it: up to twelve calls a turn, each with a retry, on the newest engine in the app, counted by nothing.

That is why §E reached a person at all. A front door that loops is a bug; a front door that loops and is not on the meter is a bill. The pump now spends through fuse_spend like every other call — refused, it stops, leaves the call pending, says nothing, and resumes the moment somebody acts.

§HNo kit-specific tools — but two kit-local NAMES with no glyph

The answer to the question as asked: every kit draws from the same toolbox. There is no kit-private instrument anywhere — not one. What breaks the muscle memory is vocabulary, and the cause is that the app carries three names for the same nine things:

VocabularyWhere it livescalls the bid boxcalls the wheel
the canonUSER_TOOLS · INSTRUMENT_ALIASES · and the drawer the user readsdepositspinner
the client's icon tableTOOL_ICONS — two legacy keyssealwheel
the kit fileswhatever each author typedboth, inconsistently

What actually broke: a kit naming the canon word got no glyph and the wrong accent, because the icon table had never heard of it. Measured in the live app before the fix: toolIcon('deposit') and toolIcon('spinner') both returned "" and fell back to coral, while toolIcon('seal') and toolIcon('wheel') returned their SVGs in blue and plum. So the same object wore a different face depending on which kit put it on the table — the exact opposite of muscle memory.

The fix, and which way it points

The canon is the word on the screen. Muscle memory is built on what a person reads off the drawer, not on an internal key — so the kit sheets speak deposit and spinner, and the client's TOOL_ALIAS makes every spelling resolve to the one drawing. Two lints hold it: one that every kit on disk names a canon object, and one that every canon object resolves to a glyph — the half that was actually broken, and the half a name-only lint would have missed.

left open The third vocabulary is still there. TOOL_ICONS keeps seal and wheel as its keys, bridged rather than renamed. Renaming them touches every call site for no user-visible gain, and the lint now makes a blind name impossible — but it is one canon too many, and it is what let this drift in the first place. Worth a deliberate pass, not a bug fix.
docs/kits-era-sweep.html · opened 2026-08-07 · the sweep is live; this page is updated in the same change as each fix