Public: anyone can read this; the crew can change it.
The turn: offer, accept, pass — and whose move it is #8
Amended by the planner. This issue also carries startTable(db, who, { gameId }) (moved from #7): host or admin, 4 to 6 seated, fixes the seat order, stores the seed, deals the charter and every seat's charge with deal() from src/board.ts (#6), sets quorum to ⌈n/2⌉+1 and the deadline, moves the table to playing, and emits table.started — refused by name with three seats, by a non-host, and on a second call. POST /api/tables/:id/start is its route.
Amended by the planner, now that #36 is merged: src/round.ts holds the arithmetic this issue used to describe, so #8 is wiring, not a rules engine.
- The act shape is already decided there and the schema stores exactly it:
{ seat, kind: "offer" | "accept" | "pass", accord?, cite?, at }, plus the ids and authorship storage needs. Do not add a field the rules do not read. whoseTurn,standing,quorum,bindsandmayActare imported, never re-derived. The turn is the act count modulo the seat count — derived, as #36 argued — so there is no pointer column and nothing to keep in step.tableStatebuilds aRoundfrom the rows and answers from these functions:turn,your_turn,standing(accord, who, the acceptances in order) andacts. A rule that appears in an action and also inround.tsis a bug; if one is missing there, add it there.- The boundary stays here and is the real work of this run:
validAccordfromsrc/board.tsagainst this table's charter, it-is-not-your-turn, no-seat, not-playing, and the rate that stops an agent spending a turn instantly — each refused by name, with the status the failure names at every door. startTable(moved here earlier) deals withdeal()and stores the seed, the charter and every charge in one transaction.
The checklist otherwise stands. src/example.ts is §10's table as data — use it in the tests rather than building a fourth copy of a charter.
Intent. The act ledger that makes it a game: one act per turn, in seat order, with the standing offer and its acceptances derived from the acts rather than stored twice.
Done looks like
makeOffer(db, who, { gameId, accord }),acceptOffer(db, who, { gameId }),passTurn(db, who, { gameId }), each writing one row toactand refusing by name when it is not your turn, you hold no seat, or the table is notplaying.offeract; its acceptances are theacceptacts after it; a new offer clears them, and a test proves an acceptance before an offer does not count for it.tableStategainsturn(the seat to act),your_turnfor the viewer,standing(the accord and who offered it),acceptances(which seats, in order) andacts(the whole public log, newest last).emit("act.made", …)andemit("turn.changed", …); the API gainsPOST /api/tables/:id/offer,/accept,/passanswering with the status the failure names.Files
src/actions.ts,src/actions.test.ts,src/routes.ts,src/server.test.ts.Risks Deriving the standing offer on every read must stay fast — index
act (game_id, id). Turn order fromordinalplus the count of acts is simpler than a stored pointer; pick one and say why in the pull.Brief that fits @ledger — an append-only ledger with the rule at the boundary. (A person assigns.)