; ; /* ============================================================================ WAR TABLE PRIMER — first-run orientation for the deployment route ---------------------------------------------------------------------------- KEEL (src/tutorial.js) teaches the battlefield. Nothing taught the screen the player has to get through BEFORE a battlefield exists: the war table's galaxy -> system -> planet -> region -> deploy chain. A new commander who opens it sees four stars, three of them locked, and no explanation of what a "region" is or why there are twelve sites; the stepper across the top names the five stages but not what any of them are for. This module walks that chain once. It is deliberately NOT part of KEEL: * KEEL is gated on live match state and only exists inside a drop. The war table is a front screen with no match, no units and no sim to read. * KEEL's panel is fixed-position HUD chrome. On this screen a floating card would have to fight the stage stepper, the mode contract row and the footer dock for the same narrow phone lane, and lose. So the card is inserted INTO the active stage panel near the choice it describes. It scrolls with that content, never overlaps chrome, and needs no z-index or pointer-events rules at all. galaxyui.js rebuilds the system, planet and region panels with innerHTML on every stage change, which would delete the card — so the poll below re-inserts it whenever it has gone missing, the same self-healing pattern tutorial.js uses to keep its card alive across renderOps(). galaxyui.js is not rewritten. A one-line stage hook (wtpOnStage) plus a takeover of openPlanetarySetup let this file compact the lecture after Training and keep the two must-see cards alive across SKIP GUIDE. ============================================================================ */ (function(){ /* --------------------------------------------------------------------------- THE SCRIPT — one card per stage, in the order the route walks them. Copy names the real controls and the real numbers the screen prints ("TAP AN UNLOCKED STAR", "4 REGIONS · 12 SITES", "3 BATTLEFIELD SITES") so the card and the panel underneath it can never disagree. --------------------------------------------------------------------------- */ var PRIMER_VERSION=2; var STAGES=[ { id:'galaxy', nm:'GALAXY', tx:'Sombrero-I is open. Select it, then tap ENTER. Other stars unlock as you conquer sites.' }, { id:'system', nm:'SYSTEM', tx:'Select the homeworld to reach its regions. Clear its 12 sites to unlock the next star.' }, { id:'planet', nm:'PLANET', tx:'Choose a region on the globe or in the list. Conquest opens neighboring regions.' }, { id:'region', nm:'REGION', /* Subtraction pass (audit item 1): region stacks this card on three ~50-word site dossiers, so it gets one line — the choice itself is on the screen. */ tx:'Choose a site. STANDARD is the balanced pick.' }, { id:'deploy', nm:'DEPLOY', tx:'Review your force, then START BATTLE. Pick landing ground and tap DEPLOY BASE HERE.' } ]; /* The two facts a first Standard visit must still teach after Training or SKIP GUIDE. Optional stages (system / planet / deploy) may be dropped. */ var MUST_SEE=['galaxy','region']; /* --------------------------------------------------------------------------- STATE — rides the existing profile save, like META.tutorial does. This module never writes its own storage blob. --------------------------------------------------------------------------- */ function primerMeta(){ if(typeof META==='undefined'||!META) return {done:false,version:0,seen:{}}; META.warPrimer=META.warPrimer||{done:false,version:0,seen:{}}; if(!META.warPrimer.seen) META.warPrimer.seen={}; return META.warPrimer; } function isMust(id){ return MUST_SEE.indexOf(id)>=0; } function mustPending(){ var M=primerMeta(); for(var i=0;i1&&r.height>1; } function activePanel(){ var scr=document.getElementById('setupScr'); if(!shown(scr)) return null; var list=scr.querySelectorAll('.mfStagePanel.on'); for(var i=0;i' +'

'+S.tx+'

' +'
' +'' +(last?'':'')+'
'; var b=d.querySelector('.wtpDone'); /* mfBindTap is the codebase's touch-safe binder; fall back to click so an OTA shell that predates it still gets a working dismiss. */ var doneFn=last?finish:dismissStage; if(typeof mfBindTap==='function') mfBindTap(b,doneFn); else b.addEventListener('click',doneFn); var sk=d.querySelector('.wtpSkip'); if(sk){ if(typeof mfBindTap==='function') mfBindTap(sk,skipGuide); else sk.addEventListener('click',skipGuide); } return d; } function sync(){ if(!armed()){ var stale=document.getElementById('wtpCard'); if(stale) stale.remove(); return; } var panel=activePanel(); if(!panel) return; var id=panel.dataset.stage,idx=stageIdx(id); if(idx<0) return; var M=primerMeta(); var card=document.getElementById('wtpCard'); if(M.seen&&M.seen[id]){ /* Optional stages already skipped still rebuild their panels. Drop a leftover must-see card so it cannot ride a hidden galaxy panel. */ if(card) card.remove(); return; } /* Re-insert on ANY of: panel rebuilt beneath us, stage advanced, or the card landing in a panel that is no longer the visible one. Comparing the rendered stage against the panel's own dataset is what makes a rebuild indistinguishable from a stage change here — both are just "the card that should be on screen is not". */ if(card&&card.dataset.stage===id&&card.parentNode===panel) return; if(card) card.remove(); var vis=visibleCount(id); /* The guide must not displace the primary map or battle-plan action from a short phone's first fold. Keep it in flow after that stage's first choice; galaxy and region still show their must-see facts beside the live map. */ var anchor=id==='galaxy'?panel.querySelector('#mfConquestContinue'): id==='region'?panel.querySelector('#mfRegionHero'): id==='deploy'?panel.querySelector('#mfQuickSetup'): id==='system'?panel.querySelector('.mfSystemTheatre'): id==='planet'?panel.querySelector('.mfPlanetViewport'):null; var next=build(STAGES[idx],idx,vis.last); if(anchor&&anchor.parentNode===panel) anchor.insertAdjacentElement('afterend',next); else panel.insertBefore(next,panel.firstChild); } /* --------------------------------------------------------------------------- SETTINGS — one replay row, appended after renderSettings() rebuilds #setList. src/game/meta.js replaces that list's entire innerHTML on every call, so the row has to be re-appended each time rather than inserted once; src/tutorial.js already wraps the same function for its own two rows and this wrap simply chains onto that one. --------------------------------------------------------------------------- */ function appendPrimerSettingsRow(){ var list=document.getElementById('setList'); if(!list||document.getElementById('wtpSetRow')) return; var M=primerMeta(),done=!!M.done&&(M.version|0)>=PRIMER_VERSION&&!mustPending(); var row=document.createElement('div'); row.className='sItem setRow'; row.id='wtpSetRow'; row.setAttribute('role','button'); row.setAttribute('tabindex','0'); row.innerHTML='
🗺 War Table Primer
' +(done?'Seen':'Armed for the next visit') +' — walks galaxy, system, planet, region and deployment
' +'
'+(done?'↺ REPLAY':'ARMED')+'
'; var act=function(ev){ if(ev){ ev.preventDefault(); } if(done) replay(); }; row.addEventListener('pointerdown',act); row.addEventListener('keydown',function(ev){ if(ev.key==='Enter'||ev.key===' ') act(ev); }); list.appendChild(row); } if(typeof renderSettings==='function'&&!window.__wtpSettingsWrapped){ window.__wtpSettingsWrapped=true; var _wtpRenderSettings=renderSettings; renderSettings=function(){ _wtpRenderSettings(); appendPrimerSettingsRow(); }; } /* --------------------------------------------------------------------------- BOOT — poll keeps the card alive across galaxyui innerHTML rebuilds. The Standard entry wrap (takeover, after initGalaxyUI) and wtpOnStage hook make the first paint immediate so SKIP / Training compact is not a 320 ms late. --------------------------------------------------------------------------- */ function onStandardOpen(){ ensureMustSeeRoute(true); try{ sync(); }catch(e){} } function wrapStandardEntry(){ if(window.__wtpOpenWrapped) return; var prev=window.openPlanetarySetup; if(typeof prev!=='function') return; window.__wtpOpenWrapped=true; window.openPlanetarySetup=function(mode){ var r=prev.apply(this,arguments); if(mode==='standard') onStandardOpen(); return r; }; } function initWarPrimer(){ if(window.__wtpInit) return; window.__wtpInit=true; wrapStandardEntry(); ensureMustSeeRoute(true); setInterval(function(){ try{ sync(); }catch(e){ if(window.console) console.error('war primer',e); } },320); } window.initWarPrimer=initWarPrimer; window.wtpEnsureMustSee=function(){ return ensureMustSeeRoute(true); }; window.wtpOnStage=function(){ try{ sync(); }catch(e){} }; /* Read-only inspection handle for verification scripts, matching the __tutDebug hook src/tutorial.js already exposes. */ window.__wtpDebug=function(){ return {STAGES:STAGES,MUST_SEE:MUST_SEE,armed:armed(), mustPending:mustPending(),trained:trainedCareer(),meta:primerMeta(), stage:(activePanel()||{dataset:{}}).dataset.stage||'', finish:finish,replay:replay,skipGuide:skipGuide}; }; /* SELF-INITIALISE. src/main.js (manifest index 68) lists initWarPrimer in the init loop it runs from boot(), but this file is index 73 - so the function did not exist yet and was SKIPPED IN SILENCE, exactly like initGalaxyUI. The war primer has been dead in every build for the same reason, and nobody noticed because nothing errored. Found by the new init-order gate in tools/bundle.mjs on its very first run. Safe here: __wtpInit makes it idempotent, and this file loads AFTER src/galaxyui.js (71), which is the ordering its own wrapStandardEntry comment requires. */ try{ initWarPrimer(); }catch(e){ if(window.console) console.error('initWarPrimer threw',e); } })();