Files
Dune/docs/AUCTION_CANCELLATION_CONTRACT.md
2026-09-08 07:37:52 +10:00

15 KiB
Raw Permalink Blame History

Auction cancellation continuation contract — 7 September 2026

Implementation follow-up: AUCTION_CANCELLATION_RECOVERY.md records the completed finite quotes, independent custody fix and verified limits. The analysis below is the original pre-implementation snapshot.

Read-only review of current engine. No gameplay simulation, runtime edits, tests, or new rules interpretation. Approximate line anchors below refer to inspected engine 67f4fb01e5ed0b1578ec5a76d348b70f6679f609febfe1b56162869c2c3bc029; identity selector game/karama-context.ts was 1fc8da8fd1a5e869d7ea83b9201e49852437ce611182d8576dca2075674ea124. Root is editing other families concurrently, so hashes identify the review snapshot, not a final release.

First implement denied Ixian Technology → normal Atreides peek as a small shared pure quote. It consumes no card from the proposed exchange, draws nothing, and changes only pendingIxTechnology = null, auction.peekKnown = false, and the immediate Atreides response (or none). Do not require the proposed replacement still to be held. The actual branch explicitly fizzles if the card disappeared, even when allowed.

Then extract a paid-sale / next-normal-lot quote shared by continueAuctionSale, auctionBonus, and nextAuction. It should calculate the existing deterministic choice/response/lot boundary from post-cost hand counts; represent draw, Richese completion, and phase-completion obligations explicitly rather than claiming they have been resolved. Finally handle canceled Ix pool preparation as a typed draw request with validated custody and deterministic pool setup after the real draw. This ordering creates useful shared runtime checks without a generic whole-Game validator or effectful preview.

A successful local quote is not permission to persist a Karama cost separately yet. The wrapper still drains automatic responses and reconciles original-actor promises. Unknown subsequent draw/phase work remains in the atomic action.

Exact producers and minimum source evidence

All five current producers operate during playing phase 3. Validate current positive safe turn and actual seated family owner, plus only the source/successor structures read below. Do not silently change the existing Basic/Advanced distinctions.

Family Actual source and canceled work Minimum evidence; checks to omit
ixAuction setAuction ~4292–4348 creates ixAuction={count,cards:[]}, null auction/active, when Ix exists, eligible bidders exist and count is nonzero. Count is frozen from normal eligibility, or current Richese round normalCount. finishResponse ~10171 draws count, clears pending, calls setAuction(g,cards). Ix owner, phase3, positive safe pending count, pending cards empty at response, no active normal auction. Validate a seated unique nonempty order and current normal-round metadata when present. Count is a prepared value: do not recompute equality against current hand eligibility; interruptions and the canceler's cost may change it. Basic Ix is supported. Short/empty physical deck is legal.
ixTechnology offerAuctionTechnology ~4349 checks Advanced, Ix nonempty hand, unused turn and offers decision. Decision ~12882 marks ixTechnologyTurn=turn, saves offered card ID and creates response. Cancellation ~10185 skips exchange, clears pending, calls offerAuctionPeek. Advanced Ix owner, current normal auction, valid integer index and current card, seated current bidder order/opener/active context, current used-turn stamp and nonempty declared replacement ID. No Richese offered lot: those Technology interactions remain blocked by richeseOfferBlock. Do not require current replacement ownership or still-nonempty Ix hand: cancellation performs no exchange. Do not reset used-turn stamp.
ixAllyCard Normal settleAuction ~4641 paid/free purchase pushes acquired card into buyer hand and writes currentAuctionSale; if buyer.ally points at Ix it saves {player,card,free}. Also Black Market settleRicheseLot ~4150 offers it; cache never does. Decision ~12905 checks buyer, actual held acquired card and buyer.ally before response {owner:ix,recipient:buyer}. Cancellation ~10209 clears pending and calls continueAuctionSale(pending.free). Bind recipient/pending player/sale winner, free flag, acquired ID to normal indexed card or sold Black Market card, sale amount/origin/seller and live source. Existing producer only checks buyer→Ix alliance, while allowed discard-frame validation additionally requires mutual alliance; valid engine alliances are mutual, but do not describe the stronger predicate as producer wording. Denial neither discards nor draws. Do not require acquired card still held for cancellation: branch deliberately skips replacement if missing. Do not re-charge purchase or re-check buyer affordability/capacity.
emperorIncome continueAuctionSale ~4666 emits for nonfree purchase when seated Emperor differs from winner, except a nonnormal sale to someone other than seller credits seller directly instead. Cancellation ~10653 does no payment and calls auctionBonus. Emperor owner distinct winner; paid sale/current normal bidder; safe amount, free=false, correct normal sale or supported nonnormal self-purchase fallback. Do not re-check current winner spice/aid or credit Emperor. Existing source supports missing currentAuctionSale using auction.bidder/bid; handle this legacy path explicitly or preserve old atomic behavior rather than silently banning it.
harkonnenBonus auctionBonus ~4685 emits for paid OR free winning Harkonnen with hand length<8. Also used after sold Richese lots. Cancellation ~10665 skips bonus draw and calls nextAuction. Harkonnen owner equals paid/free sale winner (or legacy normal auction bidder), source index/lot outcome and valid successor context. Do not require hand currently below8: incoming cards can fill it after declaration, and automatic runtime explicitly allows that response without draw. Do not require deck card availability for denial.

