hanabi: rules core and the hidden-hand spike #2

Merged
joeyr merged 1 commit from hanabi/rules-core into main 2026-08-16 16:47:38 -05:00
Owner

Two things, deliberately independent of each other.

src/rules.ttslua — the game, with no TTS in it. Deck construction (50 cards, 60 with the optional black suit), deal, turn order, clue legality and the token economy, play/misplay, fuses, deck-out final round, scoring. No GUIDs, no objects, no visibility; cards are integer ids and the id→GUID map belongs to the other layer. A full game runs outside the client in milliseconds.

src/Global.-1.ttslua — a throwaway spike. Hanabi needs each player to see every hand except their own, and TTS hand zones and fog-of-war zones both do the opposite. This answers whether setHiddenFrom hides a card from its holder while leaving a placeholder, and gets deleted once it has.

The split is the point: a bad answer from the spike costs the presentation layer, not the game.

Two rules forks, both commented at their site — a fuse-out scores 0 rather than partial credit (printed rule, commonly house-ruled away), and the deck-out final round gives every player one more turn including whoever drew the last card (follows Hanab Live; the rulebook is ambiguous and the readings differ by a point).

Verified with a scratch driver: 938 assertions including 200 randomized four-player games checking termination and that fuses/clues/score never leave legal ranges. Not committed — this repo has no test harness and adding one is a separate call.

rules.ttslua is not required by Global yet, so it is not in the bundle.

Two things, deliberately independent of each other. **`src/rules.ttslua`** — the game, with no TTS in it. Deck construction (50 cards, 60 with the optional black suit), deal, turn order, clue legality and the token economy, play/misplay, fuses, deck-out final round, scoring. No GUIDs, no objects, no visibility; cards are integer ids and the id→GUID map belongs to the other layer. A full game runs outside the client in milliseconds. **`src/Global.-1.ttslua`** — a throwaway spike. Hanabi needs each player to see every hand *except* their own, and TTS hand zones and fog-of-war zones both do the opposite. This answers whether `setHiddenFrom` hides a card from its holder while leaving a placeholder, and gets deleted once it has. The split is the point: a bad answer from the spike costs the presentation layer, not the game. Two rules forks, both commented at their site — a fuse-out scores **0** rather than partial credit (printed rule, commonly house-ruled away), and the deck-out final round gives **every** player one more turn including whoever drew the last card (follows Hanab Live; the rulebook is ambiguous and the readings differ by a point). Verified with a scratch driver: 938 assertions including 200 randomized four-player games checking termination and that fuses/clues/score never leave legal ranges. Not committed — this repo has no test harness and adding one is a separate call. `rules.ttslua` is not required by Global yet, so it is not in the bundle.
hanabi: rules core and the hidden-hand spike
All checks were successful
lint / lua (pull_request) Successful in 18s
39c8dd11b1
Hanabi inverts card visibility -- you see every hand except your own -- and TTS
has no primitive for that. Hand zones hide a hand from everyone but its owner,
and fog-of-war zones do the same. The entire game is downstream of whether
setHiddenFrom can be made to hide a card from its holder while leaving a
placeholder the rest of the table can point at, so src/Global.-1.ttslua is a
throwaway spike that answers exactly that and nothing else. It runs off chat
commands against any deck on the table. It gets deleted once the answer is in.

src/rules.ttslua is the real work and does not depend on that answer. It has no
reference to the TTS API anywhere: no GUIDs, no objects, no visibility. It owns
deck construction (50 cards, or 60 with the optional black suit), the deal,
turn order, clue legality and the token economy, play and misplay resolution,
fuses, the deck-out final round, and scoring. Cards are identified by an integer
assigned at deck build; mapping those to GUIDs is the other layer's problem.

Splitting it this way means a full game can be played out in milliseconds
outside the client, and means a bad answer from the spike costs us the
presentation layer rather than the game.

Two places where the rules genuinely fork, both commented at their site:

  - A fuse-out scores 0, not partial credit. That is the printed rule, and it is
    also the one groups house-rule away most often.
  - The deck-out final round gives every player one more turn, including whoever
    drew the last card. The rulebook is ambiguous; this follows Hanab Live,
    which is what anyone who has played online expects, and the two readings
    differ by a point of score.

Verified with a scratch driver: 938 assertions, including 200 randomized
four-player games checking that every game terminates and that fuses, clue
tokens and score never leave their legal ranges. The driver is not committed --
this repo has no test harness, and adding one is a separate decision.

rules.ttslua is not yet required by Global, so it is not in the bundle. Wiring
it up is blocked on the spike.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
joeyr merged commit d7dd4ae477 into main 2026-08-16 16:47:38 -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!2
No description provided.