Support staged group Guild transport
Verify and publish checkpoint container / container (push) Successful in 12m5s

This commit is contained in:
Quintine committed 2026-10-28 23:52:21 +11:00
1 parent 478b38f5de
commit 2fe97ddd1f
10 files changed
+424 -23

No files matched your search

+47 -15
View File
@@ -1407,6 +1407,7 @@ type PendingGuildTransport = {
elite: number;
cost: number;
allyPayment: number;
coalitionContributions?: CoalitionContribution[];
advisors: boolean;
/** Fresh board transports retain their actual source stance at foreign arrivals.
* Absent on declarations recorded before this source cutover. */
@@ -38198,12 +38199,14 @@ function guildTransportSignature(intent: PendingGuildTransport): string {
if (intent.permission !== undefined) fields.push(intent.permission);
if (intent.advisorArrivalSource !== undefined) fields.push(intent.advisorArrivalSource);
if (intent.noField !== undefined) fields.push(intent.noField);
if (intent.coalitionContributions !== undefined) fields.push(intent.coalitionContributions);
return JSON.stringify(fields);
}
function ordinaryGuildTransportFullCost(g: Game, p: Player, frame: GuildTransportOrder): number | null {
const guild = byFaction(g, 'guild');
if (!guild ||
(p.id !== guild.id && !(p.ally === guild.id && guild.ally === p.id)) ||
(p.id !== guild.id && !(p.ally === guild.id && guild.ally === p.id) &&
!(coalitionFundingAvailable(g, p) && guildGroupAlly(g, p))) ||
g.karamaShipping?.player === p.id ||
(g.guildRateBlocked?.turn === g.turn && g.guildRateBlocked.player === p.id))
return null;
@@ -38256,6 +38259,8 @@ function validateGuildTransportWindow(g: Game, response: ResponseWindow) {
Number.isSafeInteger(intent.elite) && intent.elite >= 0 && intent.elite <= intent.amount - Number(!!marker) &&
Number.isSafeInteger(intent.allyPayment) && intent.allyPayment >= 0 &&
intent.allyPayment <= intent.cost && typeof intent.advisors === 'boolean' &&
(intent.coalitionContributions === undefined || Array.isArray(intent.coalitionContributions) &&
intent.allyPayment === 0 && !marker) &&
(intent.advisorArrivalSource === undefined || g.advanced && shipper.faction === 'beneGesserit' &&
!intent.fromReserves && intent.to !== 'reserves' && intent.advisorArrivalSource === intent.origin) &&
(marker === undefined || typeof marker === 'object' && marker !== null &&
@@ -38299,6 +38304,7 @@ function guildTransportOrder(intent: PendingGuildTransport, cost = intent.cost):
group: intent.group, eliteGroup: intent.eliteGroup, to: intent.to,
sector: intent.sector, amount: intent.amount, elite: intent.elite,
cost, allyPayment: intent.allyPayment, advisors: intent.advisors,
...(intent.coalitionContributions !== undefined ? {coalitionContributions:intent.coalitionContributions.map(share => ({...share}))} : {}),
...(intent.noField ? {noField:{...intent.noField}} : {}),
...(intent.advisorArrivalSource !== undefined ? {advisorArrivalSource:intent.advisorArrivalSource} : {}),
};
@@ -38361,7 +38367,8 @@ function nativeGuildTransportPermission(g: Game, p: Player): boolean {
if (guildTransportCanceled(g, p.id)) return false;
if (p.faction === 'guild') return true;
const guild = byFaction(g, 'guild');
return !!guild && p.ally === guild.id && guild.ally === p.id;
return !!guild && (p.ally === guild.id && guild.ally === p.id ||
coalitionFundingAvailable(g, p) && guildGroupAlly(g, p));
}
function validateGuildTransportForCommit(
g: Game, p: Player, frame: GuildTransportOrder, fromReserves: boolean, guildSecretEvent?:string,
@@ -38410,8 +38417,14 @@ function validateGuildTransportForCommit(
requireRule(cost === guildShipmentCost(
to === 'reserves' ? 'reserves' : territory(to).type, amount, halfRate),
'The declared Guild transport price no longer matches its rate.');
contribution(g, p, cost, allyPayment);
checkShipmentIncomeFunding(g, p, cost, allyPayment);
if (frame.coalitionContributions !== undefined) {
requireRule(allyPayment === 0 && !frame.noField && !guildSecretEvent &&
Array.isArray(frame.coalitionContributions), 'Group funding requires a physical native Guild transport.');
coalitionShipmentQuote(g, p, cost, frame.coalitionContributions);
} else {
contribution(g, p, cost, allyPayment);
checkShipmentIncomeFunding(g, p, cost, allyPayment);
}
checkShipmentPromises(g, p, fromReserves ? { territory: to, amount, sector } : null);
}
function commitGuildTransport(
@@ -38433,18 +38446,32 @@ function commitGuildTransport(
if (guildSecretEvent) recordGuildSecretShipment(g, p.id, guildSecretEvent, 'cross',
{ ...frame, guildSecretEvent });
bindGuildCunningShipment(g, p, 'guild', frame);
payWithAlly(g, p, cost, allyPayment);
if (frame.coalitionContributions !== undefined) {
const result = coalitionRule(() => settleCoalitionFunding({seats:g.players, coalitionState:g.coalitionState,
authorizations:g.coalitionEscrow!.authorizations, pledges:g.coalitionEscrow!.pledges,
purpose:'shipment', payer:p.id, cost, ownAvailable:uncommittedSpice(g, p),
contributions:frame.coalitionContributions!}));
p.spice -= result.quote.own;
g.coalitionEscrow = {...g.coalitionEscrow!, pledges:result.pledges};
} else payWithAlly(g, p, cost, allyPayment);
if (settlement?.recipient) getPlayer(g, settlement.recipient).spice += cost;
if (!movementPreparation) {
const guild = byFaction(g, 'guild');
const guildPayment = guildShipmentIncome({
const groupShares = frame.coalitionContributions;
const groupTotal = groupShares?.reduce((sum, share) => sum + share.amount, 0) ?? 0;
const groupContributors = groupShares ? [{payer:p.id, amount:cost-groupTotal},
...groupShares.map(share => ({payer:share.donor, amount:share.amount}))]
.filter(share => share.payer !== guild?.id && share.amount > 0) : null;
const guildPayment = groupContributors ? groupContributors.reduce((sum, share) => sum + share.amount, 0) : guildShipmentIncome({
guild: guild?.id, shipper: p.id, ally: p.ally, cost, allyPayment,
bankOnly: g.karamaShipping?.player === p.id,
});
if (!settlement?.recipient && guild && guildPayment > 0)
g.response = guildPaymentResponse(g, guild.id, shipmentIncomeContributions(g, p, cost, allyPayment),
allyPayment ? undefined : p.id, [{ payer: p.id, amount: p.id === guild.id ? 0 : cost - allyPayment },
...(allyPayment && p.ally !== guild.id ? [{ payer: p.ally!, amount: allyPayment }] : [])]);
g.response = guildPaymentResponse(g, guild.id,
groupContributors ? groupContributors.map(share => share.amount) : shipmentIncomeContributions(g, p, cost, allyPayment),
groupShares || allyPayment ? undefined : p.id, groupContributors ??
[{ payer: p.id, amount: p.id === guild.id ? 0 : cost - allyPayment },
...(allyPayment && p.ally !== guild.id ? [{ payer: p.ally!, amount: allyPayment }] : [])]);
settleBankerShipmentIncome(g, p, cost, allyPayment);
}
if (fromReserves) {
@@ -45278,15 +45305,20 @@ function applyActionInner(
!(g.guildRateBlocked?.turn === g.turn && g.guildRateBlocked.player === id);
const cost = guildShipmentCost(
to === 'reserves' ? 'reserves' : territory(to).type, n, halfRate);
requireRule(
p.spice + (aidFor(g, p)?.amount ?? 0) >= cost,
'Not enough spice.',
);
const allyPayment = contribution(g, p, cost, action.allyPayment);
checkShipmentIncomeFunding(g, p, cost, allyPayment);
let coalitionContributions: CoalitionContribution[] | undefined;
if (action.coalitionContributions !== undefined) {
requireRule(execution === 'live' && Array.isArray(action.coalitionContributions) &&
!noField && !guildSecretEvent && (action.allyPayment === undefined || action.allyPayment === 0),
'Group funding requires a physical native Guild transport.');
coalitionContributions = action.coalitionContributions as CoalitionContribution[];
coalitionShipmentQuote(g, p, cost, coalitionContributions);
} else requireRule(p.spice + (aidFor(g, p)?.amount ?? 0) >= cost, 'Not enough spice.');
const allyPayment = coalitionContributions === undefined ? contribution(g, p, cost, action.allyPayment) : 0;
if (coalitionContributions === undefined) checkShipmentIncomeFunding(g, p, cost, allyPayment);
const frame: GuildTransportOrder = {
turn: g.turn, player: id, origin, group, eliteGroup, to, sector,
amount: n, elite, cost, allyPayment, advisors,
...(coalitionContributions !== undefined ? {coalitionContributions:coalitionContributions.map(share => ({...share}))} : {}),
...(noField ? {noField:{...noField}} : {}),
...(advisorArrivalSource !== undefined ? {advisorArrivalSource} : {}),
};