The dice specimen plays in 简体中文. The next step is a table that plays in ANY room language — including the mixed table(a Chinese speaker and an English speaker in one game, which the room's two-layer language system already permits). This page is the PLAN, written before the build, because the owner's warning is exactly right: some of the game's words are load-bearing in code, and renaming a load-bearing word is a migration, not a translation. Status: PLAN — no code yet.
auto_when
lets a kit name which of its own dial words means「auto」— the world never learns a
cartridge's vocabulary. The plan generalizes those two precedents; it invents nothing new.The audit that must precede any edit. logic = the machine compares this string; renaming it silently breaks rules or state. display = a human reads it; safe to localize.
| word / token | where it lives | logic or display? | risk |
|---|---|---|---|
| 斋 · 飞 · 普通(the mode enum) | the validator's comparisons ·
the envelope's dict KEYS · the interpreter's JSON contract · the client strip's state
(BS.mode)· the PERSISTED ladder in every room's state.json · the bench |
LOGIC — everywhere | the highest. Renaming = a migration across live rooms, replays, and the client API. Decision: never rename. They become frozen canonical ids that happen to be Chinese — like slugs. |
| 开盅(the button) | kit data(button.label)→ state →
the chip · the press bubble · the send-intercept compares the composer to the ACTIVE
label from state | display(data-driven already) | low — a per-language kit label just flows. Only the BENCH hardcodes it. |
| 1就是1 · 一点万能…(the ones dial VALUES) | kit dial options —
but the runner raw-matches startswith("1就是1") to derive
ones_wild | LOGIC — a hidden one | high and subtle:
the world learned a cartridge's vocabulary, against the auto_when
precedent. An English dial value would silently flip every table to wilds-on. |
| 自动摇(the shake dial) | kit dial + auto_when:"自动摇"
— the kit itself names which word means auto | the GOOD pattern | none — this is the precedent the ones dial must adopt. |
| 「N个X」(the bid shape) | the strip's composed text · the interpreter's reading · the notes and reasons | display + prompts | medium: the strip must compose in the room's language and the interpreter must read EVERY language(the mixed table)— it is an LLM, so this is prompt coverage, not parsing. |
| the validator's reasons(还没轮到… · 飞要翻倍… · 桌上一共只有N颗…) | returned as Chinese prose; embedded verbatim in void notes; quoted by the voice | borderline — prose born in code | medium: translating at voice-time would render terms inconsistently(斋 → "pure"/"zhai"/… varying per line). They must become reason CODES + params, rendered through the lexicon. |
| the flash notes(一圈叫满 · 空开自罚 · 还没摇盅 · 一共N颗骰子…) | runner-built Chinese f-strings | display(facts) | medium: the voice already writes in the room's language, but a zh note into an en room makes the voice translate ad hoc — terms drift line to line. Notes must be BUILT from the lexicon. |
| the board cells(等待中 · 开盅-输了/赢了 · 赢家 · 出局 · 当前/玩家/上一口) | runner-written rows + kit-declared columns | display | low — columns are already kit data; the runner's cells move to the lexicon. |
| the strip chrome(the pill faces 斋/飞 · the unit 个) | client
markup + bidText() | display | low — reads the
lexicon slice off the game state. NOTE: the app-chrome i18n(lib/i18n.js)
is a DIFFERENT system and stays out of this: game terms are cartridge data, not UI
copy. |
| the fidelity checks(numbers accepted as digits or 汉字) | the flash voice's guard | LOGIC over language | medium: a French room writing「deux」would be rejected as infidelity. The accept-set must extend per language, or the prompt must require digits outside CJK. |
| the kit prose / host brief | the persona's reading | display(a mind adapts) | low — the megaprompt norm already holds(directives in English, exemplars verbatim); the persona speaks the room's language today. |
zh first, en as
the proof — the display for every canonical token: the modes(斋→"pure" · 飞→"wild" ·
普通→""), the bid shape(「{n}个{x}{mode}」→ "{n} {x}s {mode}"), the button label, the
board words, the note templates(the circle, the void, the verdict, the 空开, the
nudges, the round-open), the reason templates, and the dial LABELS. The schema lints
it: every canonical token covered, every language complete — a missing term fails
the shelf lint, not a live table. Rooms pick the slice by their language; an
uncovered language falls back to zh(correct, just foreign).auto_when precedent,
finished). ones_wild stops matching「1就是1」; the dial declares
meaning per option(e.g. {"id":"plain","label":{"zh":"1就是1","en":"ones
are ones"}})and the runner reads the id. Same for any future dial the world
must understand.(code, params)(order·climb·zhai·double·locked·ceiling·convert·input);
the lexicon holds each code's template per language. The replica in the audit driver
already proved the codes cover every branch.What deliberately does NOT change: the canonical enum values(斋/飞/普通 stay the ids in code, state, the envelope keys, and the interpreter's JSON — frozen, like slugs); the append-only history; the bench's Chinese-room scenarios(they become the zh half of a two-language matrix).
STATUS — BUILT, all seven phases, 2026-08-03(the owner's go). The ids went
ENGLISH(plain/zhai/fei, dial ids, reason codes — §5's ruling)with a
read-time normalizer for the legacy zh tokens and the en synonym families(no wilds ·
wild off · spot only · pure → zhai; with wilds · wilds · wild on → fei). The kit
carries the full zh+en lexicon(key-parity linted); the runner renders every machine
surface through it; the strip reads a per-room terms slice off the envelope(the client
owns no words); a room mounted before its kit had a lexicon falls back to the shelf's
current block(caught live: an empty-string board cell). Gates: 290 bench checks green
incl. the new en-room scenario + the English-family validator rows; smoketest PASS; one
full LIVE en game confirmed — board, voids, verdict, circle, round-open all English,
the arithmetic intact. The code rides the uncommitted dice arc(one commit, batched with
the rework, after the owner's sign-off); this page records the state of the working
copy.
startswith("1就是1") is the one place
the world already learned a kit's vocabulary — an en dial value would silently turn
wilds on for every table. Fixed first for a reason.lib/i18n.js owns app chrome;
the lexicon owns game terms. Putting a game term in the UI dict(or vice versa)creates
the two-sources drift the single-source rule exists to prevent.Researched, not guessed: English-language sources for the Chinese variant
keep zhai and fei romanized(the community's own usage);「pure」appears
only in「pure bao zi」. 加点数 renders as raise the face(the standard rule
phrasing: increase the quantity, or bid the same quantity of a higher face value).
Canonical ids plain / zhai / fei — English, grep-clean, and no collision
with the ones_wild flag.
| canonical id | zh(today) | en display | player synonyms the interpreter reads |
|---|---|---|---|
plain | 普通(unspoken) | (unspoken) | bare bids —「5 fours」·「five 4s」 |
zhai | 斋 | no wilds(the pill face compacts to「no wilds」) | no wilds · wild off · spot(s) only · pure · zhai · 斋 |
fei | 飞 | with wilds(the pill face compacts to「wilds」) | with wild(s) · wild on · wilds back · fei · 飞 |
| the button | 开盅 | Open | —(a chip, not typed) |
| bid shape | 3个5斋 | 3 fives no wilds · 6 fours with wilds | the strip composes this form |
| board columns | 当前 · 玩家 · 上一口 | (arrow)· Player · Last bid | the owner's own sketch wording |
| board cells | 等待中 · 开盅-输了/赢了 · 赢家 · 出局 | waiting · opened — lost / won · WINNER · out | |
| the total | **一共12颗骰子** | **12 dice on the table** | |
| ones dial | 一点万能 · 1就是1 | 1s are wild · 1s are just 1s | ids wild_ones / plain_ones |
| shake dial | 自己摇 · 自动摇 | shake your own · auto-shake | ids self_shake / auto_shake |
| reason: order | 还没轮到X——现在该Y叫 | not your turn — {name} bids now | |
| reason: climb(加码/加点数) | 要么加数量,要么同数量抬点数 | must raise — more dice, or the same count on a higher face | |
| reason: double | 飞要翻倍 | fei must double the zhai count — {n} zhai needs {2n}+ | |
| reason: the lock | 一点已经喊过,斋锁死了 | 1s were called — zhai is locked, no going fei | |
| reason: ceiling | 桌上一共只有N颗骰子 | only {n} dice on the table — can't bid {c} | |
| verdict line(note) | 开出来:2点×3 + 1点百搭×1 = 4颗——叫注成立/不成立 | on the table: fours ×2 + wild 1s ×1 = 3 — the bid stood / busted. **{loser}** pays a die | |
| 空开 | 空开自罚 | opened on an empty round — the opener pays | |
| circle reminder | 第{n}圈叫满,顺序不变 | circle {n} complete — order stands: A → B → C, {next} to bid | |
| nudges | 还没摇盅 · 桌上安静了 | {names} haven't shaken — the house shakes any cup still sitting · table's quiet — {bid} still stands: raise it, or open |
Also settled by the owner (2026-08-03): canonical ids are ENGLISH(`plain/zhai/fei`, dial ids, reason codes)— easier debugging for a future international team. The zh→en token migration is a read-time normalizer, done now while only dev rooms carry the old tokens.