/** * Dev-only: TETHER-L2 BUILD FLOW — plays the real flow end-to-end * (menu → NEW GAME → land on the home world → build Tether - Level 2 → * the build completes) and reports what the tether field does with it: * * before: home tether id/level/radius * build: beginBuild() result (ok / refusal reason) * after : home tether id/level/radius, tetherLevelFor(home), * the surface HUD's cached level, built records * * node dev/server.mjs 8080 * node dev/cdp-firefox.mjs http://127.0.0.1:8080/dev/tether-flow.html \ * 'return window.__TETHER_FLOW__;' 40000 */ import Phaser from '../js/vendor/phaser.js'; import { config } from '../js/config/Config.js'; import { ConfigLoader } from '../js/config/ConfigLoader.js'; import { createGameConfig } from '../js/config/GameConfig.js'; import { MenuScene } from '../js/scenes/MenuScene.js'; import { GameScene } from '../js/scenes/GameScene.js'; import { SurfaceScene } from '../js/scenes/SurfaceScene.js'; const data = await ConfigLoader.load(); config.init(data); const errors = []; const origWarn = console.warn.bind(console); console.warn = (...a) => { errors.push(a.map(String).join(' ')); origWarn(...a); }; const origErr = console.error.bind(console); console.error = (...a) => { errors.push(a.map(String).join(' ')); origErr(...a); }; window.addEventListener('error', (e) => errors.push(String(e.message))); window.addEventListener('unhandledrejection', (e) => errors.push(`rejection: ${e.reason}`)); const game = new Phaser.Game({ ...createGameConfig(), scene: [MenuScene, GameScene, SurfaceScene] }); window.game = game; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); async function waitFor(fn, tries = 200, everyMs = 100) { for (let i = 0; i < tries; i++) { const v = fn(); if (v) return v; await sleep(everyMs); } return null; } const tetherInfo = (gs) => { const home = gs.planet?.discoveryName; const t = gs.tetherField?.tethers?.find( (tt) => tt.label === home || (tt.x === 0 && tt.y === 0), ); return t ? { id: t.id, x: t.x, y: t.y, level: t.level, radius: t.radius } : null; }; try { // 1) Menu → NEW GAME (the real entry). const menu = await waitFor(() => game.scene.getScene('MenuScene')?.newGameBtn ? game.scene.getScene('MenuScene') : null); if (!menu) throw new Error('menu never booted'); menu.startNewGame(); const gs = await waitFor(() => { const g = game.scene.getScene('GameScene'); return g?.ship && g?.buildState && g?.tetherField ? g : null; }, 300); if (!gs) throw new Error('GameScene never booted'); await sleep(500); const home = gs.planet.discoveryName; const before = tetherInfo(gs); const beforeLevel = gs.tetherLevelFor(home); // 2) Research is done (the build's only research gate). gs.researchState.unlock('exploration', 'tether_l2'); // 3) Land on the home world the game's own way, wait for the surface // stage (the stall guard advances after ~5 s without a clip). gs.startLanding(gs.commsTargetFor(gs.planet)); const ss = await waitFor(() => { const c = game.scene.getScene('SurfaceScene'); return c?.phase === 'surface' ? c : null; }, 150, 200); if (!ss) throw new Error('surface stage never reached'); await sleep(300); // 4) Start the Tether - Level 2 build (the scene-side rules apply). // The cost (200 minerals) is covered — a fresh ship holds none. gs.ship.setMinerals(200); const res = gs.beginBuild(ss.planetName, 'tether-l2', game.loop.now); const active = gs.buildState.getActive(); // 5) Fast-forward the build to completion (the test does not wait out // the 20 s), then let the surface's own tick apply the effects. if (active) active.startedAt -= active.durationMs; // …and let the HUD line finish re-decoding to the new level. await sleep(2500); const after = tetherInfo(gs); const out = { home, before, beforeLevel, build: { res, active: active ?? null }, after, afterLevel: gs.tetherLevelFor(home), hudLevel: ss.tetherLevel, hudLine: ss.hudLine ? ss.hudLine.text : null, built: gs.buildState.isBuilt(ss.planetName, 'tether-l2'), arcs: after ? gs.tetherField._arcs.get(after.id).length : null, errors: errors.slice(0, 5), }; window.__TETHER_FLOW__ = out; console.info('tether-flow:', JSON.stringify(out)); } catch (err) { window.__TETHER_FLOW__ = { fatal: String(err.message), errors: errors.slice(0, 5) }; console.error('tether-flow: fatal', err); }