Coordinate Fremen grants in ordered revival
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
64706cc7e0
commit
220d78c779
11 files changed
+429
-12
No files matched your search
+10
-5
@@ -4665,14 +4665,19 @@ export function botActions(g: GameView): Action[] {
|
||||
: [{ type: 'endMovement' }];
|
||||
}
|
||||
|
||||
/** In concurrent Basic Revival, let a human Fremen decide its existing grant
|
||||
* before its automated ally spends or finishes. Ready is the normal way to
|
||||
* finish that decision; responses and unrelated actions remain available. */
|
||||
/** Let a human Fremen decide a useful grant before an automated ally spends or
|
||||
* finishes. Ordered Advanced uses a per-recipient release; concurrent Basic
|
||||
* retains its normal Ready release. Responses and unrelated actions continue. */
|
||||
function waitsForHumanFremenGrant(g: Game, player: Game['players'][number]): boolean {
|
||||
if (g.status !== 'playing' || g.advanced || g.phase !== 4 || g.revivalOrder ||
|
||||
const ordered = g.advanced && !!g.revivalOrder;
|
||||
if (g.status !== 'playing' || g.phase !== 4 || (g.advanced && !ordered) ||
|
||||
(!g.advanced && !!g.revivalOrder) ||
|
||||
(ordered && g.revivalOrder!.remaining[0] !== player.id) ||
|
||||
player.tanks <= 0 || revivalPrevented(g, player.id)) return false;
|
||||
const fremen = g.players.find(seat => seat.faction === 'fremen');
|
||||
if (!fremen || fremen.bot || fremen.autopilot || g.ready.includes(fremen.id) ||
|
||||
if (!fremen || fremen.bot || fremen.autopilot || (!ordered && g.ready.includes(fremen.id)) ||
|
||||
(ordered && g.fremenRevivalWaitRelease?.turn === g.turn &&
|
||||
g.fremenRevivalWaitRelease.recipients.includes(player.id)) ||
|
||||
fremenRevivalGrantBlock(g, fremen.id, player.id) !== null) return false;
|
||||
const current = freeRevivalRemaining(g, player);
|
||||
const granted = freeRevivalRemaining({ ...g, freeRevival: [...g.freeRevival, player.id] }, player);
|
||||
|
||||
@@ -1501,6 +1501,8 @@ export type Game = {
|
||||
mentatVictoryAfterActions?: number;
|
||||
charityPromises?: CharityPromise[];
|
||||
revivalOrder?: RevivalOrder;
|
||||
/** Owner's per-ally release of the provisional ordered bot wait. */
|
||||
fremenRevivalWaitRelease?: { turn: number; recipients: string[] };
|
||||
charityOrder?: CharityOrder;
|
||||
pendingAutomaticAdvisorFlips?: AutomaticAdvisorFlipSource[];
|
||||
pendingAutomaticAdvisorTransport?: AutomaticAdvisorTransport;
|
||||
@@ -30531,6 +30533,7 @@ function beginPhase(g: Game) {
|
||||
}
|
||||
}
|
||||
if (g.phase === 4) {
|
||||
delete g.fremenRevivalWaitRelease;
|
||||
g.revivalRules = newRevivalRules();
|
||||
g.revivalRequests = {};
|
||||
g.freeRevival = [];
|
||||
@@ -46752,6 +46755,23 @@ function applyActionInner(
|
||||
};
|
||||
return g;
|
||||
}
|
||||
if (t === 'releaseFremenRevivalWait') {
|
||||
requireRule(Object.keys(action).sort().join(',') === 'target,type' &&
|
||||
g.phase === 4 && g.advanced && g.revivalOrder && p.faction === 'fremen',
|
||||
'Release an automated ally only during ordered Advanced Revival.');
|
||||
requireRule(!recruitsInteractionPending(g), 'Finish the current revival transaction or priority window first.');
|
||||
const target = stringField(action.target);
|
||||
const recipient = getPlayer(g, target);
|
||||
requireRule(!!(recipient.bot || recipient.autopilot) && g.revivalOrder.remaining.includes(target),
|
||||
'Choose an automated ally whose Revival turn remains.');
|
||||
fremenRevivalGrantRule(() => quoteFremenRevivalGrant(g, id, target));
|
||||
const released = g.fremenRevivalWaitRelease?.turn === g.turn
|
||||
? g.fremenRevivalWaitRelease.recipients : [];
|
||||
requireRule(!released.includes(target), 'This ally has already been released from the wait.');
|
||||
g.fremenRevivalWaitRelease = {turn: g.turn, recipients: [...released, target]};
|
||||
log(g, `${p.name} released ${recipient.name} to continue Revival without waiting for a Fremen grant.`);
|
||||
return g;
|
||||
}
|
||||
if (t === 'grantRevival') {
|
||||
requireRule(g.phase === 4 && p.faction === 'fremen',
|
||||
'Only Fremen may grant their ally free revival during revival.');
|
||||
|
||||
@@ -11,7 +11,8 @@ export type FremenRevivalGrantContext = Pick<
|
||||
Game,
|
||||
'status' | 'phase' | 'turn' | 'players' | 'freeRevival' | 'revivalRules' | 'recruits'
|
||||
> & Partial<Pick<Game, 'coalitionState' | 'advanced' | 'expansions' | 'homeworlds' |
|
||||
'nexusCards' | 'leaderSkills' | 'techTokens' | 'strongholdCards' | 'ecazTreachery' | 'discoveryEnabled'>>;
|
||||
'nexusCards' | 'leaderSkills' | 'techTokens' | 'strongholdCards' | 'ecazTreachery' | 'discoveryEnabled' |
|
||||
'fremenRevivalWaitRelease'>>;
|
||||
|
||||
export class FremenRevivalGrantError extends Error {
|
||||
constructor(message: string) {
|
||||
@@ -118,7 +119,7 @@ export function validateFremenRevivalGrant(
|
||||
export function projectFremenGroupRevivalGrant(
|
||||
g: FremenRevivalGrantContext,
|
||||
viewer: string,
|
||||
): Array<{recipient: string; granted: boolean; blocked: string | null}> | null {
|
||||
): Array<{recipient: string; granted: boolean; blocked: string | null; waitReleased?: boolean}> | null {
|
||||
if (!id(viewer) || !Array.isArray(g.players) ||
|
||||
g.players.filter(player => player.id === viewer && player.faction === 'fremen').length !== 1)
|
||||
return null;
|
||||
@@ -126,7 +127,9 @@ export function projectFremenGroupRevivalGrant(
|
||||
if (!recipients) return null;
|
||||
return recipients.map(recipient => ({recipient,
|
||||
granted: Array.isArray(g.freeRevival) && g.freeRevival.includes(recipient),
|
||||
blocked: fremenRevivalGrantBlock(g, viewer, recipient)}));
|
||||
blocked: fremenRevivalGrantBlock(g, viewer, recipient),
|
||||
...(g.fremenRevivalWaitRelease?.turn === g.turn &&
|
||||
g.fremenRevivalWaitRelease.recipients.includes(recipient) ? {waitReleased: true} : {})}));
|
||||
}
|
||||
|
||||
/** Owner-only status. No wallet, Tanks, prior counts or quoted free amount is
|
||||
@@ -134,7 +137,7 @@ export function projectFremenGroupRevivalGrant(
|
||||
export function projectFremenRevivalGrant(
|
||||
g: FremenRevivalGrantContext,
|
||||
viewer: string,
|
||||
): { recipient: string; granted: boolean; blocked: string | null } | null {
|
||||
): { recipient: string; granted: boolean; blocked: string | null; waitReleased?: boolean } | null {
|
||||
if (!id(viewer) || !Array.isArray(g.players)) return null;
|
||||
const owners = g.players.filter(player => player.id === viewer);
|
||||
const owner = owners[0];
|
||||
@@ -146,5 +149,7 @@ export function projectFremenRevivalGrant(
|
||||
recipient: owner.ally,
|
||||
granted: Array.isArray(g.freeRevival) && g.freeRevival.includes(owner.ally),
|
||||
blocked: fremenRevivalGrantBlock(g, viewer),
|
||||
...(g.fremenRevivalWaitRelease?.turn === g.turn &&
|
||||
g.fremenRevivalWaitRelease.recipients.includes(owner.ally) ? {waitReleased: true} : {}),
|
||||
};
|
||||
}
|
||||
+6
-3
@@ -6566,6 +6566,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
"The original owner, ally and turn remain bound while another player may prevent the grant with Karama. All-pass commits once. Under the provisional prevention policy, cancellation denies this grant for the current Revival, preserving the ally’s intrinsic free rate, Recruits and completed returns/payments. The next native Revival clears the denial.",
|
||||
"Recruits doubles the current granted rate including applicable bonuses before caps. With no other bonus, three becomes six. Provisionally accept grants before or after the card with the same calculation: deduct recorded free usage, preserve all earlier total returns and paid classifications, and give no refund. Finish a pending transaction before declaring a grant.",
|
||||
"The Fremen panel keeps status and unavailable reasons visible. In concurrent Basic Revival an automated ally can wait for a useful grant until the Fremen grants it or finishes the phase; the existing Karama response still applies. Your ally’s normal revival panel supplies the actual remaining amount. Complete modes, broader combinations and final acceptance remain unfinished.",
|
||||
"During ordered Advanced Revival, an automated ally whose turn would benefit may wait for a human Fremen's grant. Fremen can release that ally to continue without waiting, before or during its turn, and may still grant later if possible. Each eligible ally has a separate release; the choice reveals no Tanks. This is a provisional online priority.",
|
||||
"In the Basic group preview, Fremen can provisionally grant each ally the rate separately; each named declaration has its own Karama response. Automated allies wait individually when a human Fremen's available grant would help, until it resolves or Fremen is ready. Group interactions remain incomplete."
|
||||
],
|
||||
"related": [
|
||||
@@ -6590,20 +6591,22 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
{
|
||||
"area": "Player controls",
|
||||
"status": "Partial",
|
||||
"detail": "Actual pair and staged group grant callbacks, native response and persistent status/policy guidance are integrated. Busy, interrupted, already granted and denied actions stay disabled; no private balance, Tanks or usage count is read.",
|
||||
"detail": "Actual pair and staged group grant callbacks, native response and persistent status/policy guidance are integrated. Ordered automated allies have a separate per-recipient release. Busy, interrupted, already granted and denied actions stay disabled; no private balance, Tanks or usage count is read.",
|
||||
"evidence": [
|
||||
"components/fremen-revival-grant.tsx",
|
||||
"components/game-table.tsx",
|
||||
"tests/fremen-revival-grant-controls.test.tsx"
|
||||
"tests/fremen-revival-grant-controls.test.tsx",
|
||||
"tests/fremen-ordered-revival-controls.test.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"area": "AI",
|
||||
"status": "Partial",
|
||||
"detail": "Existing-profile native/Recruits paths and group grants use the same projected legal permissions. In concurrent Basic Revival each eligible automated pair or group ally waits for a useful human Fremen grant until it is resolved or Fremen is Ready. Strategy and strength calibration remain deferred.",
|
||||
"detail": "Existing-profile native/Recruits paths and group grants use the same projected legal permissions. In concurrent Basic Revival each eligible automated pair or group ally waits for a useful human Fremen grant until it is resolved or Fremen is Ready. Ordered Advanced allies use a recipient-specific release. Strategy and strength calibration remain deferred.",
|
||||
"evidence": [
|
||||
"game/bots.ts",
|
||||
"tests/fremen-revival-grant-bot-priority.test.ts",
|
||||
"tests/fremen-ordered-revival-bot-priority.test.ts",
|
||||
"tests/coalition-fremen-bot-grant.test.ts",
|
||||
"tests/recruits-controls.test.tsx",
|
||||
"docs/FREMEN_REVIVAL_GRANT.md"
|
||||
|
||||
Reference in new issue
Block a user