Renew impossible Salusa Ghola inspection answers

This commit is contained in:
Quintine committed 2026-10-27 16:01:22 +11:00
1 parent 1bb7a99836
commit 53b37adf9f
15 files changed
+307 -34

No files matched your search

+22
View File
@@ -20,6 +20,28 @@ open mode gates.
Keep identified Basic-function gaps ahead of Advanced and wider interactions.
Prototype labels and final acceptance gates remain.
**Salusa Ghola inspection renewal — 27 October. Prototyped / Partial.**
Before either full plan seals, an actual Salusa-crossing Ghola return now keeps
a feasible inspection answer or reopens an impossible original question. The
owner must answer before plans continue. The existing native-first Cunning
renewal protocol is reused without a new field, cost or cancellation. Full
seals and active Truthtrance battle promises remain guarded. The single-answer
update follows the authorized PDF's identified unofficial ruling; the paired
priority remains provisional. [Contract](HOMEWORLD_BENEFITS_RULES.md#salusa-ghola-and-renewed-inspection-answers--27-october-2026).
**Fourteen files115/115**, types/lint and two native runtime programmes pass.
Original public Advanced E/A/G base33/Homeworlds select an actually offered
Basin first battle, earn its paid star casualty, then ask and answer actual
Atreides Prescience in Arrakeen. Two prefixes total104 actions;11 further
actions distinguish impossible odd1 renewal from feasible2 retention and
finish both native battles. Nine GameTable/two BattlePreparation SSR renders
and one shared answer-adapter packet connect guidance and the legal answer.
All four minimal profiles answer legally. Amount/elite/answer/plan inputs are
not operated in a browser. An older explicitly staged Residual/Cunning fixture
now removes its obsolete Charity frame at its controlled Battle boundary.
No new played Cunning/Ghola pair, full modes, mobile, deployed or final
assurance is certified. No build was repeated for this focused rule change.
**Salusa Ghola before battle commitments — 27 October. Prototyped / Partial.**
An otherwise legal Sardaukar Ghola return can raise Salusa from one to two
during the Emperor's uncommitted battle. That same battle uses current High
+1 -1
View File
@@ -16,7 +16,7 @@ Ixian winners may exchange surviving Suboids for Cyborgs lost at that same Homew
## Explicit unfinished interactions
The [replacement source audit](HOMEWORLD_REPLACEMENT_RULES.md) does not resolve the Advanced Emperor’s returned-army allocation between Kaitain and Salusa. That Face Dance return is gated with the same server, AI and interface reason; declining remains available. The Salusa Ghola crossing remains gated after battle commitments; the [commitment-free window](HOMEWORLD_BENEFITS_RULES.md#salusa-ghola-before-battle-commitments--27-october-2026) now uses actual current population. These restrictions are development boundaries, not official prohibitions. Other Homeworld face effects, occupation rights and full interaction coverage are still required.
The [replacement source audit](HOMEWORLD_REPLACEMENT_RULES.md) does not resolve the Advanced Emperor’s returned-army allocation between Kaitain and Salusa. That Face Dance return is gated with the same server, AI and interface reason; declining remains available. The Salusa Ghola crossing remains gated after full seals or during active Truthtrance battle promises; [inspection renewal](HOMEWORLD_BENEFITS_RULES.md#salusa-ghola-and-renewed-inspection-answers--27-october-2026) before full seals uses actual current population and the original question. These restrictions are development boundaries, not official prohibitions. Other Homeworld face effects, occupation rights and full interaction coverage are still required.
## Evidence
+56
View File
@@ -2,8 +2,64 @@
Source audit, 9 September 2026. This separates publisher wording, rule composition, and remaining decisions. It does not enable Homeworld games or replace the outstanding Ambassador/Duke revival rulings.
## Salusa Ghola and renewed inspection answers — 27 October 2026
The earlier commitment-free window below now includes completed inspection
answers before either full plan is sealed. Commit the actual Ghola return and
current Salusa High benefit first. Preserve a still-feasible native answer;
if it becomes impossible, its owner must answer the same field again before
plans can be submitted. An odd dial using only now-free double-strength stars
is such a case. The old value is not made into a sealed plan, refunded or used
as permission to weaken a star. Normal modal interactions still finish first.
Authority for the update is the supplied PDF physical41 / printed39: its
outlined impossible-Prescience Q&A requires an updated valid answer before
reveal. Physical3 / printed1 identifies outlined rulings as unofficial; their
authority here is the user's adoption of this PDF, not a newly found GF9 FAQ.
Immediate Ghola remains physical39 / printed37, with no separate exception.
Reuse the existing [native-first inspection renewal](NEXUS_ATREIDES_RULES.md#residual-poison-and-two-inspected-commitments).
For Cunning, keep a feasible native answer first, then test the extra answer
jointly. If the native answer is impossible, renew its original field before
the extra original field. Retain observation history and the spent Nexus
record; do not charge again, offer another cancellation or choose new fields.
This multi-answer priority remains a provisional online composition. Full
sealed plans and active Truthtrance battle promises retain their explicit
preview guard; this slice does not define their repricing or release.
### Inspection-renewal evidence
Fourteen selected files pass115/115 with types/lint. The actual original public
Advanced E/A/G base33/Homeworld source can select Basin from the real chooser's
first-battle alternatives, earn a paid Sardaukar casualty and reach the later
Arrakeen battle. Actual Atreides declarations, counter-response passes and
Emperor answers create the initial dial1/2 commitments. No played hand, force,
purse, clock, inspection answer or battle receipt is installed. Only unopened
initial card/Spice positions and native scalar lotteries are selected.
The separate runtime uses two prefixes totaling104 actions and11 continuation
actions. Actual Ghola returns once in each: odd1 requires the owned same-field
answer2 before actual plans/calls; feasible2 stays fixed without another
question. Nine GameTable and two BattlePreparation SSR renders plus one actual
shared UI answer-adapter packet connect the current controls. Stateful input
operation/browser hydration is not claimed. All four minimal profiles provide
legal pending answers. Existing Cunning/Residual tests cover the inherited
protocol; no new played Cunning/Ghola pair is certified. The older explicitly
controlled resource fixture clears its stale Charity frame when staging Battle.
Private proof: `/home/quintine/.local/share/dune/checkpoints/20261027-salusa-ghola-inspection-proof-v2/`;
runtime: `20261027-salusa-ghola-inspection-runtime/run-v1/` beside it. Frozen
tree `1321c432fa0a204e5e8db3406be76b4608dac129409c9395fa77eac2bde1290a`
contains3351 files. Final evidence overlays are limited to this document, the
dashboard and implementation log. No build was repeated for this focused rule
change; complete modes, native-Salusa battle, browser/mobile, deployed and final
recovery/privacy/custody acceptance remain separate.
## Salusa Ghola before battle commitments — 27 October 2026
Historical first window: inspection restrictions described in this subsection
are superseded by the renewed-answer follow-up above. Its proof remains dated.
An otherwise legal Ghola may return a dead Sardaukar to Salusa during the
Emperor's battle while no plan is sealed and no inspection answer or active
Truthtrance battle promise is committed. Returning the actual counter from
+3 -3
View File
@@ -1,8 +1,8 @@
# Homeworld combat foundation and Salusa support
Current follow-up: [commitment-free Ghola](HOMEWORLD_BENEFITS_RULES.md#salusa-ghola-before-battle-commitments--27-october-2026)
can raise Salusa during an ongoing battle. The older whole-battle restriction
below is superseded only before commitments; later repricing remains guarded.
Current follow-up: [Ghola inspection renewal](HOMEWORLD_BENEFITS_RULES.md#salusa-ghola-and-renewed-inspection-answers--27-october-2026)
can raise Salusa before either full plan seals, retaining feasible answers or
renewing impossible original fields. Full-seal/promise repricing remains guarded.
9 September 2026. This extends the native-reserve runtime. Homeworld invasion and live off-planet battle actions remain unavailable; no start gate is opened.
+1 -1
View File
@@ -36,7 +36,7 @@ disclosure ability is explicitly lost under occupation.
| `charity.ts::charityQuote`, `revival.ts::freeRevivalRate` | Shared low bonus | Preserve ordinary eligibility, bank funding, inflation, Recruits and resource caps. Changing the shared helper reaches engine/view quotes. |
| `homeworld-benefits.ts::snapshotHomeworldRevival` | Tleilax physically low at Revival opening | Snapshot the verified low-penalty condition at the original phase-opening event. `tleilaxuHomeworldFreeIncomeBlocked` must continue reading that saved receipt, not current population or occupation. |
| `homeworld-benefits.ts::homeworldSardaukarFreeSupport` | Advanced Salusa high | A native high capability. Occupied Salusa's loss of Sardaukar advantage needs its own rule composition, not a physical force conversion. |
| `homeworld-benefits.ts::homeworldSardaukarGholaBlock` | Salusa population exactly one during a committed Emperor battle | Remaining support-transition preview guard; commitment-free Ghola now uses current population. If occupation changes effective support, compare the relevant before/after capability; do not remove the guard solely because occupation exists. |
| `homeworld-benefits.ts::homeworldSardaukarGholaBlock` | Salusa population exactly one after a full seal or during an active battle promise | Remaining support-transition preview guard; pre-seal Ghola uses current population and renews impossible original inspections. If occupation changes effective support, compare the relevant before/after capability; do not remove the guard solely because occupation exists. |
| `homeworld-mobility.ts::homeworldMovementForesightBlock` | Caladan low | Retained low penalty blocks a new Movement peek; it does not erase already acquired knowledge. |
| `homeworld-mobility.ts::homeworldMobileStrongholdMovementBlock` | Ix low | Retained low penalty blocks stronghold relocation, including its special Karama route; ordinary troop movement remains separate. |
| `homeworld-mobility.ts::homeworldNoFieldMovementBlock` | Richese low | Retained low penalty restricts existing marker movement. Shipment and reveal rules remain separate. |
+30
View File
@@ -1,5 +1,35 @@
# Dune implementation status
## 27 October 2026 — Salusa Ghola inspection renewal
An actual Salusa threshold return before either full plan seals now reuses the
existing inspection reconciliation: feasible answers stay fixed, impossible
original fields require an owned new answer, and Cunning retains its provisional
native-first order and spent record. No repeated charge/cancellation or new
field selection occurs. The supplied PDF's outlined impossible-answer ruling
has user-authorized unofficial provenance. Full seals and active Truthtrance
battle promises remain explicit preview boundaries.
[Contract](HOMEWORLD_BENEFITS_RULES.md#salusa-ghola-and-renewed-inspection-answers--27-october-2026).
Fourteen-file115/115 and types/lint pass. Two original public Advanced E/A/G
base33/Homeworld runtime programmes earn the actual first-battle star loss,
then actually declare and answer Atreides dial Prescience in Arrakeen. Prefixes
total104 actions;11 continuations compare odd1 renewal/answer2 with feasible2
retention and finish actual plans/calls. Nine GameTable/two BattlePreparation
SSR renders and one shared answer-adapter packet check the current control
path. Four minimal AI profiles answer legally. Stateful inputs are not operated
in a browser. An older explicitly controlled Residual/Cunning fixture removes
its stale Charity frame at its staged Battle boundary. No new played
Cunning/Ghola pair, complete modes, browser/mobile, deployed or final assurance
is certified; no build was repeated for this focused rule change.
Private proof `20261027-salusa-ghola-inspection-proof-v2/` and runtime
`20261027-salusa-ghola-inspection-runtime/run-v1/` use the existing private
checkpoint directory. Frozen tree
`1321c432fa0a204e5e8db3406be76b4608dac129409c9395fa77eac2bde1290a`
contains3351 files; final prose overlays are limited to this log,
`CURRENT_STATUS.md` and `HOMEWORLD_BENEFITS_RULES.md`.
## 27 October 2026 — Commitment-free Salusa Ghola
Remove the whole-battle threshold guard only before either sealed plan,
+4
View File
@@ -70,6 +70,10 @@ The unresolved cases were reported to the coordinator before implementation deci
## Residual Poison and two inspected commitments
The [Salusa Ghola follow-up](HOMEWORLD_BENEFITS_RULES.md#salusa-ghola-and-renewed-inspection-answers--27-october-2026)
also reuses this native-first ordered renewal before either full plan seals.
Its actual physical return occurs once; it does not buy another inspection.
**Selected provisional composition — 26 October 2026.** The 14 October
choose-recommended authority supersedes the earlier blanket product refusal.
The printed Residual Poison window remains available after two completed
+12
View File
@@ -1,5 +1,17 @@
# Rule decision index
## Salusa Ghola inspection renewal — 27 October 2026
Before either full plan is sealed, an actual Salusa-crossing Ghola return may
retain a feasible inspection or renew an impossible original field. The supplied
PDF's outlined impossible-answer Q&A (physical41 / printed39) is an accepted
unofficial ruling under user authority; physical3 / printed1 explains its
marking. Reuse the existing provisional native-first Cunning order and original
spent record, without new fields, charge or cancellation. Full seals and active
Truthtrance battle promises remain guarded separately. This supersedes only
the inspection restriction in the earlier window below.
[Contract](HOMEWORLD_BENEFITS_RULES.md#salusa-ghola-and-renewed-inspection-answers--27-october-2026).
## Salusa Ghola commitment-free battle window — 27 October 2026
Remove the whole-battle population-crossing restriction before any sealed plan,
+4
View File
@@ -29077,6 +29077,7 @@ function playMarketGhola(g: Game, p: Player, action: Action) {
function applyGholaEffect(g: Game, p: Player, action: Action, cardId?: string) {
requireRule(!revivalPrevented(g, p.id),
'Tleilaxu prevented this player’s revival, including Ghola, for the remaining Revival Phase.');
const salusaWasHigh = homeworldSardaukarFreeSupport(g, p.id);
if (action.leader === 'kwisatz') {
requireRule(
g.advanced && p.faction === 'atreides' && p.kwisatz?.dead,
@@ -29142,6 +29143,9 @@ function applyGholaEffect(g: Game, p: Player, action: Action, cardId?: string) {
techIncome(g, 'axlotl', p);
collectRevivalIncome(g, p, 0, false, true);
recordNativePrintedCardPlay(g, p.id, 'ghola');
if (!salusaWasHigh && homeworldSardaukarFreeSupport(g, p.id) && g.battle &&
[g.battle.attacker, g.battle.defender].includes(p.id))
reconcileChangedBattleInspections(g, 'the Salusa population increase', true);
}
/** Search complete legal plans without learning or consulting the opponent's sealed choices. */
function findLegalBattlePlan(
+6 -8
View File
@@ -1,7 +1,6 @@
import type { Game } from './engine';
import { homeworldContext } from './homeworld-game';
import { homeworldPopulations } from './homeworld-population';
import { committedPlanElements } from './battle-inspections';
type HomeworldBenefitContext = Pick<
Game,
@@ -60,9 +59,10 @@ export function homeworldSardaukarFreeSupport(
);
}
/** Current population can change before battle commitments. The remaining
* preview boundary protects sealed dials/support and binding answers from
* repricing; it does not freeze population or forbid Ghola for a whole battle.
/** Current population can change before sealed plans. The caller renews an
* impossible inspection answer after the return. The remaining preview boundary
* protects sealed dials/support and binding Truthtrance promises from repricing;
* it does not freeze population or forbid Ghola for a whole battle.
* Inspect submission presence, never an opponent's sealed plan contents. */
export function homeworldSardaukarGholaBlock(
context: HomeworldBenefitContext & Pick<Game, 'battle'>,
@@ -88,11 +88,9 @@ export function homeworldSardaukarGholaBlock(
if (!crossing) return null;
const battle = context.battle;
const committed = battle.revealed || Object.keys(battle.plans).length > 0 ||
[battle.attacker, battle.defender].some(
(id) => committedPlanElements(battle, id).length > 0,
) || battle.truthPromises?.some((promise) => !promise.released);
battle.truthPromises?.some((promise) => !promise.released);
return committed
? 'Ghola raising Salusa to high population after a battle plan or binding answer is not yet supported. Finish this battle first.'
? 'Ghola raising Salusa to high population after a sealed plan or during a binding battle promise is not yet supported. Finish this battle first.'
: null;
}
+4 -3
View File
@@ -102,7 +102,7 @@ export const HELP = {
'The table pauses before this power resolves. Other factions can use a legal Karama cancellation; everyone can allow the power.',
ally: 'Set aside spice for an ally’s bids or shipments. You may reclaim unused funds, but a current winning bid must remain funded.',
prescience:
'Only the requested element becomes fixed. The opponent keeps the rest of their plan private until both combatants seal their plans.',
'Only the requested element becomes fixed. Advanced Salusa Ghola may renew an impossible answer before either full plan is sealed; answer that same field again. Other plan elements stay private until reveal.',
};
const phaseDetails = [
@@ -5445,7 +5445,7 @@ export const RULE_TOPICS: RuleTopic[] = [
area: 'Implementation',
status: 'Partial',
detail:
"Fresh Basic Southern multi-star returns have provisional per-star allocation with exact child arrivals, a fixed original grant and one deferred income. All 13 cards, saved typed custody, supported-deck setup, Emperor movement, native Arrakis shipment sources, revival destinations, low charity/free-revival benefits, Tleilax’s phase-start income penalty and high-Salusa free Sardaukar support are connected. Ghola can now raise Salusa during a commitment-free battle; sealed/inspected/promised timing remains guarded. World-to-world invasion, Guild transport from Arrakis and Guild native returns use exact source pools, funding and interception. Homeworld battles connect native bonuses, limited explosion casualties, native-only Traitors and Face Dancers, and Ixian substitution. Junction offers connect sponsored board and Homeworld routes, including own returns, foreign departures and their distinct payments. Caladan foresight, Wallach advisor counts, Ix stronghold movement and Richese token movement now respect native population. High Tupile blocks Worthless sales and Ambassador payouts, including after an interrupted Ghola revival. Kaitain paid discards share the closing Bidding opportunity with CHOAM; Ecaz poison income uses actual physical disposal and current native population. Mandatory winning-card disposal follows winner casualties. Supported low Kaitain and Junction payment receipts preserve the payer’s cost and use current native population; divergent allied rounding remains guarded. Giedi grants its once-per-phase bonus on actual positive desert receipts, including settled Ecaz shares. Low Grumman uses original public entry counts across Terror and competing-reaction checks. Richese High now connects actual paid reserve companions, one combined provisional invoice, original marker proof/responses, physical withdrawal and later reserve-capped reveal. Nexus Cunning retains that explicit paid group in its pair receipt, reserving paid counters before immediate materialization; original counter and spent-card outcomes remain. Ix High now returns physical Suboids for paid Cyborgs with provisional additional allowance and shortage handling, retaining typed free allocation, original invoice and free-event income. Southern and Tleilax preserve the exact eligible revival group through one optional placement. Provisional physical entry now opens native BG, Terror and Ambassador responses, retaining the original held income, independent counters and ordered overlap; a committed Sneak group has its own arrival source. Southern placement provisionally retains the ordinary storm and HMS entry route; Tleilax retains the Ixian-only direct off-planet restriction. Fresh occupation additionally connects bank and percentage receipts, completed Southern Collection, immediate sharing, Caladan inspection, displaced Ix pool, Richese card-only choice with native terms, real Giedi receiver choice and native protections. Fresh native CHOAM entries additionally connect original Tupile slots, actual normal-limit cleanup and restored low-query authority without resetting usage. Advanced retains sole-qualified ownership until last departure. Basic Tupile provisionally follows current sole control: observed departure, contest or native high population ends its slot, while a new activation waits for earlier normal-limit cleanup. Continuous turns do not stack slots or reset intelligence use. Exceptional Duke, concealed transport and full/public acceptance remain unfinished. Already purchased ordinary Karama now composes with immune Junction through explicit unused-price acknowledgment; original tariff/payees stay unchanged and only delivered transport consumes its grant.",
"Fresh Basic Southern multi-star returns have provisional per-star allocation with exact child arrivals, a fixed original grant and one deferred income. All 13 cards, saved typed custody, supported-deck setup, Emperor movement, native Arrakis shipment sources, revival destinations, low charity/free-revival benefits, Tleilax’s phase-start income penalty and high-Salusa free Sardaukar support are connected. Ghola can raise Salusa before full seals, retaining feasible inspections or reopening impossible original fields; sealed-plan and binding-Truthtrance timing remain guarded. World-to-world invasion, Guild transport from Arrakis and Guild native returns use exact source pools, funding and interception. Homeworld battles connect native bonuses, limited explosion casualties, native-only Traitors and Face Dancers, and Ixian substitution. Junction offers connect sponsored board and Homeworld routes, including own returns, foreign departures and their distinct payments. Caladan foresight, Wallach advisor counts, Ix stronghold movement and Richese token movement now respect native population. High Tupile blocks Worthless sales and Ambassador payouts, including after an interrupted Ghola revival. Kaitain paid discards share the closing Bidding opportunity with CHOAM; Ecaz poison income uses actual physical disposal and current native population. Mandatory winning-card disposal follows winner casualties. Supported low Kaitain and Junction payment receipts preserve the payer’s cost and use current native population; divergent allied rounding remains guarded. Giedi grants its once-per-phase bonus on actual positive desert receipts, including settled Ecaz shares. Low Grumman uses original public entry counts across Terror and competing-reaction checks. Richese High now connects actual paid reserve companions, one combined provisional invoice, original marker proof/responses, physical withdrawal and later reserve-capped reveal. Nexus Cunning retains that explicit paid group in its pair receipt, reserving paid counters before immediate materialization; original counter and spent-card outcomes remain. Ix High now returns physical Suboids for paid Cyborgs with provisional additional allowance and shortage handling, retaining typed free allocation, original invoice and free-event income. Southern and Tleilax preserve the exact eligible revival group through one optional placement. Provisional physical entry now opens native BG, Terror and Ambassador responses, retaining the original held income, independent counters and ordered overlap; a committed Sneak group has its own arrival source. Southern placement provisionally retains the ordinary storm and HMS entry route; Tleilax retains the Ixian-only direct off-planet restriction. Fresh occupation additionally connects bank and percentage receipts, completed Southern Collection, immediate sharing, Caladan inspection, displaced Ix pool, Richese card-only choice with native terms, real Giedi receiver choice and native protections. Fresh native CHOAM entries additionally connect original Tupile slots, actual normal-limit cleanup and restored low-query authority without resetting usage. Advanced retains sole-qualified ownership until last departure. Basic Tupile provisionally follows current sole control: observed departure, contest or native high population ends its slot, while a new activation waits for earlier normal-limit cleanup. Continuous turns do not stack slots or reset intelligence use. Exceptional Duke, concealed transport and full/public acceptance remain unfinished. Already purchased ordinary Karama now composes with immune Junction through explicit unused-price acknowledgment; original tariff/payees stay unchanged and only delivered transport consumes its grant.",
evidence: [
'game/homeworld-module-profile.ts',
'game/faction-module-profile.ts',
@@ -5555,7 +5555,7 @@ export const RULE_TOPICS: RuleTopic[] = [
area: 'AI',
status: 'Partial',
detail:
"All four profiles share legal Southern portion/destination choices and the remaining-reserves option. Shared public choices supply native shipment counters, Imperial transfers and current low revival allowances. All four profiles use Salusa’s per-type support legality and respect the remaining special-Karama and post-commitment Ghola timing boundaries; before commitments, a real Salusa return updates the shared free-support descriptor. All four profiles handle world-to-world shipment, Guild transport from Arrakis, native returns and interception, Homeworld battle choices, typed losses, late defense and supported replacement actions. All four profiles can offer and use Junction transport through public quotes, respect population-based movement restrictions and choose legal Spiritual Advisor quantities. Market choices exclude high-Tupile Worthless sales. All four profiles use eligible unused low-Tupile requests without reading opponents’ private hands or balances. All four profiles use the shared closing opportunity for CHOAM actions, affordable Kaitain discards and readiness. Ecaz income needs no separate claim. Fresh occupied decisions supply legal bank/percentage allocation, one eligible real bonus receiver, displaced Ix/cache selection and native Richese terms. Concealed transport, remaining card-effect strategy and difficulty calibration are unfinished. All four original profiles execute the immune full offered quote with its explicit acknowledgment. This is minimal legal participation, not strategy tuning.",
"All four profiles share legal Southern portion/destination choices and the remaining-reserves option. Shared public choices supply native shipment counters, Imperial transfers and current low revival allowances. All four profiles use Salusa’s per-type support legality and respect the remaining special-Karama and post-commitment Ghola timing boundaries; before full seals, a real Salusa return updates the shared free-support descriptor and the existing owned inspection-renewal path. All four profiles handle world-to-world shipment, Guild transport from Arrakis, native returns and interception, Homeworld battle choices, typed losses, late defense and supported replacement actions. All four profiles can offer and use Junction transport through public quotes, respect population-based movement restrictions and choose legal Spiritual Advisor quantities. Market choices exclude high-Tupile Worthless sales. All four profiles use eligible unused low-Tupile requests without reading opponents’ private hands or balances. All four profiles use the shared closing opportunity for CHOAM actions, affordable Kaitain discards and readiness. Ecaz income needs no separate claim. Fresh occupied decisions supply legal bank/percentage allocation, one eligible real bonus receiver, displaced Ix/cache selection and native Richese terms. Concealed transport, remaining card-effect strategy and difficulty calibration are unfinished. All four original profiles execute the immune full offered quote with its explicit acknowledgment. This is minimal legal participation, not strategy tuning.",
evidence: ['game/homeworld-options.ts', 'game/homeworld-shipment-options.ts', 'game/guild-homeworld-shipment-options.ts', 'game/junction-transport-options.ts'],
},
{
@@ -5622,6 +5622,7 @@ export const RULE_TOPICS: RuleTopic[] = [
'tests/homeworld-salusa-engine.test.ts',
'tests/fixture-salusa-ghola.ts',
'tests/salusa-ghola-runtime.test.ts',
'tests/salusa-ghola-inspection.test.ts',
'tests/homeworld-combat.test.ts',
'tests/homeworld-battle-resolution.test.ts',
'tests/homeworld-battle-engine.test.ts',
@@ -138,6 +138,8 @@ export function createResidualCunningPriorityFixture(options: { kind?: ResidualC
residualCunningPriorityPlayer(game, asker).reserves = 15;
Object.assign(game, { phase: 6, active: asker, order: [asker, target, observer], storm: 18,
ready: [], response: null, decision: null, phaseOpening: null });
// This explicitly controlled Battle position has no live Charity turn.
delete game.charityOrder;
const cards = game.nexusCards!.cards!, index = cards.deck.indexOf('atreides'); assert.ok(index >= 0);
cards.deck.splice(index, 1); cards.deck.unshift('atreides');
game.nexusCards!.cards = drawNexusCard(cards, asker, game.players, () => 0);
+23 -4
View File
@@ -74,7 +74,9 @@ function livingLeader(game: Game, actor: string, strongest: boolean) {
/** Earned public one-turn source. Initial hands/Spice order are selected only
* while unopened; native setup, payments, typed shipments, split movement and
* the earlier combat casualty create every later resource and battle field. */
export function createSalusaGholaFixture() {
export function createSalusaGholaFixture(options: {
firstTerritory?: 'imperial_basin'|'arrakeen'; inspectionDial?: 1|2;
} = {}) {
const actor = 'emperor', atreides = 'atreides', guild = 'guild';
let game = createGame('SALUSAGHOLA',newPlayer(actor,'Emperor','emperor'),true);
joinGame(game,newPlayer(atreides,'Atreides','atreides'));
@@ -134,7 +136,8 @@ export function createSalusaGholaFixture() {
}
assert.ok(movement); assert.equal(game.phase,6);
reach(g => salusaGholaClean(g) && !g.battle && !!g.active);
const beforeFirstBattle = structuredClone(game), firstTerritory = game.active === guild ? 'imperial_basin' : 'arrakeen';
const beforeFirstBattle = structuredClone(game), firstTerritory = options.firstTerritory ??
(game.active === guild ? 'imperial_basin' : 'arrakeen');
const firstOpponent = firstTerritory === 'arrakeen' ? atreides : guild;
const firstChooser = game.active!;
assert.ok(viewGame(game,firstChooser).battleChoices.some(c => c.territory === firstTerritory));
@@ -159,6 +162,21 @@ export function createSalusaGholaFixture() {
assert.ok(viewGame(game,chooser).battleChoices.some(c => c.territory === secondTerritory));
act(chooser,{type:'chooseBattle',territory:secondTerritory,target:chooser === actor ? opponent : actor});
const preparation = structuredClone(game);
let inspection: {beforeDeclaration:Game;declared:Game;beforeAnswer:Game;answered:Game;steps:SalusaGholaStep[]}|null = null;
if (options.inspectionDial !== undefined) {
assert.equal(opponent,atreides,'An actual native Atreides opponent supplies the requested inspection.');
reach(g => salusaGholaClean(g) && g.battle?.preparation?.kind === 'prescience' &&
g.battle.preparation.owner === atreides);
const beforeDeclaration = structuredClone(game), inspectionAt = actions.length;
act(atreides,{type:'prescience',field:'dial'});
const declared = structuredClone(game);
reach(g => salusaGholaClean(g) && g.battle?.preparation?.kind === 'prescienceAnswer' &&
g.battle.preparation.owner === actor);
const beforeAnswer = structuredClone(game);
act(actor,{type:'prescienceAnswer',value:options.inspectionDial});
inspection = {beforeDeclaration,declared,beforeAnswer,answered:structuredClone(game),
steps:structuredClone(actions.slice(inspectionAt))};
}
reach(planReady);
assert.deepEqual(game.battle!.plans,{});
assert.ok(salusaGholaPlayer(game).hand.some(c => c.id === ghola.id));
@@ -170,8 +188,9 @@ export function createSalusaGholaFixture() {
territory:secondTerritory,firstTerritory,firstOpponent,firstPlans,
initial,lobby,setup,beforeDeal,afterSetup,dealOrder,spiceOrder,original,setupActions,
shipments,movement,beforeFirstBattle,firstPreparation,firstBeforePlans,firstResolution,firstActions,
beforeSecondBattle,preparation,actions,sourceActions:actions,sourceCount:actions.length,
disclosure:'Original public Advanced E/A/G with Homeworlds enabled before Ready/start, base33 and no optional modules. Only unopened starting cards, two land Spice positions and scalar lotteries selected. Real typed E4stars desert shipment costs8 and leaves Salusa1; real two-star movement creates Arrakeen contest, and real Guild1 shipment creates Basin contest. The first actual E win uses paid support1/dial2 and kills one real Sardaukar. Stop at the other actual live unsealed battle with Ghola held, Salusa1, tanks1star and two fighting stars. No later phase, force, purse, Tank, hand, battle commitment or receipt is installed.'};
beforeSecondBattle,preparation,inspection,actions,sourceActions:actions,sourceCount:actions.length,
disclosure:'Original public Advanced E/A/G with Homeworlds enabled before Ready/start, base33 and no optional modules. Only unopened starting cards, two land Spice positions and scalar lotteries selected. Real typed E4stars desert shipment costs8 and leaves Salusa1; real two-star movement creates Arrakeen contest, and real Guild1 shipment creates Basin contest. The first actual E win uses paid support1/dial2 and kills one real Sardaukar. Stop at the other actual live unsealed battle with Ghola held, Salusa1, tanks1star and two fighting stars. No later phase, force, purse, Tank, hand, battle commitment or receipt is installed.' +
(inspection ? ` Optional actual Atreides native dial Prescience and Emperor answer${options.inspectionDial} are recorded before either plan seals; the preferred first battle is selected only from the actual chooser options.` : '')};
}
export type SalusaGholaFixture = ReturnType<typeof createSalusaGholaFixture>;
+14 -14
View File
@@ -473,7 +473,7 @@ void test('an opponent-only sealed plan keeps the remaining Salusa timing bounda
assert.deepEqual(g, before);
});
void test('Salusa threshold protection also preserves an answered Prescience dial before either full plan is sealed', () => {
void test('Salusa Ghola renews an impossible answered Prescience dial before either full plan is sealed', () => {
const fixture = gholaThresholdBattle(0);
const card = fixture.card;
let g = fixture.g;
@@ -491,19 +491,19 @@ void test('Salusa threshold protection also preserves an answered Prescience dia
g = applyAction(g, 'e', { type: 'prescienceAnswer', value: 1 });
assert.equal(g.battle!.prescience!.value, 1);
assert.deepEqual(g.battle!.plans, {});
const saved = reload(g);
const reason = viewGame(saved, 'e').ghola.eliteBlock;
assert.ok(reason);
assert.throws(
() => applyAction(saved, 'e', { type: 'card', card, amount: 1, elite: 1 }),
(error) => error instanceof Error && error.message === reason,
);
assert.deepEqual(saved, g);
assert.equal(saved.battle!.prescience!.value, 1);
const done = resolve(saved, 1, 0);
assert.equal(viewGame(g, 'e').ghola.eliteBlock, null);
g = applyAction(g, 'e', { type: 'card', card, amount: 1, elite: 1 });
assert.equal(g.battle!.prescience!.field, 'dial');
assert.equal(Object.hasOwn(g.battle!.prescience!, 'value'), false);
assert.equal(g.battle!.preparation!.kind, 'prescienceAnswer');
assert.equal(g.battle!.preparation!.owner, 'e');
assert.throws(() => applyAction(g, 'e', plan(g, 2)), /battle plan is not available/);
assert.throws(() => applyAction(g, 'e', { type: 'prescienceAnswer', value: 1 }), /legal battle plan/);
g = applyAction(g, 'e', { type: 'prescienceAnswer', value: 2 });
const done = resolve(g, 2, 0);
assert.equal(done.lastBattleContext!.winner, 'e');
assert.equal(done.homeworlds!.custody!.salusa!.elite, 1);
assert.equal(emperor(done).elites!.tanks, 2);
assert.equal(done.discard.filter((held) => held.id === card).length, 0);
assert.equal(done.homeworlds!.custody!.salusa!.elite, 2);
assert.equal(emperor(done).elites!.tanks, 1);
assert.equal(done.discard.filter((held) => held.id === card).length, 1);
inventory(done);
});
+125
View File
@@ -0,0 +1,125 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { applyAction, viewGame, type Action, type Game } from '../game/engine';
import { botActions } from '../game/bots';
import {
createSalusaGholaFixture, playSalusaGholaBattle, actSalusaGhola as act,
advanceSalusaGhola as advance, salusaGholaClean as clean,
salusaGholaArmy as army, salusaGholaPlayer as p, salusaGholaInventory as inventory,
type SalusaGholaFixture,
} from './fixture-salusa-ghola';
const sources = new Map<number,SalusaGholaFixture>();
function source(answer: 1|2) {
let f = sources.get(answer);
if (!f) {
f = createSalusaGholaFixture({firstTerritory:'imperial_basin',inspectionDial:answer});
sources.set(answer,f);
}
return f;
}
function reject(game: Game, actor: string, action: Action) {
const before = structuredClone(game);
assert.throws(() => applyAction(game,actor,action));
assert.deepEqual(game,before);
}
function leader(game: Game, actor: string, strongest: boolean) {
const choices = p(game,actor).leaders.filter(l => !l.dead && !l.usedAt && !l.capturedBy);
choices.sort((a,b) => strongest ? b.strength-a.strength : a.strength-b.strength);
assert.ok(choices[0]); return choices[0].id;
}
function finish(f: SalusaGholaFixture, state: Game) {
let game = act(state,f.actor,{type:'battlePlan',leader:leader(state,f.actor,true),dial:2,support:0});
game = act(game,f.opponent,{type:'battlePlan',leader:leader(game,f.opponent,false),dial:0,support:0});
return advance(game,g => !g.battle && clean(g));
}
void test('earned Basin-first source answers real Atreides dial Prescience before either plan is sealed', () => {
const f = source(1), inspection = f.inspection;
assert.ok(inspection);
assert.equal(f.firstTerritory,'imperial_basin');
assert.equal(f.firstOpponent,'guild');
assert.equal(f.territory,'arrakeen');
assert.equal(f.opponent,'atreides');
assert.ok(viewGame(f.beforeFirstBattle,f.beforeFirstBattle.active!).battleChoices
.some(choice => choice.territory === 'imperial_basin'));
assert.equal(inspection.beforeDeclaration.battle!.preparation!.kind,'prescience');
assert.equal(inspection.beforeDeclaration.battle!.preparation!.owner,'atreides');
assert.equal(inspection.steps[0].actor,'atreides');
assert.deepEqual(inspection.steps[0].action,{type:'prescience',field:'dial'});
assert.equal(inspection.beforeAnswer.battle!.preparation!.owner,f.actor);
assert.ok(inspection.steps.some(step => step.actor === f.actor &&
step.action.type === 'prescienceAnswer' && step.action.value === 1));
assert.equal(f.game.battle!.prescience!.player,'atreides');
assert.equal(f.game.battle!.prescience!.field,'dial');
assert.equal(f.game.battle!.prescience!.value,1);
assert.deepEqual(f.game.battle!.plans,{});
assert.equal(f.game.battle!.preparation,undefined);
assert.equal(p(f.game).elites!.tanks,1);
assert.deepEqual(army(f.game),{normal:0,elite:1});
assert.equal(f.sourceCount,f.sourceActions.length);
assert.deepEqual(inventory(f.game),f.original);
});
void test('actual Ghola invalidates an impossible old odd dial and requires owned legal reanswer before native plans', () => {
const f = source(1);
let game = act(structuredClone(f.game),f.actor,f.action);
assert.deepEqual(army(game),{normal:0,elite:2});
assert.equal(game.battle!.event,f.game.battle!.event);
assert.equal(game.battle!.prescience!.field,'dial');
assert.equal(game.battle!.prescience!.player,'atreides');
assert.equal(Object.hasOwn(game.battle!.prescience!,'value'),false);
assert.equal(game.battle!.preparation?.kind,'prescienceAnswer');
assert.equal(game.battle!.preparation?.owner,f.actor);
assert.equal(game.battle!.preparation?.beneficiary,'atreides');
assert.deepEqual(game.battle!.plans,{});
assert.equal(viewGame(game,f.actor).battle!.ownForces!.eliteFreeSupport,true);
assert.equal(game.discard.filter(c => c.id === f.card).length,1);
assert.equal(p(game).elites!.tanks,0);
reject(game,f.actor,{type:'battlePlan',leader:leader(game,f.actor,true),dial:1,support:0});
reject(game,f.actor,{type:'prescienceAnswer',value:1});
reject(game,f.opponent,{type:'prescienceAnswer',value:2});
game = act(game,f.actor,{type:'prescienceAnswer',value:2});
assert.equal(game.battle!.prescience!.value,2);
assert.equal(game.battle!.preparation,undefined);
assert.equal(p(game).elites!.tanks,0);
game = finish(f,game);
assert.equal(game.lastBattleContext!.winner,f.actor);
assert.equal(p(game).elites!.tanks,1);
assert.deepEqual(army(game),{normal:0,elite:2});
assert.equal(game.discard.filter(c => c.id === f.card).length,1);
assert.deepEqual(inventory(game),f.original);
});
void test('actual Ghola retains a still-feasible native dial2 answer and finishes without another inspection', () => {
const f = source(2), result = playSalusaGholaBattle(f);
assert.equal(f.game.battle!.prescience!.value,2);
assert.equal(result.returned.battle!.prescience!.value,2);
assert.equal(result.returned.battle!.preparation,undefined);
assert.equal(result.returned.battle!.prescience!.player,'atreides');
assert.equal(result.afterPlan.battle!.plans[f.actor].dial,2);
assert.equal(result.afterPlan.battle!.plans[f.actor].support,0);
assert.equal(result.actions.filter(step => step.action.type === 'prescience' ||
step.action.type === 'prescienceAnswer').length,0);
assert.equal(result.game.lastBattleContext!.winner,f.actor);
assert.equal(result.game.discard.filter(c => c.id === f.card).length,1);
assert.deepEqual(inventory(result.game),f.original);
});
void test('all four minimal profiles can supply a legal current Salusa Prescience reanswer', () => {
const f = source(1), pending = act(structuredClone(f.game),f.actor,f.action);
for (const profile of ['Easy','Medium','Hard','Brutal'] as const) {
const game = structuredClone(pending), view = viewGame(game,f.actor);
view.players.find(p => p.id === f.actor)!.bot = profile;
const action = botActions(view).find(action => action.type === 'prescienceAnswer');
assert.ok(action,`${profile} needs the actual owned reanswer action.`);
assert.notEqual(action.value,1);
const next = act(game,f.actor,action);
assert.equal(next.battle!.prescience!.value,action.value);
assert.equal(next.battle!.preparation,undefined);
assert.deepEqual(next.battle!.plans,{});
assert.equal(next.discard.filter(c => c.id === f.card).length,1);
assert.deepEqual(army(next),{normal:0,elite:2});
assert.deepEqual(inventory(next),f.original);
}
});