File size: 14,920 Bytes
3597f5a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
;
;
/* ============================================================================
   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 as its first child. It
   scrolls with the content it describes, can never overlap 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 the only open star. Andromeda, Orion and Helios stay locked β€” tap a locked chip and you remain on this galaxy, no warp. Drag the hologram, TAP AN UNLOCKED STAR, then ENTER SOMBRERO-I.' },
 { id:'system', nm:'SYSTEM',
   tx:'One playable homeworld; other bodies on the rings are lore, not drops. The dossier reads 4 REGIONS Β· 12 SITES β€” clearing them unlocks the next star. Tap the homeworld, then ENTER AELOS.' },
 { id:'planet', nm:'PLANET',
   tx:'The planet splits into four regions, each a different battlefield theme and hazard. Drag the globe or use the chips below it, then tap a region. Locked regions open as neighbouring ones are liberated.' },
 { id:'region', nm:'REGION',
   tx:'Three sites per region, in order: COMPACT (_small, 2.2 km), STANDARD (_medium, 2.6 km), LARGE (_large, 3.2 km). A Standard War Room drop lands on the medium theatre β€” that is the map the mode is balanced around. Pick STANDARD / medium unless you want the short Compact fight.' },
 { id:'deploy', nm:'DEPLOY',
   tx:'The deployment plan: commander, objective, threat, modifiers and payout, all editable below. START BATTLE sends the carrier β€” you still pick landing ground, then DEPLOY BASE HERE. In the drop the HUD pop counter reads n/1K; 1K is this commander\'s cap.' }
];
var CLOSER='That is the route. Twelve sites per system, and the next star opens when the last one falls.';
/* 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;i<MUST_SEE.length;i++) if(!M.seen[MUST_SEE[i]]) return true;
  return false;
}
function markSeen(id){
  var M=primerMeta();
  M.seen=M.seen||{};
  if(id) M.seen[id]=true;
}
function markOptionalSeen(){
  var M=primerMeta(),changed=false,i,id;
  M.seen=M.seen||{};
  for(i=0;i<STAGES.length;i++){
    id=STAGES[i].id;
    if(!isMust(id)&&!M.seen[id]){ M.seen[id]=true; changed=true; }
  }
  return changed;
}
function trainedCareer(){
  /* Training / KEEL skip never open the war table. Compact to must-see so
     Standard is not a five-card lecture after the field guide. */
  if(typeof META==='undefined'||!META||!META.tutorial) return false;
  var T=META.tutorial;
  return !!(T.done||T.skipped);
}
function ensureMustSeeRoute(persist){
  /* A prior SKIP GUIDE set done without recording the two facts. Re-open
     only those cards β€” do not rebuild the five-stage lecture. Replay sets
     forceFull so a Training graduate can still walk all five on purpose. */
  var M=primerMeta(),changed=false;
  if(M.forceFull) return false;
  if(mustPending()&&(M.done||trainedCareer())) changed=markOptionalSeen();
  if(changed&&persist&&typeof metaSave==='function') metaSave();
  return changed;
}
function unseenLeft(){
  var M=primerMeta(),i;
  for(i=0;i<STAGES.length;i++) if(!M.seen[STAGES[i].id]) return true;
  return false;
}
function visibleCount(id){
  var M=primerMeta(),n=0,at=0,i,sid;
  for(i=0;i<STAGES.length;i++){
    sid=STAGES[i].id;
    if(M.seen&&M.seen[sid]&&sid!==id) continue;
    n++;
    if(sid===id) at=n;
  }
  return {at:at,n:n,last:at===n};
}
function armed(){
  ensureMustSeeRoute(false);
  if(mustPending()) return true;
  var M=primerMeta();
  return !M.done||(M.version|0)<PRIMER_VERSION;
}
function finish(){
  var M=primerMeta(),i;
  M.done=true; M.version=PRIMER_VERSION; M.forceFull=false;
  M.seen=M.seen||{};
  /* finish() is the end of the route, never a skip. Must-see are already
     seen when we get here; stamp the rest so a later load does not revive them. */
  for(i=0;i<STAGES.length;i++) M.seen[STAGES[i].id]=true;
  if(typeof metaSave==='function') metaSave();
  var card=document.getElementById('wtpCard');
  if(card) card.remove();
  if(typeof sfx==='function'){ try{ sfx('ui'); }catch(e){} }
  if(typeof renderSettings==='function'&&shown(document.getElementById('settingsScr'))) renderSettings();
}
function replay(){
  var M=primerMeta();
  M.done=false; M.version=0; M.seen={}; M.forceFull=true;
  if(typeof metaSave==='function') metaSave();
  if(typeof sfx==='function'){ try{ sfx('ui'); }catch(e){} }
  if(typeof running!=='undefined'&&running){ if(typeof toast==='function') toast('War table primer armed β€” it shows on the next deployment'); }
  else if(typeof showFrontScreen==='function') showFrontScreen('setupScr');
  if(typeof renderSettings==='function') renderSettings();
}

