clone

a browser grid puzzle game where you push words around to rewrite the rules of each level

feat(index): place bundled proofs on one campaign sheet

Dami committed 0067097
commit
00670975ccf72e2c22b423b62a7383fd1dfb250f
parent
77acd7d5cf5caf349c715f61fda20f42b832d2cf
author
Dami <dami.etoile@gmail.com>
2 files changed, +52 -7
Msrc/scenes/map.js26++++++++++++++++++-----
Mtests/map.test.js33+++++++++++++++++++++++++++++-
M src/scenes/map.js +20 -6
@@ -112,15 +112,28 @@ function normalizeEntry(level, fallbackPackId = 'core', fallbackPackName = 'Miau
112112 };113113 }114114 115+const BUNDLED_PACK_IDS = new Set(['core', 'garden-basics', 'word-lab']);116+function isBundledEntry(level) {117+ return level.origin === undefined118+ ? BUNDLED_PACK_IDS.has(level.packId)119+ : level.origin === 'bundled';120+}121+115122 function groupedEntries(source) {116- // Catalogs expose a flat, ordered list. The nested form is useful to small117- // scene tests and keeps the map API independent of catalog implementation.123+ // Catalogs expose a flat, ordered list. Bundled proofs share one campaign124+ // sheet; imported packs each get their own sheet range.118125 const flat = levelEntries(source);119126 const groups = new Map();120127 for (const level of flat) {121128 const entry = normalizeEntry(level);122- if (!groups.has(entry.packId)) groups.set(entry.packId, { id: entry.packId, name: entry.packName, entries: [] });123- groups.get(entry.packId).entries.push(entry);129+ // Keep historical core sheet IDs stable while grouping all bundled packs.130+ const groupId = isBundledEntry(level) ? 'core' : entry.packId;131+ if (!groups.has(groupId)) groups.set(groupId, {132+ id: groupId,133+ name: groupId === 'core' && isBundledEntry(level) ? 'Proofs' : entry.packName,134+ entries: [],135+ });136+ groups.get(groupId).entries.push(entry);124137 }125138 return [...groups.values()];126139 }
@@ -179,8 +192,9 @@ function makeSheet(group, sheetEntries, sheetIndex, globalSheetIndex, allSheetId
179192 }180193 181194 /**182- * Lay out catalog levels in pack order. Each pack starts its own sheet range;183- * a pack with more than fifteen levels continues onto another sheet.195+ * Lay out catalog levels in pack order. Bundled proofs share one sheet range;196+ * imported packs start their own range, and any range over fifteen proofs197+ * continues onto another sheet.184198 */185199 export function createIndexLayout(source) {186200 const groups = groupedEntries(source);
M tests/map.test.js +32 -1
@@ -9,11 +9,12 @@ import {
99 getProgressKey,1010 } from '../src/scenes/map.js';1111 12-const level = (packId, localId, title = localId) => ({12+const level = (packId, localId, title = localId, origin = packId === 'core' ? 'bundled' : 'local') => ({1313 id: `${packId}:${localId}`,1414 localId,1515 packId,1616 packName: packId === 'core' ? 'Miau Core' : 'Word Lab',17+ origin,1718 title,1819 width: 1,1920 height: 1,
@@ -37,6 +38,36 @@ test('INDEX auto layout is serpentine and continues across sheets', () => {
3738 assert.equal(layout.nodes.at(-1).id, 'word-lab:first');3839 });3940 41+test('bundled core packs share one sheet while imported packs start new sheets', () => {42+ const levels = [43+ ...Array.from({ length: 3 }, (_, index) => level('core', `core-${index + 1}`)),44+ ...Array.from({ length: 3 }, (_, index) => level('garden-basics', `garden-${index + 1}`, undefined, 'bundled')),45+ ...Array.from({ length: 3 }, (_, index) => level('word-lab', `lab-${index + 1}`, undefined, 'bundled')),46+ level('imported-pack', 'intro'),47+ ];48+ const layout = createIndexLayout(levels);49+ assert.equal(layout.sheets.length, 2);50+ assert.equal(layout.sheets[0].nodes.length, 9);51+ assert.equal(layout.sheets[0].id, 'core:sheet-1');52+ assert.deepEqual(layout.sheets[0].nodes.map(node => node.packId), [53+ 'core', 'core', 'core', 'garden-basics', 'garden-basics', 'garden-basics', 'word-lab', 'word-lab', 'word-lab',54+ ]);55+ assert.equal(layout.sheets[1].packId, 'imported-pack');56+ assert.equal(layout.sheets[0].nextSheetId, 'imported-pack:sheet-1');57+ assert.equal(layout.sheets[1].previousSheetId, 'core:sheet-1');58+});59+60+test('bundled proofs continue after fifteen without merging imported packs', () => {61+ const levels = [62+ ...Array.from({ length: 16 }, (_, index) => level('core', `core-${index + 1}`)),63+ ...Array.from({ length: 16 }, (_, index) => level('imported-pack', `proof-${index + 1}`)),64+ ];65+ const layout = createIndexLayout(levels);66+ assert.deepEqual(layout.sheets.map(sheet => [sheet.id, sheet.nodes.length]), [67+ ['core:sheet-1', 15], ['core:sheet-2', 1], ['imported-pack:sheet-1', 15], ['imported-pack:sheet-2', 1],68+ ]);69+});70+4071 test('progress uses stable pack/level keys and locks the next proof', () => {4172 const storage = new Map();4273 const fakeStorage = {