Retain independent36, original Homeworlds and Nexus12 through classic fresh setup with optional Skills and phase modules. Resolve own-World Reinforcements fees before computing temporary Sardaukar roles, preserving original Cunning declarations, printed traits and physical counters. Record the provisional ordinary-nonrole-first policy and align human/AI quotes and captions. Validation: types/lint/build, 128 affected tests across 15 files, bounded rule-source review and finite original-action smoke for Recruits, Harass, local-fee Cunning, all four legal policies and rendered source controls. Verified runtime c219ec587a54b44079539168d93835a5af904c8afb4de58bcec0a3d60c809f0c; documentation-only proof update 8ee21598e7be0c89a08fbad50786c346ebc8ae705492d8b31ea00fae0ffefa3c. Full modes, combinations, assurance and deployed acceptance remain open. No saved-room/provider changes.
37 lines
2.0 KiB
TypeScript
37 lines
2.0 KiB
TypeScript
'use client';
|
|
|
|
import type { GameView } from '@/game/engine';
|
|
import type { NativeReserveSelections } from '@/game/homeworld-native-reserves';
|
|
import { homeworldName } from '@/game/homeworld-shipment-options';
|
|
|
|
export function reinforcementsSourceLabel(game: GameView, sources: NativeReserveSelections): string {
|
|
return Object.entries(sources).map(([world, forces]) =>
|
|
`${homeworldName(game, world)}: ${forces.normal} normal + ${forces.elite} special`).join('; ');
|
|
}
|
|
|
|
export function ReinforcementsSources({ game, choices, selected, onChange, busy }: {
|
|
game: GameView;
|
|
choices: readonly NativeReserveSelections[];
|
|
selected: number;
|
|
onChange: (index: number) => void;
|
|
busy: boolean;
|
|
}) {
|
|
if (!choices.length) return null;
|
|
return <fieldset disabled={busy} className="space-y-3 rounded-lg border border-[#a88b60]/50 p-3">
|
|
<legend>Reinforcements reserve sources</legend>
|
|
<p className="fine">Three original native reserves go to the Tanks, normal first. These are not the invading army or future Harass returns.</p>
|
|
{choices.length > 1 ? <label className="block space-y-1">
|
|
<span>Native reserve fee allocation</span>
|
|
<select value={selected} onChange={event => onChange(Number(event.target.value))}>
|
|
{choices.map((sources, index) => <option key={index} value={index}>
|
|
{reinforcementsSourceLabel(game, sources)}
|
|
</option>)}
|
|
</select>
|
|
</label> : <p className="fine">{reinforcementsSourceLabel(game, choices[0])}</p>}
|
|
{choices.length > 1 && <p className="fine" aria-live="polite">
|
|
Selected fee: {reinforcementsSourceLabel(game, choices[selected])}.
|
|
</p>}
|
|
<p className="notice">Provisional own-World composition: counters paying the fee cannot also fund your physical dial. Ordinary counters without a temporary Sardaukar role pay first; if needed, paying converted counters reduces that role. The original printed face and support traits stay fixed; the bonus adds no physical counters.</p>
|
|
</fieldset>;
|
|
}
|