Honor Caladan auction knowledge in Truthtrance
Verify and publish checkpoint container / container (push) Canceled after 0s
Verify and publish checkpoint container / container (push) Canceled after 0s
This commit is contained in:
1 parent
34f533ae8d
commit
4ec4f843a7
13 files changed
+241
-25
No files matched your search
@@ -0,0 +1,15 @@
|
||||
import type { Game } from './engine';
|
||||
|
||||
/** Physical card identity is not a lot identity: the same card can later
|
||||
* re-enter an ordinary auction. Return null when no current lot exists. */
|
||||
export function originalAuctionInspectionLot(
|
||||
game: Pick<Game, 'richeseAuction' | 'auction' | 'turn'>,
|
||||
): string | null {
|
||||
if (game.richeseAuction) return typeof game.richeseAuction.event === 'string'
|
||||
? game.richeseAuction.event : null;
|
||||
const auction = game.auction;
|
||||
if (!auction || !Number.isSafeInteger(auction.index) || auction.index < 0 ||
|
||||
!Array.isArray(auction.cards) || !auction.cards[auction.index] ||
|
||||
typeof auction.cards[auction.index].id !== 'string') return null;
|
||||
return JSON.stringify([auction.occupiedEvent ?? game.turn, auction.index, auction.cards[auction.index].id]);
|
||||
}
|
||||
+2
-5
@@ -145,6 +145,7 @@ import { diplomatRetreatChoices, type DiplomatRetreatSelection, type DiplomatRet
|
||||
import { ECAZ_START_FORCES, quoteEcazStartingForces } from './ecaz-setup';
|
||||
import { battleCardSlotEligible, battleCategoryInspectionValue, fixedBattleInspectionMatches, validBattleSlotPair, usesNativeBattleSpecial } from './battle-card-slots';
|
||||
import { HARASS_WITHDRAW_CARD, HarassWithdrawError, isHarassWithdraw, quoteHarassWithdraw, defaultHarassWithdrawAllocation, harassWithdrawNeedsAllocation, type HarassWithdrawSelection, type HarassWithdrawContext, type HarassWithdrawPreview } from './harass-withdraw';
|
||||
import { originalAuctionInspectionLot } from './auction-inspection-source';
|
||||
import { discoveryChoices, discoveryStashSignature, type DiscoveryStash } from './discovery-actions';
|
||||
import { createStormSource, validateStormSource, discoveryStormOffer, createDiscoveryStorm, chooseDiscoveryStorm, validateDiscoveryStorm, finishDiscoveryStorm, discoveryStormDistance, discoveryStormSaphoModes, reorderDiscoveryStorm, type StormMovementSource, type DiscoveryStorm } from './discovery-storm';
|
||||
import { completeDiscoveryFlight, discoveryFlightOffer, quoteDiscoveryFlight, validateDiscoveryFlight, type DiscoveryFlightReceipt } from './discovery-flight';
|
||||
@@ -16146,15 +16147,11 @@ function auctionTechnologyQuote<T>(run: () => T): T {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/** Card identity alone is not a lot: Technology can return a previously sold card. */
|
||||
function originalAuctionInspectionLot(g: Game) {
|
||||
return g.richeseAuction?.event ?? JSON.stringify([g.auction!.occupiedEvent ?? g.turn,
|
||||
g.auction!.index, g.auction!.cards[g.auction!.index].id]);
|
||||
}
|
||||
function openOccupiedAuctionInspection(g: Game) {
|
||||
if (!g.homeworldOccupationPreview) return;
|
||||
const authority = quoteOccupiedBiddingAuthority(g, 'atreidesInspection');
|
||||
const lot = originalAuctionInspectionLot(g);
|
||||
requireRule(lot !== null, 'The original Caladan inspection needs an active auction lot.');
|
||||
g.homeworldAuctionInspection = { lot, audience: authority.blocked ? [] : [...authority.inspectionAudience],
|
||||
native: authority.provider, occupation: JSON.stringify(quoteStableHomeworldOccupation(g, 'caladan')), occupiedKnown: false };
|
||||
}
|
||||
|
||||
+2
-2
@@ -3314,7 +3314,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
steps: [
|
||||
'Choose a recorded prediction faction, compare its turn, or compare the target’s current submitted storm dial or known Fremen forecast. Exactly, at least and at most are available for numeric facts.',
|
||||
'Ask whether Atreides’ currently known next Spice Card is a sandworm, Sandtrout, or a blow at a named printed territory. If the peek was canceled or is no longer available, the answer is I don’t know.',
|
||||
'Ask whether the current ordinary auction card or Advanced Richese Black Market card inspected by Atreides has a named Treachery Card face. The Black Market question supports normal, Once Around and Silent bidding. If the inspection was canceled or the lot is no longer available, the answer is I don’t know. Occupied Homeworld inspections are not yet included.',
|
||||
'Ask whether the current ordinary auction card or Advanced Richese Black Market card inspected by Atreides or an entitled Caladan occupier has a named Treachery Card face. The Black Market question supports normal, Once Around and Silent bidding. If the inspection was canceled or the lot is no longer available, the answer is I don’t know; an occupied inspection remains separate from cancellation of the native Atreides inspection.',
|
||||
'In a Basic or Advanced Richese cache auction, ask any participant whether the currently offered public cache card has a named face. If no supported cache lot is active, the answer is I don’t know. This is separate from the Atreides inspection question.',
|
||||
'In Basic or Advanced Bidding, ask Ixians whether their recorded inspected auction group contains a named card. This refers to the recorded group, including cards later sold, rather than what remains for sale. If the inspection is unavailable or belongs to another player, the answer is I don’t know.',
|
||||
'Ask an entitled participant whether an already answered Prescience reveals a named leader, weapon or defense, no element, or an exact force dial. A pending, unavailable or differently inspected element yields I don’t know.',
|
||||
@@ -3326,7 +3326,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
],
|
||||
related: ['card-truthtrance', 'truthtrance-spice', 'privacy'],
|
||||
checklist: [
|
||||
{ area: 'Implementation', status: 'Partial', detail: 'Stored prediction, entitled storm knowledge, native Atreides peeks including all Advanced Black Market methods, revealed Prescience, the Ixian recorded auction group and the current public Richese cache face use shared three-valued fact evaluation; occupied inspection remains separate.', evidence: ['game/truthtrance-knowledge.ts', 'game/truthtrance.ts', 'game/engine.ts'] },
|
||||
{ area: 'Implementation', status: 'Partial', detail: 'Stored prediction, entitled storm knowledge, native Atreides and eligible Caladan-occupier peeks including Advanced Black Market methods, revealed Prescience, the Ixian recorded auction group and the current public Richese cache face use shared three-valued fact evaluation.', evidence: ['game/truthtrance-knowledge.ts', 'game/truthtrance.ts', 'game/engine.ts'] },
|
||||
{ area: 'Player controls', status: 'Partial', detail: 'Fact choices, numeric comparisons, public faction and card names, bounded inputs and compound question previews.', evidence: ['components/truthtrance-knowledge.tsx', 'components/mixed-shipment-fields.tsx'] },
|
||||
{ area: 'AI', status: 'Partial', detail: 'All four profiles answer the owner-only verified scalar through the existing Truthtrance flow. Strategic question selection and freeform understanding remain incomplete.', evidence: ['game/bots.ts', 'tests/truthtrance-knowledge.test.ts'] },
|
||||
{ area: 'Documentation', status: 'Partial', detail: 'Current-knowledge semantics, unknown cases and original FAQ authority are explicit.', evidence: ['docs/TRUTHTRANCE_KNOWLEDGE.md'] },
|
||||
|
||||
@@ -3,6 +3,7 @@ import { leaders, SPICE_CARDS } from './cards';
|
||||
import { gameTerritories, territory } from './board';
|
||||
import { TRUTH_CARD_NAMES } from './truthtrance-card-names';
|
||||
import { battleCategoryInspectionValue } from './battle-card-slots';
|
||||
import { originalAuctionInspectionLot } from './auction-inspection-source';
|
||||
import type { Game, Player } from './engine';
|
||||
|
||||
type Comparison = 'eq' | 'gte' | 'lte';
|
||||
@@ -98,17 +99,24 @@ export function parseKnowledgeFact(value: unknown): KnowledgeFact {
|
||||
}
|
||||
|
||||
export function truthKnowledgeOf(
|
||||
g: Pick<Game, 'phase' | 'turn' | 'advanced' | 'stormDials' | 'stormDialers' | 'stormCard' | 'stormCardKnown' | 'spicePeekKnown' | 'spiceDeck' | 'auction' | 'richeseAuction' | 'richeseCache' | 'richesePeekKnown' | 'richeseOfferedCard' | 'ixAuction' | 'ixAuctionKnown' | 'homeworldOccupationPreview' | 'battle' | 'players' | 'dukeVidal' | 'mobileStronghold' | 'discoveries'>,
|
||||
g: Pick<Game, 'phase' | 'turn' | 'advanced' | 'stormDials' | 'stormDialers' | 'stormCard' | 'stormCardKnown' | 'spicePeekKnown' | 'spiceDeck' | 'auction' | 'richeseAuction' | 'richeseCache' | 'richesePeekKnown' | 'richeseOfferedCard' | 'ixAuction' | 'ixAuctionKnown' | 'homeworldOccupationPreview' | 'homeworldAuctionInspection' | 'battle' | 'players' | 'dukeVidal' | 'mobileStronghold' | 'discoveries'>,
|
||||
p: Pick<Player, 'id' | 'faction' | 'prediction'>,
|
||||
): TruthKnowledge {
|
||||
const dial = g.stormDials[p.id];
|
||||
const peek = g.phase === 5 && p.faction === 'atreides' && g.spicePeekKnown
|
||||
? g.spiceDeck[0] : undefined;
|
||||
const lot = g.phase === 3 && p.faction === 'atreides' && !g.richeseAuction && !g.ixAuction ? g.auction : null;
|
||||
// The private lot is read only after the same inspection entitlement used by
|
||||
// the ordinary auction view has resolved successfully for this faction.
|
||||
const richeseLot = g.phase === 3 && g.advanced && p.faction === 'atreides' &&
|
||||
g.richeseAuction?.source === 'blackMarket' && !g.richeseAuction.outcome && g.richesePeekKnown
|
||||
// Match the private auction projection's original-lot and audience checks.
|
||||
// The occupied share may succeed even if native Atreides was canceled.
|
||||
const source = g.phase === 3 && g.homeworldOccupationPreview ? originalAuctionInspectionLot(g) : null;
|
||||
const receipt = g.homeworldAuctionInspection;
|
||||
const occupied = !!source && !!receipt && receipt.lot === source && receipt.occupiedKnown === true &&
|
||||
p.id !== receipt.native && Array.isArray(receipt.audience) && receipt.audience.includes(p.id) &&
|
||||
g.players.some(seat => seat.id === p.id);
|
||||
const lot = g.phase === 3 && !g.richeseAuction && !g.ixAuction &&
|
||||
((p.faction === 'atreides' && g.auction?.peekKnown) || occupied) ? g.auction : null;
|
||||
const richeseLot = g.phase === 3 && g.advanced &&
|
||||
g.richeseAuction?.source === 'blackMarket' && !g.richeseAuction.outcome &&
|
||||
((p.faction === 'atreides' && g.richesePeekKnown) || occupied)
|
||||
? g.richeseAuction : null;
|
||||
// The offered copy is only valid for this actual lot. Older frames can
|
||||
// recover the still-reserved physical card from the seller's hand.
|
||||
@@ -116,7 +124,7 @@ export function truthKnowledgeOf(
|
||||
? g.richeseOfferedCard : richeseLot
|
||||
? g.players.find(seat => seat.id === richeseLot.owner)?.hand.find(card => card.id === richeseLot.cardId)
|
||||
: undefined;
|
||||
const lotName = lot?.peekKnown ? lot.cards[lot.index]?.name : offered?.name;
|
||||
const lotName = lot ? lot.cards[lot.index]?.name : offered?.name;
|
||||
// A Richese cache lot is public in Basic and Advanced; it is not the
|
||||
// owner's private cache inventory or an Advanced Black Market card.
|
||||
const cacheLot = g.phase === 3 && g.players.some(seat => seat.id === p.id) &&
|
||||
@@ -216,7 +224,7 @@ export function knowledgeFactText(fact: KnowledgeFact): string {
|
||||
if (fact.kind === 'presciencePeek')
|
||||
return `the ${fact.field} you observed through battle Prescience is ${fact.field === 'dial' ? fact.value : fact.name}`;
|
||||
if (fact.kind === 'auctionPeek')
|
||||
return `the current auction card you inspected as Atreides is ${fact.name}`;
|
||||
return `the current auction card you inspected is ${fact.name}`;
|
||||
if (fact.kind === 'cacheLot')
|
||||
return `the current public Richese cache auction card is ${fact.name}`;
|
||||
if (fact.kind === 'ixPoolPeek')
|
||||
|
||||
Reference in new issue
Block a user