For a normal paid receipt validate: auction exists; index is a safe integer in [0,cards.length); bidder is seated winner; nonnegative safe bid and sale.amount===bid; sale origin normal/seller null; free boolean; nonempty indexed card with stable physical identity. For new paid producers, the sale is mandatory; legacy fallback can be a separate explicitly supported variant rather than inventing a new receipt. No winning bid or ally debit is repeated. Normal auction has no event field: bind turn/index/card/order, not a fabricated event.

For sold Black Market/cache successor receipts, bind current Richese lot event, source, owner/seller, card ID, sold winner/amount, round turn/stage/position and free=false. ixAllyCard specifically excludes cache. Keep these separate from the disputed canceled richeseAuction count response: that guard is unchanged.

Shared finite successor proposal

Use a private typed result along these lines (names illustrative):

  • peek: retain current lot, set peekKnown false, optional seated Atreides response.
  • income: existing paid receipt plus next Emperor response, or exact seller credit and next bonus boundary. Validate safe current seller balance plus amount only when this suffix will credit it.
  • bonus: optional Harkonnen response using post-cost current hand count; otherwise continue to next-lot quote. Credit has already occurred before this boundary if applicable.
  • normalLot: clear sale, increment valid index exactly once; reset bid/allyPayment/bidder/passed; rotate opener to next nonfull seated player; set active; offer Technology decision if Advanced Ix currently has a hand and current turn unused, else optional Atreides peek.
  • normalEnd: return unauctioned suffix cards to deck front; clear auction/sale; either Richese cache decision, current round completion then phase end, or ordinary phase end.
  • richeseEnd: existing finishRicheseLot branch, with exact source metadata; stop before actual cache/declaration event generation or downstream normal pool draw, unless those get their own shared execution contract.
  • drawPool: requested frozen count plus validated physical draw source; actual drawing is deferred. After draw, shared deterministic setAuction selection handles no eligible bidders, zero cards, and first opener.

Before opener loops, validate order is nonempty, unique, seated; opener is an integer in range; index/card arrays valid. nextAuction currently checks equality with cards.length, so corrupt index beyond end would otherwise select undefined cards. This is a malformed-save prerequisite, not a reproduced valid-state defect. Derive able using current post-cost hands and actual handLimit; preserve separate existing transfer/reservation guards rather than changing eligibility rules. Preserve the current behavior that only index advances and unauctioned cards return; earlier sold array elements are historical references.

No pure quote should assign random IDs, shuffle, draw actual cards, mutate balances, construct a new choice answer, or call the global normalizer. Returned internal context may need private card IDs for custody; it must never be added wholesale to public GameView or eligibility controls.

Physical custody, draws, and the cost boundary

