Connect Emperor Nexus retention to original Ambassador purchases

This commit is contained in:
Quintine committed 2026-10-15 10:51:03 +11:00
1 parent d3e1c2f242
commit 3ce2e1d32a
19 files changed
+1228 -107

No files matched your search

+5 -24
View File
@@ -6,7 +6,7 @@ import {
emperorNexusPurchaseEvent,
signEmperorNexusPurchase,
validateEmperorNexusPurchase,
type EmperorNexusPurchase,
type EmperorNexusBankPurchase,
} from '../game/nexus-emperor-purchase';
import { emperorNexusEvent } from '../game/nexus-emperor-secret-ally';
@@ -19,8 +19,8 @@ const context = {
],
};
function purchase(): EmperorNexusPurchase {
const receipt: EmperorNexusPurchase = {
function purchase(): EmperorNexusBankPurchase {
const receipt: EmperorNexusBankPurchase = {
kind: 'purchase',
event: emperorNexusPurchaseEvent(3, 'winner', 2, 1, 4),
owner: 'winner',
@@ -41,9 +41,6 @@ function purchase(): EmperorNexusPurchase {
}
void test('only a positive whole bid affordable personally and without ally contribution can be quoted', () => {
assert.equal(quoteEmperorNexusPurchase(1, 1, 0), undefined);
assert.equal(quoteEmperorNexusPurchase(5, 9, 0), undefined);
assert.equal(quoteEmperorNexusPurchase(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, 0), undefined);
for (const [price, ownSpice, allyPayment] of [
[0, 10, 0], [-1, 10, 0], [1.5, 10, 0], [NaN, 10, 0],
[Infinity, 10, 0], [Number.MAX_SAFE_INTEGER + 1, Number.MAX_SAFE_INTEGER + 1, 0],
@@ -58,26 +55,10 @@ void test('only a positive whole bid affordable personally and without ally cont
}
});
void test('typed signatures are stable across property order and JSON reload; earlier turns remain valid', () => {
const receipt = purchase();
assert.equal(receipt.signature, signEmperorNexusPurchase({ ...receipt, signature: 'ignored' }));
assert.equal(receipt.signature, signEmperorNexusPurchase({
signature: '', afterSpice: 7, beforeSpice: 7, card: 'treachery-17', price: 4, auctionIndex: 1,
advanced: false, faction: 'atreides', sequence: 2, phase: 3,
turn: 3, owner: 'winner', event: receipt.event, kind: 'purchase',
}));
assert.doesNotThrow(() => validateEmperorNexusPurchase(context, JSON.parse(JSON.stringify(receipt))));
assert.doesNotThrow(() => validateEmperorNexusPurchase({ ...context, advanced: true }, {
...receipt, advanced: true, signature: signEmperorNexusPurchase({ ...receipt, advanced: true }),
}));
const minimum = { ...receipt, price: 1, event: emperorNexusPurchaseEvent(3, 'winner', 2, 1, 1), beforeSpice: 1, afterSpice: 1 };
minimum.signature = signEmperorNexusPurchase(minimum);
assert.doesNotThrow(() => validateEmperorNexusPurchase(context, minimum));
});
void test('receipt fields and financial transitions cannot be changed after signing', () => {
const receipt = purchase();
const invalid = (patch: Partial<EmperorNexusPurchase>) =>
const invalid = (patch: Partial<EmperorNexusBankPurchase>) =>
assert.throws(
() => validateEmperorNexusPurchase(context, { ...receipt, ...patch }),
EmperorNexusPurchaseError,
@@ -100,7 +81,7 @@ void test('receipt fields and financial transitions cannot be changed after sign
void test('even re-signed invalid receipts reject future, nonauction, malformed, or non-reimbursed records', () => {
const receipt = purchase();
const invalid = (patch: Partial<EmperorNexusPurchase>) => {
const invalid = (patch: Partial<EmperorNexusBankPurchase>) => {
const changed = { ...receipt, ...patch };
changed.signature = signEmperorNexusPurchase(changed);
assert.throws(() => validateEmperorNexusPurchase(context, changed), EmperorNexusPurchaseError);