hanabi: rebuild the presentation as a 2D board #15

Merged
joeyr merged 1 commit from hanabi/2d-board into main 2026-08-16 23:24:31 -05:00
Owner

No objects. No cards spawned, no hand zones for layout, nothing positioned. The board is ui/Global.xml, one tree every player sees, and the only difference between viewers is whose hand shows backs.

That difference is the whole game, and on a UI it is an attribute rather than a discovery. Each slot holds two overlapping images: the face, visible to every seat except the holder, and the back, visible only to the holder. visibility is evaluated per player, so one tree renders differently for each viewer. The object build needed a spike to establish setHiddenFrom could do this; here it is stated directly.

Everything the object build spent iterations on is gone: hand radius, slot spacing, seat arc, mat scale, zone clearance, table meshes, whether a hand zone would adopt a card and reorder it. Those concepts no longer exist.

rules.ttslua untouched — the point of keeping TTS out of it. All 945 assertions still mean what they meant.

ui/Global.xml is generated by tools/make_ui.py. Fifty card images before fireworks and buttons, each with an id the Lua addresses by name; ninety hand-maintained ids is how XML and script quietly stop agreeing. The generator also refuses to emit -- inside a comment, which silently kills the whole UI while the Lua loads fine — it did exactly that on the first generated file, and the lint gate caught it.

Card faces are 31 Custom UI Assets, since XML <Image> cannot offset into a spritesheet. Thirty-one fetches at load instead of one: the unavoidable price.

Harness rewritten — it asserted object behaviour that no longer exists. It now checks the thing that matters most directly: for every card in every hand, the face is not visible to its holder and the back is visible to nobody else. 300 seeded games, zero failures.

Known limitation: hand rows are in fixed positions rather than always-yours-at-the-bottom, because "me" differs per viewer and there is one tree.

**No objects.** No cards spawned, no hand zones for layout, nothing positioned. The board is `ui/Global.xml`, one tree every player sees, and the only difference between viewers is whose hand shows backs. That difference is the whole game, and on a UI it is an attribute rather than a discovery. Each slot holds two overlapping images: the face, visible to every seat **except** the holder, and the back, visible **only** to the holder. `visibility` is evaluated per player, so one tree renders differently for each viewer. The object build needed a spike to establish `setHiddenFrom` could do this; here it is stated directly. Everything the object build spent iterations on is gone: hand radius, slot spacing, seat arc, mat scale, zone clearance, table meshes, whether a hand zone would adopt a card and reorder it. Those concepts no longer exist. **`rules.ttslua` untouched** — the point of keeping TTS out of it. All 945 assertions still mean what they meant. `ui/Global.xml` is **generated** by `tools/make_ui.py`. Fifty card images before fireworks and buttons, each with an id the Lua addresses by name; ninety hand-maintained ids is how XML and script quietly stop agreeing. The generator also refuses to emit `--` inside a comment, which silently kills the whole UI while the Lua loads fine — it did exactly that on the first generated file, and the lint gate caught it. Card faces are 31 Custom UI Assets, since XML `<Image>` cannot offset into a spritesheet. Thirty-one fetches at load instead of one: the unavoidable price. **Harness rewritten** — it asserted object behaviour that no longer exists. It now checks the thing that matters most directly: for every card in every hand, the face is not visible to its holder and the back is visible to nobody else. **300 seeded games, zero failures.** Known limitation: hand rows are in fixed positions rather than always-yours-at-the-bottom, because "me" differs per viewer and there is one tree.
hanabi: rebuild the presentation as a 2D board
All checks were successful
lint / lua (pull_request) Successful in 17s
c66ee9e16f
No objects. No cards are spawned, no hand zones are used for layout, nothing is
positioned. The board is ui/Global.xml, one tree every player sees, and the only
difference between viewers is whose hand shows backs.

That difference is the whole game, and on a UI it is an attribute rather than a
discovery. Each slot holds two overlapping images: the face, visible to every
seat except the one holding the card, and the back, visible only to that seat.
`visibility` is evaluated per player, so one tree renders differently for each
person looking at it. The object build needed a spike to establish that
setHiddenFrom could hide a card from its holder without hiding it from the
table; here the asymmetry is stated directly.

Everything the object build spent iterations on is simply gone: hand radius,
slot spacing, seat arc, mat scale, zone clearance, table meshes, whether a hand
zone would adopt a card and reorder it. None of those concepts exist now.

rules.ttslua is untouched, which was the point of keeping TTS out of it. Every
rule, the token economy, deck-out timing and scoring carry over with their 945
assertions still meaning what they meant.

ui/Global.xml is generated by tools/make_ui.py rather than hand-written. Five
seats of five cards, each needing a face and a back, is fifty images before
fireworks, labels and buttons, and every one has an id the Lua addresses by
name. Ninety hand-maintained ids is how the XML and the script quietly stop
agreeing. The generator also refuses to emit a double hyphen inside a comment,
because that silently kills the entire UI in TTS while the Lua half loads fine
-- which it did, on the first generated file, and the lint gate caught it.

Card faces are registered as Custom UI Assets, one per face, because TTS's XML
<Image> addresses an asset by name and cannot offset into a spritesheet. That is
thirty-one fetches at load instead of one, and it is the unavoidable price of a
UI build.

The harness was rewritten alongside, since it asserted object behaviour that no
longer exists. It now checks the thing that matters most directly: for every
card in every hand, that its face is not visible to its holder and its back is
visible to nobody else. 300 seeded games, zero failures.

Hand rows sit in fixed positions rather than "opponents above, me below",
because "me" differs per viewer and there is one tree. Every seat gets a row;
yours is the one showing backs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
joeyr merged commit 0d37720088 into main 2026-08-16 23:24:31 -05:00
joeyr referenced this pull request from a commit 2026-08-16 23:24:32 -05:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
joeyr/tts-games!15
No description provided.