draw ~2001 rejects live pendingTreacheryDiscard or a paid Box. It draws from deck head; on an empty deck it shuffles the entire current discard once and clears discard. Each pool iteration calls it, so a depleted combined pool simply yields fewer cards; there is no mandatory supply minimum. Allowed Harkonnen bonus similarly tolerates no card and logs that outcome.

  • Printed cancellation cost is discarded before the current finishResponse. BG Worthless was discarded at declaration; a later successful conversion sees whatever the present discard/deck has become after legal interruptions. Quote the actual intended post-cost pools, not the initial declaration's old pools.
  • Thus a just-spent Karama may join the refill and appear in the canceled Ix pool. A future frame must clear its own committed discard control before a legitimate actual draw. Never pre-sample that refill or replay it during recovery.
  • Validate actual custody pools and canonical card entries; keep normal auction.cards[0..index] and sale IDs as references, not additional physical copies. Current purchased card is in the winner's hand (or has since moved through permitted effects); prior sold cards also remain referenced in the auction array. Unauctioned suffix and pending Ix drawn pool are physical holdings. Blind global duplicate checking across every auction array entry would reject valid states.
  • Denied ixTechnology and denied ixAllyCard do not require draw validation themselves. The latter can still reach a Harkonnen bonus automatically, so a whole-action proof would need that later draw boundary too.
  • The existing allowed Ix ally replacement frame ~2800/~3093 validates consumed acquired card, paid source, source index/event and alliance, clears frame before draw, then continues sale exactly once. Reuse its paid-source evidence where appropriate, but do not invoke its denied-card custody or replacement-draw requirements when cancellation skips those effects.

Automatic closure and end-of-Bidding boundary

settleAutomaticContinuations ~12034 ignores a nominal response barrier if no unpassed player holds a usable cancellation card. Full Harkonnen hands skip that check and auto-finish without drawing. Consequently:

  1. Canceled Ix Technology can immediately allow Atreides peek (peekKnown=true); a quote ending at response creation does not promise that peek remains unknown.
  2. Canceled Emperor income may immediately allow a Harkonnen bonus, consuming a real random refill, then start another lot.
  3. Canceled Ix ally replacement may credit Black Market seller, open Emperor income for a normal sale, allow it, then allow Harkonnen bonus and advance.
  4. Canceled Harkonnen bonus does not draw that bonus, but the next normal lot can expose Technology choice/Atreides response; finishing Black Market may prepare the later normal pool.
  5. Canceled Ix pool response always performs its count draws before any next choice.

finishNormalBidding ~4020 opens a final Richese cache decision when applicable, otherwise nextPhase ~7308 opens CHOAM Market or advances. With CHOAM the apparently terminal market can auto-finish when the current public phase3 hand conditions in finishAutomaticDecision ~5318 hold. Actual phase advance settles tech income, refunds every aid credit, resets ready/active, opens the optional Ix phase-opening window, then initializes phase4 revival records. These obligations are beyond an isolated next-lot quote. Validate current refund/tech prerequisites only in a dedicated end-of-Bidding suffix; do not run them as speculative gameplay. Stopping at a real player decision is valid bounded evidence; CHOAM auto-empty decision is the documented exception.

Tests for the next slice

  • Actual Ix Technology declaration, printed/BG denial, countercanceled BG restoring original exchange; stale lot/index/card/turn/family rejected before cost. Offered card missing must remain a legal denied/fizzled exchange, not a new hand-custody requirement. Pure quote zero RNG/no mutation.
  • Actual paid normal auctions for Emperor and Harkonnen; free vs paid; buyer also canceler to expose post-cost eligibility. Preserve original payment, seller credit and bonus exactly once. Separate legacy missing-sale fixture if retained.
  • Consecutive lots, all-full after incoming card, a just-freed canceler slot, last lot, zero supply, and unused Ix Technology decision. Keep physical current/past card references distinct from unsold ownership.
  • Ix cancellation with initially empty deck and spend card entering discard, and pool shortage0/1. Existing tests/ix-technology.test.ts already covers shortage avoiding an impossible selection; do not regress it with supply minimums.
  • Actual auto-allow chains with final opposing Karama spent: Emperor denial→Harkonnen draw→next lot, denied replacement→income→bonus, plus controlled responder retained to prove genuine stop boundary.
  • Richese Black Market replacement and normal-end cache decision only under existing supported source contracts; no new canceled Richese count or Technology-on-cache behavior.

Separate valid-state defect assessment

No confirmed valid-state deadlock found in this read-only pass. Empty/one-card Ix supply is explicitly handled, empty-handed Technology owner can decline, full Harkonnen bonus auto-finishes, and empty eligible bidder sets finish/return unauctioned cards. Missing current paid receipt, malformed order/index/count and inconsistent seller/funding amounts are saved-state corruption risks; do not present them as live-action exploits without a genuine declaration sequence. The existing stronger mutual-alliance replacement-frame check versus one-way producer check matters for legacy/corrupt fixture compatibility, not evidence of a legal alliance mutation during phase3.