File size: 28,999 Bytes
84bc42a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bd26019
84bc42a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
;
;
/* ============================================================================
   FIRST-CAREER ONBOARDING — one choice, two real teaching systems
   ----------------------------------------------------------------------------
   Training already owns the live RTS course (tutorial.js), and the War Table
   Primer owns galaxy -> deployment navigation (warprimer.js). This file does
   not duplicate either system or mark either one complete. It gives a fresh
   career an explicit, skippable choice and exposes durable KEEL hint events so
   later battle/space HUD presenters can render the same authored message.
   ============================================================================ */
(function(){
'use strict';

var ONBOARDING_VERSION=2;
var PRESENTER_EVENT='massfront:keel-hint';
var CHOICE_EVENT='massfront:onboarding-choice';
var presenter=null,choiceNode=null,lastFocus=null,initialized=false,activeOfferOptions=null;

/* This is a career contract, not a cut-scene asset list. The space module
   stages these beats with its existing live system view and story rail, so a
   missing video or portrait can never block a new career. */
var NEW_CAREER_SEQUENCE={
  schema:'massfront.new-career-onboarding.v2',entryView:'live-space',required:true,
  stages:[
    {id:'world-introduction',surface:'space-story-rail',scene:'deep-space',
      title:'THE FRACTURED FRONTIER',text:'Humanity crossed the relay network and found a frontier already at war.'},
    {id:'planet-approach',surface:'space-story-rail',scene:'planet-approach',
      title:'APPROACHING AELOS',text:'UGA traffic control has cleared one protected landing corridor for your first field operation.'},
    {id:'orientation-choice',surface:'space-story-rail',scene:'aelos-orbit',
      title:'YOUR FIRST COMMAND DECISION',text:'Take the short planetary basics course, or skip directly to required faction commissioning.'},
    {id:'protected-training',optional:true,surface:'battle-minimap',next:'faction-commissioning'},
    {id:'faction-commissioning',required:true,next:'starter-commander-1'},
    {id:'starter-commander-1',required:true,next:'full-uga-space'},
    {id:'full-uga-space',required:true}
  ],
  invariants:{mainMenuPreserved:true,trainingOptional:true,factionRequired:true,
    commanderGrant:'selected-faction-commander-1',keelAffiliation:'uga',
    keelSelectable:false,battleTransmissionSurface:'battle-minimap',
    spaceTransmissionSurface:'space-story-rail'}
};

/* Routing is declarative here. The Galactic integration owns the actual space
   entry and career transitions; callers receive these outcomes through the
   choice event/callback and route them on their own surface. */
var FLOWS={
  'default':{
    entryView:'front-command',trainingOutcome:'protected-planetary-training',skipOutcome:'front-command',
    title:'FIELD BASICS BEFORE DEPLOYMENT?',
    lead:'KEEL can guide one protected planetary drop, or you can continue to the current MASSFRONT command menu. This choice never replaces the main menu.',
    trainingTitle:'10-MINUTE PLANETARY BASICS',
    trainingText:'Learn camera control, deployment, Commander selection, economy, construction, production, army selection and orders in a protected mission.',
    skipTitle:'STRATEGIC NAVIGATION',
    skipText:'The War Table Primer teaches galaxy, system, planet, region and deployment when you enter Standard.',
    trainingLabel:'BEGIN PLANETARY BASICS',skipLabel:'KEEP CURRENT MAIN MENU',
    foot:'The tutorial remains replayable from the War Room, Settings, and UGA Command.'
  },
  'experimental-space':{
    entryView:'live-space',trainingOutcome:'protected-planetary-training',skipOutcome:'required-faction-selection',
    title:'CHOOSE YOUR FIRST PLANETARY DROP',
    lead:'Aelos is below. KEEL can run a short protected RTS orientation, or an experienced player can continue directly to commissioning.',
    trainingTitle:'BASIC ORIENTATION · RECOMMENDED',
    trainingText:'Ten essential objectives: camera, landing, Commander, mass, power, Factory, production, army selection, orders and extraction.',
    skipTitle:'COMMISSION IMMEDIATELY',
    skipText:'Skip the mission, then choose a faction and receive that faction\'s real Commander 1. Faction selection is still required.',
    trainingLabel:'LAND FOR BASIC TRAINING',skipLabel:'SKIP TRAINING · COMMISSION',
    foot:'Both paths converge on required faction selection and Commander 1 before full UGA-space access.'
  }
};

/* These ids match tutorial.js's new-career BASIC course. The longer field
   certification remains replayable, but it is not allowed to turn a first
   launch into a twenty-one-objective campaign. Progress remains a high-water
   mark; this layer never awards or advances a lesson. */
var RTS_STEPS=[
  ['camera','Camera'],['deploy','Landing'],['commander','Commander'],['mex','Mass economy'],
  ['power','Power grid'],['fac','Factory'],['queue','Production'],['train','Army selection'],
  ['orders','Basic orders'],['objective','Extraction']
];
var NAV_STEPS=[
  ['galaxy','Galaxy'],['system','System'],['planet','Planet'],['region','Region'],['deploy','Deployment plan']
];

/* Contexts are stable integration points. UGA can call presentContext() from a
   space surface; battle HUD code can use the battle-* contexts. Text names
   controls and rules that exist today — no placeholder missions or rewards. */
var HINTS={
  'menu-command':{
    context:'menu',surface:'front-end',durationMs:5600,priority:30,animationId:'keel-link',
    text:'Training is a protected live-fire orientation. Standard continues through the strategic deployment route.'
  },
  'war-table-route':{
    context:'war-table',surface:'front-end',durationMs:6000,priority:35,animationId:'keel-route',
    text:'Follow the active route: galaxy, system, planet, region, then deployment. Locked destinations remain on the current stage.'
  },
  'uga-command':{
    context:'uga-command',surface:'space-story-rail',durationMs:6200,priority:45,animationId:'keel-space-link',
    text:'UGA Command can route you to the protected Training Operation or a live deployment. Training progress remains with this profile.'
  },
  'uga-training':{
    context:'uga-training',surface:'space-story-rail',durationMs:5800,priority:50,animationId:'keel-space-link',
    text:'Training bypasses strategic deployment and starts a fixed protected operation under KEEL guidance.'
  },
  'space-first-choice':{
    context:'space-intro',surface:'space-story-rail',durationMs:6000,priority:70,animationId:'keel-space-link',
    text:'Live-space link stable. Deploy to protected planetary training, or continue directly to required faction commissioning.'
  },
  'battle-camera':{
    context:'battle-camera',surface:'battle-minimap',durationMs:5200,priority:50,animationId:'keel-tactical-link',
    text:'Drag to pan, pinch to zoom, and open VIEW for rotation and tilt.'
  },
  'battle-economy':{
    context:'battle-economy',surface:'battle-minimap',durationMs:6000,priority:55,animationId:'keel-tactical-link',
    text:'Build an Extractor on a mass deposit, then add Reactor power before expanding production.'
  },
  'battle-production':{
    context:'battle-production',surface:'battle-minimap',durationMs:5900,priority:55,animationId:'keel-tactical-link',
    text:'Select a Factory, queue units, then open ARMY to select the force you produced.'
  },
  'battle-orders':{
    context:'battle-orders',surface:'battle-minimap',durationMs:6200,priority:60,animationId:'keel-tactical-link',
    text:'Select combat units. Single-tap ground issues attack-move; double-tap breaks contact and retreats.'
  },
  'battle-intel':{
    context:'battle-intel',surface:'battle-minimap',durationMs:5800,priority:60,animationId:'keel-tactical-link',
    text:'Long-press suspected enemy ground to mark intel, then scout beyond friendly vision to confirm the contact.'
  }
};

function stamp(){ return Date.now(); }
function save(){
  if(typeof metaSave!=='function') return false;
  try{ return metaSave()!==false; }catch(e){ return false; }
}
function onboardingMeta(){
  if(typeof META==='undefined'||!META) return {version:ONBOARDING_VERSION,choice:'',hints:{}};
  var M=META.onboarding;
  if(!M||typeof M!=='object'||Array.isArray(M)) M=META.onboarding={};
  if(typeof M.version!=='number') M.version=0;
  if(M.choice!=='training'&&M.choice!=='skipped') M.choice='';
  if(!M.hints||typeof M.hints!=='object'||Array.isArray(M.hints)) M.hints={};
  return M;
}
function hintMeta(id){
  var M=onboardingMeta(),H=M.hints[id];
  if(!H||typeof H!=='object'||Array.isArray(H)) H=M.hints[id]={};
  return H;
}
function tutorialMeta(){
  return typeof META!=='undefined'&&META&&META.tutorial&&typeof META.tutorial==='object'?META.tutorial:{};
}
function primerMeta(){
  return typeof META!=='undefined'&&META&&META.warPrimer&&typeof META.warPrimer==='object'?META.warPrimer:{};
}
function trainingState(){
  try{ if(typeof window.trainingUiState==='function') return window.trainingUiState()||{}; }catch(e){}
  return {};
}
function cloneMap(src){
  if(Array.isArray(src)) return src.map(cloneMap);
  if(!src||typeof src!=='object') return src;
  var out={},k;
  for(k in src) if(Object.prototype.hasOwnProperty.call(src,k)){
    var v=src[k];
    out[k]=cloneMap(v);
  }
  return out;
}

/* Public progress is derived from the two authoritative systems. "Complete"
   therefore always means the real saved lesson/stage says so. */
function skills(){
  var T=tutorialMeta(),P=primerMeta(),TS=trainingState();
  var tDone=!!(T.basicDone||T.done||TS.done),active=!!TS.active,seen=P.seen||{},navDone=!!P.done;
  var stored=typeof T.basicProgress==='number'?T.basicProgress:(T.done?RTS_STEPS.length:T.progress|0);
  var progress=Math.max(0,Math.min(RTS_STEPS.length,TS.course==='basic'&&active?TS.progress|0:stored));
  var nav=NAV_STEPS.map(function(S){
    return {id:S[0],label:S[1],state:(navDone||!!seen[S[0]])?'complete':'pending'};
  });
  var rts=RTS_STEPS.map(function(S,i){
    var state=tDone||i<progress?'complete':active&&i===progress?'current':T.skipped?'deferred':'pending';
    return {id:S[0],label:S[1],state:state};
  });
  var navSeen=nav.filter(function(S){return S.state==='complete';}).length;
  var rtsSeen=rts.filter(function(S){return S.state==='complete';}).length;
  return [
    {id:'strategic-navigation',label:'Strategic navigation',source:'War Table Primer',
      status:navDone?'complete':navSeen?'in-progress':'pending',complete:navSeen,total:nav.length,steps:nav},
    {id:'rts-foundations',label:'RTS field skills',source:'KEEL Training Operation',
      status:tDone?'complete':active?'in-progress':T.skipped?'deferred':rtsSeen?'in-progress':'pending',
      complete:rtsSeen,total:rts.length,steps:rts}
  ];
}

function eligible(){
  if(typeof META==='undefined'||!META) return false;
  var M=onboardingMeta(),T=tutorialMeta();
  if(M.choice) return false;
  if(((META.matches|0)+(META.standardMatches|0))>0) return false;
  if(T.done||T.basicDone||T.skipped||T.basicSkipped||(T.progress|0)>0||(T.basicProgress|0)>0) return false;
  if(trainingState().active) return false;
  return true;
}
function experimentalEnabled(){
  return window.__MF_BUILD_HAS_GALACTIC_EXPLORATION===true||window.__MF_OTA_HAS_GALACTIC_DELIVERY===true;
}
function automaticEligible(){
  /* Experimental new careers make their choice after live space appears. A
     base-menu modal would reverse that sequence and cover the world intro. */
  return eligible()&&!experimentalEnabled();
}
function flowDef(id){ return FLOWS[id]||FLOWS['default']; }
function careerSequence(){ return cloneMap(NEW_CAREER_SEQUENCE); }
function phase(){
  try{
    var gate=window.MFNewCareerFactionGate;
    var G=gate&&typeof gate.state==='function'?gate.state():null;
    if(G&&G.phase==='ready') return 'ready';
    if(G&&G.phase==='faction-selection') return 'faction-commissioning';
    if(G&&G.phase==='training') return 'protected-training';
  }catch(e){}
  var M=onboardingMeta(),T=tutorialMeta(),TS=trainingState();
  if(!M.choice) return experimentalEnabled()?'world-introduction':'orientation-choice';
  if(M.choice==='skipped') return M.flowId==='experimental-space'?'faction-commissioning':'front-command';
  if(T.basicDone||T.done||TS.done) return M.flowId==='experimental-space'?'faction-commissioning':'front-command';
  return 'protected-training';
}
function choiceContract(id){
  id=FLOWS[id]?id:'default';
  var F=flowDef(id);
  return {schema:'massfront.onboarding-flow.v2',flowId:id,entryView:F.entryView,
    choiceContext:id==='experimental-space'?'space-intro':'front-command',
    speaker:'KEEL',speakerId:'keel',affiliation:'uga',speakerRole:'UGA EXPEDITION GUIDE',
    channel:'UGA PERSONNEL LINK',voiceId:'keen',profileId:'uga-keel-expedition-guide',
    animationId:id==='experimental-space'?'keel-space-link':'keel-link',
    title:F.title,text:F.lead,careerSequence:careerSequence(),
    continuation:id==='experimental-space'?{
      schema:'massfront.new-career-continuation.v1',required:true,
      afterTraining:'faction-commissioning',afterSkip:'faction-commissioning',
      afterFaction:'starter-commander-1',afterCommander:'full-uga-space'
    }:null,
    actions:[
      {choice:'training',label:F.trainingLabel,outcome:F.trainingOutcome,nextStep:'protected-planetary-training'},
      {choice:'skipped',label:F.skipLabel,outcome:F.skipOutcome,
        nextStep:id==='experimental-space'?'faction-commissioning':'front-command'}
    ]};
}
function state(){
  var M=onboardingMeta();
  return {version:ONBOARDING_VERSION,choice:M.choice||'',flowId:M.flowId||'',outcome:M.outcome||'',
    eligible:eligible(),automaticEligible:automaticEligible(),awaitingSpaceChoice:eligible()&&experimentalEnabled(),
    phase:phase(),trainingOptional:true,factionCommissioningRequired:experimentalEnabled(),
    offeredAt:M.offeredAt||0,decidedAt:M.decidedAt||0,offerCount:M.offerCount|0,
    careerSequence:careerSequence(),hints:cloneMap(M.hints||{}),skills:skills()};
}

function shown(el){
  if(!el) return false;
  try{ var s=getComputedStyle(el); return s.display!=='none'&&s.visibility!=='hidden'; }
  catch(e){ return !el.hidden; }
}
function menuReady(){
  if(typeof document==='undefined'||!document.body) return false;
  if(typeof bootConfirmed!=='undefined'&&!bootConfirmed) return false;
  if(typeof running!=='undefined'&&running) return false;
  if(document.body.classList.contains('mfIntroOpen')||document.body.classList.contains('trainingOperation')) return false;
  try{ if(/(?:^|[?&])galacticRoute=/.test(String(location.search||''))) return false; }catch(e){}
  var front=document.getElementById('startScreen');
  if(!shown(front)||front.getAttribute('aria-hidden')==='true') return false;
  var blockers=['mfPreAlphaIntro','apOverlay','apConfirmOverlay','accDlg','loadScr'];
  for(var i=0;i<blockers.length;i++){
    var b=document.getElementById(blockers[i]);
    if(b&&shown(b)&&!b.hidden&&b.getAttribute('aria-hidden')!=='true') return false;
  }
  return true;
}

function signal(type,detail,cancelable){
  if(typeof window==='undefined'||typeof window.dispatchEvent!=='function') return true;
  try{
    var ev;
    if(typeof CustomEvent==='function') ev=new CustomEvent(type,{detail:detail,cancelable:!!cancelable});
    else if(document&&document.createEvent){ ev=document.createEvent('CustomEvent');ev.initCustomEvent(type,false,!!cancelable,detail); }
    if(!ev) return true;
    var accepted=window.dispatchEvent(ev);
    if(accepted===false) detail.handled=true;
    return accepted;
  }catch(e){ return true; }
}
function payloadFor(id,def,opt){
  opt=opt||{};
  return {
    schema:'massfront.keel-hint.v1',hintId:id,context:opt.context||def.context,
    /* Surface is authored by context and cannot be redirected by callers.
       In battle KEEL takes the minimap receiver; in normal space she takes the
       existing story rail. This prevents a malformed hint from creating a
       third talking-head panel over either game surface. */
    surface:def.surface,speaker:'KEEL',speakerId:'keel',affiliation:'uga',
    speakerRole:'UGA EXPEDITION GUIDE',channel:'UGA PERSONNEL LINK',
    /* KEEL is UGA personnel. Do not let a caller skin the shared presenter as
       a selectable-faction voice or commander profile. */
    voiceId:'keen',profileId:'uga-keel-expedition-guide',
    animationId:opt.animationId||def.animationId||'keel-link',
    text:String(opt.text||def.text),durationMs:Math.max(1000,Math.min(12000,opt.durationMs||def.durationMs||5000)),
    priority:Math.max(0,opt.priority==null?def.priority||50:opt.priority|0),
    issuedAt:stamp(),handled:false
  };
}
function battleSurfaceActive(){
  if(typeof running==='undefined'||!running) return false;
  if(typeof matchLive==='undefined'||!matchLive) return false;
  if(typeof gameEnded!=='undefined'&&gameEnded) return false;
  try{
    var b=document&&document.body;
    if(b&&b.classList&&(b.classList.contains('mfMenuOpen')||b.classList.contains('menuMode')))return false;
  }catch(e){}
  return true;
}
function deliver(P){
  if(typeof presenter==='function'){
    try{ if(presenter(P)===true) P.handled=true; }
    catch(e){ if(window.console) console.error('MFOnboarding presenter',e); }
  }
  signal(PRESENTER_EVENT,P,true);
  return !!P.handled;
}
function retryBattle(P,attempt){
  if(P.handled||attempt>=24||!battleSurfaceActive()){
    if(!P.handled)P.presenter='battle-minimap-timeout';
    return;
  }
  setTimeout(function(){
    if(P.handled||!battleSurfaceActive())return;
    P.issuedAt=stamp();
    if(!deliver(P))retryBattle(P,attempt+1);
  },180);
}
function fallbackPresent(P){
  try{
    var D=typeof window.__tutDebug==='function'?window.__tutDebug():null;
    if(D&&typeof D.speak==='function'){
      D.speak(P.text,P.durationMs/1000,'onboarding',P.hintId);
      P.handled=true;P.presenter='keel-bubble';return;
    }
  }catch(e){}
  if(typeof toast==='function'){
    try{ toast('KEEL: '+P.text);P.handled=true;P.presenter='toast'; }catch(e2){}
  }
}
function present(id,opt){
  opt=opt||{};
  var def=HINTS[id];
  if(!def) return null;
  var H=hintMeta(id);
  if(!opt.force&&(H.completed||H.dismissed||(def.once!==false&&H.seen))) return null;
  var P=payloadFor(id,def,opt);
  if(opt.persist!==false){
    H.seen=true;H.count=(H.count|0)+1;H.lastAt=P.issuedAt;save();
  }
  deliver(P);
  if(!P.handled&&P.surface==='battle-minimap'&&battleSurfaceActive()){
    P.presenter='battle-minimap-pending';retryBattle(P,0);
  } else if(!P.handled) fallbackPresent(P);
  return P;
}
function presentContext(context,opt){
  var ids=Object.keys(HINTS).filter(function(id){return HINTS[id].context===context;});
  ids.sort(function(a,b){return (HINTS[b].priority|0)-(HINTS[a].priority|0);});
  for(var i=0;i<ids.length;i++){
    var out=present(ids[i],opt);
    if(out) return out;
  }
  return null;
}
function completeHint(id){
  if(!HINTS[id]) return false;
  var H=hintMeta(id);H.completed=true;H.dismissed=false;H.completedAt=stamp();save();return true;
}
function dismissHint(id){
  if(!HINTS[id]) return false;
  var H=hintMeta(id);H.dismissed=true;H.dismissedAt=stamp();save();return true;
}
function setPresenter(fn){
  presenter=typeof fn==='function'?fn:null;
  return function(){ if(presenter===fn) presenter=null; };
}

/* Onboarding is product UI, so its design belongs in the shipped stylesheet
   rather than a late inline block that wins the cascade and cannot be reviewed
   alongside the rest of the HUD. */
function ensureStyle(){}
function bindTap(el,fn){
  if(typeof mfBindTap==='function') mfBindTap(el,fn); else el.addEventListener('click',fn);
}
function onChoiceKey(ev){
  if(!choiceNode) return;
  /* Skip is a career decision, not a dialog dismissal. Escape cannot silently
     turn into a persisted skip; players choose one of the two explicit 52px
     actions. */
  if(ev.key==='Escape'){ ev.preventDefault();return; }
  if(ev.key!=='Tab') return;
  var list=Array.prototype.slice.call(choiceNode.querySelectorAll('button:not([disabled])'));
  if(!list.length) return;
  var first=list[0],last=list[list.length-1];
  if(ev.shiftKey&&document.activeElement===first){ev.preventDefault();last.focus();}
  else if(!ev.shiftKey&&document.activeElement===last){ev.preventDefault();first.focus();}
}
function closeChoice(restore){
  if(!choiceNode) return;
  document.removeEventListener('keydown',onChoiceKey,true);
  if(choiceNode.contains(document.activeElement)&&document.activeElement.blur) document.activeElement.blur();
  var old=choiceNode;choiceNode=null;
  if(old.parentNode) old.parentNode.removeChild(old);
  document.body.classList.remove('mfOnboardingOpen');
  if(restore&&lastFocus&&lastFocus.focus){ try{lastFocus.focus({preventScroll:true});}catch(e){lastFocus.focus();} }
  lastFocus=null;activeOfferOptions=null;
}
function decisionOptions(opt){
  var out={},src=activeOfferOptions||{},k;
  for(k in src) if(Object.prototype.hasOwnProperty.call(src,k)) out[k]=src[k];
  src=opt||{};
  for(k in src) if(Object.prototype.hasOwnProperty.call(src,k)) out[k]=src[k];
  out.flowId=FLOWS[out.flowId]?out.flowId:'default';
  return out;
}
function markChoice(value,opt){
  if((value!=='training'&&value!=='skipped')||(!opt.force&&!eligible())) return null;
  if(opt.flowId==='experimental-space'&&!experimentalEnabled()&&!opt.force) return null;
  if(opt.flowId==='default'&&experimentalEnabled()&&!opt.force) return null;
  var F=flowDef(opt.flowId),outcome=value==='training'?F.trainingOutcome:F.skipOutcome;
  var M=onboardingMeta(),before=cloneMap(M);
  M.version=ONBOARDING_VERSION;M.choice=value;M.flowId=opt.flowId;
  M.outcome=outcome;M.decidedAt=stamp();
  /* A cross-document training/faction handoff must never be emitted if the
     local decision was not durably saved. Revert the whole onboarding record
     on failure so a reload cannot observe half of a career transition. */
  if(!save()){
    if(typeof META!=='undefined'&&META)META.onboarding=before;
    if(typeof toast==='function')try{toast('Career choice could not be saved — try again');}catch(e){}
    return null;
  }
  var detail={schema:'massfront.onboarding-choice.v2',flowId:opt.flowId,entryView:F.entryView,
    choice:value,outcome:outcome,decidedAt:M.decidedAt,
    nextStep:value==='training'?'protected-planetary-training':
      (opt.flowId==='experimental-space'?'faction-commissioning':'front-command'),
    continuation:opt.flowId==='experimental-space'?{
      required:true,trainingOptional:true,factionRequired:true,
      commanderGrant:'selected-faction-commander-1',finalStep:'full-uga-space'
    }:null};
  signal(CHOICE_EVENT,detail,false);
  return detail;
}
function chooseTraining(opt){
  var cfg=decisionOptions(opt);
  if(typeof cfg.onTraining!=='function'&&cfg.launchTraining!==false&&typeof window.resumeTrainingMission!=='function'){
    if(typeof toast==='function') toast('Training is still loading — try again in a moment');
    return false;
  }
  var detail=markChoice('training',cfg);if(!detail)return false;closeChoice(false);
  var launch=function(){
    try{
      if(typeof cfg.onTraining==='function') cfg.onTraining(detail);
      else if(cfg.launchTraining!==false) window.resumeTrainingMission();
    }catch(e){if(window.console)console.error('Training launch',e);}
  };
  if(typeof requestAnimationFrame==='function')requestAnimationFrame(launch);else setTimeout(launch,0);
  return true;
}
function skip(opt){
  var cfg=decisionOptions(opt),detail=markChoice('skipped',cfg);if(!detail)return false;closeChoice(true);
  if(typeof cfg.onSkip==='function'){
    try{cfg.onSkip(detail);}catch(e){if(window.console)console.error('Onboarding skip route',e);}
  }
  /* Skipping the prompt is not tutorial completion. Leave the real Training
     card available, then give one concise menu orientation through the same
     presenter contract future HUDs will consume. */
  if(cfg.flowId==='default')setTimeout(function(){
    /* A delayed menu hint used to survive a fast Training launch and then
       appear over the battlefield. Re-check the surface at delivery time. */
    if((typeof running==='undefined'||!running)&&menuReady()) present('menu-command');
  },120);
  return true;
}
function decide(choice,opt){
  return choice==='training'?chooseTraining(opt):choice==='skipped'?skip(opt):false;
}
function offer(opt){
  opt=opt||{};
  if(choiceNode) return true;
  if(!opt.force&&(!eligible()||!menuReady())) return false;
  if(typeof document==='undefined'||!document.body) return false;
  var flowId=FLOWS[opt.flowId]?opt.flowId:'default',F=flowDef(flowId);
  activeOfferOptions=decisionOptions(opt);activeOfferOptions.flowId=flowId;
  ensureStyle();lastFocus=document.activeElement;
  var d=document.createElement('section');d.id='mfOnboardingChoice';d.setAttribute('role','dialog');
  d.dataset.flow=flowId;d.setAttribute('aria-modal','true');d.setAttribute('aria-labelledby','mfOnboardingTitle');
  d.innerHTML='<div class="mfOnboardingFrame"><div class="mfOnboardingScene" aria-hidden="true">'+
      '<span class="mfOnboardingPlanet"></span><span class="mfOnboardingMoon"></span><span class="mfOnboardingVector"></span>'+ 
      '<b>AELOS // PROTECTED CORRIDOR</b><i>UGA APPROACH VECTOR 07</i></div>'+ 
    '<div class="mfOnboardingCard"><header class="mfOnboardingHeader"><span class="mfOnboardingKeel" aria-hidden="true">K</span><span><b>KEEL</b><small>UGA EXPEDITION GUIDE · NEUTRAL PERSONNEL</small></span></header>'+ 
    '<div class="mfOnboardingPath" aria-label="New career sequence"><span class="done"><b>01</b>SPACE ARRIVAL</span><span class="on"><b>02</b>ORIENTATION</span><span><b>03</b>FACTION</span><span><b>04</b>COMMANDER 1</span></div>'+ 
    '<main><div class="mfOnboardingEyebrow">NEW CAREER · PLANETARY APPROACH</div>'+ 
    '<h2 id="mfOnboardingTitle">'+F.title+'</h2><p class="mfOnboardingLead">'+F.lead+'</p>'+ 
    '<div class="mfOnboardingTracks"><section class="mfOnboardingTrack recommended"><small>RECOMMENDED</small><b>'+F.trainingTitle+'</b><span>'+F.trainingText+'</span></section>'+ 
    '<section class="mfOnboardingTrack"><small>EXPERIENCED RTS PLAYERS</small><b>'+F.skipTitle+'</b><span>'+F.skipText+'</span></section></div>'+ 
    '<div class="mfOnboardingSteps" aria-label="Basic RTS tutorial objectives"><span>CAMERA</span><span>LAND</span><span>COMMANDER</span><span>MASS</span><span>POWER</span><span>BUILD</span><span>PRODUCE</span><span>SELECT</span><span>ORDER</span><span>EXTRACT</span></div>'+ 
    '<div class="mfOnboardingActions"><button id="mfOnboardingBegin" type="button"><small>OPTION 01</small>'+F.trainingLabel+'</button><button id="mfOnboardingSkip" class="quiet" type="button"><small>OPTION 02</small>'+F.skipLabel+'</button></div>'+ 
    '<p class="mfOnboardingFoot">'+F.foot+'</p></main></div></div>';
  choiceNode=d;document.body.appendChild(d);document.body.classList.add('mfOnboardingOpen');
  bindTap(document.getElementById('mfOnboardingBegin'),chooseTraining);
  bindTap(document.getElementById('mfOnboardingSkip'),skip);
  document.addEventListener('keydown',onChoiceKey,true);
  var M=onboardingMeta(),before=cloneMap(M);M.offeredAt=stamp();M.offerCount=(M.offerCount|0)+1;
  if(!save()){
    if(typeof META!=='undefined'&&META)META.onboarding=before;
    closeChoice(false);if(typeof toast==='function')try{toast('Career setup is unavailable until saving recovers');}catch(e){}
    return false;
  }
  var focus=function(){var b=document.getElementById('mfOnboardingBegin');if(b)try{b.focus({preventScroll:true});}catch(e){b.focus();}};
  if(typeof requestAnimationFrame==='function')requestAnimationFrame(focus);else setTimeout(focus,0);
  return true;
}

function definitions(){
  return Object.keys(HINTS).map(function(id){var d=cloneMap(HINTS[id]);d.id=id;return d;});
}
function init(){
  if(initialized) return;initialized=true;
  var poll=function(){ try{
    /* The account/offline gate can open a frame after the menu first becomes
       eligible. If that happens while this choice is already mounted, yield
       the modal layer and offer again after the blocking surface closes. */
    if(choiceNode&&!menuReady()){closeChoice(false);return;}
    if(automaticEligible()&&menuReady()) offer();
  }catch(e){if(window.console)console.error('MFOnboarding',e);} };
  setInterval(poll,450);
  if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',poll,{once:true});
  else setTimeout(poll,0);
}

window.MFOnboarding={
  VERSION:ONBOARDING_VERSION,PRESENTER_EVENT:PRESENTER_EVENT,CHOICE_EVENT:CHOICE_EVENT,
  state:state,skills:skills,eligible:eligible,automaticEligible:automaticEligible,
  sequence:choiceContract,choiceContract:choiceContract,careerSequence:careerSequence,offer:offer,decide:decide,
  chooseTraining:chooseTraining,skip:skip,
  definitions:definitions,present:present,presentContext:presentContext,
  completeHint:completeHint,dismissHint:dismissHint,setPresenter:setPresenter
};
try{init();}catch(e){if(window.console)console.error('init onboarding',e);}
})();