Connect group allied transit custody
Verify and publish checkpoint container / container (push) Successful in 10m27s

This commit is contained in:
Quintine committed 2026-10-28 22:59:38 +11:00
1 parent ea3b21f92c
commit 4798a3579b
5 files changed
+152 -23

No files matched your search

+13 -5
View File
@@ -101,14 +101,14 @@ export function makeBasicAlliedShipment(
...(allies.length > 1 ? { allies: witnesses } : {}) };
}
/** Quote physical visitors remaining alongside a current ally; territory is an ID. */
export function quoteBasicAlliedShipment(
/** A live witness is also needed for a concealed zero-force marker. */
export function basicAlliedShipmentHasWitness(
context: ShipmentContext & Readonly<{ turn: number }>, player: ShipmentPlayer,
visit: BasicAlliedShipment | null | undefined,
): { territory: string; normal: number; elite: number } | null {
): boolean {
const allies = currentShipmentAllies(context, player);
if (!visit || context.advanced || context.phase !== 5 || visit.player !== player.id ||
visit.turn !== context.turn) return null;
visit.turn !== context.turn) return false;
if (visit.allies !== undefined) {
if (context.coalitionState === undefined || !Array.isArray(visit.allies) || !visit.allies.length ||
visit.allies.some(id => typeof id !== 'string' || !id || id === player.id) ||
@@ -119,7 +119,15 @@ export function quoteBasicAlliedShipment(
// For group receipts the original witnesses may leave while another current
// ally remains, so consider the complete current membership instead.
const candidates = visit.allies === undefined ? allies.filter(id => id === visit.ally) : allies;
if (!presentShipmentAllies(context, player, visit.territory, candidates).length) return null;
return presentShipmentAllies(context, player, visit.territory, candidates).length > 0;
}
/** Quote physical visitors remaining alongside a current ally; territory is an ID. */
export function quoteBasicAlliedShipment(
context: ShipmentContext & Readonly<{ turn: number }>, player: ShipmentPlayer,
visit: BasicAlliedShipment | null | undefined,
): { territory: string; normal: number; elite: number } | null {
if (!visit || !basicAlliedShipmentHasWitness(context, player, visit)) return null;
const pool = alliedShipmentPhysicalPool(player, visit.territory);
const normal = Math.max(0, pool.normal - visit.baseline.normal);
const elite = Math.max(0, pool.elite - visit.baseline.elite);
+6 -6
View File
@@ -748,8 +748,9 @@ import {
settleAdvisors,
arrivalAsAdvisor,
} from './advisors';
import { isAlliedShipmentVisit, makeBasicAlliedShipment, quoteBasicAlliedShipment,
import { isAlliedShipmentVisit, makeBasicAlliedShipment, quoteBasicAlliedShipment, basicAlliedShipmentHasWitness,
type BasicAlliedShipment } from './allied-separation';
import type { CoalitionState } from './coalition-alliance';
import { casualtyOptions, maxCombatDial, maxCombatSupport, validCombatForces, type Casualties, type CombatForces } from './combat';
import { FACTIONS, faction, type FactionId } from './catalog';
import { quoteLobbyBotConfiguration } from './lobby-bot-configuration';
@@ -1430,6 +1431,8 @@ type RicheseAllyOffer = {
payer: string;
};
export type Game = {
/** Unexposed group-alliance integration state; absent in existing games. */
coalitionState?: CoalitionState;
/** Public written agreements. Their prose is honored by players, not parsed. */
deals?: PublicDeal[];
/** A human-to-human lobby offer, pending the recipient's consent. */
@@ -19742,7 +19745,7 @@ function allowedEntry(
'That sector is in storm.',
);
const blocked = occupancyRule(() =>
territoryEntryBlock(g.players, p.id, t, advisors, temporaryAlliedShipment),
territoryEntryBlock(g.players, p.id, t, advisors, temporaryAlliedShipment, g.coalitionState),
);
requireRule(!blocked, blocked ?? 'This territory cannot be entered.');
}
@@ -23121,10 +23124,7 @@ function alliedSeparationView(g: Game, p: Player): {territories: string[]; noFie
function basicAlliedShipmentView(g: Game, p: Player) {
const visit = g.basicAlliedShipment;
if (g.advanced || g.phase !== 5 || !visit || visit.player !== p.id ||
visit.turn !== g.turn || p.ally !== visit.ally) return null;
const ally = getPlayer(g, visit.ally);
if (ally.ally !== p.id || !at(ally, visit.territory) || sharesEcazOccupation(g, p, visit.territory)) return null;
if (!visit || !occupancyRule(() => basicAlliedShipmentHasWitness(g, p, visit))) return null;
const quote = occupancyRule(() => quoteBasicAlliedShipment(g, p, visit));
const deployed = p.noField?.deployed;
const noField = !!visit.noField && deployed?.tokenId === visit.noField && deployed.controller === p.id &&