/* ---------------------------------------------------------------------------
   PLACEMENT
   --------------------------------------------------------------------------- */
function shown(el){
  if(!el||!el.isConnected) return false;
  var s=getComputedStyle(el);
  if(s.display==='none'||s.visibility==='hidden') return false;
  var r=el.getBoundingClientRect();
  return r.width>1&&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<list.length;i++) if(shown(list[i])) return list[i];
  return null;
}
function stageIdx(id){
  for(var i=0;i<STAGES.length;i++) if(STAGES[i].id===id) return i;
  return -1;
}
function dismissStage(){
  /* GOT IT hides THIS stage only. The old DISMISS called finish() and wiped
     the rest of the route β€” so a player who cleared the galaxy card to see
     the hologram never learned that Standard is the medium theatre. */
  var panel=activePanel(),id=panel&&panel.dataset.stage;
  markSeen(id);
  if(!unseenLeft()){ finish(); return; }
  if(typeof metaSave==='function') metaSave();
  var card=document.getElementById('wtpCard');
  if(card) card.remove();
  if(typeof sfx==='function'){ try{ sfx('ui'); }catch(e){} }
}
function skipGuide(){
  /* SKIP GUIDE drops the optional lecture. It must not stamp done until the
     two must-see cards have actually been on screen β€” otherwise SKIP on
     galaxy erases the medium-theatre card. */
  var panel=activePanel(),id=panel&&panel.dataset.stage;
  markSeen(id);
  markOptionalSeen();
  if(!mustPending()){ finish(); return; }
  if(typeof metaSave==='function') metaSave();
  var card=document.getElementById('wtpCard');
  if(card) card.remove();
  if(typeof sfx==='function'){ try{ sfx('ui'); }catch(e){} }
}
function build(S,idx,last){
  var vis=visibleCount(S.id);
  var d=document.createElement('aside');
  d.id='wtpCard';
  d.className='wtpCard'+(last?' wtpLast':'');
  d.dataset.stage=S.id;
  d.setAttribute('aria-label','War table orientation');
  d.innerHTML='<header><span>WAR TABLE ORIENTATION</span><b>'+vis.at+' / '+vis.n+' Β· '+S.nm+'</b></header>'
    +'<p>'+S.tx+(last?' '+CLOSER:'')+'</p>'
    +'<div class="wtpFoot"><i>'+STAGES.map(function(q,n){
        return '<em class="'+(n===idx?'on':n<idx?'past':'')+'">'+q.nm+'</em>';
      }).join('<u>β€Ί</u>')+'</i>'
    +'<button type="button" class="wtpDone">'+(last?'GOT IT Β· FINISH':'GOT IT')+'</button>'
    +(last?'':'<button type="button" class="wtpSkip">SKIP GUIDE</button>')+'</div>';
  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);
  panel.insertBefore(build(STAGES[idx],idx,vis.last),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='<div class="sTx"><b>πŸ—Ί War Table Primer</b><div class="sDs">'
    +(done?'Seen':'Armed for the next visit')
    +' β€” walks galaxy, system, planet, region and deployment</div></div>'
    +'<div class="sBuy">'+(done?'β†Ί REPLAY':'ARMED')+'</div>';
  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}; };

})();