Implement Enhancements, Mainline modifiers and Maneuvers; fix unplayable track

This commit is contained in:
Jesse
2026-07-31 11:02:54 -04:00
parent 401b879140
commit b085d5a0bb
20 changed files with 1853 additions and 50 deletions
+18 -14
View File
@@ -49,9 +49,10 @@ const newSolitaireGame = (seed = 1234) =>
// ---------------------------------------------------------------------------
describe('card catalogue (component 1)', () => {
it('composes the 115-card deck from the design', () => {
// Transcribed from docs/Deck cards2.xlsx; the sheet's own total is 115.
assert.equal(DECK_SIZE, 115);
it('composes the 133-card deck from the design', () => {
// Transcribed from docs/Deck cards2.xlsx, whose own total is 115 — plus the 18 extra industry
// cards added for Gap 12, taking industries from 9 to 27 (see INDUSTRY_PROFILES).
assert.equal(DECK_SIZE, 133);
assert.equal(buildDeck().length, DECK_SIZE);
});
@@ -59,7 +60,8 @@ describe('card catalogue (component 1)', () => {
const byCategory = Object.fromEntries(deckComposition().map((c) => [c.category, c.count]));
assert.deepEqual(byCategory, {
office: 7,
industry: 9,
// 27, not the sheet's 9 — Gap 12 industry density; see INDUSTRY_PROFILES.
industry: 27,
modifier: 23,
train: 22,
spaceUse: 12,
@@ -72,13 +74,13 @@ describe('card catalogue (component 1)', () => {
it('removes opponent-directed cards from a solitaire deck', () => {
// Q6 — Space-use and Action cards can only be played AT another player, so in a one-player
// game they would be 22 of 115 draws (19%) that do nothing.
assert.equal(SOLITAIRE_DECK_SIZE, 93);
// game they would be 22 of 133 draws (17%) that do nothing.
assert.equal(SOLITAIRE_DECK_SIZE, 111);
const solo = buildDeck('solitaire');
assert.equal(solo.length, 93);
assert.equal(solo.length, 111);
assert.ok(!solo.some((c) => c.kind.kind === 'spaceUse' || c.kind.kind === 'action'));
// A competitive deck keeps them.
assert.equal(buildDeck('competitive').length, 115);
assert.equal(buildDeck('competitive').length, 133);
});
it('keeps track OUT of the deck, as a per-player supply', () => {
@@ -190,9 +192,11 @@ describe('card catalogue (component 1)', () => {
assert.equal(nextOfficeTier('terminal'), null);
});
it('supplies 62 rolling stock pieces', () => {
assert.equal(TOTAL_ROLLING_STOCK, 62);
assert.equal(buildRollingStock().length, 62);
it('supplies the full rolling stock roster', () => {
// 80 pieces after the Gap 12 supply scale-up. Asserted against the constant rather than a
// literal so the invariant is "the yard holds exactly the roster", not a number to re-edit.
assert.equal(TOTAL_ROLLING_STOCK, 80);
assert.equal(buildRollingStock().length, TOTAL_ROLLING_STOCK);
});
it('never demands more cars than the supply can furnish', () => {
@@ -340,7 +344,7 @@ describe('game setup (component 2)', () => {
...g.decks.salvageYard,
...[...g.decks.hands.values()].flat(),
];
// A solitaire deck omits the 22 opponent-directed cards (Q6), so it holds 93, not 115.
// A solitaire deck omits the 22 opponent-directed cards (Q6), so it holds 111, not 133.
assert.equal(all.length, SOLITAIRE_DECK_SIZE, 'cards lost or duplicated');
assert.equal(new Set(all).size, SOLITAIRE_DECK_SIZE, 'duplicate card ids');
});
@@ -357,9 +361,9 @@ describe('game setup (component 2)', () => {
assert.equal(newSolitaireGame().freeTrays.length, 4);
});
it('puts all 62 rolling stock pieces in the Division Yard', () => {
it('puts every rolling stock piece in the Division Yard', () => {
const g = newSolitaireGame();
assert.equal(g.yards.divisionYard.length, 62);
assert.equal(g.yards.divisionYard.length, TOTAL_ROLLING_STOCK);
assert.equal(g.yards.classificationYard.length, 0);
});