Route classic Homeworld Harass returns through native custody
This commit is contained in:
1 parent
5509abf1e5
commit
643add3f85
18 files changed
+1174
-83
No files matched your search
@@ -423,8 +423,10 @@ function calculate(input: BattleResolutionInput): BattleResolutionQuote {
|
||||
if (harass) {
|
||||
requireQuote(validBattleSlotPair(slots[0], slots[1]),
|
||||
'The Harass & Withdraw plan has an invalid weapon and defense pair.');
|
||||
requireQuote(side.harassWithdraw && !input.homeworld,
|
||||
'Harass & Withdraw needs its supported physical context and cannot combine with Homeworld battles.');
|
||||
requireQuote(side.harassWithdraw,
|
||||
'Harass & Withdraw needs its supported physical context.');
|
||||
requireQuote(!homeworld || homeworld.native !== side.id,
|
||||
'Harass & Withdraw cannot be used on your own Homeworld.');
|
||||
const profile = input.ecazOccupy?.lead === side.id ? input.ecazOccupy : undefined;
|
||||
const normalization = side.harassWithdraw.occupy;
|
||||
if (profile) {
|
||||
|
||||
@@ -120,7 +120,7 @@ export function cardPresentation(card: VisibleCard): CardPresentation {
|
||||
availability: ecaz.card.effect === 'recruits'
|
||||
? 'Recruits has development controls during Revival. Earlier paid revivals, pending transactions, later free-rate changes and repeated use this turn remain guarded pending their rulings.'
|
||||
: ecaz.card.effect === 'harassWithdraw'
|
||||
? 'Harass & Withdraw has controls in either battle-card slot, with physical allocation after reveal. Under provisional materialization-first order, an own concealed No-Field reveals natively before actual undialed counters return; token history is not reset. Stone Burner provisionally compares forces remaining after successful withdrawal. Other profile and optional-module limits remain guarded.'
|
||||
? 'Harass & Withdraw has controls in either battle-card slot, with physical allocation after reveal. Classic Homeworlds returns actual typed counters into native reserve custody: Advanced Emperor normal to Kaitain/Sardaukar to Salusa; Basic preserves its original primary World. The card is forbidden on any own Homeworld. Original sealed commitment/support remains. An own concealed No-Field separately follows provisional materialization first without resetting history. Stone compares post-withdrawal counters provisionally. Other profile/module limits remain guarded.'
|
||||
: 'Reinforcements has controls in either battle-card slot, including the explicit Advanced Occupy card variant. It spends three own reserves and adds +2 normal score, not support or physical tokens. Own No-Field reveal must leave three original reserves before this cost; later Harass returns cannot fund it. Original/copy Stone ignores the score bonus but still charges reserves. All-outcome payment and native cost-before-return order are provisional. Other profile/module limits remain guarded.',
|
||||
topics: RULE_TOPICS.filter((topic) => topic.id === (ecaz.card.effect === 'harassWithdraw' ? 'card-harass-withdraw' : ecaz.card.effect === 'reinforcements' ? 'card-reinforcements' : 'card-recruits')),
|
||||
};
|
||||
|
||||
+48
-26
@@ -170,6 +170,7 @@ import {
|
||||
import {
|
||||
quoteNativeReserveWithdrawal,
|
||||
quoteNativeReserveDeposit,
|
||||
quoteHomeworldReserveReturn,
|
||||
NativeReserveError,
|
||||
type NativeReserveSelections,
|
||||
} from './homeworld-native-reserves';
|
||||
@@ -2199,7 +2200,7 @@ export type Game = {
|
||||
kind: 'winner' | 'explosion';
|
||||
pool: HomeworldForces;
|
||||
options: HomeworldForces[];
|
||||
commitment?: { forces: CombatForces; dial: number; support: number };
|
||||
commitment?: { forces: CombatForces; dial: number; support: number; fullyCommitted?: true };
|
||||
} | null;
|
||||
lastBattleContext?: {
|
||||
event: string;
|
||||
@@ -7984,23 +7985,23 @@ function withdrawNativeReserves(
|
||||
observeOccupation(g);
|
||||
return result.receipts;
|
||||
}
|
||||
/** Adds counters to a faction's reserves, depositing into the native Homeworld
|
||||
* custody when Homeworlds are enabled. Used by revival and by the authorized
|
||||
* Nexus Secret Ally reserve return (user ruling 7 October 2026). */
|
||||
function addNativeReserves(g: Game, p: Player, amount: number, elite: number) {
|
||||
/** Adds counters to native reserves. A Homeworld source returns its exact
|
||||
* visitor pool and deposits in the same quote; route permission is external. */
|
||||
function addNativeReserves(g: Game, p: Player, amount: number, elite: number, sourceHomeworld?: string) {
|
||||
requireRule(sourceHomeworld === undefined || g.homeworlds?.custody,
|
||||
'Returning Homeworld forces requires their original physical custody.');
|
||||
if (!g.homeworlds?.custody) {
|
||||
p.reserves += amount;
|
||||
if (p.elites) p.elites.reserves += elite;
|
||||
return;
|
||||
}
|
||||
const result = homeworldRule(() =>
|
||||
quoteNativeReserveDeposit(
|
||||
homeworldContext(g),
|
||||
g.homeworlds!.custody!,
|
||||
p.id,
|
||||
{ normal: amount - elite, elite },
|
||||
),
|
||||
);
|
||||
const result = homeworldRule(() => {
|
||||
const context = homeworldContext(g);
|
||||
const forces = { normal: amount - elite, elite };
|
||||
return sourceHomeworld === undefined
|
||||
? quoteNativeReserveDeposit(context, g.homeworlds!.custody!, p.id, forces)
|
||||
: quoteHomeworldReserveReturn(context, g.homeworlds!.custody!, p.id, sourceHomeworld, forces);
|
||||
});
|
||||
const changed = result.players.find((seat) => seat.id === p.id)!;
|
||||
p.reserves = changed.reserves;
|
||||
if (p.elites) p.elites.reserves = changed.eliteReserves;
|
||||
@@ -8191,9 +8192,13 @@ function homeworldBattleLossIntegrity(g: Game) {
|
||||
options = quoteHomeworldBattleRules(pending.territory, g.players, rules).explosion.options;
|
||||
} else {
|
||||
const commitment = pending.commitment;
|
||||
requireRule(commitment && commitment.forces.normal === pool.normal && commitment.forces.elite === pool.elite,
|
||||
requireRule(commitment && commitment.forces.normal === pool.normal && commitment.forces.elite === pool.elite &&
|
||||
(commitment.fullyCommitted === undefined || commitment.fullyCommitted === true),
|
||||
'The Homeworld winner needs its committed physical forces.');
|
||||
options = casualtyOptions(commitment.forces, commitment.dial, commitment.support).map(({normal, elite}) => ({normal, elite}));
|
||||
options = casualtyOptions(commitment.forces, commitment.dial, commitment.support)
|
||||
.filter(choice => !commitment.fullyCommitted ||
|
||||
choice.normal === commitment.forces.normal && choice.elite === commitment.forces.elite)
|
||||
.map(({normal, elite}) => ({normal, elite}));
|
||||
}
|
||||
requireRule(options.length > 0 && JSON.stringify(options) === JSON.stringify(pending.options),
|
||||
'The Homeworld casualty choices do not match the committed outcome.');
|
||||
@@ -10674,12 +10679,22 @@ export function initializeSpiceBankerIncomeGameForAudit(state: Game): Game {
|
||||
if (advancedMoritaniLeaderSkillsProfile(initialized)) initializeMoritaniAssassinateState(initialized);
|
||||
return initialized;
|
||||
}
|
||||
function classicEcazHomeworldProfile(g: Game): boolean {
|
||||
return !!g.homeworlds && g.ecazTreachery === true && g.expansions.length === 0 &&
|
||||
g.players.length >= 2 && g.players.length <= 6 &&
|
||||
g.players.every(p => CLASSIC_FACTIONS[p.faction]) &&
|
||||
!g.nexusCards && !g.leaderSkills && !g.techTokens && !g.strongholdCards &&
|
||||
!g.discoveryEnabled && !g.discoveries && !g.discoveryStash && !g.greatMaker && !g.sandtrout;
|
||||
}
|
||||
/** Gated development setup for the independent three-card Ecaz variant. */
|
||||
export function initializeEcazTreacheryGameForAudit(state: Game): Game {
|
||||
requireRule(!state.ecazTreachery, 'Ecaz Treachery Cards cannot redeal an existing inventory.');
|
||||
const g = structuredClone(state);
|
||||
g.ecazTreachery = true;
|
||||
return initializeSetupGameForAudit(g, false, false, false, false, false, false, true, true);
|
||||
const homeworlds = !!g.homeworlds;
|
||||
requireRule(!homeworlds || classicEcazHomeworldProfile(g),
|
||||
'Independent cards with Homeworlds require a fresh classic roster without other modules.');
|
||||
return initializeSetupGameForAudit(g, homeworlds, false, false, false, false, false, true, true);
|
||||
}
|
||||
function initializeSetupGameForAudit(state: Game, homeworlds: boolean, nexus = false, ix = false, discovery = false, leaderSkills = false, choam = false, factions = false, ecazTreachery = false, strongholdFactions = false): Game {
|
||||
nexusCardsIntegrity(state);
|
||||
@@ -20705,19 +20720,25 @@ function inspectedPlanMatches(p: Player, field: PlanField, fixed: unknown, candi
|
||||
function harassModuleBlock(g: Game): string | null {
|
||||
if (!g.ecazTreachery)
|
||||
return 'Enable the independent Ecaz Treachery Cards variant before using Harass & Withdraw.';
|
||||
if (g.homeworlds || g.nexusCards || g.leaderSkills || g.techTokens || g.strongholdCards || g.discoveryEnabled)
|
||||
return 'Harass & Withdraw with other optional modules, including Homeworlds, is still being implemented.';
|
||||
if ((g.homeworlds && !classicEcazHomeworldProfile(g)) ||
|
||||
g.nexusCards || g.leaderSkills || g.techTokens || g.strongholdCards || g.discoveryEnabled)
|
||||
return 'Harass & Withdraw with these other optional modules is still being implemented.';
|
||||
return combinedCardBattleBlock(g);
|
||||
}
|
||||
function harassWithdrawContext(g: Game, p: Player): HarassWithdrawContext {
|
||||
const b = g.battle!;
|
||||
const other = getPlayer(g, b.attacker === p.id ? b.defender : b.attacker);
|
||||
let blocked = harassModuleBlock(g);
|
||||
const home = homeworldBattleLocation(g, b.territory);
|
||||
if (!blocked && (g.status !== 'playing' || g.phase !== 6 || ![b.attacker, b.defender].includes(p.id)))
|
||||
blocked = 'Use Harass & Withdraw in your own battle.';
|
||||
const locations = Object.fromEntries(Object.entries(p.forces)
|
||||
.filter(([key, amount]) => amount > 0 && splitLocation(key).territory === b.territory)
|
||||
.map(([key, amount]) => [key, { normal: amount - (p.elites?.forces[key] ?? 0), elite: p.elites?.forces[key] ?? 0 }]));
|
||||
if (!blocked && home?.native === p.id)
|
||||
blocked = 'Harass & Withdraw cannot be used on your own Homeworld.';
|
||||
const locations = home
|
||||
? { [b.territory]: combatArmy(g, p.id, b.territory) }
|
||||
: Object.fromEntries(Object.entries(p.forces)
|
||||
.filter(([key, amount]) => amount > 0 && splitLocation(key).territory === b.territory)
|
||||
.map(([key, amount]) => [key, { normal: amount - (p.elites?.forces[key] ?? 0), elite: p.elites?.forces[key] ?? 0 }]));
|
||||
const materializes = prospectiveNoFieldForces(p, b.territory);
|
||||
if (materializes) {
|
||||
const marker = p.noField!.deployed!;
|
||||
@@ -23238,7 +23259,8 @@ function resolveBattle(g: Game, retreat?: DiplomatRetreatSelection) {
|
||||
log(g, `${player.name}'s Harass & Withdraw was canceled by an opposing traitor. No forces returned through this card.`);
|
||||
for (const withdrawal of quote.harassWithdraw ?? []) {
|
||||
const player = getPlayer(g, withdrawal.player);
|
||||
for (const [key, group] of Object.entries(withdrawal.locations)) {
|
||||
const sourceHomeworld = g.battle!.territory.startsWith('homeworld:') ? g.battle!.territory : undefined;
|
||||
if (!sourceHomeworld) for (const [key, group] of Object.entries(withdrawal.locations)) {
|
||||
player.forces[key] -= group.normal + group.elite;
|
||||
if (!player.forces[key]) delete player.forces[key];
|
||||
if (group.elite && player.elites) {
|
||||
@@ -23246,11 +23268,10 @@ function resolveBattle(g: Game, retreat?: DiplomatRetreatSelection) {
|
||||
if (!player.elites.forces[key]) delete player.elites.forces[key];
|
||||
}
|
||||
}
|
||||
player.reserves += withdrawal.returned.normal + withdrawal.returned.elite;
|
||||
if (player.elites) player.elites.reserves += withdrawal.returned.elite;
|
||||
addNativeReserves(g, player, withdrawal.returned.normal + withdrawal.returned.elite,
|
||||
withdrawal.returned.elite, sourceHomeworld);
|
||||
log(g, `${player.name} used Harass & Withdraw to return ${withdrawal.returned.normal + withdrawal.returned.elite} undialed physical forces (${withdrawal.returned.elite} elite) to reserves before battle losses. Their leader still resolves normally.`);
|
||||
}
|
||||
if (quote.harassWithdraw?.length) observeOccupation(g);
|
||||
const explosion = quote.explosion;
|
||||
if (ac && dc) {
|
||||
dead(al);
|
||||
@@ -23579,7 +23600,8 @@ function resolveBattle(g: Game, retreat?: DiplomatRetreatSelection) {
|
||||
kind: quote.homeworldExplosion ? 'explosion' : 'winner',
|
||||
pool: combatArmy(g, owner, b.territory),
|
||||
options: (quote.homeworldExplosion?.options ?? casualtyCommitment!.options).map(({normal, elite}) => ({normal, elite})),
|
||||
...(casualtyCommitment ? { commitment: { forces: {...casualtyCommitment.forces}, dial: casualtyCommitment.dial, support: casualtyCommitment.support } } : {}),
|
||||
...(casualtyCommitment ? { commitment: { forces: {...casualtyCommitment.forces}, dial: casualtyCommitment.dial, support: casualtyCommitment.support,
|
||||
...(casualtyCommitment.fullyCommitted ? { fullyCommitted: true as const } : {}) } } : {}),
|
||||
};
|
||||
}
|
||||
if (discarded.length) {
|
||||
|
||||
@@ -139,6 +139,29 @@ export function quoteNativeReserveWithdrawal(
|
||||
});
|
||||
}
|
||||
|
||||
function nativeReserveDeposits(
|
||||
context: HomeworldCustodyContext,
|
||||
custody: HomeworldCustody,
|
||||
actor: string,
|
||||
forces: HomeworldForces,
|
||||
): HomeworldCustodyChange[] {
|
||||
const homes = nativeHomes(context, custody, actor);
|
||||
typed(forces);
|
||||
const primary = homes.find((home) => !home.secondary)!;
|
||||
const secondary = homes.find((home) => home.secondary);
|
||||
const placements: [string, HomeworldForces][] = secondary
|
||||
? [
|
||||
[primary.id, { normal: forces.normal, elite: 0 }],
|
||||
[secondary.id, { normal: 0, elite: forces.elite }],
|
||||
]
|
||||
: [[primary.id, { normal: forces.normal, elite: forces.elite }]];
|
||||
return placements.flatMap(([homeworld, deposit]) =>
|
||||
deposit.normal + deposit.elite > 0
|
||||
? [{ homeworld, player: actor, withdraw: empty(), deposit }]
|
||||
: [],
|
||||
);
|
||||
}
|
||||
|
||||
/** Authorized native-reserve deposits: E3 p.10 restores Advanced Emperor's
|
||||
* normal forces to Kaitain and Sardaukar to Salusa. Used by revival and by the
|
||||
* Nexus Secret Ally reserve return (user ruling 7 October 2026); it does not
|
||||
@@ -149,25 +172,22 @@ export function quoteNativeReserveDeposit(
|
||||
actor: string,
|
||||
forces: HomeworldForces,
|
||||
) {
|
||||
return checked(() => {
|
||||
const homes = nativeHomes(context, custody, actor);
|
||||
typed(forces);
|
||||
const primary = homes.find((home) => !home.secondary)!;
|
||||
const secondary = homes.find((home) => home.secondary);
|
||||
const placements: [string, HomeworldForces][] = secondary
|
||||
? [
|
||||
[primary.id, { normal: forces.normal, elite: 0 }],
|
||||
[secondary.id, { normal: 0, elite: forces.elite }],
|
||||
]
|
||||
: [[primary.id, { normal: forces.normal, elite: forces.elite }]];
|
||||
return quoteHomeworldCustody(
|
||||
context,
|
||||
custody,
|
||||
placements.flatMap(([homeworld, deposit]) =>
|
||||
deposit.normal + deposit.elite > 0
|
||||
? [{ homeworld, player: actor, withdraw: empty(), deposit }]
|
||||
: [],
|
||||
),
|
||||
);
|
||||
});
|
||||
return checked(() => quoteHomeworldCustody(
|
||||
context, custody, nativeReserveDeposits(context, custody, actor, forces),
|
||||
));
|
||||
}
|
||||
|
||||
/** A separately authorized force return: remove the exact source pool and
|
||||
* deposit into native reserves in one custody quote, never as deaths. */
|
||||
export function quoteHomeworldReserveReturn(
|
||||
context: HomeworldCustodyContext,
|
||||
custody: HomeworldCustody,
|
||||
actor: string,
|
||||
source: string,
|
||||
forces: HomeworldForces,
|
||||
) {
|
||||
return checked(() => quoteHomeworldCustody(context, custody, [
|
||||
{ homeworld: source, player: actor, withdraw: forces, deposit: empty() },
|
||||
...nativeReserveDeposits(context, custody, actor, forces),
|
||||
]));
|
||||
}
|
||||
+8
-6
@@ -4058,6 +4058,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'Homeworlds are an optional module from Ecaz & Moritani, available under the official rules with Basic or Advanced play. They are separate locations outside Arrakis; they do not receive ordinary storm movement or count as ordinary strongholds.',
|
||||
'Fresh private Homeworld prototypes retain all fourteen Skills, Discovery7/8 or both in classic or supported E1/E2 and standalone Ecaz OR Moritani tables. Original Nexus entries additionally support single or paired one-family E1/E2, natives from BOTH selected Ix+CHOAM families with union47, and standalone OR paired Ecaz/Moritani/ecaz33 with classics, with or without Skills. Basic/Advanced2–6, Tech3+ and Advanced Strongholds2+ retain original setup, native placement, phase-end industry and source-local held cards. Advanced Moritani/paired E3 excludes Harkonnen; otherwise legal standalone no-Skills Ecaz/Harkonnen does not lift its separate Duke Cunning exclusion. Winning typed native Suk saves stay at that home; external Emperor returns allocate physical normal/elite counters to original homes as the existing interpretation. Normal revival separately deposits Sardaukar at Salusa. Nested battles receive no Homeworld bonus. Mixed E3/three-family Nexus, pending effects, public starts and played-save conversion remain separate.',
|
||||
'Each faction uses its own card and matching world token. In Basic play the Emperor uses Kaitain alone. Advanced play adds Salusa Secundus for its Sardaukar; movement can transfer both force types between the two Emperor worlds.',
|
||||
'A fresh classic Basic/Advanced independent36-card/Homeworld prototype additionally connects Harass on Arrakis or a foreign World, never an own World. Exact typed counters return through native reserves: Basic primary; Advanced Emperor normal to Kaitain/Sardaukar to Salusa. Original sealed commitment/support and fully-committed casualty complement stay fixed across population changes. Other variant cards, expansion rosters and overlays keep their separate guards.',
|
||||
'Native reserves determine high or low population. Salusa counts Sardaukar, while the other cards count the faction’s reserves there. Reaching the high face’s minimum restores high population. Occupation also applies the low penalty.',
|
||||
'Low population normally grants one additional free force revival and one additional bank spice when collecting CHOAM Charity. Salusa grants neither bonus; CHOAM’s Tupile reverses the usual pattern by granting an advantage while low and a penalty while high.',
|
||||
'A qualifying charity claim separates its ordinary payer from the extra bank spice. Inflation doubles both portions or prevents the whole collection. Canceling Bene Gesserit charity prevents both portions of that claim.',
|
||||
@@ -5022,14 +5023,15 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'In explicitly selected Advanced Occupy, only the actual card user’s undialed forces return. An Ecaz user preserves its original mandatory fixed count at free full strength; an allied user uses its native variable commitment, while canceled Occupy uses its selected lead’s ordinary own pool. Returned reserves remain outside losses and Face Dance. Basic/unprofiled co-side, public starts and full acceptance remain gated.',
|
||||
'In supported same-plan Reinforcements, the provisional order pays three original own reserves before Harass returns undialed counters. Future returns cannot fund or change that reserve quote; neither the +2 score bonus nor that cost adds to the physical commitment. Both cards discard after use. An opposing successful Traitor cancels Harass but not the provisional Reinforcements cost.',
|
||||
'With an own concealed No-Field, this prototype provisionally uses the original materialization-first order. Before sealing, privately forecast its actual ordinary counters at the original marker sector; do not move them early. Both plans seal before native revelation, then any return allocation uses actual counters. Marker0 remains legal with a leader/cards, reserve shortage caps materialization, and withdrawal does not return/reset the token. Opposing successful Traitor cancels Harass, not that native reveal.',
|
||||
'Clean classic2–6 Basic/Advanced Homeworlds may be selected before the independent36-card setup. Harass works on Arrakis or a foreign Homeworld, never any own World. Return only actual undialed board/visitor counters into native reserve custody, not Tanks: Basic primary; Advanced Emperor normal to Kaitain and Sardaukar to Salusa. Original dial/support and all remaining committed counters stay fixed even if returned population crosses a threshold. Native-only Traitors, late defense/explosion and mandatory disposal remain; other cards and overlays retain their guards.',
|
||||
],
|
||||
related: ['battle-cards', 'battle', 'card-recruits', 'ecaz-modules', 'implementation-checklist'],
|
||||
related: ['battle-cards', 'battle', 'card-recruits', 'homeworlds', 'ecaz-modules', 'implementation-checklist'],
|
||||
checklist: [
|
||||
{ area: 'Implementation', status: 'Partial', detail: 'Physical slots, private category answers, typed/sector return, committed losses, Traitor cancellation and mandatory disposal remain. Own No-Field now projects reserve-limited sector counters before commitment and uses actual native revelation/history. Same-plan cost cannot be funded by future returns. Other module/profile boundaries remain.', evidence: ['game/harass-withdraw.ts', 'game/stone-burner.ts', 'game/battle-card-slots.ts', 'game/battle-resolution-quote.ts', 'game/engine.ts'] },
|
||||
{ area: 'Player controls', status: 'Partial', detail: 'Either slot and after-reveal typed/sector choices remain. Owner-only No-Field forecasts and post-reveal reserve notices distinguish virtual marker from actual counters; zero marker, mixed-sector full return, blocked-cost fallback and funded card controls are usable.', evidence: ['components/battle-preparation.tsx', 'components/harass-withdraw.tsx', 'components/revealed-battle.tsx', 'components/game-table.tsx'] },
|
||||
{ area: 'AI', status: 'Partial', detail: 'All four profiles have a legal shared-quote path and preserve inspection secrecy. Retreat strategy and combined-game strength remain uncalibrated.', evidence: ['game/bots.ts', 'tests/ecaz-battle-bots.test.ts'] },
|
||||
{ area: 'Documentation', status: 'Partial', detail: 'Printed effects, provisional materialization-first and Stone/withdrawal timing are distinguished from remaining profile/module limits and final assurance.', evidence: ['docs/HARASS_WITHDRAW_RUNTIME.md', 'docs/ECAZ_TREACHERY_RULES.md', 'docs/STONE_BURNER_ENGINE_AUDIT.md', 'docs/RICHESE_NO_FIELD_RULES.md'] },
|
||||
{ area: 'Verification', status: 'Partial', detail: 'Original Stone/Harass/Occupy proof plus actual No-Field shipment/reveal cases cover zero, caps, mixed sectors, original costs, history and Traitor cancellation. Current mobile/legal-policy proof remains bounded, not full combination/deployed or new recovery certification.', evidence: ['tests/harass-no-field.test.ts', 'tests/fixture-harass-no-field.ts', 'tests/no-field-reinforcements.test.ts', 'tests/stone-harass-quote.test.ts', 'tests/stone-harass-runtime.test.ts', 'tests/fixture-stone-harass.ts', 'tests/ecaz-occupy-harass-quote.test.ts', 'tests/harass-allocation-engine.test.ts', 'tests/harass-allocation-recovery.test.ts', 'tests/harass-withdraw-choice-controls.test.tsx', 'tests/harass-withdraw.test.ts', 'tests/ecaz-battle-controls.test.tsx', 'tests/ecaz-battle-bots.test.ts', 'tests/ecaz-battle-recovery.test.ts'] },
|
||||
{ area: 'Implementation', status: 'Partial', detail: 'Original slots, secrecy, physical returns, costs and No-Field/Stone composition remain. Clean classic Homeworlds now withdraw exact visitor/board counters and deposit through native typed reserves. Own-World use is forbidden; committed Homeworld losses retain the full Harass complement across later population changes.', evidence: ['game/harass-withdraw.ts', 'game/stone-burner.ts', 'game/battle-card-slots.ts', 'game/battle-resolution-quote.ts', 'game/engine.ts', 'game/homeworld-native-reserves.ts', 'tools/prototype-room.ts', 'tools/start-prototype.ts'] },
|
||||
{ area: 'Player controls', status: 'Partial', detail: 'Either slot and native typed allocation remain. World names, Basic versus Advanced reserve destinations and fixed-commitment timing are explicit. Invaders no longer receive forbidden Traitor controls. Four actual390px journeys complete Basic foreign loss, Advanced foreign victory, Salusa threshold return and own-Salusa ban/plain alternative into Collection.', evidence: ['components/battle-preparation.tsx', 'components/harass-withdraw.tsx', 'components/revealed-battle.tsx', 'components/game-table.tsx', 'components/harass-withdraw-choice.tsx'] },
|
||||
{ area: 'AI', status: 'Partial', detail: 'All four profiles use legal shared precommit quotes and exact after-reveal allocations without an invader vote. Sixteen native World programs accept36 continuation actions and validate original precommit proposals through normal/explosion/own-World alternatives. Retreat strategy and combined strength remain deferred.', evidence: ['game/bots.ts', 'tests/ecaz-battle-bots.test.ts'] },
|
||||
{ area: 'Documentation', status: 'Partial', detail: 'Printed own-World restriction and explicit native destination/fixed-commitment composition are separated from original materialization/Stone policies and remaining faction/module/final gates.', evidence: ['docs/HARASS_WITHDRAW_RUNTIME.md', 'docs/ECAZ_TREACHERY_RULES.md', 'docs/STONE_BURNER_ENGINE_AUDIT.md', 'docs/RICHESE_NO_FIELD_RULES.md'] },
|
||||
{ area: 'Verification', status: 'Partial', detail: 'Native Homeworld regressions cover real typed invasion/Arrakis shipments, both slots/modes, selected full commitment, Basic primary/Advanced split reserves, native-only Traitors, winner disposal, explosion exception and Salusa threshold follow-on. Four mobile and16 policy programs reach original Collection. Earlier No-Field/Stone/recovery evidence remains historical, not complete-mode/new assurance or deployed acceptance.', evidence: ['tests/harass-homeworld.test.ts', 'tests/fixture-harass-homeworld.ts', 'tests/harass-homeworld-arrakis.test.ts', 'tests/fixture-harass-homeworld-arrakis.ts', 'tests/harass-no-field.test.ts', 'tests/fixture-harass-no-field.ts', 'tests/no-field-reinforcements.test.ts', 'tests/stone-harass-quote.test.ts', 'tests/stone-harass-runtime.test.ts', 'tests/fixture-stone-harass.ts', 'tests/ecaz-occupy-harass-quote.test.ts', 'tests/harass-allocation-engine.test.ts', 'tests/harass-allocation-recovery.test.ts', 'tests/harass-withdraw-choice-controls.test.tsx', 'tests/harass-withdraw.test.ts', 'tests/ecaz-battle-controls.test.tsx', 'tests/ecaz-battle-bots.test.ts', 'tests/ecaz-battle-recovery.test.ts'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user