Connect provisional Advanced Richese mixed battles and exchanges
This commit is contained in:
1 parent
4ed6beb33a
commit
85729f9819
13 files changed
+127
-67
No files matched your search
+1
-5
@@ -3266,15 +3266,13 @@ function policyActions(g: GameView): Action[] {
|
||||
: null;
|
||||
if (
|
||||
reveal &&
|
||||
((g.advanced && physicalAlongsideMarker) ||
|
||||
!me.shipped ||
|
||||
(!me.shipped ||
|
||||
(me.moved ?? 0) >= (me.movesAllowed ?? 1))
|
||||
)
|
||||
return [reveal];
|
||||
const markerTargets = targets.filter(
|
||||
(to) =>
|
||||
to.t !== MOBILE_STRONGHOLD &&
|
||||
(!g.advanced || countAt(me, to.t) === 0) &&
|
||||
!territoryEntryBlock(g.players, me.id, to.t),
|
||||
);
|
||||
if (noField?.canShip && ownNoField && !marker && !me.shipped) {
|
||||
@@ -3380,7 +3378,6 @@ function policyActions(g: GameView): Action[] {
|
||||
(me.faction !== 'fremen' || fremenReserveEntry(to.t)),
|
||||
)
|
||||
.slice(0, 24)) {
|
||||
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),
|
||||
@@ -3512,7 +3509,6 @@ function policyActions(g: GameView): Action[] {
|
||||
for (const to of targets
|
||||
.filter(
|
||||
(to) =>
|
||||
(!g.advanced || marker?.location.territory !== to.t) &&
|
||||
movingSources.some((source) =>
|
||||
botGroundMoveAllowed(g, me, source.from, to.key, source.elite),
|
||||
),
|
||||
|
||||
+13
-17
@@ -730,8 +730,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[];
|
||||
/** Provisional mixed-pool participation, separate from the hidden value. */
|
||||
mixedNoFieldPlayers?: string[];
|
||||
ecazSpecialKarama?: EcazSpecialKaramaDeclaration;
|
||||
truthPromises?: BattlePromise[];
|
||||
poisonTooth?: Record<string, boolean>;
|
||||
@@ -3937,8 +3937,6 @@ function noFieldAllyOfferBlock(
|
||||
return 'No-Field inventory is not initialized.';
|
||||
if (ally.faction === 'fremen')
|
||||
return 'Fremen reserves are on the planet; this ability ships off-planet reserves.';
|
||||
if (g.advanced && (ally.faction === 'guild' || g.karamaShipping?.player === ally.id))
|
||||
return 'Allied No-Field pricing with Guild or Karama discounts awaits a ruling.';
|
||||
if (
|
||||
g.richeseAllyBlocked?.turn === g.turn &&
|
||||
g.richeseAllyBlocked.recipient === ally.id
|
||||
@@ -4095,10 +4093,8 @@ function validateNullentropyBegin(g: Game, owner: Player, cardId: string) {
|
||||
requireRule(!reserved, reserved ?? 'The Box is already committed.');
|
||||
const limit = handLimit(owner);
|
||||
requireRule(
|
||||
g.advanced ? owner.hand.length < limit : owner.hand.length <= limit,
|
||||
g.advanced
|
||||
? 'Full-hand Nullentropy Box use is unresolved; the current guard requires a pre-existing free hand slot.'
|
||||
: 'The final exchanged hand must not exceed its hand limit.',
|
||||
owner.hand.length <= limit,
|
||||
'The final exchanged hand must not exceed its hand limit.',
|
||||
);
|
||||
const guild = byFaction(g, 'guild');
|
||||
requireRule(
|
||||
@@ -4166,7 +4162,7 @@ function finishNullentropy(
|
||||
cardId,
|
||||
remaining.map((c) => c.id),
|
||||
handLimit(owner),
|
||||
!g.advanced,
|
||||
true,
|
||||
);
|
||||
} catch (error) {
|
||||
throw new RuleError(
|
||||
@@ -4193,7 +4189,7 @@ function finishNullentropy(
|
||||
cardId,
|
||||
order,
|
||||
handLimit(owner),
|
||||
!g.advanced,
|
||||
true,
|
||||
);
|
||||
owner.hand = result.ownerHand;
|
||||
g.discard = result.discard;
|
||||
@@ -4257,7 +4253,7 @@ function nullentropyView(g: Game, owner: Player) {
|
||||
(c) => richeseCardDefinition(c)?.card.effect === 'nullentropyBox',
|
||||
);
|
||||
if (!card) return null;
|
||||
const fullHandExchange = !g.advanced && owner.hand.length === handLimit(owner);
|
||||
const fullHandExchange = owner.hand.length === handLimit(owner);
|
||||
if (pending?.player === owner.id) {
|
||||
const blocked = nullentropyIntegrity(g);
|
||||
return {
|
||||
@@ -31450,15 +31446,15 @@ 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);
|
||||
let basicMixedNoFieldPlayers: string[] | undefined;
|
||||
let mixedNoFieldPlayers: 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);
|
||||
requireRule(!combined,
|
||||
'Combined Occupy No-Field battle dialing awaits a ruling. Ordinary mixed battles use the visible provisional pool.');
|
||||
(mixedNoFieldPlayers ??= []).push(playerId);
|
||||
}
|
||||
if (combined) {
|
||||
requireRule(ecazOccupyCompositionSupported(g),
|
||||
@@ -31478,7 +31474,7 @@ function applyActionInner(
|
||||
? { preLeader: { event: battleEvent, ready: [], closed: false } }
|
||||
: {}),
|
||||
...(noFieldPlayers.length ? { noFieldPlayers } : {}),
|
||||
...(basicMixedNoFieldPlayers ? { basicMixedNoFieldPlayers } : {}),
|
||||
...(mixedNoFieldPlayers ? { mixedNoFieldPlayers } : {}),
|
||||
prepared: true,
|
||||
plans: {},
|
||||
revealed: false,
|
||||
@@ -32991,7 +32987,7 @@ export function viewGame(state: Game, id: string) {
|
||||
) as Record<string, StrongholdId | null>,
|
||||
tieWinner: battleTieWinner(g),
|
||||
noFieldPlayers: [b.attacker, b.defender].filter(player => battleNoFieldPlan(g, player)),
|
||||
...(b.basicMixedNoFieldPlayers?.includes(id) ? { basicMixedNoField: true as const } : {}),
|
||||
...(b.mixedNoFieldPlayers?.includes(id) ? { mixedNoField: true as const } : {}),
|
||||
ownForces: [b.attacker, b.defender].includes(id)
|
||||
? planCombatForces(g, me, getPlayer(g, b.attacker === id ? b.defender : b.attacker))
|
||||
: null,
|
||||
|
||||
@@ -160,7 +160,7 @@ export const FACTION_RULES: Record<
|
||||
'Before the regular auction sequence, Black Market may offer a concealed card from your hand. The buyer pays you. An unsold card stays in your hand; a completed sale reduces the regular auction pool by one. Atreides can inspect the offered card.',
|
||||
'Once per game, spend Karama and three spice to acquire an eligible cache card privately for your hand. This is separate from an auction purchase.',
|
||||
'Richese occupation of Tuek’s Sietch also prevents the Fremen special victory in Advanced play.',
|
||||
'Black Market self-purchase, Ixian substitution on special lots and wider card interactions remain guarded in development games.',
|
||||
'Ordinary mixed No-Field battles and Guild/active-Karama allied prices share Basic’s visibly provisional policies, with native Advanced support and shipment-stop timing. Full-hand Box exchange is likewise provisional. Black Market self-purchase and wider card interactions remain guarded; supplied p34 excludes Ixian Technology from Richese auctions.',
|
||||
],
|
||||
},
|
||||
ecaz: {
|
||||
|
||||
+8
-8
@@ -949,10 +949,10 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'During the paid decision, inspect the cards and use Take on one card to finish. Inspection itself does not select anything. There is no free cancellation after viewing the pile and no second confirmation after selection. If exactly one legal card is available, the server selects it automatically without asking for an identical acknowledgement.',
|
||||
'Payment is recorded once before inspection. Refresh or reconnect restores an unfinished paid search for its owner without charging again. While it is open, other gameplay actions wait so they cannot change the reserved Box or discard pile. The completed search saves the recovered card and shuffled pile before restoring an interrupted response, decision or phase opening. Recovery does not charge, select or shuffle again, and does not replay the earlier action.',
|
||||
'The selected card enters your hand privately. Other players do not receive the selected identity or the shuffled discard faces. Temporary search access ends on completion; the normal discard pile remains private, even though the played Box is placed face up on top. Past inspection records do not become a permanent live discard browser.',
|
||||
'An active Truthtrance question and exact card or transaction commitments must resolve as required. Basic full-hand holders now have a visibly provisional atomic exchange: retrieve one non-Box and discard the played Box, leaving the final hand at its current limit. The printed intermediate-capacity question remains unresolved. Advanced still requires a pre-existing spare slot, and Basic over-limit hands are rejected.',
|
||||
'An active Truthtrance question and exact card or transaction commitments must resolve as required. Basic and Advanced full-hand holders have a visibly provisional atomic exchange: retrieve one non-Box and discard the played Box, leaving the final hand at its current limit. The printed intermediate-capacity question remains unresolved; over-limit hands are rejected.',
|
||||
'A pending Guild refund claim on a discarded shipping Karama also blocks starting the search while that provisional refund policy is unresolved. The guard protects the competing claim to that card; it is not a general ban on Box during Shipment. Empty or only-Box searches are rejected before payment under the current unsupported-empty-search policy; there is no fallback deck draw.',
|
||||
'All four AI profiles start only when their own projected availability and spice reserve allow it. They do not inspect unpaid discard contents. Once paid, they choose from their entitled cards, prefer existing functional effects over unfinished Richese faces and complete a legal selection even if only unfinished effects remain. A paid search takes priority over optional actions.',
|
||||
'The paid search and continuations are integrated in development fixtures, with a connected Basic full-hand prototype. Advanced full-hand activation and the provisional Guild refund interaction remain guarded. The explicit Semuta preview can recover the used Box after a clean completed search without replaying payment or shuffle; other discard compositions remain separate. Full Richese starts and complete browser/mobile/full-game acceptance remain gated.',
|
||||
'The paid search and continuations are integrated in development fixtures, with a connected full-hand prototype in both modes. The provisional Guild refund interaction remains guarded. The explicit Semuta preview can recover the used Box after a clean completed search without replaying payment or shuffle; other discard compositions remain separate. Full Richese starts and complete browser/mobile/full-game acceptance remain gated.',
|
||||
],
|
||||
related: [
|
||||
'card-richese-nullentropy-box',
|
||||
@@ -966,7 +966,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
area: 'Implementation',
|
||||
status: 'Partial',
|
||||
detail:
|
||||
'Paid inspection, exact private custody, shuffled remainder, Box on top, sole-choice completion and restored parent continuations are integrated. Basic full-hand exchange has a connected provisional final-capacity policy. Advanced full-hand and Guild-refund guards remain; wider discard/commitment refinement is separate.',
|
||||
'Paid inspection, exact private custody, shuffled remainder, Box on top, sole-choice completion and restored parent continuations are integrated. Basic and Advanced full-hand exchange use a connected provisional final-capacity policy. Guild-refund guards remain; wider discard/commitment refinement is separate.',
|
||||
},
|
||||
{
|
||||
area: 'Player controls',
|
||||
@@ -1017,15 +1017,15 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'A normal No-Field shipment pays the one-force price. The concealed token moves like a force. Before Battle, Richese may reveal it voluntarily; a token involved in battle is revealed with the Battle Plan. Revelation brings the indicated forces from reserves, limited by what remains there.',
|
||||
'The revealed token stays face up until another token is placed. Revealing it does not clear the previous-shipment restriction. An allied No-Field shipment reveals immediately and uses the ally’s reserves; an existing token must be revealed before another shipment.',
|
||||
'Before an eligible ally uses its shipment, the game holds an offer opportunity for Richese. Richese may pass or privately propose a token, destination and payment. The ally accepts the exact offer and chooses its permitted regular/elite mix, or declines without payment. Success consumes the ally’s shipment, not Richese’s shipment or either player’s movement.',
|
||||
'Ordinary allied shipment costs one spice into a stronghold or two elsewhere, funded by either ally or one each at cost two. Basic Guild recipients and already-active Karama rates now use a visibly provisional one-spice price, paid entirely to bank even if Richese funds it; their Advanced counterparts remain guarded. Prevention precedes payment and the old-marker/new-token reveals. Fremen reserves are on the planet and remain ineligible.',
|
||||
'Ordinary allied shipment costs one spice into a stronghold or two elsewhere, funded by either ally or one each at cost two. Guild recipients and already-active Karama rates use a visibly provisional one-spice price in both modes, paid entirely to bank even if Richese funds it. Prevention precedes payment and the old-marker/new-token reveals. Fremen reserves are on the planet and remain ineligible.',
|
||||
'Storm and worm exposure reveal the token and destroy its forces. A zero token still had one-force presence before revelation. Trip to Gamont forces a reveal, then returns one force if present. Revealing a token already in a territory does not trigger Terror a second time.',
|
||||
'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.',
|
||||
'Basic and Advanced 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 support, 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.',
|
||||
'Advanced mixed-force battles, combined Ecaz Occupy, Advanced Guild/Karama allied prices and entire-plan special Karama inspection against a No-Field remain guarded. Homeworld custody, broader Truthtrance and interrupted compositions remain unfinished. Basic mixed-pool and discounted-price prototypes are not publisher-adjudicated formulas. Richese starts remain disabled.',
|
||||
'Combined Ecaz Occupy and entire-plan special Karama inspection against a No-Field remain guarded. Homeworld custody, broader Truthtrance and interrupted compositions remain unfinished. Mixed-pool and discounted-price prototypes in both modes are not publisher-adjudicated formulas. Richese starts remain disabled.',
|
||||
],
|
||||
related: [
|
||||
'richese-cards',
|
||||
@@ -1039,7 +1039,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
area: 'Implementation',
|
||||
status: 'Partial',
|
||||
detail:
|
||||
'Own shipment, movement, reveal, effective presence, collection, hazards, Gamont and marker-only battles work in development fixtures. Basic mixed battles have a provisional private pool and native aftermath. Allied consent, typed forces, full/split funding, prevention and immediate reveal are integrated; Basic Guild/active-Karama offers have a shared provisional one-spice/bank quote. Advanced/combined mixed battles, Advanced discounted allied prices, whole-plan inspection and wider combinations remain separate.',
|
||||
'Own shipment, movement, reveal, effective presence, collection, hazards, Gamont and marker-only battles work in development fixtures. Basic and Advanced mixed battles have a provisional private pool and native support/aftermath. Allied consent, typed forces, full/split funding, prevention and immediate reveal are integrated; Guild/active-Karama offers in both modes have a shared provisional one-spice/bank quote. Combined mixed battles, whole-plan inspection and wider combinations remain separate.',
|
||||
},
|
||||
{
|
||||
area: 'Player controls',
|
||||
@@ -1051,7 +1051,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. 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.',
|
||||
'All four profiles support own shipment, marker movement/reveal and marker-only battles using entitled projections. Mixed groups in both modes use the private authoritative battle pool; Basic and Advanced Easy planning/aftermath smokes pass. Allied offer/acceptance preserves human ownership and avoids repeated declined offers and forbidden dial prescience. Complete Richese-game strategy remains unverified.',
|
||||
},
|
||||
{
|
||||
area: 'Documentation',
|
||||
|
||||
@@ -27,19 +27,18 @@ export function guildShipmentCost(
|
||||
const ordinaryAlliedNoFieldPricing = Object.freeze({
|
||||
stronghold: 1, other: 2, bankOnly: false, policy: null,
|
||||
});
|
||||
const basicDiscountedAlliedNoFieldPricing = Object.freeze({
|
||||
const discountedAlliedNoFieldPricing = Object.freeze({
|
||||
stronghold: 1, other: 1, bankOnly: true,
|
||||
policy: 'Provisional Basic pricing: a Guild recipient or already-active Karama rate uses the rounded one-force Guild fee (1 spice), paid entirely to the bank. This price/payee precedence is not an adjudicated publisher ruling.',
|
||||
policy: 'Provisional pricing: a Guild recipient or already-active Karama rate uses the rounded one-force Guild fee (1 spice), paid entirely to the bank. This price/payee precedence is not an adjudicated publisher ruling.',
|
||||
});
|
||||
|
||||
/** Basic first version; Advanced keeps its separate unresolved-entry guard. */
|
||||
/** Shared first-version price/payee policy for Basic and Advanced. */
|
||||
export function alliedNoFieldShipmentPricing(
|
||||
context: { advanced: boolean; karamaShipping?: { player: string } | null },
|
||||
recipient: { id: string; faction: FactionId },
|
||||
) {
|
||||
return !context.advanced &&
|
||||
(recipient.faction === 'guild' || context.karamaShipping?.player === recipient.id)
|
||||
? basicDiscountedAlliedNoFieldPricing : ordinaryAlliedNoFieldPricing;
|
||||
return recipient.faction === 'guild' || context.karamaShipping?.player === recipient.id
|
||||
? discountedAlliedNoFieldPricing : ordinaryAlliedNoFieldPricing;
|
||||
}
|
||||
|
||||
/** A pledge is already escrowed. Selecting a share never spends a donor's
|
||||
|
||||
Reference in new issue
Block a user