Use actual Collection receipts for co-present Orgiz payers
This commit is contained in:
1 parent
bc09ac3cfd
commit
bcd1b1041a
13 files changed
+287
-172
No files matched your search
@@ -169,6 +169,9 @@ export type CollectionQuote = {
|
||||
desert: number;
|
||||
balance: number;
|
||||
}[];
|
||||
/** Positive ordinary map collections in actual storm order, before Orgiz.
|
||||
* Shared escrow has no recipient until its original allocation settles. */
|
||||
deposits: { player: string; location: string; amount: number }[];
|
||||
/** Removed from the map, but not credited until the allies resolve allocation. */
|
||||
shared: SharedSpiceLot[];
|
||||
collectionBonus: {
|
||||
@@ -292,6 +295,7 @@ export function quoteSpiceCollection(
|
||||
const shared: SharedSpiceLot[] = [];
|
||||
const pooled = new Set<string>();
|
||||
const receipts: CollectionQuote['receipts'] = [];
|
||||
const deposits: CollectionQuote['deposits'] = [];
|
||||
for (const id of g.order) {
|
||||
const p = players.find((p) => p.id === id)!,
|
||||
original = g.players.find((p) => p.id === id)!;
|
||||
@@ -334,6 +338,7 @@ export function quoteSpiceCollection(
|
||||
continue;
|
||||
}
|
||||
const amount = Math.min(spice[key] ?? 0, capacity);
|
||||
if (amount > 0) deposits.push({ player: id, location: key, amount });
|
||||
collected += amount;
|
||||
if (sites.some((site) => site.id === t && site.type === 'sand'))
|
||||
desert += amount;
|
||||
@@ -346,7 +351,7 @@ export function quoteSpiceCollection(
|
||||
);
|
||||
receipts.push({ player: id, strongholds, collected, desert, balance });
|
||||
}
|
||||
return { released, spice, receipts, shared, collectionBonus };
|
||||
return { released, spice, receipts, deposits, shared, collectionBonus };
|
||||
}
|
||||
|
||||
export type BattlePhaseEndContext = CollectionContext & {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { isAdvisor } from './advisors';
|
||||
import { gameTerritories, splitLocation, type MobileBoard } from './board';
|
||||
import type { AdvisorRelease } from './board-resolution-quote';
|
||||
import type { AdvisorRelease, CollectionQuote } from './board-resolution-quote';
|
||||
import type { Player } from './engine';
|
||||
import type { SpiceAllocationReceipt } from './ecaz-spice-allocation';
|
||||
import { presenceAt, type ForcePresence } from './force-presence';
|
||||
@@ -40,6 +40,8 @@ export type DiscoveryBaseCollectionQuote = {
|
||||
/** Deposits remaining after ordinary and shared collection. */
|
||||
spice: Readonly<Record<string, number>>;
|
||||
receipts: readonly DiscoveryBaseCollectionReceipt[];
|
||||
/** Actual positive ordinary source receipts, never inferred from occupancy. */
|
||||
deposits: readonly CollectionQuote['deposits'][number][];
|
||||
shared: readonly {
|
||||
territory: string;
|
||||
ecaz: string;
|
||||
@@ -103,36 +105,15 @@ export function discoveryCollectionOccupants(
|
||||
).sort((left,right) => context.order.indexOf(left.id)-context.order.indexOf(right.id));
|
||||
}
|
||||
|
||||
/** Public effective presence only; an ambiguous source cannot identify a payer. */
|
||||
function collectorAt(
|
||||
context: DiscoveryCollectionContext,
|
||||
key: string,
|
||||
released: readonly AdvisorRelease[],
|
||||
): DiscoveryCollectionSeat | undefined {
|
||||
const { territory, sector } = splitLocation(key);
|
||||
let collector: DiscoveryCollectionSeat | undefined;
|
||||
for (const player of context.players) {
|
||||
if (!isCollectingFighter(player, territory, released)) continue;
|
||||
const marker = player.noField?.deployed?.location;
|
||||
if (
|
||||
(player.forces[key] ?? 0) <= 0 &&
|
||||
!(marker?.territory === territory && marker.sector === sector)
|
||||
) continue;
|
||||
if (collector) return undefined;
|
||||
collector = player;
|
||||
}
|
||||
return collector;
|
||||
}
|
||||
|
||||
function transferOrgiz(
|
||||
key: string,
|
||||
collector: DiscoveryCollectionSeat | undefined,
|
||||
collector: string,
|
||||
ownerReceipt: DiscoveryBaseCollectionReceipt,
|
||||
receipts: DiscoveryBaseCollectionReceipt[],
|
||||
effects: DiscoveryCollectionQuote['effects'],
|
||||
) {
|
||||
if (!collector || collector.id === ownerReceipt.player) return false;
|
||||
const collectorReceipt = receipts.find(entry => entry.player === collector.id)!;
|
||||
if (collector === ownerReceipt.player) return false;
|
||||
const collectorReceipt = receipts.find(entry => entry.player === collector)!;
|
||||
if (collectorReceipt.balance === 0) return false;
|
||||
requireCollection(
|
||||
Number.isSafeInteger(ownerReceipt.balance + 1),
|
||||
@@ -141,7 +122,7 @@ function transferOrgiz(
|
||||
collectorReceipt.balance -= 1;
|
||||
ownerReceipt.balance += 1;
|
||||
effects.push({
|
||||
kind: 'orgiz', player: ownerReceipt.player, from: collector.id,
|
||||
kind: 'orgiz', player: ownerReceipt.player, from: collector,
|
||||
location: key, amount: 1, source: 'player',
|
||||
});
|
||||
return true;
|
||||
@@ -152,10 +133,9 @@ function transferOrgiz(
|
||||
* over the canonical ordinary quote. Ordinary `collected` and `desert` remain
|
||||
* factual: Cistern is bank income; Orgiz transfers available player spice.
|
||||
*
|
||||
* Owners resolve in original storm order. Advanced chooses one actual foreign
|
||||
* collector per territory in storm order; Basic retains the provisional
|
||||
* positive-deposit trigger. Ambiguous collected sectors withhold the territory
|
||||
* effect. Shared lots settle after bilateral allocation, not in this quote.
|
||||
* Owners and actual foreign payers resolve in original storm order. Advanced
|
||||
* groups canonical deposits by territory; Basic groups by positive deposit.
|
||||
* Shared escrow settles after bilateral allocation, not in this quote.
|
||||
*/
|
||||
export function quoteDiscoveryCollection(
|
||||
context: DiscoveryCollectionContext,
|
||||
@@ -184,6 +164,7 @@ export function quoteDiscoveryCollection(
|
||||
record(collection.spice) &&
|
||||
Array.isArray(collection.receipts) &&
|
||||
Array.isArray(collection.shared) &&
|
||||
Array.isArray(collection.deposits) &&
|
||||
Array.isArray(collection.released) &&
|
||||
collection.released.every(
|
||||
(entry) => record(entry) &&
|
||||
@@ -250,6 +231,13 @@ export function quoteDiscoveryCollection(
|
||||
),
|
||||
'Discovery collection needs valid retained shared-spice lots.',
|
||||
);
|
||||
requireCollection(
|
||||
collection.deposits.every(entry => record(entry) &&
|
||||
typeof entry.player === 'string' && context.order.includes(entry.player) &&
|
||||
typeof entry.location === 'string' && Object.hasOwn(context.spice, entry.location) &&
|
||||
whole(entry.amount) && entry.amount > 0 && entry.amount <= context.spice[entry.location]),
|
||||
'Discovery collection needs actual positive ordinary deposit receipts.',
|
||||
);
|
||||
|
||||
const effects: DiscoveryCollectionQuote['effects'] = [];
|
||||
const cistern = discoveryCollectionOccupants(context, CISTERN, collection.released);
|
||||
@@ -271,45 +259,26 @@ export function quoteDiscoveryCollection(
|
||||
const orgiz = discoveryCollectionOccupants(context, ORGIZ_PROCESSING_STATION, collection.released);
|
||||
if (orgiz.length) {
|
||||
const ownerReceipts = orgiz.map(owner => receipts.find(entry => entry.player === owner.id)!);
|
||||
const transfers = context.advanced
|
||||
? new Map<string, {
|
||||
collectors: { location: string; collector: DiscoveryCollectionSeat }[];
|
||||
ambiguous: boolean;
|
||||
}>()
|
||||
: undefined;
|
||||
const transfers = new Map<string, CollectionQuote['deposits']>();
|
||||
for (const deposit of collection.deposits) {
|
||||
const event = context.advanced ? splitLocation(deposit.location).territory : deposit.location;
|
||||
const candidates = transfers.get(event);
|
||||
if (candidates) candidates.push(deposit);
|
||||
else transfers.set(event, [deposit]);
|
||||
}
|
||||
// Preserve original source-key event order, independent of force-map order.
|
||||
for (const key of sourceKeys) {
|
||||
if (context.spice[key] <= collection.spice[key]) continue;
|
||||
const { territory } = splitLocation(key);
|
||||
if (
|
||||
collection.shared.some(
|
||||
(lot) => lot.amount > 0 && lot.territory === territory,
|
||||
)
|
||||
) continue;
|
||||
const collector = collectorAt(context, key, collection.released);
|
||||
if (!transfers) {
|
||||
for (const ownerReceipt of ownerReceipts)
|
||||
transferOrgiz(key, collector, ownerReceipt, receipts, effects);
|
||||
continue;
|
||||
}
|
||||
let pending = transfers.get(territory);
|
||||
if (!pending) {
|
||||
pending = { collectors: [], ambiguous: false };
|
||||
transfers.set(territory, pending);
|
||||
}
|
||||
if (!collector) pending.ambiguous = true;
|
||||
else if (!pending.collectors.some(entry => entry.collector.id === collector.id))
|
||||
pending.collectors.push({ location: key, collector });
|
||||
const event = context.advanced ? splitLocation(key).territory : key;
|
||||
const candidates = transfers.get(event);
|
||||
if (!candidates) continue;
|
||||
transfers.delete(event);
|
||||
candidates.sort((left, right) =>
|
||||
context.order.indexOf(left.player) - context.order.indexOf(right.player));
|
||||
for (const ownerReceipt of ownerReceipts)
|
||||
for (const candidate of candidates)
|
||||
if (transferOrgiz(candidate.location, candidate.player, ownerReceipt, receipts, effects))
|
||||
break;
|
||||
}
|
||||
if (transfers)
|
||||
for (const pending of transfers.values()) {
|
||||
if (pending.ambiguous) continue;
|
||||
pending.collectors.sort((left, right) =>
|
||||
context.order.indexOf(left.collector.id) - context.order.indexOf(right.collector.id));
|
||||
for (const ownerReceipt of ownerReceipts)
|
||||
for (const candidate of pending.collectors)
|
||||
if (transferOrgiz(candidate.location, candidate.collector, ownerReceipt, receipts, effects))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { receipts, effects };
|
||||
|
||||
+6
-5
@@ -4226,7 +4226,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'The five revealed location tokens become separate, initially empty territories inside their surroundings. A private peek does not open the location. Normal ground movement enters through the surrounding territory, shipment uses stronghold prices, at most two factions may occupy it, and forces inside are protected from storms and sandworms. Only Jacurutu Sietch counts toward stronghold victory.',
|
||||
'Native typed forces enter as their actual ordinary and elite groups, never as passengers from the mobile stronghold or the concealed value of a No-Field. Richese may ship or move a concealed marker into an actually revealed nested location at sector zero; it remains one effective presence until its original reveal materializes available reserves. CHOAM Card Stash uses its five-card limit, drawing first and choosing any held discard afterward.',
|
||||
'At the start of the next turn, before the storm and mobile stronghold movement, each eligible faction may move any positive subset of its non-advisor groups from the surrounding territory into the newly revealed location. The signed choice preserves ordinary and elite forces across sectors, normal source storm and occupancy restrictions, and costs no spice, shipment or normal movement. The printed text gives no sequence and does not restate the source-storm restriction; processing tokens in reveal order and factions in storm order while excluding storm sources are source inferences.',
|
||||
'During Collection, after ordinary advisor releases, every Cistern fighter occupant receives two bank spice. Orgiz fighter occupants resolve in original storm order. Advanced charges once per territory from the first definite actual foreign collector in storm order that can pay; Basic keeps one transfer per positive deposit with a unique rival collector. Empty purses never create debt. Shared Ecaz desert lots wait for their actual bilateral split, then consider positive foreign recipients Ecaz first, ally second, excluding self and zero shares. Payer and occupant ordering remain provisional. Ambiguous collected-sector payers still withhold their territory effect without blocking ordinary Collection.',
|
||||
'During Collection, after advisor releases, every Cistern fighter occupant receives two bank spice. Orgiz occupants resolve in original storm order using canonical positive ordinary deposit receipts, not force presence or old wallets. Advanced charges once per territory; Basic provisionally charges once per positive deposit. The first actual foreign collector in storm order with available spice pays; self and zero actual collection do not qualify. Advanced shared Ecaz desert lots wait for their real split, then consider positive foreign recipients Ecaz first, ally second. Basic shared settlement remains pending. Payer/occupant ordering is provisional; empty purses never create debt.',
|
||||
'Jacurutu pays one bank spice for each opposing undialed physical force actually sent to the Tanks. Where an Advanced dial admits different physical counts, the defeated player chooses an exact ordinary/elite/paid reading before support, casualties or rewards settle; unique readings remain automatic. This choice timing is a visibly provisional implementation interpretation. Original Harass returns bind their physical dial and actual withdrawal/retreat counters do not earn spice.',
|
||||
'Each non-advisor Testing Station occupant may decrease ordinary storm movement by one, keep it or increase it by one. The provisional order is the original storm order, once per owner, with cumulative adjustments on current distance. Choose after the ordinary card window; traversal/protection starts only after all choices. Weather Control is unaffected. Original natural source/base and existing sole-frame history remain authoritative; legacy movement without a source is not invented.',
|
||||
'A current non-advisor Shrine occupant may play a physical Truthtrance card as Karama, or a physical Karama as Truthtrance, within the implemented timing and effect paths. The printed identity and actual card custody remain unchanged; a committed Truthtrance conversion survives departure. This does not create missing Karama powers or settle their pending rules.',
|
||||
@@ -4236,8 +4236,8 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
checklist: [
|
||||
{
|
||||
area: 'Implementation', status: 'Partial',
|
||||
detail: 'Original classic and supported native Skills/Nexus/Discovery entries retain their original selected modules, Maker votes/typed rides, source-local held benefits, training and owner-labelled coalition aftermath. Mixed Jacurutu now pauses for an exact defeated-player physical reading before settlement, excluding actual returns from Tanks income. Testing Station uses a signed cumulative storm-order owner queue before one traversal. Cistern pays each fighter occupant, and Orgiz charges each occupant in storm order without debt; Advanced shared Ecaz lots settle their real allocation before the transfer. Advanced Orgiz aggregates territory Collection; Basic stays provisional. Other pending sources and public starts remain guarded.',
|
||||
evidence: ['game/discovery-module-profile.ts', 'game/richese-no-field.ts', 'game/discoveries.ts', 'game/discovery-actions.ts', 'game/discovery-entry.ts', 'game/discovery-collection.ts', 'game/discovery-flight.ts', 'game/discovery-battle.ts', 'game/discovery-storm.ts', 'game/shrine.ts', 'game/great-maker.ts', 'game/board.ts', 'game/engine.ts'],
|
||||
detail: 'Original classic and supported native Skills/Nexus/Discovery entries retain their selected modules and rule controls. Mixed Jacurutu uses an exact defeated-player physical reading before native settlement. Testing Station queues signed cumulative storm-order choices before one traversal. Cistern pays each fighter occupant; Orgiz consumes actual positive ordinary deposit receipts in original storm order, grouping by territory in Advanced and deposit in Basic without guessing co-present payers or creating debt. Advanced shared lots wait for bilateral allocation; Basic shared settlement and broader mode acceptance remain open.',
|
||||
evidence: ['game/discovery-module-profile.ts', 'game/richese-no-field.ts', 'game/discoveries.ts', 'game/discovery-actions.ts', 'game/discovery-entry.ts', 'game/discovery-collection.ts', 'game/board-resolution-quote.ts', 'game/discovery-flight.ts', 'game/discovery-battle.ts', 'game/discovery-storm.ts', 'game/shrine.ts', 'game/great-maker.ts', 'game/board.ts', 'game/engine.ts'],
|
||||
},
|
||||
{
|
||||
area: 'Player controls', status: 'Partial',
|
||||
@@ -4251,15 +4251,16 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
},
|
||||
{
|
||||
area: 'Documentation', status: 'Partial',
|
||||
detail: 'Sourced identities and native/classic-skill boundaries are recorded. Advanced Orgiz follows territory Collection; Basic remains provisional per deposit. Recommended Discovery occupant/payer ordering, defeated-player Jacurutu allocation and cumulative Testing Station choices are explicit visible interpretations, not publisher rulings. Great Maker, entry, source-storm inferences and ambiguous collected-sector Orgiz sources remain open.',
|
||||
detail: 'Sourced identities and native/classic-skill boundaries are recorded. Advanced Orgiz follows territory Collection and actual ordinary receipts; Basic retains its provisional positive-deposit mapping. Recommended Discovery occupant/payer ordering, Jacurutu allocation and cumulative Testing Station choices are visible interpretations, not publisher rulings. Great Maker, entry/source-storm inferences and Basic shared Orgiz settlement remain open.',
|
||||
evidence: ['docs/DISCOVERY_COMPONENTS.md', 'docs/DISCOVERY_PROTOTYPE.md'],
|
||||
},
|
||||
{
|
||||
area: 'Verification', status: 'Partial',
|
||||
detail: 'Current Collection batch passes27/27 in3 related files, types/lint and build. Genuine setup, token reveal/entry, alliance, departure and paid shipments produce actual shared desert allocation; equal/full/zero shares, acceptance timing, wrong actor, stale allocation, payer order and depletion are exercised. Actual390px proposal4/0 preserves wallets until acceptance, then produces Ecaz10/Guild12 and one Orgiz transfer with Ready restored. The earlier Jacurutu/Testing Station batch passes104/104 in12 files with native dial/sole-source/two-owner traversal evidence. Broader combinations, assurance, strategy and deployed acceptance remain open.',
|
||||
detail: 'Current canonical Collection batch passes72/72 in6 related files, types/lint and build. Genuine Basic/Advanced co-present Sihaya Ridge collectors take2 each or exhaust the6-spice pile in actual storm order; zero actual collection cannot qualify through old wallets. Actual390px Finish shipment & movement produces phase7, Ecaz8/Guild14 from7/10 and one Orgiz transfer with Ready restored, no overflow or errors. Earlier shared-allocation proof passes27/27 and Jacurutu/Testing Station proof104/104, each source-bound. Basic shared settlement, combinations, assurance, strategy and deployed acceptance remain open.',
|
||||
evidence: ['tests/mixed-nexus-modules-runtime.test.ts', 'tests/paired-e3-nexus-modules-runtime.test.ts', 'tests/sample-custody.test.ts', 'tests/ambassador-terror-overlap-engine.test.ts', 'tests/e3-nexus-ecaz-runtime.test.ts', 'tests/e3-nexus-moritani-runtime.test.ts', 'tests/single-nexus-e1-runtime.test.ts', 'tests/single-nexus-e2-runtime.test.ts', 'tests/homeworld-paired-nexus-runtime.test.ts', 'tests/homeworld-standalone-nexus-runtime.test.ts',
|
||||
'tests/fixture-jacurutu-mixed-dial.ts', 'tests/jacurutu-mixed-dial-runtime.test.ts', 'tests/fixture-testing-station-contest.ts', 'tests/testing-station-contest-runtime.test.ts',
|
||||
'tests/fixture-discovery-occupied-collection.ts', 'tests/discovery-occupied-collection-runtime.test.ts', 'tests/fixture-discovery-shared-orgiz.ts', 'tests/discovery-shared-orgiz-runtime.test.ts',
|
||||
'tests/board-resolution-quote.test.ts', 'tests/ecaz-collection-quote.test.ts',
|
||||
'tests/homeworld-classic-nexus-skills-runtime.test.ts', 'tests/homeworld-classic-discovery-nexus-runtime.test.ts', 'tests/homeworld-native-e1e2-modules-runtime.test.ts', 'tests/homeworld-native-e3-modules-runtime.test.ts', 'tests/homeworld-classic-discovery-runtime.test.ts', 'tests/homeworld-skills-discovery-runtime.test.ts', 'tests/homeworld-optional-modules-runtime.test.ts', 'tests/discovery-native-e1-skills-runtime.test.ts', 'tests/discovery-native-e2-skills-runtime.test.ts', 'tests/discovery-native-e3-skills-runtime.test.ts', 'tests/discovery-skills-strongholds-runtime.test.ts', 'tests/discovery-native-strongholds-runtime.test.ts', 'tests/ecaz-stronghold-runtime.test.ts', 'tests/e3-native-tech-runtime.test.ts', 'tests/discovery-classic-nexus-lifecycle.test.ts', 'tests/discovery-classic-nexus-effects.test.ts', 'tests/discovery-orgiz-advanced-runtime.test.ts', 'tests/discovery-classic-skills-runtime.test.ts', 'tests/discovery-module-profile.test.ts', 'tests/discovery-native-typed-runtime.test.ts', 'tests/discovery-native-marker-runtime.test.ts', 'tests/discovery-native-e3-runtime.test.ts', 'tests/discoveries.test.ts', 'tests/discovery-runtime.test.ts', 'tests/great-maker.test.ts', 'tests/discovery-board.test.ts', 'tests/discovery-admission.test.ts', 'tests/discovery-controls.test.ts', 'tests/discovery-entry.test.ts', 'tests/discovery-entry-runtime.test.ts', 'tests/discovery-entry-controls.test.ts', 'tests/discovery-entry-review.test.ts', 'tests/discovery-collection.test.ts', 'tests/discovery-flight.test.ts', 'tests/discovery-flight-engine.test.ts', 'tests/discovery-flight-recovery.test.ts', 'tests/discovery-battle.test.ts', 'tests/discovery-battle-recovery.test.ts', 'tests/discovery-storm.test.ts', 'tests/discovery-storm-recovery.test.ts', 'tests/discovery-shrine-engine.test.ts', 'tests/discovery-shrine-recovery.test.ts', 'docs/HOMEWORLD_RUNTIME_IMPLEMENTATION.md'],
|
||||
},
|
||||
],
|
||||
|
||||
Reference in new issue
Block a user