/** * TutorialGuide test (dev tool, run with Node — no browser): * * node dev/tutorial.test.mjs * * Pins the starter quest's POINTING FINGER beat table * (js/quests/TutorialGuide.js — the pure core of the opening-arc * guidance, applied per frame by GameScene._applyTutorialHighlight onto * ActionBar.setHighlight / ResearchWindow.setHighlight): * - the welcome sequence acknowledged (oac-intro → oac-tether → * oac-scan all on the run's comms played-ledger) and SCAN not yet * pressed → 'scan'; * - OAC's 'research a Tether Level 2 upgrade' line (oac-upgrade) heard * → 'research' (console closed) / 'console' (console open); * - the goal node's research in flight / done → REST (null); * - missing records and the data's master switches degrade to rest; * - the data the beats stand on (data/comms.json's newGame/firstScan * triggers) is what the module's constants name. * The rim's drawing is the surface's job (ActionBar's update gate — * dev/actionbar.test.mjs pins the standby hiding). */ import fs from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import assert from 'node:assert/strict'; import { tutorialTarget, WELCOME_COMMS, UPGRADE_COMM } from '../js/quests/TutorialGuide.js'; const dataDir = join(dirname(fileURLToPath(import.meta.url)), '..', 'data'); const comms = JSON.parse(fs.readFileSync(join(dataDir, 'comms.json'), 'utf8')); const quests = JSON.parse(fs.readFileSync(join(dataDir, 'quests.json'), 'utf8')); // --------------------------------------------------------------------------- // The data the beats stand on — the module's constants name REAL comms, // in the triggers' real order (a typo here would be a beat that never // arms — the finger would rest where it should be pointing). // --------------------------------------------------------------------------- { const byTrigger = (t) => comms.comms.filter((c) => c.trigger === t).map((c) => c.id); assert.deepEqual( WELCOME_COMMS, byTrigger('newGame'), 'WELCOME_COMMS = the newGame sequence, in order', ); assert.ok(byTrigger('firstScan').includes(UPGRADE_COMM), 'UPGRADE_COMM is a firstScan comm'); assert.equal(UPGRADE_COMM, 'oac-upgrade', 'the scan reaction is oac-upgrade'); // The starter quest's research check is the arc's goal — the tutorial's // console beat (category + node) points at the same node. const starter = quests.quests.find((q) => q.starter === true); const researchCheck = starter.checks.find((c) => c.type === 'research'); assert.equal(quests.tutorial.console.category, researchCheck.category, 'tutorial category = the starter check\u2019s category'); assert.equal(quests.tutorial.console.node, researchCheck.node, 'tutorial node = the starter check\u2019s node'); assert.ok(quests.tutorial.scan.slot, 'the scan beat names a slot'); assert.ok(quests.tutorial.research.slot, 'the research beat names a slot'); } // A run's records: the starter granted + the played-ledger as an id set. const records = (...ids) => ({ starterGranted: true, hasPlayed: (id) => ids.includes(id), }); // --------------------------------------------------------------------------- // Fresh run — the comms own the screen (the welcome is mid-sequence). // --------------------------------------------------------------------------- assert.equal(tutorialTarget({}), null, 'no records → rest'); assert.equal(tutorialTarget(records()), null, 'a fresh ledger → rest (the comms own the screen)'); assert.equal(tutorialTarget(records('oac-intro')), null, 'welcome beat one playing → rest'); assert.equal(tutorialTarget(records('oac-intro', 'oac-tether')), null, 'welcome beat two playing → rest'); // --------------------------------------------------------------------------- // Beat 1 — the welcome is acknowledged: the deck's SCAN slot is the finger. // --------------------------------------------------------------------------- assert.equal(tutorialTarget(records('oac-intro', 'oac-tether', 'oac-scan')), 'scan', 'welcome ack\u2019d, SCAN unpressed → scan'); // The player pressed SCAN (the scene's one-shot): the beat is past — the // finger rests until oac-upgrade starts (which re-arms it, below). assert.equal( tutorialTarget({ ...records('oac-intro', 'oac-tether', 'oac-scan'), scanPressed: true }), null, 'welcome ack\u2019d, SCAN already pressed → rest (the comm is coming)', ); // --------------------------------------------------------------------------- // Beat 2 — oac-upgrade is on the ledger (the 'research a Tether Level 2' // line): the deck's RESEARCH slot (console closed) or the console itself // (console open — the tab's rim, then the node's, by the window). // --------------------------------------------------------------------------- assert.equal( tutorialTarget(records('oac-intro', 'oac-tether', 'oac-scan', 'oac-upgrade')), 'research', 'oac-upgrade heard, console closed → research', ); assert.equal( tutorialTarget({ ...records('oac-intro', 'oac-tether', 'oac-scan', 'oac-upgrade'), consoleOpen: true }), 'console', 'oac-upgrade heard, console open → console', ); assert.equal( tutorialTarget({ ...records('oac-intro', 'oac-tether', 'oac-scan', 'oac-upgrade'), consoleOpen: true, scanPressed: true }), 'console', 'the scan one-shot is irrelevant once the upgrade line is heard', ); // The scan beat's slot and the research beat's slot are the data's own // names (GameScene reads them — the pure table just picks the beat). assert.notEqual(quests.tutorial.scan.slot, quests.tutorial.research.slot, 'the two deck beats point at different slots'); // --------------------------------------------------------------------------- // REST — the arc's goal is underway / done: the finger steps off. // --------------------------------------------------------------------------- assert.equal( tutorialTarget({ ...records('oac-intro', 'oac-tether', 'oac-scan', 'oac-upgrade'), nodeResolving: true }), null, 'the goal research in flight → rest', ); assert.equal(tutorialTarget({ ...records(), nodeResolving: true }), null, 'goal done on a fresh ledger → rest'); // --------------------------------------------------------------------------- // Degradation — missing records / the data's master switches → rest. // --------------------------------------------------------------------------- assert.equal( tutorialTarget({ ...records('oac-intro', 'oac-tether', 'oac-scan'), starterGranted: false }), null, 'the starter quest not granted → rest', ); assert.equal( tutorialTarget({ starterGranted: true }), null, 'no played-ledger to read → rest (never a throw)', ); assert.equal( tutorialTarget({ ...records('oac-intro', 'oac-tether', 'oac-scan'), enabled: false }), null, 'the data\u2019s master switch off → rest', ); console.log('all TutorialGuide checks passed \u2714');