Make Fremen allied revival grants cancelable

Bind the original Fremen/ally/turn grant before Karama allowance or prevention.
A prevented grant leaves intrinsic free rates and prior payments intact. Permit
late grants under the explicit provisional Recruits current-rate policy, with
shared controls, minimal bot support and native public chronology evidence.
Complete rules and browser/deployed acceptance remain open.
This commit is contained in:
Quintine committed 2026-10-24 18:16:03 +11:00
1 parent d63efccb55
commit 0e3d6a250e
23 files changed
+1183 -44

No files matched your search

+66
View File
@@ -0,0 +1,66 @@
'use client';
import { ArrowRight } from 'lucide-react';
import { Button } from '@/components/ui/button';
import type { Action, GameView } from '@/game/engine';
export function FremenRevivalGrant({
game,
act,
busy,
}: {
game: GameView;
act: (action: Action) => void;
busy: boolean;
}) {
const grant = game.fremenRevivalGrant;
const me = game.players.find((player) => player.id === game.me);
if (!grant || game.status !== 'playing' || game.phase !== 4 ||
me?.faction !== 'fremen' || me.ally !== grant.recipient) return null;
const recipient = game.players.find((player) => player.id === grant.recipient)?.name ?? 'your ally';
const disabled = busy || !!game.truthtrance || !!game.nexusTraitors?.pending ||
!!grant.blocked || grant.granted;
return (
<section className="notice space-y-3" aria-label="Fremen ally free revival">
<h3 className="font-serif text-xl">Fremen ally revival</h3>
<p className="muted">
Grant {recipient} an ordinary free force revival rate of three this phase. This
replaces their native ordinary rate rather than adding three. Ordinary total
revival and special-force limits still apply.
</p>
<p className="fine">
Provisional current-rate policy: Recruits doubles the current granted rate, including applicable bonuses,
before caps. Without other bonuses, three becomes six.
Later grants use the same current-rate calculation;
your ally’s revival controls show the actual remaining allowance.
Previously recorded free returns use the allowance. Earlier paid returns keep their payments and count
toward the total revival limit; there is no refund or retroactive
reclassification.
</p>
<p className="fine">
Another player may use Karama before the original alliance grant takes effect.
Canceling blocks this Fremen grant to the current ally for this Revival;
the ally retains its intrinsic rate and Recruits. Completed revivals and
payments stay unchanged. This response timing and prevention scope are provisional.
</p>
{grant.granted && (
<p className="muted">Free revival has been granted to {recipient} for this phase.</p>
)}
{grant.blocked && (
<p className="notice" id="fremen-revival-grant-unavailable">{grant.blocked}</p>
)}
<Button
data-testid="fremen-revival-grant-submit"
className="game-action"
disabled={disabled}
aria-describedby={grant.blocked ? 'fremen-revival-grant-unavailable' : undefined}
onClick={() => {
if (!disabled) act({ type: 'grantRevival' });
}}
>
Grant ally free revival
<ArrowRight size={15} />
</Button>
</section>
);
}
+12 -11
View File
@@ -52,6 +52,7 @@ import { NexusSardaukar } from './nexus-sardaukar';
import { TerrorBoardMarkers } from './terror-board-markers';
import { AmbassadorBoardMarkers } from './ambassador-board-markers';
import { CharityClaim } from './charity-claim';
import { FremenRevivalGrant } from './fremen-revival-grant';
import { homeworldRevivalActionBlock } from '@/game/homeworld-revival-deployment-options';
import { GuildHomeworldShipment } from './guild-homeworld-shipment';
import { JunctionTransport } from './junction-transport';
@@ -2423,6 +2424,7 @@ export function GameTable({
advisor: 'Spiritual advisor',
emperorGift: 'Emperor spice gift',
emperorRevival: 'Emperor extra revival',
fremenRevivalGrant: 'Fremen allied free revival',
bgCharity: 'Bene Gesserit charity',
choamCharity: 'CHOAM charity income',
choamInflation: 'CHOAM Inflation placement',
@@ -2546,6 +2548,14 @@ export function GameTable({
allow five forces.
</p>
)}
{g.response.kind === 'fremenRevivalGrant' && (
<p className="notice">
Fremen offered their current ally the ordinary free-revival rate of three.
Karama can prevent this grant before it takes effect. Recruits uses the
current valid rate after settlement, with prior returns and payments retained.
Response timing and prevention of this grant for the current Revival are provisional.
</p>
)}
{g.response.kind === 'choamCharity' && (
<p className="muted">
CHOAM receives{' '}
@@ -4367,19 +4377,10 @@ export function GameTable({
? ' · Tleilaxu half-price discount'
: ''}
</p>
{me.faction === 'fremen' &&
me.ally &&
!g.freeRevival.includes(me.ally) &&
actionButton('Grant ally three free revivals', {
type: 'grantRevival',
}, !!g.recruitsPreview?.grantRevivalBlocked,
g.recruitsPreview?.grantRevivalBlocked ? 'recruits-grant-unavailable' : undefined)}
{me.faction === 'fremen' && me.ally && !g.freeRevival.includes(me.ally) && g.recruitsPreview?.grantRevivalBlocked && (
<p className="notice" id="recruits-grant-unavailable">{g.recruitsPreview.grantRevivalBlocked}</p>
)}
<FremenRevivalGrant game={g} act={act} busy={busy} />
{g.freeRevival.includes(me.id) && !g.revival.freeBlocked && (
<p className="muted">
Fremen allow you up to {g.recruitsPreview?.active ? 'six' : 'three'} free force revivals this phase.
Fremen’s grant is active. Your current free allowance above includes Recruits and any applicable bonuses.
</p>
)}
{amountInput}