Support sector-specific Truthtrance shipments

This commit is contained in:
Quintine committed 2026-10-27 21:19:07 +11:00
1 parent 8006e53580
commit 536d334543
27 files changed
+1121 -71

No files matched your search

+19 -7
View File
@@ -4,7 +4,7 @@ import type { TruthAnswer } from './truthtrance';
import { nativePromiseScopeSupported } from './native-promise-scope';
/** Every shipment leaf describes the same eventual reserve shipment, or its absence. */
export type ShipmentLeaf = { territory: string; minimum: number };
export type ShipmentLeaf = { territory: string; minimum: number; sector?: number };
export type ShipmentExpression =
| ShipmentLeaf
| { constant: TruthAnswer }
@@ -12,7 +12,7 @@ export type ShipmentExpression =
/** Old flat questions and saved promises remain valid without migration. */
export type ShipmentClaim =
| (ShipmentLeaf & { claim?: never })
| { claim: ShipmentExpression; territory?: never; minimum?: never };
| { claim: ShipmentExpression; territory?: never; minimum?: never; sector?: never };
export const SHIPMENT_EXPRESSION_MAX_DEPTH = 4;
export const SHIPMENT_EXPRESSION_MAX_LEAVES = 16;
export class ShipmentClaimError extends Error {}
@@ -37,6 +37,14 @@ function parseLeaf(value: Record<string, unknown>): ShipmentLeaf {
value.minimum <= 20,
'Choose a minimum of one to twenty physical forces.',
);
if (Object.hasOwn(value, 'sector')) {
check(
typeof value.sector === 'number' && Number.isSafeInteger(value.sector) &&
TERRITORIES.find((t) => t.id === value.territory)!.sectors.includes(value.sector),
'Choose a printed sector of the selected destination territory.',
);
return { territory: value.territory, minimum: value.minimum, sector: value.sector };
}
return { territory: value.territory, minimum: value.minimum };
}
/** Bounded software input, not an additional printed limit on asking questions. */
@@ -77,7 +85,8 @@ export function parseShipmentExpression(
return { constant: value.constant };
}
check(
Object.keys(value).sort().join(',') === 'minimum,territory' &&
(Object.keys(value).sort().join(',') === 'minimum,territory' ||
Object.keys(value).sort().join(',') === 'minimum,sector,territory') &&
++leaves <= SHIPMENT_EXPRESSION_MAX_LEAVES,
'Use at most sixteen reserve-shipment statements; other fact or action types cannot be mixed in.',
);
@@ -93,7 +102,8 @@ export function parseShipmentClaim(
check(object(value), 'Choose a valid shipment statement.');
if (Object.hasOwn(value, 'claim')) {
check(
!Object.hasOwn(value, 'territory') && !Object.hasOwn(value, 'minimum'),
!Object.hasOwn(value, 'territory') && !Object.hasOwn(value, 'minimum') &&
!Object.hasOwn(value, 'sector'),
'Choose either a legacy shipment statement or an expression, not both.',
);
return { claim: parseShipmentExpression(value.claim, options) };
@@ -120,7 +130,8 @@ export function validShipmentClaim(
}
export function shipmentExpressionOf(claim: ShipmentClaim): ShipmentExpression {
return (
claim.claim ?? { territory: claim.territory!, minimum: claim.minimum! }
claim.claim ?? { territory: claim.territory!, minimum: claim.minimum!,
...(claim.sector === undefined ? {} : { sector: claim.sector }) }
);
}
export function shipmentClaimDestinations(claim: ShipmentClaim): string[] {
@@ -143,7 +154,7 @@ export function shipmentClaimText(
);
return 'op' in expression
? `(${expression.terms.map(text).join(expression.op === 'and' ? ' AND ' : ' OR ')})`
: `ship at least ${expression.minimum} physical forces from your reserves to ${territory(expression.territory).name}`;
: `ship at least ${expression.minimum} physical forces from your reserves to ${territory(expression.territory).name}${expression.sector === undefined ? '' : `, sector ${expression.sector}`}`;
};
return text(shipmentExpressionOf(claim));
}
@@ -212,7 +223,7 @@ export type ShipmentPromise = ShipmentClaim & {
};
export function matchesShipment(
claim: ShipmentClaim,
shipment: { territory: string; amount: number } | null,
shipment: { territory: string; amount: number; sector?: number } | null,
): boolean | null {
const matches = (expression: ShipmentExpression): boolean | null => {
if ('constant' in expression)
@@ -231,6 +242,7 @@ export function matchesShipment(
}
return !!shipment &&
shipment.territory === expression.territory &&
(expression.sector === undefined || shipment.sector === expression.sector) &&
shipment.amount >= expression.minimum;
};
return matches(shipmentExpressionOf(claim));