Support Truthtrance No-Field shipment promises
Verify and publish checkpoint container / container (push) Canceled after 0s

This commit is contained in:
Quintine committed 2026-10-29 10:07:00 +11:00
1 parent 9ab9b9a613
commit 7fccd62814
16 files changed
+658 -32

No files matched your search

+49 -18
View File
@@ -506,11 +506,13 @@ import {
parseShipmentClaim,
validShipmentClaim,
shipmentClaimDestinations,
shipmentClaimHasNoField,
liveShipmentPromises,
shipmentPromiseModeSupported,
shipmentPromiseTurnAvailable,
type ShipmentClaim,
type ShipmentPromise,
type ShipmentReceipt,
} from './shipment-promises';
import { portableSnooperPlanBlock } from './portable-snooper';
import {
@@ -13095,7 +13097,7 @@ function guildBetrayalAuthority(g: Game, continuation: GuildBetrayalContinuation
approvedSupplier = {donor:shipment.alliedNoField.owner,amount:quote.ownerPayment};
}
if (!approvedSupplier) checkShipmentIncomeFunding(g, p, shipment.cost, shipment.allyPayment);
checkShipmentPromises(g, getPlayer(g, shipment.player), shipment);
checkShipmentPromises(g, getPlayer(g, shipment.player), shipmentPromiseReceipt(g, shipment));
shipper = shipment.player; price = shipment.cost;
allyPayment = approvedSupplier?.amount ?? shipment.allyPayment;
originalReceiver = guildShipmentIncome({guild: guild.id, shipper, ally: p.ally,
@@ -33213,6 +33215,7 @@ function shipmentPromiseIntegrity(g: Game) {
(p) => p.id === promise.asker && p.id !== promise.player,
) &&
validShipmentClaim(promise, {allowConstants: promise.mixed !== undefined}) &&
(!shipmentClaimHasNoField(promise) || g.players.some(p => p.id === promise.player && p.faction === 'richese')) &&
typeof promise.answer === 'boolean' &&
(promise.released === undefined ||
typeof promise.released === 'boolean') &&
@@ -33436,6 +33439,8 @@ function findShipmentCompletion(
p.reserves,
p.tanks,
p.elites,
p.noField,
p.noFieldEvent,
g.homeworlds,
p.hand.map((c) => c.id),
g.aid,
@@ -33443,6 +33448,22 @@ function findShipmentCompletion(
]);
if (visited.has(signature)) continue;
visited.add(signature);
if (p.faction === 'richese' && p.noField && !p.noField.deployed && p.noFieldEvent && !noFieldShipBlock(g, p)) {
for (const destination of destinations) for (const sector of territory(destination).sectors) {
const receipt: ShipmentReceipt = { territory: destination, sector, amount: 0, noField: true };
if (!promises.every(promise => matchesShipment(promise, receipt) === promise.answer)) continue;
for (const token of p.noField.tokens.filter(token => token.id !== p.noField!.lastShipped)) {
const action: Action = { type: 'ship', noField: token.id, event: p.noFieldEvent,
territory: destination, sector };
try {
applyActionInner(g, p.id, action, 'movementPreparation');
return { early, actions: [...actions, action] };
} catch (error) {
if (!(error instanceof RuleError)) throw error;
}
}
}
}
for (const destination of destinations) for (let amount = 1; amount <= p.reserves; amount++) {
for (const sector of territory(destination).sectors) {
if (!promises.every(p => matchesShipment(p, { territory: destination, amount, sector }) === p.answer)) continue;
@@ -33530,6 +33551,8 @@ function findShipmentCompletion(
// conversion effects. More reserves/lower cost cannot remove
// a previously available choice of physical shipment count.
const preparations: Action[] = [];
if (p.faction === 'richese' && p.noField?.deployed && p.noFieldEvent)
preparations.push({ type: 'revealNoField', token: p.noField.deployed.tokenId, event: p.noFieldEvent });
if ((g.aid[p.id]?.amount ?? 0) > outgoingFloor)
preparations.push({ type: 'pledgeAid', amount: outgoingFloor });
const karama = p.hand.find((c) => canUseAsKaramaRole(g, p, c));
@@ -33637,7 +33660,7 @@ function bindShipmentTruth(
function checkShipmentPromises(
g: Game,
p: Player,
shipment: { territory: string; amount: number; sector?: number } | null,
shipment: ShipmentReceipt | null,
) {
requireRule(
liveShipmentPromises(g.shipmentPromises ?? [], p.id, g.turn).every(
@@ -33646,24 +33669,36 @@ function checkShipmentPromises(
'Honor your Truthtrance shipment answer before using this shipment opportunity.',
);
if (g.phase === 5 && g.active === p.id) guardTurnActionShipmentPriceBoundary(g, p.id,
liveTurnActionPromises(g.turnActionPromises ?? [], p.id, g.turn), undefined, shipment);
liveTurnActionPromises(g.turnActionPromises ?? [], p.id, g.turn), undefined,
shipment?.noField && shipment.amount === 0 ? null : shipment);
}
/** Preserve physical counts while explicitly recording an owned marker. */
function shipmentPromiseReceipt(g: Game, shipment: PendingShipment): ShipmentReceipt | null {
if (shipment.alliedNoField) return null;
if (!shipment.noField) return { territory: shipment.territory, sector: shipment.sector, amount: shipment.amount };
return { territory: shipment.territory, sector: shipment.sector, noField: true,
amount: shipment.richesePair
? currentRichesePairForces(g, getPlayer(g, shipment.player), shipment.noFieldReserves ?? 0, shipment.richesePair.revealedTokenId).physical
: (shipment.noFieldReserves ?? 0) + (shipment.smugglerCompanion ? 1 : 0) };
}
function finishShipmentPromises(
g: Game,
p: Player,
shipment: { territory: string; amount: number; sector?: number } | null,
shipment: ShipmentReceipt | null,
) {
checkShipmentPromises(g, p, shipment);
// Other promise families retain their original physical-force meaning.
const physical = shipment?.noField && shipment.amount === 0 ? null : shipment;
const probe = (g as TurnActionCardProbe)._turnActionShipmentReceipt;
if (probe?.player === p.id && probe.event === undefined)
probe.event = shipment ? { territory: shipment.territory, amount: shipment.amount,
...(shipment.sector === undefined ? {} : { sector: shipment.sector }) } : null;
probe.event = physical ? { territory: physical.territory, amount: physical.amount,
...(physical.sector === undefined ? {} : { sector: physical.sector }) } : null;
if (g.phase === 5 && g.turnActionPromises) g.turnActionPromises = turnActionRule(() =>
recordTurnActionShipment(g.turnActionPromises!, p.id, g.turn, shipment ? { territory: shipment.territory, amount: shipment.amount,
...(shipment.sector === undefined ? {} : { sector: shipment.sector }) } : null));
recordTurnActionShipment(g.turnActionPromises!, p.id, g.turn, physical ? { territory: physical.territory, amount: physical.amount,
...(physical.sector === undefined ? {} : { sector: physical.sector }) } : null));
if (g.phase === 5 && g.travelPromises) g.travelPromises = travelRule(() => recordTravelShipment(g.travelPromises!, p.id, g.turn,
shipment ? { territory: shipment.territory, amount: shipment.amount,
...(shipment.sector === undefined ? {} : { sector: shipment.sector }) } : null));
physical ? { territory: physical.territory, amount: physical.amount,
...(physical.sector === undefined ? {} : { sector: physical.sector }) } : null));
for (const promise of liveShipmentPromises(
g.shipmentPromises ?? [],
p.id,
@@ -33711,7 +33746,7 @@ function reconcileShipmentPromises(
voluntary.action.type === 'richeseBid' && typeof voluntary.action.amount === 'number' && voluntary.action.amount > 0 ||
voluntary.action.type === 'ready' && [2, 4].includes(voluntary.phase ?? g.phase));
const ownVoluntary = voluntary?.actor === p.id &&
(['card', 'bribe', 'charity', 'alliance', 'pledgeAid', 'emperorGift', 'richeseGift'].includes(voluntary.action.type) ||
(['card', 'bribe', 'charity', 'alliance', 'pledgeAid', 'emperorGift', 'richeseGift', 'revealNoField'].includes(voluntary.action.type) ||
earlyVoluntary ||
future && voluntary.action.type === 'decision');
// A foreign pending parent may spend or refund its approved funding. Wait
@@ -40508,11 +40543,7 @@ function commitShipment(g: Game, shipment: PendingShipment, settlement?: {recipi
finishShipmentPromises(
g,
getPlayer(g, shipment.player),
shipment.noField ? (shipment.richesePair
? { territory: shipment.territory, sector: shipment.sector, amount: currentRichesePairForces(g, getPlayer(g, shipment.player), shipment.noFieldReserves ?? 0, shipment.richesePair.revealedTokenId).physical }
: shipment.noFieldReserves || shipment.smugglerCompanion
? { territory: shipment.territory, sector: shipment.sector, amount: (shipment.noFieldReserves ?? 0) + (shipment.smugglerCompanion ? 1 : 0) }
: null) : shipment.alliedNoField ? null : shipment,
shipmentPromiseReceipt(g, shipment),
);
const {
territory: to,
@@ -47352,8 +47383,8 @@ function applyActionInner(
}),
...(noField ? { noField } : {}),
};
if (noField) checkShipmentPromises(g, p, noFieldGroup!.physical
? { territory: to, amount: noFieldGroup!.physical, sector: s } : null);
if (noField) checkShipmentPromises(g, p,
{ territory: to, amount: noFieldGroup!.physical, sector: s, noField: true });
if (noField && (g.leaderSkills || noFieldReserves) && !richesePair) {
shipment.noFieldSkillProof = noFieldSkillProof(shipment);
validatePhysicalShipment(g, shipment);