Dialogue.  · Language-agnostic games

Language-agnostic games — the plan and the pitfalls

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.

The one principle. Separate identity from display. Every token the MACHINE compares stays a frozen canonical id — never renamed, never localized, exactly as persona slugs and event kinds already work. Every word a HUMAN sees becomes data: declared by the cartridge, per language, in one place(the LEXICON), and rendered at the last moment. The specimen already proved the pattern twice: the button's label(开盅)is pure cartridge data flowing untouched through state, and 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.

1 · The inventory — where the words actually live

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 / tokenwhere it liveslogic 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 — everywherethe 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 statedisplay(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_wildLOGIC — a hidden onehigh 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 autothe 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 reasonsdisplay + 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 codemedium: 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-stringsdisplay(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 columnsdisplay 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()displaylow — 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 guardLOGIC over languagemedium: 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 briefthe 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.

2 · The design — one new block, two refits

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

3 · The build order(each phase shippable alone)

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.

  1. P1 — the dial-id refit. Smallest, closes the hidden logic hole. Bench: an en-labelled dial flips nothing.
  2. P2 — the lexicon block + shelf lint, with zh filled for the dice kit(pure relocation of today's strings — zero behavior change, the diff proves it).
  3. P3 — the runner renders through the lexicon: notes, board cells, reasons (as codes), the tally fact. zh output byte-comparable to today's.
  4. P4 — the seam prompts go multilingual: the interpreter's sysp gains the lexicon's synonyms + non-CJK examples; the voice writes in the room language with the lexicon's terms IN the note(no ad-hoc translation left to do); the number-fidelity accept-set keyed by language.
  5. P5 — the client strip reads the lexicon slice off the game state(pill faces, unit, composed bid text).
  6. P6 — the en kit half: fill the dice lexicon's en column; translate the kit's dial labels; leave the brief bilingual(the persona adapts).
  7. P7 — prove it: the bench matrix(every dice scenario × zh and en)+ the audit driver in an en room AND a MIXED table(one bidder typing English, one Chinese, one room language)— the mixed table is the real acceptance test.

4 · The pitfalls, named

  1. The enum is not a word. 斋/飞/普通 look like display but are compared in five layers and persisted in every played room. Renaming them corrupts old ladders on resume and breaks the client contract. They stay; only their RENDERING localizes.
  2. The hidden dial match. 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.
  3. Voice-time translation drifts. Letting the flash voice translate a zh note per line renders 斋 three different ways in one game. Terms must be translated ONCE, in the note builder, from the lexicon.
  4. The mixed table is the real case. Layer A already lets players type any language. The interpreter must read all of them into the canonical enum; the machine's OUTPUT language follows the ROOM, not the bidder. Test explicitly.
  5. Number-word fidelity. The guard accepts digits and 汉字 today; every added language either extends the accept-set or instructs digits-only output. Silent rejections would resurrect the fallback churn the specimen just cured.
  6. The bench is full of literals. Every zh assertion(开盅 · VOID reasons · board cells)must assert through the lexicon or run in the zh half of the matrix — otherwise the en build breaks 278 green checks and the temptation is to weaken them.
  7. Two i18n systems, one boundary. 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.
  8. The kit's dual-mode prose. The classic path(no flow block)plays from the brief alone — a translated table needs the BRIEF's rules readable too. The persona translates prose fine; the DIALS it declares out loud must use the lexicon's labels or the spoken table and the machine disagree.
  9. Fallback asymmetry. A partially-filled lexicon must fall back to zh WHOLE per surface(never word-by-word)— a half-en half-zh verdict line is worse than a Chinese one.
  10. The specimen's laws still bind. Notes stay facts(no hardcoded persona speech — templates carry FACTS in the room's words, the voice still performs them); one number in view; markers minted by code; persist after the advance. Localization touches none of these.

5 · The dice lexicon — the translations, for the owner's review

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 idzh(today)en displayplayer synonyms the interpreter reads
plain普通(unspoken)(unspoken)bare bids —「5 fours」·「five 4s」
zhaino wilds(the pill face compacts to「no wilds」)no wilds · wild off · spot(s) only · pure · zhai · 斋
feiwith wilds(the pill face compacts to「wilds」)with wild(s) · wild on · wilds back · fei · 飞
the button开盅Open—(a chip, not typed)
bid shape3个5斋3 fives no wilds · 6 fours with wildsthe strip composes this form
board columns当前 · 玩家 · 上一口(arrow)· Player · Last bidthe owner's own sketch wording
board cells等待中 · 开盅-输了/赢了 · 赢家 · 出局waiting · opened — lost / won · WINNER · out
the total**一共12颗骰子****12 dice on the table**
ones dial一点万能 · 1就是11s 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.

6 · Done means

Dialogue · language-agnostic games · PLAN drafted 2026-08-03, before any code · siblings: the dice specimen · room language(layers A+B) · the game device · status: BUILT 2026-08-03 — see §3