diff --git a/docs/CURRENT_STATUS.md b/docs/CURRENT_STATUS.md index 87fc348b..134686fd 100644 --- a/docs/CURRENT_STATUS.md +++ b/docs/CURRENT_STATUS.md @@ -217,11 +217,13 @@ saved positions. Every previously stalled Southern Hemisphere occupied-benefit capture and every previously stalled odd-force Basic Ecaz Occupy capture now completes (`homeworld-occupation-atreides-4/5-basic`, `ixians-6-basic`, `moritani-6-basic`, plus five Basic Occupy bodies finishing in 135–688 actions). -Four Advanced captures still stop on the open high-Homeworld-threshold revival- -deployment timing, and one Basic `native-homeworld-skills` overlay capture stops -on the explicitly gated combined-Occupy composition. No capture was reset, +The four Advanced captures that still stopped on the high-Homeworld-threshold +revival-deployment timing now also complete (78/68/932/133 further actions) after +that question received a visibly provisional resulting-population policy. One +Basic `native-homeworld-skills` capture still stops on the explicitly gated +combined-Occupy optional-overlay composition. No capture was reset, force-completed or bypassed; results and logs are under -`/tmp/dune-resume-wave2-20261009`. See +`/tmp/dune-resume-wave2-20261009` and `/tmp/dune-resume-wave3-20261009`. See [the resumption evidence](IMPLEMENTATION_STATUS.md#9-october-2026--gated-captures-resumed-under-the-basic-occupation-and-odd-force-occupy-prototypes). **7 October 2026 — five pending rulings resolved by the user.** The user diff --git a/docs/HOMEWORLD_BENEFITS_RULES.md b/docs/HOMEWORLD_BENEFITS_RULES.md index f2b2e514..3009bad8 100644 --- a/docs/HOMEWORLD_BENEFITS_RULES.md +++ b/docs/HOMEWORLD_BENEFITS_RULES.md @@ -2,6 +2,30 @@ 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. +## Provisional high-threshold revival deployment — 9 October 2026 + +The previously blocked case "a supported revival whose own return first reaches +the Homeworld's high threshold" now has a visibly provisional policy: the +population **resulting** from that return decides the deployment ability, so the +crossing return may itself place its revived starred (Fremen Southern +Hemisphere) or ordinary free (Tleilaxu High) group instead of only returning it +to reserves. The printed card grants the ability while the faction is at High, +and the returned forces are what make it High, so evaluating after the deposit +is the natural composition of the two texts. + +This is a first-version implementation choice, **not a located publisher timing +ruling**. The Tleilaxu low-income condition still snapshots native status on +entry to Revival, so crossing high later does not erase that printed +start-condition. Partial groups, split destinations, Southern storm placement, +allied-Homeworld limits and entry effects requiring a shipment classification +remain guarded. + +Evidence: the `homeworld-revival` union (65 cases) passes with the crossing +return staging its own placement choice, and the four preserved Advanced +captures that previously stopped at `phase 4` on this question now complete +(78/68/932/133 further actions) with zero rejections. See +[the resumption record](IMPLEMENTATION_STATUS.md#9-october-2026--gated-captures-resumed-under-the-basic-occupation-and-odd-force-occupy-prototypes). + ## Publisher facts and provenance | Source | Verified material | diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md index dd891e50..a4833913 100644 --- a/docs/IMPLEMENTATION_STATUS.md +++ b/docs/IMPLEMENTATION_STATUS.md @@ -40,6 +40,23 @@ Homeworld threshold awaits a timing ruling` remain gated.` — an explicitly gated optional-overlay composition rather than a defect. No capture was reset, force-completed or bypassed. +### Later the same day — the threshold-deployment question resolved provisionally + +The provisional resulting-population policy recorded in +[Homeworld benefits](HOMEWORLD_BENEFITS_RULES.md#provisional-high-threshold-revival-deployment--9-october-2026) +was then applied, and the four Advanced captures above were resumed again into +`/tmp/dune-resume-wave3-20261009`: **all 16 saved copies now complete** +(`e3-homeworld-nexus-discovery-ecaz-moritani-…` 78 actions, +`e3-homeworld-nexus-discovery-skills-stronghold-tech-…` 68, +`mixed-homeworld-nexus-discovery-skills-ix-choam-ixians-richese-…` 932 and +`mixed-homeworld-nexus-discovery-skills-stronghold-ix-choam-ixians-richese-…` +133; each reaching turn 6–10 phase 8) with zero rejected actions. + +That leaves **one** previously gated capture, the Basic +`native-homeworld-skills-ecaz-…` sample, which still stops on the explicitly +gated combined-Occupy optional-overlay composition rather than on a rules +question. + ## 7 October 2026 — preserved captures resumed under the five rulings Every preserved `failed-*.json` capture under `/tmp/dune-*/` (123 distinct diff --git a/docs/RULE_DECISIONS.md b/docs/RULE_DECISIONS.md index 402deb63..740929e6 100644 --- a/docs/RULE_DECISIONS.md +++ b/docs/RULE_DECISIONS.md @@ -478,6 +478,7 @@ These are unresolved in the cited records. Do not infer an answer from elapsed t | Ordinary Guild cancellation | Unaffordable repricing, replacement/retry/decline, consumed opportunity, contributor adjustment and ordering with special shipment-stop. The affordability question was already sent. | [Material timing](GUILD_RATE_KARAMA_RULES.md#material-unresolved-timing) | | Repeat Karama on a canceled compulsory cache lot | After one Karama cancels the compulsory Richese cache auction and restores the ordinary pool, the engine currently still accepts a second Karama against the resumed window and re-derives the ordinary count from the frozen pre-cache field. Whether a repeat cancellation should be refused is not settled; the invariant that a canceled lot never restores more than its frozen pre-cache count does hold. | [Richese auction source](RICHESE_AUCTION_RULES.md), [exhausted-cache runtime](IMPLEMENTATION_STATUS.md) | | Base special Karama | Unfunded auction recovery, stopped-shipment settlement and Atreides full-plan commitment order remain provisional. Native full-plan/Voice/Prescience/Truthtrance/Ghola interactions now have genuine-setup focused evidence, but the source audit does not settle the special offer/deadline or target-first interpretation. | [Explicit base interpretations](BASE_ADVANCED_READINESS_20260907.md#explicit-unresolved-base-interpretations), [full-plan interaction audit](ATREIDES_FULL_PLAN_TIMING.md) | +| Homeworld high-threshold revival deployment | The publisher does not state whether a return that itself first reaches the high threshold may deploy on that same return. The content-first prototype evaluates the resulting population, so the crossing return qualifies; this is a visibly provisional composition, not a located timing ruling. The Tleilaxu low-income start-condition snapshot is unchanged, and partial groups, split destinations, storm placement, allied-Homeworld limits and shipment-classified entry effects remain guarded. | [Benefits contract](HOMEWORLD_BENEFITS_RULES.md#provisional-high-threshold-revival-deployment--9-october-2026), [resumption record](IMPLEMENTATION_STATUS.md#9-october-2026--gated-captures-resumed-under-the-basic-occupation-and-odd-force-occupy-prototypes) | | Occupied Homeworlds | The publisher question about retention after departure, replacement or contest, unique-power composition and shared income remains open; the first-occupier/current-occupier question was already sent. The content-first Basic benefit selector now uses the visibly provisional current-sole policy above (Tupile excepted), which is an implementation choice, not an answer to that question. | [Occupation questions](HOMEWORLD_OCCUPATION_RULES.md#questions-the-retrieved-sources-do-not-settle), [lifecycle audit](HOMEWORLD_OCCUPATION_LIFECYCLE_AUDIT.md), [Basic policy](HOMEWORLD_OCCUPATION_RULES.md#basic-current-sole-benefit-policy--9-october-2026) | | Nexus responses and composition | Betrayal private-response timing, partial revival/payment choices, Emperor fewer-than-five, fresh advisor/storm cases, Duke restoration and remaining faction-specific combinations. Tleilaxu Secret Ally force accounting, leader slot/eligibility and force-only use remain unresolved after requested publisher/designer source searches; do not infer answers from native Tleilaxu revival or Ghola. | [Common boundaries](NEXUS_CARD_RULES.md#remaining-source-questions-and-release-boundaries), [Tleilaxu source search](NEXUS_TLEILAXU_RULES.md#source-search-follow-up) and individual faction source/runtime documents | | Moritani Atomics after alliance change | The publisher's persistent −1 hand-limit text names Moritani and “your ally,” but not whether the penalty follows a later ally, remains on the original ally or affects both. Requested online source searches found no clarification; the alliance-change case remains gated. | [Terror source boundary](MORITANI_TERROR_RULES.md#material-gaps-and-ordinary-compositions) | diff --git a/game/homeworld-revival-deployment.ts b/game/homeworld-revival-deployment.ts index 0a4d66f1..25ecd7bc 100644 --- a/game/homeworld-revival-deployment.ts +++ b/game/homeworld-revival-deployment.ts @@ -49,7 +49,10 @@ function physicalWorlds( /** Quote the eligible subset of one actual typed native revival deposit. * Timing, destinations, splitting and durable completion belong to the caller. - * A newly attained high threshold remains an explicit unresolved boundary. */ + * Provisional first version: the high-threshold placement ability is evaluated + * on the population *resulting* from this return, so the redeployment that + * itself reaches the high side qualifies. This is a visibly provisional + * composition policy, not a located publisher timing ruling. */ export function quoteHomeworldRevivalDeployment( before: HomeworldRevivalDeploymentContext, after: HomeworldRevivalDeploymentContext, @@ -127,9 +130,6 @@ export function quoteHomeworldRevivalDeployment( elite: kind === 'fedaykin' ? group.elite : 0, beforePopulation: prior.population, afterPopulation: current.population, - blocked: - prior.side === 'low' - ? 'Revival deployment when this return first reaches the high Homeworld threshold awaits a timing ruling.' - : null, + blocked: null, }; } diff --git a/game/homeworld-revival-return.ts b/game/homeworld-revival-return.ts index ca415e6a..af1302c4 100644 --- a/game/homeworld-revival-return.ts +++ b/game/homeworld-revival-return.ts @@ -161,12 +161,14 @@ function validateOriginal(frame: Original): void { ) fail(); const quote = frame.quote; + // Provisional composition policy: the population resulting from this return + // decides the ability, so the crossing return's frame is valid even though + // the pre-return population was below the high threshold. if (quote.kind === 'fedaykin') { if ( quote.normal !== 0 || !count(quote.elite, 1, 3) || - quote.elite !== frame.group.elite || - quote.beforePopulation < 3 + quote.elite !== frame.group.elite ) fail(); } else if (quote.kind === 'tleilax') { @@ -175,8 +177,7 @@ function validateOriginal(frame: Original): void { frame.group.elite !== 0 || quote.elite !== 0 || quote.normal !== frame.group.free || - quote.normal < 1 || - quote.beforePopulation < 9 + quote.normal < 1 ) fail(); } else fail(); diff --git a/game/reference.ts b/game/reference.ts index f741b46c..ec7251ea 100644 --- a/game/reference.ts +++ b/game/reference.ts @@ -3881,9 +3881,9 @@ export const RULE_TOPICS: RuleTopic[] = [ '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.', 'Quote a group of normal revivals using population before that group returns. A later group uses the resulting population: a low Fremen group can return four free forces, but returning a smaller group that restores high population can remove the fourth allowance. La La La prevents the full free rate.', - 'When Southern Hemisphere begins a supported revival at high population, Fremen may place the newly revived starred forces together in one territory already containing their forces, or leave them in reserves. This includes supported ordinary, Emperor-funded and Ghola revivals. Basic keeps the starred counters; Advanced still limits Fedaykin revival to one per turn.', + 'When Southern Hemisphere supports a revival that leaves Fremen at high population — including a return that first reaches the high side: the population resulting from the return decides the ability — Fremen may place the newly revived starred forces together in one territory already containing their forces, or leave them in reserves. This includes supported ordinary, Emperor-funded and Ghola revivals. Evaluating the resulting population is a visibly provisional composition policy, not a located publisher timing ruling. Basic keeps the starred counters; Advanced still limits Fedaykin revival to one per turn.', 'When Tleilax begins ordinary Free Revival at high population, Tleilaxu may send the newly revived free group together to one permitted territory or Homeworld, or leave it in reserves. Paid counters in the same return and Ghola returns do not join this group. An allied Homeworld is forbidden. Placement costs no additional spice and uses no ordinary shipment or movement allowance.', - 'The revival placement choice shows unavailable destinations. A return that first restores high population, partial groups, split destinations, Southern storm placement and entry effects requiring a shipment classification remain guarded while their rules are unresolved. Independent revival income waits until placement and supported Ambassador reactions finish; refreshing does not repeat the return or payment.', + 'The revival placement choice shows unavailable destinations. A return that first restores high population is now admitted under the provisional resulting-population policy above; partial groups, split destinations, Southern storm placement, allied-Homeworld limits and entry effects requiring a shipment classification remain guarded while those rules are unresolved. Independent revival income waits until placement and supported Ambassador reactions finish; refreshing does not repeat the return or payment.', 'While Tupile has at least eleven native forces, CHOAM cannot discard Worthless cards for spice through either ordinary sale price or a CHOAM Ambassador. Non-Worthless duplicate sales, allied trades and the Advanced CHOAM Karama cash-in retain their own rules. If a Ghola revival raises Tupile before a declared sale settles, that Worthless card stays in hand and earns no spice.', 'At the end of Bidding, high-population Kaitain lets Emperor pay two spice per selected Treachery Card to discard it. This does not play the card or begin another auction. Emperor and CHOAM share the closing opportunity, so sales, allied trades and paid discards can occur in either order. Current hands, spice and native population determine each action. Finish when ready; a new action clears readiness.', 'In supported payment cases, low Kaitain halves Emperor’s Treachery payment income and low Junction halves eligible Guild shipping income, rounded up. The payer still pays the full cost. For example, a five-spice auction payment gives low-population Emperor three spice and leaves two in the bank. The income opportunity shows the original payment, current receipt and bank remainder. A population change before collection updates the split; ordinary Karama can still prevent the separate faction income.', diff --git a/tests/homeworld-benefits-recovery.test.ts b/tests/homeworld-benefits-recovery.test.ts index 8e5330b3..58c5debf 100644 --- a/tests/homeworld-benefits-recovery.test.ts +++ b/tests/homeworld-benefits-recovery.test.ts @@ -382,16 +382,15 @@ void test('Tleilaxu low phase-entry receipt survives a later physical population }; assert.deepEqual(g.homeworldRevival, receipt); f.save(g); - await assert.rejects( - f.rooms.act( - f.code, - f.auths[0], - g.version, - { type: 'revive', amount: 2, elite: 0 }, - clock, - ), - /timing ruling/, + // The crossing return is now legal under the provisional post-return + // population policy. Prove it on an isolated clone so the persisted low + // receipt and write count stay untouched. + const crossingTrial = engine.applyAction( + JSON.parse(JSON.stringify(g)), + g.players[0].id, + { type: 'revive', amount: 2, elite: 0 }, ); + assert.equal(crossingTrial.homeworldRevivalReturn!.stage, 'choice'); assert.equal(f.writes.length, 0); assert.deepEqual(await f.rooms.readRoom(f.code), g); await f.rooms.act( diff --git a/tests/homeworld-revival-benefits-engine.test.ts b/tests/homeworld-revival-benefits-engine.test.ts index 2ba576f6..411fbaa6 100644 --- a/tests/homeworld-revival-benefits-engine.test.ts +++ b/tests/homeworld-revival-benefits-engine.test.ts @@ -291,10 +291,12 @@ void test('actual Bidding exit snapshots low Tleilax; later crossing high suppre }); const initial = p(g, 'tleilaxu').spice; const before = structuredClone(g); - assert.throws( - () => applyAction(g, 'tleilaxu', { type: 'revive', amount: 2, elite: 0 }), - /timing ruling/, - ); + // The crossing return now qualifies under the provisional post-return + // population policy: it stages its own redeployment choice instead of + // blocking, while the source state stays untouched. + const crossing = applyAction(reload(g), 'tleilaxu', { type: 'revive', amount: 2, elite: 0 }); + assert.equal(crossing.homeworldRevivalReturn!.stage, 'choice'); + assert.equal(viewGame(crossing, 'tleilaxu').homeworldRevivalDeployment?.player, 'tleilaxu'); assert.deepEqual(g, before); g = applyAction(g, 'tleilaxu', { type: 'revive', amount: 1, elite: 0 }); assert.equal(p(g, 'tleilaxu').reserves, 8); diff --git a/tests/homeworld-revival-deployment-engine.test.ts b/tests/homeworld-revival-deployment-engine.test.ts index 9bbb7322..ff045c3d 100644 --- a/tests/homeworld-revival-deployment-engine.test.ts +++ b/tests/homeworld-revival-deployment-engine.test.ts @@ -253,7 +253,7 @@ void test('Southern Ghola retires its physical card before choice and restores t } }); -void test('low populations do not invent a deployment; first reaching high rejects ordinary and Ghola returns before payment or custody changes', () => { +void test('low populations do not invent a deployment; a return that first reaches high stages its own redeployment choice', () => { for (const ghola of [false, true]) { let g = homeworldRevivalFixture(); positionRevivalForces(g, 'f', { native: 1, tanks: 5, eliteTanks: 2 }); @@ -267,12 +267,22 @@ void test('low populations do not invent a deployment; first reaching high rejec assert.equal(viewGame(low, 'f').homeworldRevivalDeployment, null); assert.equal(p(low, 'f').reserves, 2); stable(low); - reject(g, 'f', { ...action, amount: 2 }, /timing ruling/); + // The crossing return now qualifies under the provisional post-return + // population policy and may redeploy instead of only returning to reserves. + const crossing = applyAction(reload(g), 'f', { ...action, amount: 2 }); + assert.notEqual(crossing.homeworldRevivalReturn ?? null, null); + assert.equal(viewGame(crossing, 'f').homeworldRevivalDeployment?.player, 'f'); + const placed = applyAction(reload(crossing), 'f', choice(crossing, 'f')); + assert.equal(placed.homeworldRevivalReturn!.stage, 'complete'); + stable(placed); } let t = homeworldRevivalFixture({ tleilaxu: true }); positionRevivalForces(t, 't', { native: 8, tanks: 5 }); t = enterHomeworldRevival(t); - reject(t, 't', { type: 'revive', amount: 1, elite: 0 }, /timing ruling/); + const crossing = applyAction(reload(t), 't', { type: 'revive', amount: 1, elite: 0 }); + assert.notEqual(crossing.homeworldRevivalReturn ?? null, null); + assert.equal(viewGame(crossing, 't').homeworldRevivalDeployment?.player, 't'); + stable(applyAction(reload(crossing), 't', choice(crossing, 't'))); const card = holdRevivalCard(t, 't', 'ghola'); const done = applyAction(t, 't', { type: 'card', card, amount: 2, elite: 0 }); assert.equal( @@ -348,35 +358,25 @@ void test('real deployment projections expose destinations only to their owner a } }); -void test('every bot profile avoids actual low-to-high ordinary and Ghola star returns while the shared block permits a leader return', () => { +void test('every bot profile uses legal returns across the low-to-high crossing and the shared block permits a leader return', () => { for (const advanced of [false, true]) { let g = homeworldRevivalFixture({ advanced }); positionRevivalForces(g, 'f', { native: 2, tanks: 3, eliteTanks: 3 }); g = enterHomeworldRevival(g); const ghola = holdRevivalCard(g, 'f', 'ghola'); const ownView = viewGame(g, 'f'); - assert.match( - homeworldRevivalActionBlock(ownView, { - type: 'revive', - amount: 1, - elite: 1, - })!, - /timing ruling/, - ); - if (!advanced) - assert.match( - homeworldRevivalActionBlock(ownView, { type: 'card', card: ghola })!, - /timing ruling/, + // A return that itself reaches the high side is now legal under the + // provisional post-return population policy. + for (const action of [ + { type: 'revive', amount: 1, elite: 1 }, + { type: 'card', card: ghola, amount: 1, elite: 1 }, + { type: 'card', card: ghola, amount: 1 }, + ] as Action[]) + assert.equal( + homeworldRevivalActionBlock(ownView, action), + null, + JSON.stringify(action), ); - assert.match( - homeworldRevivalActionBlock(ownView, { - type: 'card', - card: ghola, - amount: 1, - })!, - /timing ruling/, - ); - reject(g, 'f', { type: 'card', card: ghola, amount: 1 }, /timing ruling/); for (const profile of ['Easy', 'Medium', 'Hard', 'Brutal'] as const) { const view = viewGame(reload(g), 'f'); view.players.find((player) => player.id === 'f')!.bot = profile; @@ -385,13 +385,6 @@ void test('every bot profile avoids actual low-to-high ordinary and Ghola star r actions.length, `${profile} must retain a legal way to advance.`, ); - assert.ok( - actions.every( - (action) => - action.type !== 'revive' && - !(action.type === 'card' && action.card === ghola), - ), - ); for (const action of actions) { assert.equal(homeworldRevivalActionBlock(view, action), null); const next = applyAction(reload(g), 'f', action); diff --git a/tests/homeworld-revival-deployment.test.ts b/tests/homeworld-revival-deployment.test.ts index d448e935..cf82a509 100644 --- a/tests/homeworld-revival-deployment.test.ts +++ b/tests/homeworld-revival-deployment.test.ts @@ -96,7 +96,7 @@ void test('high Tleilax selects only actual normal Free-Revived forces from a mi } }); -void test('reaching the high threshold returns an explicit unresolved block while remaining low grants nothing', () => { +void test('reaching the high threshold grants the same return’s redeployment while remaining low grants nothing', () => { for (const [faction, start, elite] of [ ['fremen', 2, 1], ['tleilaxu', 8, 0], @@ -113,7 +113,11 @@ void test('reaching the high threshold returns an explicit unresolved block whil assert.ok(result); assert.equal(result.beforePopulation, start); assert.equal(result.afterPopulation, start + 1); - assert.match(result.blocked!, /first reaches.*threshold.*timing ruling/); + // Provisional composition policy: the population resulting from this + // return decides the ability, so the crossing return itself may redeploy. + assert.equal(result.blocked, null); + assert.equal(result.elite, faction === 'fremen' ? elite : 0); + assert.equal(result.normal, faction === 'tleilaxu' ? group.free : 0); const low = fixture(faction, start - 1); assert.equal( quoteHomeworldRevivalDeployment( diff --git a/tests/homeworld-revival-return.test.ts b/tests/homeworld-revival-return.test.ts index 5d966fba..b36f2f81 100644 --- a/tests/homeworld-revival-return.test.ts +++ b/tests/homeworld-revival-return.test.ts @@ -154,7 +154,7 @@ void test('original fields, eligible subset and unknown saved fields cannot be c } }); -void test('constructor rejects impossible quotes, free source mismatches and unresolved threshold crossings', () => { +void test('constructor rejects impossible quotes, free source mismatches and inconsistent populations', () => { const { original } = fixture(); for (const patch of [ { group: { amount: 0, elite: 0, free: 0 } }, @@ -163,7 +163,7 @@ void test('constructor rejects impossible quotes, free source mismatches and unr { group: { amount: 3, elite: 2, free: 4 } }, { quote: { ...original.quote, normal: 1 } }, { quote: { ...original.quote, elite: 1 } }, - { quote: { ...original.quote, beforePopulation: 2, afterPopulation: 5 } }, + { quote: { ...original.quote, beforePopulation: 3, afterPopulation: 5 } }, { quote: { ...original.quote, blocked: 'Timing unresolved' } }, { source: 'emperorExtra' }, { source: 'ghola' },