Skip to content

The saloon deals a second hand: two names the port answered wrong - #287

Merged
dhobi merged 1 commit into
masterfrom
dust-saloon-games
Aug 23, 2026
Merged

The saloon deals a second hand: two names the port answered wrong#287
dhobi merged 1 commit into
masterfrom
dust-saloon-games

Conversation

@dhobi

@dhobi dhobi commented Aug 23, 2026

Copy link
Copy Markdown
Owner

Dust's card games are written against two primitives the engine had under names it did not answer to, and both games break on both. Reported from play: a second round of blackjack still had round one's cards and result on the table, and poker's showdown showed a hand name that did not match the cards under it.

indextoprop / result()

Blackjack and poker both clear the table between rounds with the pair:

for count = 1 to countprops ()
    temp = indextoprop (count)
    if result () = "salgames.prp"
        propvisible (temp, false)

Every card, both score readouts and the WINNER banner, hidden in one pass and by file — so the saloon hides its own props and not the interface band's. Only hittest wrote result(), so the comparison was never true and nothing was hidden.

The name it answers is now the one the prop is registered under, not its sprite group's. Those come apart for a propinstance copy, which shares the group it draws with, and the dealer's score readout is one: salgames.prp ships a single bjscores group and initgame instances bjscores2 off it. Reporting the group hid the player's readout twice and never named the dealer's, so the opponent's last total stayed on the table. Poker's per-seat hand names are 27 more of the same shape (nopair2/nopair3/nopair4 off one nopair), which is why its banner was stale too.

variable (name)

It now resolves a computed name the way a name written out in full does — the running block's locals first, then the globals. Poker's hasxkind counts faces into thirteen locals and reads them back with it:

local card2, card3, … card14
…
for count = 2 to 14
    if variable ("card" @ numtostring (count)) = num

A globals-only lookup answered 0 for all thirteen, so the classifier never found a pair. Every hand at the showdown scored as its high card: four aces came out a "straight", most hands read "nopair" over a table of visible pairs and flushes. The winner still looked right, because all four hands were mis-scored the same way and the comparison is between them.

The setter still creates a global for a name the block did not declare local, so Dust's crowd storing a walk phase under its own instance name (variable (me, 1), extra.cst) reaches the table the next switch variable (me) reads.

Titanic is untouched

Checked rather than assumed:

  • No TAOOT script calls indextoprop between a hittest and its result(). Its callers feed the name straight back into name-resolving commands (sendtoprop, propowner, propscript), which resolve by the same key the walk now returns.
  • blkjack.stg is the only Titanic file using variable, always as who @ "…" against playercount/dealertotal/…, while its blocks declare thecard/x/y/cardwidth/cardcount. No name in either set collides.

Tests

dust/tests/salgames.ts runs the game's own scripts out of SALGAMES.FLT:

  1. the clear-the-table loop over two open shops with the dealer's instance standing, plus a round-trip invariant that every index's name resolves back to that prop;
  2. calcscore over ten hands, one per category;
  3. makehands over 40 seeds — four whole hands, all real cards, no card in two hands.

All three fail on master. The adjust4ace test added to taoot/tests/auto/interp.ts is the guard on the Titanic side: its loops write by computed name and its soft-17 rule reads dealertotal written out, so it fails if either half of the variable contract moves. Confirmed by breaking each fix in turn.

538 tests pass, tsc clean, npm run build:dust clean.

Releases as Dust 0.3.7 (dust-v0.3.7 on the merged commit).

🤖 Generated with Claude Code

Dust's card games are written against two primitives the engine had under
names it did not answer to, and both games break on both.

`indextoprop` walks the one prop table and `result()` names the FILE the prop
came from. Blackjack and poker both clear the table between rounds with the
pair:

    for count = 1 to countprops ()
        temp = indextoprop (count)
        if result () = "salgames.prp"
            propvisible (temp, false)

Every card, both score readouts and the WINNER banner, hidden in one pass and
by file so the saloon hides its own props and not the interface band's. Only
`hittest` wrote `result()`, so the comparison was never true and a second hand
was dealt on top of the first one's cards and its result.

The name it answers is now the one the prop is REGISTERED under, not its
sprite group's. Those come apart for a `propinstance` copy, which shares the
group it draws with, and the dealer's score readout is one — salgames.prp
ships a single `bjscores` group and `initgame` instances `bjscores2` off it.
Reporting the group hid the player's readout twice and never named the
dealer's, so the opponent's last total stayed on the table. Poker's per-seat
hand names are 27 more of the same shape (`nopair2`/`nopair3`/`nopair4` off one
`nopair`), which is why its banner was stale too.

`variable (name)` resolves a computed name the way a name written out in full
does: the running block's locals first, then the globals. Poker's `hasxkind`
counts faces into thirteen locals and reads them back with it —

    local card2, card3, … card14
    …
    for count = 2 to 14
        if variable ("card" @ numtostring (count)) = num

— and a globals-only lookup answered 0 for all thirteen, so the classifier
never found a pair. Every hand at the showdown scored as its high card: four
aces came out a "straight", most hands read "nopair" over a table of visible
pairs and flushes. The winner still looked right, because all four hands were
mis-scored the same way and the comparison is between them. The SETTER still
creates a global for a name the block did not declare local, so Dust's crowd
storing a walk phase under its own instance name (`variable (me, 1)`) reaches
the table the next `switch variable (me)` reads.

Titanic is untouched, and checked rather than assumed. No TAOOT script calls
`indextoprop` between a `hittest` and its `result()`; its callers feed the name
straight back into name-resolving commands, which resolve by the same key the
walk now returns. blkjack.stg is the only Titanic file using `variable`, always
as `who @ "…"` against playercount/dealertotal/… while its blocks declare
thecard/x/y/cardwidth/cardcount — no name in either set collides.

dust/tests/salgames.ts runs the game's own scripts: the clear-the-table loop
over two open shops with the dealer's instance standing, a round-trip
invariant that every index's name resolves back to that prop, `calcscore` over
ten hands, and `makehands` over 40 seeds for four whole hands out of one deck.
The `adjust4ace` test added to taoot/tests/auto/interp.ts is the guard on the
Titanic side: its loops write by computed name and its soft-17 rule reads
`dealertotal` written out, so it fails if either half moves.

Dust 0.3.7.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dhobi
dhobi merged commit 3c063f9 into master Aug 23, 2026
2 checks passed
@dhobi
dhobi deleted the dust-saloon-games branch August 23, 2026 20:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant