Prototype Jacurutu income, Testing Station and Shrine effects
Connect automatic unambiguous Jacurutu physical-force income, a sole-occupant Testing Station storm choice and Shrine reciprocal physical card roles to engine actions, player controls, legal AI and saved continuation. Preserve Weather Control exclusion, exact card/force custody, private hand boundaries and committed effects after restart. Shared role checks cover existing ordinary and special Karama paths; reserved cards cannot enter another pending use. Keep Orgiz, mixed-force Jacurutu allocation and contested Cistern/Testing Station benefits explicitly pending. These are bounded prototypes; complete Discovery/Advanced/expansion and publication gates remain closed. Validation: types, lint and 4,345 offline tests passed (32 added), production build passed, and 40 HTTP tests passed. Independent cross-reviews repaired reserved-card deadlock, physical-card selection, alternate-role permission, storm source/token binding and public-entitlement bot ordering. The first broad run found five failures; focused repairs preceded the stable repeated check. Browser A5EJQ2W4 used genuine Discovery setup/reveal/free entry with targeted QA positioning. A human increased storm movement from two to three sectors, moving sector 6 to 9. Refresh restored station forces and the private Snooper. This was targeted control/recovery evidence, not an unmodified full game. All 187 opening saved rooms remained unchanged; 31 QA/HTTP rooms were added (218 total). Reused the running server; no restart or save reset. Source-bound report: /home/quintine/.local/share/dune/checkpoints/20260913-discovery-location-effects/verification-final/report.json Source fingerprint: beecb85ab7ec39319115389c115434d161591e8549802c542a6343a0fc19bea6 Durations: check 140540ms; build 6741ms; integration 24956ms. Aggregate goal counter 63,116,444 to 64,295,091 at final verification: 1,178,647 tokens before final Git/push overhead for three bounded functions. Standing authorization: Push all from now on.
This commit is contained in:
1 parent
ab0ab52849
commit
f0d3d9c464
31 files changed
+2524
-138
No files matched your search
+113
@@ -0,0 +1,113 @@
|
||||
import { fighterCount } from './advisors';
|
||||
import type { Card } from './cards';
|
||||
import type { FactionId } from './catalog';
|
||||
import type { ForcePresence } from './force-presence';
|
||||
import { canUseAsKarama } from './karama';
|
||||
|
||||
export const SHRINE = 'shrine';
|
||||
|
||||
type ShrinePlayer = {
|
||||
id: string;
|
||||
faction: FactionId;
|
||||
advisors?: Record<string, { lockedTurn?: number }>;
|
||||
} & ForcePresence;
|
||||
|
||||
type ShrineContext = {
|
||||
advanced: boolean;
|
||||
discoveries?: {
|
||||
tokens: readonly {
|
||||
face: string | null;
|
||||
status: string;
|
||||
revealedTurn: number | null;
|
||||
}[];
|
||||
} | null;
|
||||
};
|
||||
|
||||
type ShrineBotContext = ShrineContext & {
|
||||
status: string;
|
||||
phase: number;
|
||||
truthtrance?: object | null;
|
||||
response?: object | null;
|
||||
decision?: object | null;
|
||||
phaseOpening?: object | null;
|
||||
automaticContinuationPending?: boolean;
|
||||
battle?: {
|
||||
attacker: string;
|
||||
defender: string;
|
||||
revealed: boolean;
|
||||
} | null;
|
||||
};
|
||||
|
||||
/** Public board entitlement only. A private peek at Shrine does not activate it. */
|
||||
export function occupiesRevealedShrine(
|
||||
context: ShrineContext,
|
||||
player: ShrinePlayer,
|
||||
): boolean {
|
||||
const revealed = context.discoveries?.tokens.some(
|
||||
(token) =>
|
||||
token.face === SHRINE &&
|
||||
token.status === 'placed' &&
|
||||
Number.isSafeInteger(token.revealedTurn) &&
|
||||
(token.revealedTurn ?? 0) >= 1,
|
||||
);
|
||||
return !!revealed && fighterCount(player, SHRINE) > 0;
|
||||
}
|
||||
|
||||
/** Printed identity remains unchanged; this answers only the card's role at use. */
|
||||
export function canUseAsKaramaRole(
|
||||
context: ShrineContext,
|
||||
player: ShrinePlayer,
|
||||
card: Card,
|
||||
): boolean {
|
||||
return (
|
||||
canUseAsKarama(context.advanced, player.faction, card) ||
|
||||
(card.effect === 'truthtrance' &&
|
||||
occupiesRevealedShrine(context, player))
|
||||
);
|
||||
}
|
||||
|
||||
/** A committed conversion is recorded by the Truthtrance queue; this checks a new use. */
|
||||
export function canUseAsTruthtranceRole(
|
||||
context: ShrineContext,
|
||||
player: ShrinePlayer,
|
||||
card: Card,
|
||||
): boolean {
|
||||
return (
|
||||
card.effect === 'truthtrance' ||
|
||||
(card.effect === 'karama' && occupiesRevealedShrine(context, player))
|
||||
);
|
||||
}
|
||||
|
||||
/** Bounded policy: a Shrine-occupying combatant may ask through the existing AI question path. */
|
||||
export function shrineTruthtranceBotAction(
|
||||
context: ShrineBotContext,
|
||||
player: ShrinePlayer & { hand?: readonly Card[] },
|
||||
) {
|
||||
const battle = context.battle;
|
||||
if (
|
||||
context.status !== 'playing' ||
|
||||
context.phase !== 6 ||
|
||||
!battle ||
|
||||
battle.revealed ||
|
||||
![battle.attacker, battle.defender].includes(player.id) ||
|
||||
context.truthtrance ||
|
||||
context.response ||
|
||||
context.decision ||
|
||||
context.phaseOpening ||
|
||||
context.automaticContinuationPending ||
|
||||
!occupiesRevealedShrine(context, player)
|
||||
)
|
||||
return null;
|
||||
const card = player.hand?.find(
|
||||
(candidate) =>
|
||||
candidate.effect === 'karama' &&
|
||||
canUseAsTruthtranceRole(context, player, candidate),
|
||||
);
|
||||
return card
|
||||
? {
|
||||
type: 'card',
|
||||
card: card.id,
|
||||
shrineTruthtrance: [card.id],
|
||||
}
|
||||
: null;
|
||||
}
|
||||
Reference in new issue
Block a user