[#84] Simultaneous turn phases: phase-start snapshots for public reads #85

Merged
robertismo merged 1 commit from 84-simult into master 2026-09-30 00:12:49 +00:00
Owner

Closes #84.

The bug

All players are meant to act simultaneously in each phase, but three public reads leaked current-turn information by seat order:

  1. deposit-rate hooks run in seat order and each posted rate is written immediately, so a later seat's market-deposit-rates / market-avg-rate read this turn's rates from earlier seats.
  2. bond-offers hooks: a later seat's market-bonds saw earlier seats' fresh offers.
  3. bond-take hooks: a later seat saw a market already thinned by earlier seats' takes.

The fix

Per-phase snapshots, taken at the phase's start, served to every seat during the phase:

  • Game.snapDepositPhase() / Game.snapBondPhase() capture the posted rates (+ deposit-weighted average) and a value copy of the standing bond market.
  • market-deposit-rates, market-avg-rate, market-bonds serve the snapshot while their phase is in progress; the snapshot is cleared at phase end, so reads in other phases (e.g. bond-market reads during the loan phase) stay live and unchanged.
  • The human offers and takes FIRST in their phases, against exactly the market their screen shows (preview and fill always agree — issue #69 discipline).
  • AI bond-take wants clamp to the phase-start market cash.

Tests

simult_test.go pins all three phases: seat 0 acts (posts 10%, offers 900x10, takes 5000), and seat 1's hooks must see exactly the phase-start view. All three pass; full suite green.

Verification

  • go build + go test -count=1 (all pass) + go vet ./... clean.
  • Spectate smokes, mains roster and specialist roster: 0 [ai errors, 0 ACCT/CF DIVERGENCE.
  • Bench smoke (3 games x 13 presets + 1-game gauntlet): 0 FATALs.
  • docs/ai-api.md documents the simultaneity contract.

Behavior note: brains now see last-turn rates during the deposit phase (as intended); bench win distributions may shift slightly vs the pre-fix baseline.

Closes #84. ## The bug All players are meant to act simultaneously in each phase, but three public reads leaked current-turn information by seat order: 1. `deposit-rate` hooks run in seat order and each posted rate is written immediately, so a later seat's `market-deposit-rates` / `market-avg-rate` read this turn's rates from earlier seats. 2. `bond-offers` hooks: a later seat's `market-bonds` saw earlier seats' fresh offers. 3. `bond-take` hooks: a later seat saw a market already thinned by earlier seats' takes. ## The fix Per-phase snapshots, taken at the phase's start, served to every seat during the phase: - `Game.snapDepositPhase()` / `Game.snapBondPhase()` capture the posted rates (+ deposit-weighted average) and a value copy of the standing bond market. - `market-deposit-rates`, `market-avg-rate`, `market-bonds` serve the snapshot while their phase is in progress; the snapshot is cleared at phase end, so reads in other phases (e.g. bond-market reads during the loan phase) stay live and unchanged. - The human offers and takes FIRST in their phases, against exactly the market their screen shows (preview and fill always agree — issue #69 discipline). - AI bond-take wants clamp to the phase-start market cash. ## Tests `simult_test.go` pins all three phases: seat 0 acts (posts 10%, offers 900x10, takes 5000), and seat 1's hooks must see exactly the phase-start view. All three pass; full suite green. ## Verification - `go build` + `go test -count=1` (all pass) + `go vet ./...` clean. - Spectate smokes, mains roster and specialist roster: 0 `[ai` errors, 0 ACCT/CF DIVERGENCE. - Bench smoke (3 games x 13 presets + 1-game gauntlet): 0 FATALs. - `docs/ai-api.md` documents the simultaneity contract. Behavior note: brains now see last-turn rates during the deposit phase (as intended); bench win distributions may shift slightly vs the pre-fix baseline.
[#84] Simultaneous turn phases: phase-start snapshots for public reads
All checks were successful
build / build (push) Successful in 23s
4900d471c6
Robert's ruling: ALL players act simultaneously in every phase — no seat
may see another seat's current-turn action. The engine had three
seat-order leaks (deposit rates, fresh bond offers, bond takes), and
the AI API leaked them through live market reads. Fixed with per-phase
snapshots:

- Game gains snapDepositPhase / snapBondPhase: value snapshots of the
  posted rates (and the deposit-weighted average) and of the standing
  bond market, taken at the phase's start.
- market-deposit-rates / market-avg-rate serve the snapshot during the
  deposit phase; market-bonds serves the snapshot during the bond
  phases. Outside a decision phase reads are live again (cleared at
  phase end), so loan-phase bond reads and post-phase displays are
  unchanged.
- The human now offers and takes FIRST in their phases, against the
  phase-start market their screen displays — the preview and the
  engine's fill always see the same market (issue #69 discipline).
- AI bond-take wants clamp to the phase-start market cash, matching
  what the hook can actually see.

Regression tests (simult_test.go): seat 0 posts a rate / places an
offer / takes from the market, and seat 1's hooks must see exactly the
phase-start view — all three pins pass, plus the existing suite.

Gate: go build + go test -count=1 (all pass) + go vet clean; spectate
smokes for the mains roster and the specialist roster: 0 [ai errors,
0 ACCT/CF DIVERGENCE; bench smoke 3 games x 13 presets + 1-game
gauntlet: 0 FATALs (empire 13 / adapt 10 / mill 6 / vault 6).
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
Winnie/bankwars!85
No description provided.