Show the banker the amount to count out, not just whether the player is up

The standings had a column headed "Cash out" and it did not hold an amount to pay.
It held the difference against the player's buy-in. Measured on a table where Ann
bought in for $200 and sits behind $340:

    PLAYER   NET   CASH OUT
    Ann      $0     +$140

She is handed $340. The column called "Cash out" says $140, under a heading that
invites reading it as the payout, at the exact moment somebody is counting money onto
the table. The number a banker's hands actually follow — what the player is holding —
was computed all along and written to the CSV, and never shown on screen.

Three columns now, and they say what they are: Player, Chips, Up/down. Chips is a
plain amount and never signed, because it is a quantity of money to hand over. Up/down
keeps the sign and the colour, because it is a result. The two differ by exactly the
buy-in, which is asserted rather than assumed.

Chips counts what is out in the middle for the hand in progress as still theirs, for
the same reason the up/down figure does: the panel is read mid-hand constantly and a
stack that drops by the size of a bet looks like money going missing.

Net — won less paid in, from the hand log — comes off the screen to make room. It is
the weakest of the three: it only moves when a pot is resolved through the wizard, so
at a table where the dealer settles pots by hand it reads $0 all night beside two
columns that don't, and up/down already answers "how are they doing". It stays in the
CSV export, which loses nothing.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
This commit is contained in:
2026-09-01 23:36:14 -06:00
co-authored by Claude Opus 5
parent c70cb150db
commit 8efc14f61f
5 changed files with 97 additions and 13 deletions
@@ -110,8 +110,9 @@
<span>Player</span> <span>Player</span>
<span class="standingsInHead">In</span> <span class="standingsInHead">In</span>
<span class="standingsWonHead">Won</span> <span class="standingsWonHead">Won</span>
<span>Net</span> <span class="standingsNetHead">Net</span>
<span title="Chips in front of them, less the cash they handed over">Cash out</span> <span class="standingsChipsHead" title="What they are holding — the cash to count out if they leave now">Chips</span>
<span title="Those chips, less the cash they handed over">Up/down</span>
</div> </div>
<div id="standingsList"></div> <div id="standingsList"></div>
</div> </div>
@@ -270,6 +271,7 @@
<span class="standingsIn"></span> <span class="standingsIn"></span>
<span class="standingsWon"></span> <span class="standingsWon"></span>
<span class="standingsNet"></span> <span class="standingsNet"></span>
<span class="standingsChips"></span>
<span class="standingsCashOut"></span> <span class="standingsCashOut"></span>
</div> </div>
</template> </template>
+21 -8
View File
@@ -1816,31 +1816,44 @@ button,
white-space: nowrap; white-space: nowrap;
} }
/* The per-hand columns are detail: how the night's swings happened. They're the /* The per-hand columns are detail: how the night's swings happened. They're the first
first thing to go when there isn't room, because Net and Cash out are what the to go when there isn't room, and Net goes with them — it only moves when a pot is
banker is actually reading. */ resolved through the wizard, so on a table where the dealer settles pots by hand it
reads $0 all night beside two columns that don't.
What survives at every width is the pair the banker actually works from: what the
player is holding, and whether that leaves them up or down. */
.standingsIn, .standingsIn,
.standingsWon, .standingsWon,
.standingsNet,
.standingsInHead, .standingsInHead,
.standingsWonHead { .standingsWonHead,
.standingsNetHead {
display: none; display: none;
} }
@container (min-width: 30rem) { @container (min-width: 30rem) {
.standingsRow { .standingsRow {
grid-template-columns: minmax(5rem, 1fr) auto auto auto auto; grid-template-columns: minmax(5rem, 1fr) auto auto auto auto auto;
} }
.standingsIn, .standingsIn,
.standingsWon, .standingsWon,
.standingsNet,
.standingsInHead, .standingsInHead,
.standingsWonHead { .standingsWonHead,
.standingsNetHead {
display: block; display: block;
} }
} }
/* The settle-up column is the one a banker reads while counting out cash, so it /* The chips column is the one a banker's hands follow while counting money onto the
carries the weight the per-hand columns don't. */ table, so it carries the weight. Up/down is read once, at the end. */
.standingsChips {
font-weight: 600;
color: #e6e6e6;
}
.standingsCashOut { .standingsCashOut {
font-weight: 600; font-weight: 600;
} }
@@ -818,9 +818,15 @@ const Render = {
netEl.classList.toggle("netUp", row.net > 0); netEl.classList.toggle("netUp", row.net > 0);
netEl.classList.toggle("netDown", row.net < 0); netEl.classList.toggle("netDown", row.net < 0);
// What they'd actually be handed if the night ended now: chips in front of // The cash to count out, which is a plain amount and never signed — this
// them less the cash they gave the banker. This was computed and written to // is the one the banker's hands follow. It sat only in the CSV: on screen
// the CSV all along, but never shown to the person doing the paying out. // the column headed "Cash out" held the difference against their buy-in
// instead, so a player up $140 on a $340 stack read as "$140" at the exact
// moment somebody was counting money onto the table.
node.querySelector(".standingsChips").textContent = Money.format(row.chipsInFront);
// And whether that leaves them up or down, which is the question the rest
// of the table asks and the one the old column was actually answering.
const cashEl = node.querySelector(".standingsCashOut"); const cashEl = node.querySelector(".standingsCashOut");
cashEl.textContent = Money.formatSigned(row.cashPosition); cashEl.textContent = Money.formatSigned(row.cashPosition);
cashEl.classList.toggle("netUp", row.cashPosition > 0); cashEl.classList.toggle("netUp", row.cashPosition > 0);
@@ -683,6 +683,14 @@ const HoldemState = {
net: Money.round(t.won - t.in), net: Money.round(t.won - t.in),
boughtIn, boughtIn,
chipsNow, chipsNow,
// Everything still theirs: the stack in front of them plus whatever
// is out in the middle for the hand in progress. This is the number
// a banker counts out when somebody leaves, and it was the one
// figure the standings never showed — the column called "Cash out"
// held the difference against their buy-in, so a player up $140 on
// a $340 stack read as "$140" at the exact moment cash changed
// hands.
chipsInFront: Money.round(chipsNow + inFrontOfThem),
cashPosition: Money.round(chipsNow + inFrontOfThem - boughtIn), cashPosition: Money.round(chipsNow + inFrontOfThem - boughtIn),
notes, notes,
}; };
+55
View File
@@ -167,3 +167,58 @@ describe("does the cash box cover the chips on the table", () => {
assert.equal(HoldemState.bankReconciliation(state).balanced, true); assert.equal(HoldemState.bankReconciliation(state).balanced, true);
}); });
}); });
describe("the number the banker counts out", () => {
// The standings had two signed columns and neither was an amount to pay. A player
// up $140 on a $340 stack read "$140" under a heading saying "Cash out", at the
// exact moment somebody was counting money onto the table.
test("chips in front is what they are holding, not what they are up", () => {
const state = makeTable({ seatCount: 6, seated: [0, 1], stack: 340 });
state.seats[0].name = "Ann";
state.seats[0].buyIns = 200;
state.seats[1].buyIns = 200;
state.seats[1].stack = 60;
const rows = HoldemState.sessionStandings(state);
const ann = rows.find(r => r.name === "Ann");
assert.equal(ann.chipsInFront, 340, "the cash to count out");
assert.equal(ann.cashPosition, 140, "and separately, that they are up $140");
});
test("a player who is down still has chips to be handed", () => {
const state = makeTable({ seatCount: 6, seated: [0, 1], stack: 60 });
state.seats[0].name = "Cal";
state.seats.forEach(p => { p.buyIns = 200; });
const cal = HoldemState.sessionStandings(state).find(r => r.name === "Cal");
assert.equal(cal.chipsInFront, 60, "they leave with $60 of chips");
assert.equal(cal.cashPosition, -140, "having lost $140 of the $200 they brought");
});
test("chips still out in the middle are counted as theirs", () => {
// Cashing out mid-hand isn't a thing, but the panel is read mid-hand constantly
// and a stack that drops by the size of a bet looks like money going missing.
const state = makeTable({ seatCount: 6, seated: [0, 1], stack: 200 });
state.seats[0].name = "Dee";
state.seats.forEach(p => { p.buyIns = 200; });
Betting.beginHand(state);
Betting.raiseTo(state, 0, 50);
const dee = HoldemState.sessionStandings(state).find(r => r.name === "Dee");
assert.equal(dee.chipsInFront, 200, "stack plus what's in the middle for them");
assert.equal(dee.cashPosition, 0, "so they are still level");
});
test("chips and up/down always differ by exactly the buy-in", () => {
const state = makeTable({ seatCount: 9, seated: [0, 1, 2, 3], stack: 100 });
[340, 150, 60, 250].forEach((chips, i) => {
state.seats[i].stack = chips;
state.seats[i].buyIns = 200 + i * 50;
});
HoldemState.sessionStandings(state).forEach(row => {
assert.equal(Money.round(row.chipsInFront - row.boughtIn), row.cashPosition,
`${row.name}'s two columns should tell one story`);
});
});
});