diff --git a/components/game-table.tsx b/components/game-table.tsx index c0eb0ed1..780564e3 100644 --- a/components/game-table.tsx +++ b/components/game-table.tsx @@ -5001,7 +5001,9 @@ export function GameTable({

{ownNoField && g.battle.noFieldPlayers.includes(me.id) - ? `Your concealed No-Field can reveal ${battleForces} physical forces from your current reserves. This estimate is private.` + ? g.battle.basicMixedNoField + ? `Provisional Basic mixed No-Field pool: ${battleForces} fighters, combining forces already here with the token's reserve-limited materialization. This estimate is private; the mixed-pool formula is not an adjudicated publisher ruling.` + : `Your concealed No-Field can reveal ${battleForces} physical forces from your current reserves. This estimate is private.` : occupyProfile ? `${battleForceOwnerName} supplies ${battleForces} physical fighters for the variable dial.` : `You have ${battleForces} fighting forces here.`} diff --git a/docs/CURRENT_STATUS.md b/docs/CURRENT_STATUS.md index 9d9c2440..f8a2c00d 100644 --- a/docs/CURRENT_STATUS.md +++ b/docs/CURRENT_STATUS.md @@ -53,6 +53,12 @@ no-exit policy sends only excess new visitors to Tanks when the turn ends; old co-occupation timing remains separate. Three brief engine scenarios pass, including Medium departure, old/ally force preservation and stronghold capacity. Advanced entry and concealed/optional arrivals remain unchanged. +**Basic Richese mixed No-Field battle prototype.** Physical forces plus a +concealed token can enter ordinary Basic battles. The private pool uses existing +physical forces plus reserve-limited token materialization, visibly marked as a +provisional formula. Native sealing/reveal, casualties, controls and legal AI +are connected. One before/after engine smoke completes the battle; types pass. +Advanced mixed battles and combined Occupy remain guarded. diff --git a/docs/RICHESE_NO_FIELD_ENGINE_AUDIT.md b/docs/RICHESE_NO_FIELD_ENGINE_AUDIT.md index aba7e122..d1fd0db3 100644 --- a/docs/RICHESE_NO_FIELD_ENGINE_AUDIT.md +++ b/docs/RICHESE_NO_FIELD_ENGINE_AUDIT.md @@ -2,6 +2,18 @@ Read-only audit, 2026-09-06, followed by a bounded integration checkpoint. The hook findings below describe the engine at the original audit; line numbers are approximate because implementation is concurrent. Function/action names are the durable anchors. It supplements `RICHESE_NO_FIELD_RULES.md` and does not certify a playable Richese expansion. +## Basic mixed runtime amendment — 8 October 2026 + +Ordinary Basic physical-plus-marker battle admission now has a connected, +visibly provisional additive private pool, native joint reveal and actual +casualty/aftermath path. Basic bots may form mixed groups; Advanced/combined +Occupy guards remain. One direct before/after smoke and current types pass; +actual SSR Battle Wheel/plan controls were observed in Chromium. +[The current contract](RICHESE_NO_FIELD_RULES.md#basic-mixed-battle-prototype--8-october-2026) +states the unresolved formula and refinement boundaries. The earlier +mixed-battle absence/avoidance claims in the checkpoint below are historical, +not the current Basic implementation. + ## Current resolution of the audited hooks - **Implemented in development fixtures:** separate physical `Player.noField` custody; public `force-presence` counting and location keys; own No-Field shipment with cancellation/payment stages; physical-plus-marker or marker-only movement; event-fenced voluntary reveal; collection and occupancy/control consumers; exact exposed storm sector and unprotected worm materialization/casualties; marker-only prospective battle pools, joint reveal and persistent zero-battle identity; server-enforced ordinary prescience no-dial restriction; allied opportunity/private consent/typed immediate shipment with fixed full or equal-split funding; Gamont reveal at a selected marker location followed by one actual force returned when present, including zero and colocated ordinary-unit cases. diff --git a/docs/RICHESE_NO_FIELD_RULES.md b/docs/RICHESE_NO_FIELD_RULES.md index 46012454..d48e613e 100644 --- a/docs/RICHESE_NO_FIELD_RULES.md +++ b/docs/RICHESE_NO_FIELD_RULES.md @@ -2,6 +2,39 @@ Audit date: 2026-09-06. This document distinguishes the printed token lifecycle from engine integration and unresolved combinations. It does not certify Richese, Homeworlds, or any other gated game mode. +## Basic mixed battle prototype — 8 October 2026 + +The Basic-first pass now admits an ordinary Richese battle containing both +physical forces and a concealed No-Field. It reuses the existing private +prospective pool: **physical forces already in the territory plus +`min(token value, current reserves)`**. This is a visibly provisional +mixed-pool interpretation, not an adjudication of the FAQ's reserve-cap +ambiguity below. Advanced mixed battles and combined Ecaz Occupy remain +guarded for their separate pass/composition work. + +`Battle.basicMixedNoFieldPlayers` retains initial mixed participation. +Only the participating owner's view receives `battle.basicMixedNoField` +and its private prospective bounds. The actual Battle Wheel explains the +formula and its provisional status; marker-only wording no longer claims +that the mixed pool consists entirely of reserve materialization. +Basic AI may form mixed groups and uses the authoritative own force quote, +without consulting an opponent's hidden denomination. + +The existing sealed-plan path materializes the token once after both plans +are submitted, preserving ordinary physical units. Native losses and +aftermath then operate on actual counters. Ordinary Atreides dial secrecy, +the whole-plan inspection guard, token history and public-mode gates are +unchanged. Reserve changes during sealed plans and wider mixed-sector, +typed-unit, skill and allied interactions remain for refinement. + +**Brief smoke:** reproduced the old Basic admission rejection; the same +two-force/three-token fixture now quotes five fighters privately, provides +legal Easy plans, keeps the first plan concealed, materializes three once, +and completes native losses/aftermath with twenty total physical counters. +Chromium observed actual `GameTable` SSR Battle Wheel/plan controls and the +five-fighter provisional warning. Types pass. No broad suite or deployed +acceptance was run. + ## Authority - [GF9 CHOAM & Richese rulebook](https://www.gf9games.com/dune/wp-content/uploads/2021/11/CHOAM-Rulebook-low-res.pdf), printed pp.5–6,10–12. The full publisher-authored PDF was read from `/tmp/dune-rules/choam-lelekan-mirror.pdf` and its extracted text; fresh publisher-indexed excerpts corroborate the No-Field paragraph and FAQ. The official direct URL is inaccessible from this environment, while the older local files named `choam-primary.pdf` and `choam-publisher-audit.pdf` are HTML errors. @@ -55,7 +88,7 @@ This table records the original adapter contract. The implemented subset and cur ## Unresolved or compositional boundaries 1. **Reserve shortage when destroyed.** Voluntary/battle text explicitly caps by reserves; the destruction sentence says to lose the indicated quantity without repeating the shortage clause. The pure model caps materialization to avoid creating physical units, but that is conservation-based composition for storm/worm losses, not an independently answered FAQ. It does not select substitute losses from unrelated board forces. -2. **Mixed forces and battle dials.** The FAQ forbids dialing more than reserves in a No-Field battle. A territory can also contain ordinary forces (the Smuggler answer demonstrates mixed presence), but no retrieved answer expressly defines that limit with existing forces, multiple sectors, typed units or independently committed reserve uses. Keep ordinary board forces and token materialization separate; do not certify a guessed global cap formula. +2. **Mixed forces and battle dials.** The FAQ forbids dialing more than reserves in a No-Field battle. A territory can also contain ordinary forces (the Smuggler answer demonstrates mixed presence), but no retrieved answer expressly defines that limit with existing forces, multiple sectors, typed units or independently committed reserve uses. The Basic prototype above uses an explicitly provisional additive pool; this does not settle the source question. Advanced and combined Occupy remain guarded. 3. **Full-plan special Karama and structured Truthtrance.** The ordinary Atreides dial prohibition is explicit. Whether a broader entire-plan power or a particular promise question overrides it was not resolved by a retrieved specific answer. Do not leak the value through private legal-plan previews, public maximum dials, error strings or bot reasoning. 4. **Homeworld use.** R3 recommends Richese in a Homeworld combination but supplies no retrieved No-Field-specific rule defining off-planet hidden custody, one-on-planet versus off-planet coexistence, or homeworld threshold disclosure. General module availability is not an answer to those lifecycle questions. The isolated model represents a single Arrakis marker and does not authorize Homeworld placement. 5. **Capturing, transferring or returning a concealed marker as a force.** Movement-as-a-force and Gamont's explicit forced reveal do not establish a general conversion for every card or Guild transport-to-reserves. A generic return helper must not erase a hidden token without a source-backed reveal/custody cause. This module exposes no such automatic escape from last-use history. @@ -80,7 +113,7 @@ Root integration review also corrected the Polar Sink address: its ordinary boar `game/force-presence.ts` distinguishes presence from actual units, and map/occupancy, battle discovery, collection and control use that distinction. A marker is one collector even at zero. Storm checks the exact exposed marker sector before materialization/casualties; sheltered Imperial Basin and other sectors are preserved in focused checks. Unprotected worm destruction runs the actual reveal/casualty path, including a real Thumper trigger. Protected-worm and additional removal effects remain bounded by the source interpretations above. -Marker-only battle admission computes the owner's prospective pool from denomination and remaining reserves. The view exposes that pool only to its owning combatant; another player sees its own pool or none. Both plans seal before public reveal materializes the marker. The battle's `noFieldPlayers` flag persists, so zero still has its leader/cards/traitor resolution. Ordinary Atreides dial prescience is rejected on the server and avoided by controls/AI. Mixed physical-plus-marker battle admission and special whole-plan inspection are guarded pending the unresolved rules. Physical mixed movement being supported is not approval of a guessed mixed battle dial formula. Broader Truthtrance remains an audit boundary. +Marker-only battle admission computes the owner's prospective pool from denomination and remaining reserves. The view exposes that pool only to its owning combatant; another player sees its own pool or none. Both plans seal before public reveal materializes the marker. The battle's `noFieldPlayers` flag persists, so zero still has its leader/cards/traitor resolution. Ordinary Atreides dial prescience is rejected on the server and avoided by controls/AI. Basic physical-plus-marker admission now has the provisional prototype above; Advanced/combined mixed admission and special whole-plan inspection remain guarded. Broader Truthtrance remains an audit boundary. **Player controls — Partial.** `components/richese-no-field.tsx` provides concealed board markers plus owner-only denomination inventory, last-use status, token selection, ordinary one-force price/ally-share feedback and authoritative unavailable reasons. The parent table adds explicit marker movement selection and private prospective battle bounds. Token identifiers and hidden values are absent from rival board projections. The separate `components/richese-allied-no-field.tsx` supplies owner opportunity/proposal/payment controls and private recipient accept/decline/typed-force controls. Own shipment, marker movement and invitation/reveal have passed browser checks; an allied owner proposal was checked, but recipient browser acceptance and mobile remain pending. diff --git a/docs/RULE_DECISIONS.md b/docs/RULE_DECISIONS.md index 9ace19e6..48fa8b6d 100644 --- a/docs/RULE_DECISIONS.md +++ b/docs/RULE_DECISIONS.md @@ -1,6 +1,24 @@ # Rule decision index -Updated 7 October 2026. This is the authoritative navigation index for existing decisions, not a replacement rulebook or a new ruling. Detailed linked contracts retain their source citations and exact supported boundaries. Runtime follow-ups supersede historical absence claims; passing tests establish software behavior, not publisher authority. +Updated 8 October 2026. This is the authoritative navigation index for existing decisions, not a replacement rulebook or a new ruling. Detailed linked contracts retain their source citations and exact supported boundaries. Runtime follow-ups supersede historical absence claims; passing tests establish software behavior, not publisher authority. + +## Content-first Basic prototypes — 8 October 2026 + +The user's Basic-first instruction authorizes usable first versions before +interaction refinement. The following implementation policies are explicitly +provisional, **not numerical user rulings or newly located publisher answers**. +They supersede earlier software-absence claims only within their named scope. + +| Basic behavior | First-version policy | Canonical contract | +| --- | --- | --- | +| Odd-force Ecaz Occupy | Main paragraph: ceiling contribution/loss, floor survivors; conflicting FAQ remains unresolved. | [Occupy](ECAZ_OCCUPY_RULES.md) | +| New-ally Duke loan | Optional existing-disc loan after Ambassador alliance consent; one battle and unused turn-end set-aside. | [Duke acquisition](ECAZ_DUKE_ACQUISITION.md) | +| Tleilaxu Ambassador leader alternative | First-death own native leader revives free instead of forces, independently of ordinary revival allowance. | [Ambassador](TLEILAXU_AMBASSADOR_RULES.md) | +| Ordinary allied shipment transit | Depart during the shipper's turn; excess new physical visitors still present at ending go to Tanks, even when exit was prevented. Old co-occupation timing is separate. | [Allied transit](ALLIED_TRANSIT_RULES.md) | +| Ordinary mixed No-Field battle | Private pool is physical forces plus reserve-limited token materialization; both plans seal before one reveal. Advanced and combined Occupy remain separate. | [No-Field](RICHESE_NO_FIELD_RULES.md) | + +No publication or faction/mode gate is opened. The exact 7 October user +rulings below remain distinct from these prototype choices. ## User rulings — 7 October 2026 diff --git a/game/bots.ts b/game/bots.ts index 7e521bb7..2b4eb95b 100644 --- a/game/bots.ts +++ b/game/bots.ts @@ -3260,7 +3260,7 @@ function policyActions(g: GameView): Action[] { : null; if ( reveal && - (physicalAlongsideMarker || + ((g.advanced && physicalAlongsideMarker) || !me.shipped || (me.moved ?? 0) >= (me.movesAllowed ?? 1)) ) @@ -3268,7 +3268,7 @@ function policyActions(g: GameView): Action[] { const markerTargets = targets.filter( (to) => to.t !== MOBILE_STRONGHOLD && - countAt(me, to.t) === 0 && + (!g.advanced || countAt(me, to.t) === 0) && !territoryEntryBlock(g.players, me.id, to.t), ); if (noField?.canShip && ownNoField && !marker && !me.shipped) { @@ -3374,7 +3374,7 @@ function policyActions(g: GameView): Action[] { (me.faction !== 'fremen' || fremenReserveEntry(to.t)), ) .slice(0, 24)) { - if (marker?.location.territory === to.t) continue; + if (g.advanced && marker?.location.territory === to.t) continue; const desired = Math.min( me.reserves, level === 0 ? (quoteSmugglerShipment(g, me.id, to.t, 2) ? 2 : 1) : Math.max([1, 3, 4, 5][level], to.enemy + 2), @@ -3506,7 +3506,7 @@ function policyActions(g: GameView): Action[] { for (const to of targets .filter( (to) => - marker?.location.territory !== to.t && + (!g.advanced || marker?.location.territory !== to.t) && movingSources.some((source) => botGroundMoveAllowed(g, me, source.from, to.key, source.elite), ), diff --git a/game/engine.ts b/game/engine.ts index 04759038..c49fe617 100644 --- a/game/engine.ts +++ b/game/engine.ts @@ -725,6 +725,8 @@ export type Battle = { preLeader?: { event: string; ready: string[]; closed: boolean }; /** Public marker participation persists after a zero-token reveal. */ noFieldPlayers?: string[]; + /** Provisional Basic mixed-pool participation, separate from the hidden value. */ + basicMixedNoFieldPlayers?: string[]; truthPromises?: BattlePromise[]; poisonTooth?: Record; stoneBurner?: Record; @@ -31382,13 +31384,16 @@ function applyActionInner( (player.id === choice.attacker || player.id === choice.defender || (g.advanced && connected && ecaz && ally?.ally === ecaz.id && player.id === ally.id && (choice.attacker === ecaz.id || choice.defender === ecaz.id)))).map(player => player.id); - for (const playerId of noFieldPlayers) - requireRule( - Object.entries(getPlayer(g, playerId).forces).every( - ([key, n]) => !n || splitLocation(key).territory !== choice.territory, - ), - 'Mixed ordinary-force and No-Field battle dialing awaits a ruling. Reveal voluntarily before Battle while that option is available.', + let basicMixedNoFieldPlayers: string[] | undefined; + for (const playerId of noFieldPlayers) { + const mixed = Object.entries(getPlayer(g, playerId).forces).some( + ([key, n]) => n > 0 && splitLocation(key).territory === choice.territory, ); + if (!mixed) continue; + requireRule(!g.advanced && !combined, + 'Mixed Advanced or combined Occupy No-Field battle dialing awaits a ruling. Basic ordinary mixed battles use the visible provisional pool.'); + (basicMixedNoFieldPlayers ??= []).push(playerId); + } if (combined) { requireRule(ecazOccupyCompositionSupported(g), 'Combined Occupy requires source-selected native factions; optional overlays remain gated.'); @@ -31407,6 +31412,7 @@ function applyActionInner( ? { preLeader: { event: battleEvent, ready: [], closed: false } } : {}), ...(noFieldPlayers.length ? { noFieldPlayers } : {}), + ...(basicMixedNoFieldPlayers ? { basicMixedNoFieldPlayers } : {}), prepared: true, plans: {}, revealed: false, @@ -32919,6 +32925,7 @@ export function viewGame(state: Game, id: string) { ) as Record, tieWinner: battleTieWinner(g), noFieldPlayers: [b.attacker, b.defender].filter(player => battleNoFieldPlan(g, player)), + ...(b.basicMixedNoFieldPlayers?.includes(id) ? { basicMixedNoField: true as const } : {}), ownForces: [b.attacker, b.defender].includes(id) ? planCombatForces(g, me, getPlayer(g, b.attacker === id ? b.defender : b.attacker)) : null, diff --git a/game/reference.ts b/game/reference.ts index 19426a80..de9023ea 100644 --- a/game/reference.ts +++ b/game/reference.ts @@ -1008,6 +1008,7 @@ export const RULE_TOPICS: RuleTopic[] = [ title: 'Richese No-Field tokens', category: 'Advanced & expansions', coverage: 'Partial', + developmentStage: 'Prototyped', summary: 'Concealed zero, three and five tokens represent one force until they are revealed.', steps: [ @@ -1020,9 +1021,10 @@ export const RULE_TOPICS: RuleTopic[] = [ 'Development tables support your own concealed shipment, movement of a marker alone or with a physical group, voluntary reveal and one-force board presence. Physical reserves remain unchanged until reveal. Karama can prevent the No-Field shipment, with Guild shipment prevention and payment handled separately. Price and chosen allied funding are shown before shipment.', 'The map shows a concealed marker without its denomination to everyone. Richese privately sees its token values, last-use restriction and reveal control. Movement selection keeps the marker separate from physical forces. No hidden denomination or physical token identity is included in another player’s projected board state.', 'Marker-only battles use the owner’s private reserve-limited force pool. Both plans must be sealed before the token materializes. A zero-token battle still resolves with its leader, cards and traitor decisions. Ordinary Atreides prescience cannot request this opponent’s dial; other eligible plan elements remain available.', + 'Basic ordinary mixed battles have a provisional private pool: physical forces already here plus the token value capped by current reserves. The Battle Wheel labels that interpretation explicitly. Both plans seal before materialization, then native casualties and aftermath use actual forces. This first version does not adjudicate the FAQ mixed dial cap.', 'Concealed collection, occupancy and control use one-force presence. Exposed storm sectors and unprotected worm destruction reveal and remove the resulting actual forces. Storm shelter and other sectors remain separate. Trip to Gamont at a location with a marker reveals it, even if ordinary forces also occupy that sector, then returns one actual force if present. It is still used if zero forces remain. Canceling the CHOAM power leaves the marker concealed.', 'All four AI profiles choose from their own private token inventory, pay only the one-force shipment price, move the marker explicitly and reveal positive tokens when useful. They retain zero-token presence and use only public information to assess opponents. They also offer safe positive tokens from their own inventory during the allied opportunity, fund them with their own spice and accept or decline private offers using their own force and payment information. A human Richese player’s offer window holds an AI ally’s shipment. Pending responses, decisions and Truthtrance retain priority.', - 'Mixed-force battle dials and entire-plan special Karama inspection against a No-Field are guarded pending rules resolution. Guild-recipient and active-Karama allied pricing, Homeworld custody, broader Truthtrance and some interrupted entry combinations remain unfinished. Ordinary physical movement can coexist with a marker, but this does not settle the mixed battle formula. Richese starts remain disabled.', + 'Advanced mixed-force battles, combined Ecaz Occupy and entire-plan special Karama inspection against a No-Field remain guarded. Guild-recipient and active-Karama allied pricing, Homeworld custody, broader Truthtrance and interrupted compositions remain unfinished. The Basic mixed-pool prototype is not a completed or publisher-verified formula. Richese starts remain disabled.', ], related: [ 'richese-cards', @@ -1036,7 +1038,7 @@ export const RULE_TOPICS: RuleTopic[] = [ area: 'Implementation', status: 'Partial', detail: - 'Own shipment, movement, voluntary reveal, effective presence, collection, storm/unprotected-worm destruction, Gamont reveal/return and marker-only battle integration work in development fixtures. Allied offers, typed recipient consent, either full payer or a one-each two-spice split, prevention and immediate reveal are integrated. Mixed battles, whole-plan inspection, Guild/Karama allied pricing and broader combinations remain guarded or unfinished.', + 'Own shipment, movement, voluntary reveal, effective presence, collection, storm/unprotected-worm destruction, Gamont reveal/return and marker-only battle integration work in development fixtures. Basic mixed physical/marker battle has a connected provisional pool, native reveal and aftermath. Allied offers, typed recipient consent, full/split funding, prevention and immediate reveal are integrated. Advanced/combined mixed battles, whole-plan inspection, Guild/Karama allied pricing and broader combinations remain guarded or unfinished.', }, { area: 'Player controls', @@ -1048,7 +1050,7 @@ export const RULE_TOPICS: RuleTopic[] = [ area: 'AI', status: 'Partial', detail: - 'All four profiles support own shipment, marker movement/reveal and marker-only battles using entitled projections. They handle the allied offer/acceptance window, preserve human ownership and avoid repeated declined offers, unsupported mixed groups and forbidden dial prescience. Complete Richese-game strategy remains unverified.', + 'All four profiles support own shipment, marker movement/reveal and marker-only battles using entitled projections. Basic mixed groups now use the private authoritative battle pool; one Easy planning/aftermath smoke passes. Allied offer/acceptance preserves human ownership and avoids repeated declined offers and forbidden dial prescience. Complete Richese-game strategy remains unverified.', }, { area: 'Documentation', diff --git a/tests/richese-no-field-bots.test.ts b/tests/richese-no-field-bots.test.ts index b1f4859e..d71a9606 100644 --- a/tests/richese-no-field-bots.test.ts +++ b/tests/richese-no-field-bots.test.ts @@ -188,18 +188,16 @@ void test('positive markers reveal before a new shipment while zero and empty pr } }); -void test('marker candidates honor public occupancy, storm, own physical custody and authoritative blocked flags', () => { +void test('marker candidates honor public occupancy, storm and authoritative blocked flags', () => { for (const level of DIFFICULTIES) { const g = fixture(); g.players[1].forces = { 'arrakeen:9': 2 }; g.players[2].forces = { 'arrakeen:9': 2 }; - g.players[0].forces = { 'carthag:11': 3 }; const v = projection(g, level); assert.ok( noFieldShips(v).every( (a) => a.territory !== 'arrakeen' && - a.territory !== 'carthag' && a.sector !== 18, ), ); @@ -216,17 +214,6 @@ void test('marker candidates honor public occupancy, storm, own physical custody assert.ok( !botActions(foreign).some((a) => a.noField || a.type === 'revealNoField'), ); - const separate = deploy(fixture(), 0); - separate.players[0].shipped = false; - separate.players[0].forces = { 'arrakeen:9': 4 }; - assert.ok( - botActions(projection(separate, level)) - .filter( - (a) => ['ship', 'move', 'guildShip'].includes(a.type) && !a.noField, - ) - .every((a) => a.territory !== 'imperial_basin'), - 'ordinary groups do not create an unsupported mixed marker battle', - ); } });