betterwithage commited on
Commit
77c22aa
·
verified ·
1 Parent(s): c3db402

Warhacker: expand demo tab to full 5x5 = 25 demos (per-problem demos, real tamper, honest formula panels, sovereign canvas globe fallback)

Browse files

Backend: 25-demo registry reusing KhipuChain/Merkle/RFC-6962 inclusion/point-in-polygon/conformal/CPA; real perf_counter timeline, always-on single-byte tamper test breaking the signed SHA-256 Merkle chain, honest formula statuses (locked-proven {F11,M2,CP1,G1,S2,B1}; conformal=W5-3/CP1; Lambda=Conjecture 1). register25() mounts /warhacker/index + /warhacker/run/{problem}/{demo}; /wh-demo backward-compat kept. Frontend: 5x5 picker, nominal/tamper, full panels, 23 vendored-lib viz renderers (zero CDN), sovereign 2D-canvas orbital fallback, mobile-responsive.

Files changed (2) hide show
  1. pages/warhacker.html +796 -281
  2. szl_warhacker_demos.py +0 -0
pages/warhacker.html CHANGED
@@ -3,9 +3,9 @@
3
  <head>
4
  <meta charset="UTF-8"/>
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
- <title>a11oy — Warhacker Demos · SZL Holdings</title>
7
- <meta name="description" content="Five exhaustive, step-by-step Warhacker demos: each runs a real mechanism in-image, shows the step timeline, the catch tree that caught the line-crossing, a single-byte tamper test that breaks the signed chain, and the proven formulas behind the fix. CANNONICO is real today; the other four show the proven horizontal substrate on labelled sample data."/>
8
- <!-- SOVEREIGN: zero runtime CDN. System font stack only; no network fonts, no external scripts. -->
9
  <style>
10
  :root{
11
  --ground:#0a0a0a; --panel:#0c0c0c; --panel2:#070707;
@@ -20,12 +20,12 @@
20
  --display:'Segoe UI','Helvetica Neue',Arial,system-ui,-apple-system,sans-serif;
21
  }
22
  *{box-sizing:border-box;}
23
- html,body{margin:0;padding:0;background:var(--ground);color:var(--cream);
24
  font-family:var(--display);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;}
25
  .mono{font-family:var(--mono);}
26
  a{color:inherit;}
27
  :focus-visible{outline:2px solid var(--gold);outline-offset:3px;border-radius:3px;}
28
- .ribbon{position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:1.1rem;
29
  flex-wrap:wrap;padding:0.5rem 1.25rem;font-family:var(--mono);font-size:10px;
30
  letter-spacing:0.12em;text-transform:uppercase;color:var(--gold);
31
  background:rgba(10,10,10,0.92);backdrop-filter:blur(10px);
@@ -38,7 +38,7 @@
38
  .live-dot{width:6px;height:6px;border-radius:50%;background:var(--live);
39
  box-shadow:0 0 6px var(--live);animation:pulseDot 2.2s ease-in-out infinite;}
40
  @keyframes pulseDot{0%,100%{opacity:1;}50%{opacity:.35;}}
41
- .hero{position:relative;overflow:hidden;padding:3rem 1.5rem 2rem;text-align:center;}
42
  .grid-bg{position:absolute;inset:0;pointer-events:none;
43
  background-image:
44
  linear-gradient(rgba(255,255,255,.025) 1px,transparent 1px),
@@ -48,91 +48,97 @@
48
  mask-image:radial-gradient(ellipse at center,#000 30%,transparent 75%);}
49
  .hero-inner{position:relative;z-index:2;max-width:64rem;margin:0 auto;}
50
  .eyebrow{display:inline-flex;align-items:center;gap:.5rem;padding:.3rem .85rem;
51
- border-radius:999px;border:1px solid var(--gold-line);background:var(--gold-soft);margin-bottom:1.5rem;}
52
  .eyebrow-dot{width:6px;height:6px;border-radius:50%;background:var(--gold);
53
  animation:pulseDot 2.2s ease-in-out infinite;}
54
  .eyebrow-text{font-family:var(--mono);font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--gold);}
55
- .headline{font-size:clamp(2.1rem,5vw,3.8rem);font-weight:300;line-height:1.05;
56
- letter-spacing:-.03em;margin:0 0 1.1rem;color:var(--cream);}
57
  .headline .accent{background:linear-gradient(120deg,var(--cream) 20%,var(--gold) 90%);
58
  -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent;}
59
- .support{font-size:15.5px;line-height:1.65;color:var(--paragraph);max-width:46rem;margin:0 auto 1.6rem;}
60
- .runall{display:inline-flex;align-items:center;gap:.5rem;padding:.7rem 1.5rem;font-size:12.5px;
61
- font-weight:600;font-family:var(--mono);border-radius:8px;border:1px solid var(--gold);
62
- background:var(--gold);color:var(--ground);cursor:pointer;letter-spacing:.06em;margin-top:.3rem;}
63
- .runall:hover{background:var(--gold-bright);}
64
- .runall:disabled{opacity:.5;cursor:not-allowed;}
65
- .stats{display:flex;flex-wrap:wrap;justify-content:center;gap:.75rem;max-width:60rem;margin:1.8rem auto 0;}
66
- .stat{flex:1 1 150px;min-width:140px;border:1px solid var(--gold-line);border-radius:8px;
67
- background:var(--panel);padding:.85rem 1rem;text-align:left;}
68
  .stat .k{font-family:var(--mono);font-size:9px;letter-spacing:.16em;text-transform:uppercase;color:var(--muted);}
69
- .stat .v{font-size:1.3rem;font-weight:600;color:var(--gold);margin-top:.25rem;}
70
  .stat .v.teal{color:var(--teal);}
71
- .stat .v.err{color:var(--err-bright);}
72
 
73
- /* tabs */
74
- .tabs-bar{position:sticky;top:0;z-index:40;display:flex;gap:.35rem;flex-wrap:wrap;justify-content:center;
75
- padding:.6rem 1rem;background:rgba(10,10,10,.95);backdrop-filter:blur(10px);border-bottom:1px solid var(--gold-line);}
76
- .tab-btn{font-family:var(--mono);font-size:11px;letter-spacing:.03em;padding:.5rem .85rem;border-radius:7px;
77
  border:1px solid var(--gold-line);background:transparent;color:var(--paragraph);cursor:pointer;transition:all .18s ease;}
78
  .tab-btn:hover{color:var(--gold);border-color:rgba(201,183,135,.35);}
79
  .tab-btn.active{background:var(--gold);color:var(--ground);border-color:var(--gold);font-weight:600;}
80
- .tab-btn .dot{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--dim);margin-right:.4rem;vertical-align:middle;}
81
- .tab-btn .dot.ok{background:var(--live-bright);box-shadow:0 0 5px var(--live);}
82
- .tab-btn .dot.err{background:var(--err-bright);box-shadow:0 0 5px var(--err);}
83
- .tab-btn .dot.loading{background:var(--gold);animation:pulseDot 1s ease-in-out infinite;}
84
 
85
- .panel-wrap{max-width:62rem;margin:1.6rem auto 3rem;padding:0 1.25rem;}
86
  .panel{display:none;}
87
  .panel.active{display:block;animation:fade .3s ease;}
88
  @keyframes fade{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:none;}}
89
  .p-head{margin-bottom:.9rem;}
90
  .p-titlerow{display:flex;align-items:center;gap:.7rem;flex-wrap:wrap;margin-bottom:.4rem;}
91
- .p-title{font-size:1.55rem;font-weight:400;letter-spacing:-.02em;margin:0;color:var(--cream);}
92
  .label-real,.label-roadmap{font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;
93
  padding:.2rem .55rem;border-radius:5px;font-weight:600;}
94
  .label-real{color:var(--live-bright);border:1px solid rgba(121,199,154,.45);background:rgba(90,138,110,.12);}
95
  .label-roadmap{color:var(--amber);border:1px solid rgba(214,178,74,.4);background:rgba(214,178,74,.08);}
96
- .p-blurb{font-size:13px;line-height:1.6;color:var(--paragraph);max-width:46rem;margin:0;}
97
  .roadmap-note{font-size:11.5px;line-height:1.55;color:var(--amber);background:rgba(214,178,74,.06);
98
  border:1px solid rgba(214,178,74,.25);border-radius:7px;padding:.55rem .8rem;margin:.7rem 0 0;}
99
 
100
- .seg{display:inline-flex;gap:.3rem;margin:.85rem 0 .4rem;flex-wrap:wrap;}
101
- .card-controls{display:flex;flex-wrap:wrap;gap:.5rem;margin:.7rem 0 1rem;}
 
 
 
 
 
 
 
 
 
 
 
 
102
  .r-btn{display:inline-flex;align-items:center;gap:.4rem;padding:.5rem 1rem;font-size:11.5px;
103
  font-weight:500;font-family:var(--mono);border-radius:6px;border:1px solid var(--gold-line);
104
  background:transparent;color:var(--gold);cursor:pointer;letter-spacing:.04em;
105
  transition:background .18s ease,border-color .18s ease;}
106
  .r-btn:hover{background:rgba(201,183,135,.08);border-color:rgba(201,183,135,.35);}
107
- .r-btn:active{opacity:.75;}
108
  .r-btn:disabled{opacity:.4;cursor:not-allowed;}
109
  .r-btn-primary{background:var(--gold);color:var(--ground);border-color:var(--gold);}
110
  .r-btn-primary:hover{background:var(--gold-bright);border-color:var(--gold-bright);}
111
  .r-btn-danger{color:var(--err-bright);border-color:rgba(176,106,90,.45);}
112
  .r-btn-danger:hover{background:rgba(176,106,90,.1);}
113
- .seg .r-btn.sel{background:var(--gold);color:var(--ground);border-color:var(--gold);}
114
 
115
  .verdict{display:flex;align-items:center;gap:.5rem;font-family:var(--mono);font-size:12.5px;
116
- padding:.5rem .8rem;border-radius:7px;margin-bottom:.8rem;visibility:hidden;font-weight:600;}
117
- .verdict.show{visibility:visible;}
118
  .verdict.ok{color:var(--live-bright);border:1px solid rgba(90,138,110,.45);background:rgba(90,138,110,.1);}
119
  .verdict.err{color:var(--err-bright);border:1px solid rgba(176,106,90,.5);background:rgba(176,106,90,.1);}
120
 
121
- /* section blocks */
122
  .block{border:1px solid var(--gold-line);border-radius:10px;background:var(--panel);
123
  padding:.9rem 1rem 1rem;margin-bottom:1rem;}
124
  .block-h{font-family:var(--mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;
125
- color:var(--muted);margin:0 0 .65rem;display:flex;align-items:center;gap:.5rem;}
126
  .block-h .pillc{color:var(--teal);border:1px solid var(--teal-line);background:var(--teal-soft);
127
  border-radius:4px;padding:.05rem .35rem;font-size:9px;}
128
 
 
 
 
 
 
 
 
 
 
129
  /* step timeline */
130
  .tl{list-style:none;margin:0;padding:0;}
131
  .tl li{display:flex;gap:.65rem;align-items:flex-start;padding:.5rem 0;border-bottom:1px solid var(--gold-soft);}
132
  .tl li:last-child{border-bottom:none;}
133
  .tl .ic{flex:0 0 auto;width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;
134
  font-family:var(--mono);font-size:10px;margin-top:1px;border:1px solid var(--dim);color:var(--dim);background:transparent;}
135
- .tl li.pending .ic{border-color:var(--dim);color:var(--dim);}
136
  .tl li.running .ic{border-color:var(--gold);color:var(--gold);animation:pulseDot 1s ease-in-out infinite;}
137
  .tl li.complete .ic{background:rgba(90,138,110,.18);color:var(--live-bright);border-color:rgba(90,138,110,.5);}
138
  .tl li.failed .ic{background:rgba(176,106,90,.18);color:var(--err-bright);border-color:rgba(176,106,90,.55);}
@@ -147,7 +153,7 @@
147
  /* catch tree */
148
  .ct{list-style:none;margin:0;padding:0;}
149
  .ct li{font-family:var(--mono);font-size:11.5px;padding:.32rem .6rem;border-radius:5px;margin-bottom:.25rem;
150
- display:flex;align-items:flex-start;gap:.5rem;border:1px solid transparent;}
151
  .ct li.pass{color:var(--paragraph);}
152
  .ct li.pass .mark{color:var(--live-bright);}
153
  .ct li.fail{color:var(--err-bright);background:rgba(176,106,90,.1);border-color:rgba(176,106,90,.4);}
@@ -160,10 +166,10 @@
160
  .ct .firstbadge{font-size:8.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--err-bright);
161
  border:1px solid rgba(224,138,118,.5);border-radius:3px;padding:.02rem .3rem;margin-left:.45rem;}
162
 
163
- /* tamper test */
164
  .tamper-grid{display:grid;grid-template-columns:1fr 1fr;gap:.5rem;}
165
  @media(max-width:560px){.tamper-grid{grid-template-columns:1fr;}}
166
- .tamper-cell{border:1px solid var(--gold-soft);border-radius:6px;padding:.5rem .65rem;}
167
  .tamper-cell .tk{font-family:var(--mono);font-size:8.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);}
168
  .tamper-cell .tv{font-family:var(--mono);font-size:11px;margin-top:.2rem;word-break:break-all;}
169
  .tv.bad{color:var(--err-bright);} .tv.good{color:var(--live-bright);}
@@ -174,6 +180,13 @@
174
  .bytediff .b4{color:var(--paragraph);} .bytediff .af{color:var(--err-bright);}
175
  .bytediff .hl{background:rgba(224,138,118,.35);color:#fff;border-radius:2px;padding:0 1px;}
176
 
 
 
 
 
 
 
 
177
  /* formula panel */
178
  .fp{list-style:none;margin:0;padding:0;}
179
  .fp li{border:1px solid var(--gold-soft);border-radius:7px;padding:.55rem .7rem;margin-bottom:.5rem;}
@@ -186,27 +199,20 @@
186
  .fp .status b.roadmap{color:var(--amber);}
187
  .fp .where{font-family:var(--mono);font-size:9.5px;color:var(--muted);margin-top:.3rem;}
188
 
189
- /* raw out */
190
  .out{position:relative;border:1px solid var(--gold-line);border-radius:8px;background:var(--panel2);overflow:hidden;}
191
  .out-label{display:flex;align-items:center;justify-content:space-between;padding:.45rem .85rem;
192
  border-bottom:1px solid var(--gold-line);font-family:var(--mono);font-size:9px;
193
  letter-spacing:.1em;text-transform:uppercase;color:var(--muted);cursor:pointer;}
194
- .out-status{display:inline-flex;align-items:center;gap:.4rem;}
195
- .status-dot{width:6px;height:6px;border-radius:50%;background:var(--dim);transition:background .3s;}
196
- .status-dot.ok{background:var(--live-bright);box-shadow:0 0 6px var(--live);}
197
- .status-dot.err{background:var(--err-bright);box-shadow:0 0 6px var(--err);}
198
- .status-dot.loading{background:var(--gold);animation:pulseDot 1s ease-in-out infinite;}
199
  .out-pre{margin:0;padding:.85rem;font-family:var(--mono);font-size:10px;line-height:1.55;
200
  color:var(--paragraph);white-space:pre-wrap;word-break:break-word;max-height:340px;overflow-y:auto;
201
  scrollbar-width:thin;scrollbar-color:var(--dim) transparent;display:none;}
202
  .out-pre.show{display:block;}
203
- .out-pre:empty::before{content:"— run this demo to call the live mechanism —";color:var(--dim);font-style:italic;}
204
  .honesty-note{font-family:var(--mono);font-size:10.5px;color:var(--teal);line-height:1.55;
205
  margin-top:.8rem;border-left:2px solid var(--teal-line);padding-left:.7rem;}
206
 
207
  .footer{border-top:1px solid var(--gold-line);padding:2rem 1.5rem;text-align:center;}
208
  .footer .note{font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--dim);}
209
- .honest{max-width:62rem;margin:0 auto 2rem;padding:0 1.25rem;}
210
  .honest-box{border:1px solid var(--teal-line);border-radius:8px;background:var(--teal-soft);
211
  padding:.9rem 1.1rem;font-size:12px;color:var(--paragraph);line-height:1.6;}
212
  .honest-box b{color:var(--teal);}
@@ -217,66 +223,56 @@
217
  <body>
218
  <div class="ribbon">
219
  <span class="live"><span class="live-dot"></span>a11oy</span>
220
- <span class="sep">·</span><span>Warhacker Demos</span>
221
- <span class="sep">·</span><span class="teal">in-image · real mechanism · signed · re-verifiable</span>
222
  <a href="/console">console →</a>
223
  </div>
224
 
225
  <section class="hero">
226
  <div class="grid-bg"></div>
227
  <div class="hero-inner">
228
- <span class="eyebrow"><span class="eyebrow-dot"></span><span class="eyebrow-text">Five exhaustive demos · live on this Space</span></span>
229
- <h1 class="headline">Prove the fix.<br/><span class="accent">Step by step. No hollow PASS.</span></h1>
230
- <p class="support">Each demo below runs a <b>real mechanism</b> inside this container and shows its work:
231
- an ordered step timeline with real durations and computed values, the catch tree that caught the exact
232
- line-crossing, a single-byte tamper test that visibly breaks the signed chain, and the proven formulas
233
- behind the fix. CANNONICO is real today; the other four run the proven horizontal substrate on clearly
234
- labelled sample data while the vertical stands up.</p>
235
- <button class="runall" id="runAll">▶ Run all five (nominal)</button>
236
  <div class="stats">
237
- <div class="stat"><div class="k">Demos run</div><div class="v" id="stRun">0 / 5</div></div>
238
- <div class="stat"><div class="k">Records signed</div><div class="v teal" id="stSigned">0</div></div>
 
239
  <div class="stat"><div class="k">Tamper caught</div><div class="v teal" id="stTamper">0 / 0</div></div>
240
- <div class="stat"><div class="k">Backend</div><div class="v teal" id="stBackend">—</div></div>
241
  </div>
242
  </div>
243
  </section>
244
 
245
- <div class="tabs-bar" id="tabsBar">
246
- <button class="tab-btn active" data-tab="cannonico"><span class="dot" id="dot-cannonico"></span>1 · CANNONICO</button>
247
- <button class="tab-btn" data-tab="tychee"><span class="dot" id="dot-tychee"></span>2 · TYCHEE</button>
248
- <button class="tab-btn" data-tab="hangar2apps"><span class="dot" id="dot-hangar2apps"></span>3 · HANGAR2APPS</button>
249
- <button class="tab-btn" data-tab="cyber_rts"><span class="dot" id="dot-cyber_rts"></span>4 · CYBER RTS</button>
250
- <button class="tab-btn" data-tab="raven"><span class="dot" id="dot-raven"></span>5 · RAVEN</button>
251
- <button class="tab-btn" data-tab="ai-sbom"><span class="dot" id="dot-ai-sbom"></span>6 · AI-SBOM</button>
252
- <button class="tab-btn" data-tab="agentic-provenance"><span class="dot" id="dot-agentic-provenance"></span>7 · AGENTIC PROV</button>
253
- </div>
254
-
255
- <div class="panel-wrap" id="panelWrap">
256
- <!-- panels injected by JS from a template, one per problem -->
257
- </div>
258
 
259
  <div class="honest">
260
  <div class="honest-box">
261
- <b>What is real here.</b> Every demo runs live in this container. CANNONICO is a real, working mechanism today:
262
- a 13-axis conjunctive ROE gate, an STL robustness value, PolyCARP-style geofence containment, and a conformal
263
- confidence interval — all computed live, sealed into a DSSE envelope signed by an in-image ECDSA-P256 key
264
- (verifiable against <a href="/cosign.pub" style="color:var(--teal)">/cosign.pub</a>) and chained into a SHA-256
265
- Merkle/Khipu log with a Rekor-style inclusion proof. Change one byte and the chain breaks on screen. The other
266
- four demos run the <b>proven horizontal substrate</b> — signed bundles, SHA-256/Merkle integrity, conjunctive
267
- admission, FHIR parse, reimplemented SGP4 propagation, TPM-quote attestation — on clearly labelled sample/replay
268
- data; they are labelled ROADMAP because the operational vertical (real registry, real feed, real TPM hardware,
269
- production ATO) is a fast stand-up, not claimed as done. The trust score (&Lambda;) uniqueness is Conjecture 1
270
- (conditional/CI-green; unconditional is false). Conjunctive gate soundness is P2 PROVEN. SLSA L1 is honest; L2 is roadmap.
 
271
  </div>
272
  </div>
273
 
274
- <footer class="footer"><span class="note">a11oy · SZL Holdings · Warhacker demos · in-image · signed · re-verifiable · zero CDN</span></footer>
275
 
 
276
  <script>
277
  (function(){
278
  "use strict";
279
- // SOVEREIGN: no external scripts. API base: /api/a11oy/v1 (Node proxy) with /v1 fallback.
280
  var BASES = ["/api/a11oy/v1", "/v1"];
281
  function api(path, opts){
282
  var i = 0;
@@ -284,130 +280,544 @@
284
  return fetch(BASES[i] + path, opts).then(function(r){
285
  if(!r.ok && r.status === 404 && i < BASES.length-1){ i++; return attempt(); }
286
  return r.json().then(function(j){ return {ok:r.ok, status:r.status, json:j}; });
287
- }).catch(function(e){
288
- if(i < BASES.length-1){ i++; return attempt(); }
289
- throw e;
290
- });
291
  }
292
  return attempt();
293
  }
294
  function getJSON(p){ return api(p, {method:"GET"}); }
295
- function postJSON(p, body){
296
- return api(p, {method:"POST", headers:{"Content-Type":"application/json"}, body:JSON.stringify(body||{})});
297
- }
298
  var $ = function(id){ return document.getElementById(id); };
299
  function esc(s){ return String(s==null?"":s).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"); }
300
  function pretty(o){ try{ return JSON.stringify(o, null, 2); }catch(e){ return String(o); } }
301
  function compactVal(v){
302
  if(v==null) return "";
303
  if(typeof v !== "object") return String(v);
304
- // one-line compact JSON, trimmed
305
  var s = JSON.stringify(v);
306
- return s.length>220 ? s.slice(0,217)+"…" : s;
307
- }
308
-
309
- // ---- the five problems (tab key -> meta) ----
310
- var PROBLEMS = [
311
- {key:"cannonico", title:"CANNONICO — AI oversight for autonomous drones",
312
- blurb:"An autonomous (lost-contact) drone is checked against its full authorized envelope. The 13-axis conjunctive ROE gate, the STL robustness value, the PolyCARP-style geofence and the conformal confidence interval are all computed live, then sealed into a signed, tamper-evident record. Run it nominal, then run the tamper test to see one changed byte break the chain.",
313
- nominalLabel:"Nominal flight (inside envelope)", tamperLabel:"Line-crossing flight"},
314
- {key:"tychee", title:"TYCHEE — air-gapped ground-software deploy",
315
- blurb:"A satellite ground-software bundle (UDS-style, Zarf-packaged) is admitted into an air-gapped cluster only if every OCI layer digest matches the cosign attestation and the Merkle bundle root verifies. The tamper test flips one byte in a layer — the digest no longer matches and admission is blocked.",
316
- nominalLabel:"Clean bundle", tamperLabel:"Tampered layer"},
317
- {key:"hangar2apps", title:"HANGAR2APPS — readiness dashboard + audit",
318
- blurb:"A service member's FHIR R4 record (sample, no PHI) is parsed and run through a 5-axis conjunctive readiness gate. A signed Task attestation is chained for the audit trail. The tamper test deletes the influenza immunization — the same gate flips the member to NON-DEPLOYABLE.",
319
- nominalLabel:"Medically ready", tamperLabel:"Missing immunization"},
320
- {key:"cyber_rts", title:"CYBER RTS — trajectory/orbit operational context",
321
- blurb:"A real ISS TLE is parsed and propagated by our reimplemented SGP4 mean-element propagator; CPA/TCPA against a labelled synthetic object is computed live and run through the collision/accuracy gate. The tamper test ages the TLE epoch by 21 days — the accuracy gate flags it before operational use.",
322
- nominalLabel:"Fresh TLE", tamperLabel:"Stale epoch (+21d)"},
323
- {key:"raven", title:"RAVEN — authorize a workload at the disconnected edge",
324
- blurb:"A workload is admitted on a disconnected edge node only if the TPM quote matches the known-good measurement, the image is cosign-verified, and the mission is authorized (conjunctive admission). The tamper test injects an unknown binary into the IMA PCR — the quote no longer matches and admission is denied.",
325
- nominalLabel:"Trusted node", tamperLabel:"PCR drift (implant)"},
326
- {key:"ai-sbom", title:"AI-SBOM — air-gap binding of running model weights",
327
- blurb:"A CycloneDX 1.6 MLBOM is generated in-image and the running model weights are SHA-256'd. The conjunctive admission gate admits the deployment only if the running weight hash equals the authorized MLBOM hash (one of 5 conjunctive axes) and the MLBOM is DSSE-signed. The tamper test swaps the weights — the running hash no longer matches the authorized MLBOM hash, axis S1 flips FALSE, the deployment is REJECTED and the rejection is logged indelibly to the append-only Merkle chain.",
328
- nominalLabel:"Authorized weights", tamperLabel:"Shadow model (weight swap)"},
329
- {key:"agentic-provenance", title:"AGENTIC PROVENANCE — who authorized that action?",
330
- blurb:"A per-action provenance chain binds an autonomous action to (a) the exact agent version hash, (b) the operator delegation receipt, (c) the inputs hash, and (d) the policy-gate Λ vector — each a DSSE-signed leaf in the append-only chain. Four provenance links are then verified plus an inclusion proof. The tamper test forges the operator delegation — the delegation_chain_valid link FAILS and the action is shown to be non-attributable.",
331
- nominalLabel:"Authorized delegation", tamperLabel:"Forged delegation"}
332
- ];
333
-
334
- var state = { run:{}, signed:0, tamperCaught:0, tamperRun:0 };
335
-
336
- // ---- build panel DOM for each problem ----
337
- var wrap = $("panelWrap");
338
- PROBLEMS.forEach(function(p, idx){
339
- var sec = document.createElement("section");
340
- sec.className = "panel" + (idx===0?" active":"");
341
- sec.id = "panel-"+p.key;
342
- sec.innerHTML =
343
- '<div class="p-head">'+
344
- '<div class="p-titlerow">'+
345
- '<h2 class="p-title">'+esc(p.title)+'</h2>'+
346
- '<span id="lbl-'+p.key+'"></span>'+
347
- '</div>'+
348
- '<p class="p-blurb">'+esc(p.blurb)+'</p>'+
349
- '<div id="rmnote-'+p.key+'"></div>'+
350
- '</div>'+
351
- '<div class="card-controls">'+
352
- '<button class="r-btn r-btn-primary" data-act="nominal" data-k="'+p.key+'">▶ '+esc(p.nominalLabel)+'</button>'+
353
- '<button class="r-btn r-btn-danger" data-act="tamper" data-k="'+p.key+'">⚠ Tamper test — '+esc(p.tamperLabel)+'</button>'+
354
- '</div>'+
355
- '<div id="verdict-'+p.key+'" class="verdict"></div>'+
356
- '<div class="block"><div class="block-h"><span class="pillc">step timeline</span> real wall-clock durations · computed values</div>'+
357
- '<ul class="tl" id="tl-'+p.key+'"><li class="empty">— run a demo to populate the timeline —</li></ul></div>'+
358
- '<div class="block" id="ctblock-'+p.key+'"><div class="block-h"><span class="pillc">catch tree</span> first failing condition auto-expanded</div>'+
359
- '<ul class="ct" id="ct-'+p.key+'"><li class="empty">— run a demo —</li></ul></div>'+
360
- '<div class="block" id="chainselfblock-'+p.key+'"><div class="block-h"><span class="pillc">live chain</span> this run\u2019s own signed chain — re-verified intact</div>'+
361
- '<div id="chainself-'+p.key+'"><div class="empty">— run a demo to verify the live signed chain —</div></div></div>'+
362
- '<div class="block" id="tamperblock-'+p.key+'"><div class="block-h"><span class="pillc">tamper test</span> one byte flipped → chain breaks</div>'+
363
- '<div id="tamper-'+p.key+'"><div class="empty">— run the tamper test to flip one byte and watch the signed chain break —</div></div></div>'+
364
- '<div class="block"><div class="block-h"><span class="pillc">formula-proof panel</span> exact formulas + honest status</div>'+
365
- '<ul class="fp" id="fp-'+p.key+'"><li class="empty">— run a demo —</li></ul></div>'+
366
- '<div class="out">'+
367
- '<div class="out-label" data-toggle="raw-'+p.key+'"><span>raw signed record (click to expand)</span>'+
368
- '<span class="out-status"><span class="status-dot" id="dotr-'+p.key+'"></span><span id="status-'+p.key+'">idle</span></span></div>'+
369
- '<pre class="out-pre" id="raw-'+p.key+'"></pre></div>'+
370
- '<div class="honesty-note" id="honesty-'+p.key+'"></div>';
371
- wrap.appendChild(sec);
372
- });
373
 
374
- // ---- tab switching ----
375
- var tabBtns = document.querySelectorAll(".tab-btn");
376
- tabBtns.forEach(function(b){
377
- b.addEventListener("click", function(){
378
- tabBtns.forEach(function(x){ x.classList.remove("active"); });
379
- b.classList.add("active");
380
- document.querySelectorAll(".panel").forEach(function(pp){ pp.classList.remove("active"); });
381
- $("panel-"+b.dataset.tab).classList.add("active");
 
 
382
  });
383
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
 
385
- // ---- raw toggles ----
386
- document.querySelectorAll(".out-label[data-toggle]").forEach(function(el){
387
- el.addEventListener("click", function(){ var t=$(el.dataset.toggle); if(t) t.classList.toggle("show"); });
388
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
 
390
- function setStatus(key, st, txt){
391
- var d=$("dotr-"+key); if(d) d.className="status-dot"+(st?(" "+st):"");
392
- var s=$("status-"+key); if(s) s.textContent=txt||st||"";
 
 
 
 
 
393
  }
394
- function setTabDot(key, st){ var d=$("dot-"+key); if(d) d.className="dot"+(st?(" "+st):""); }
395
 
396
- function refreshStats(){
397
- var n = Object.keys(state.run).length;
398
- $("stRun").textContent = n + " / 5";
399
- $("stSigned").textContent = state.signed;
400
- $("stTamper").textContent = state.tamperCaught + " / " + state.tamperRun;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401
  }
402
 
403
- // ---- renderers ----
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
404
  function renderTimeline(key, timeline, animate){
405
- var ul = $("tl-"+key); ul.innerHTML = "";
406
  var lis = [];
407
  (timeline||[]).forEach(function(t){
408
  var li = document.createElement("li");
409
- var status = animate ? "pending" : (t.ok ? "complete" : "failed");
410
- li.className = status;
411
  var icon = animate ? "•" : (t.ok ? "✓" : "✕");
412
  li.innerHTML =
413
  '<span class="ic">'+icon+'</span>'+
@@ -418,49 +828,37 @@
418
  ul.appendChild(li); lis.push({li:li, t:t});
419
  });
420
  if(!animate) return Promise.resolve();
421
- // animate: pending -> running -> complete/failed in sequence
422
  return new Promise(function(resolve){
423
  var i = 0;
424
  function tick(){
425
  if(i >= lis.length){ resolve(); return; }
426
- var o = lis[i];
427
- o.li.className = "running";
428
- o.li.querySelector(".ic").textContent = "•";
429
- setTimeout(function(){
430
- var ok = o.t.ok;
431
- o.li.className = ok ? "complete" : "failed";
432
- o.li.querySelector(".ic").textContent = ok ? "✓" : "✕";
433
- i++; tick();
434
- }, Math.min(380, 120 + (o.t.duration_ms||0)*4));
435
  }
436
  tick();
437
  });
438
  }
439
-
440
  function renderCatchTree(key, tree, firstFail){
441
- var ul = $("ct-"+key); ul.innerHTML = "";
442
  if(!tree || !tree.length){ ul.innerHTML = '<li class="empty">— no catch tree —</li>'; return; }
443
  tree.forEach(function(c){
444
  var pass = !!c.pass;
445
  var li = document.createElement("li");
446
- li.className = pass ? "pass" : "fail" + (c.node===firstFail ? " firstfail" : "");
447
  var marginTxt = (c.margin!=null) ? ("margin "+c.margin) : (c.detail||"");
448
  li.innerHTML =
449
  '<span class="mark">'+(pass?"✓":"✕")+'</span>'+
450
  '<span class="nm">'+esc(c.node)+'</span>'+
451
  (c.node===firstFail ? '<span class="firstbadge">caught here</span>' : '')+
452
  '<span class="margin">'+esc(marginTxt)+'</span>';
453
- // label line under name
454
- if(c.label){
455
- var sub = document.createElement("div");
456
  sub.style.cssText="font-size:10px;color:var(--muted);width:100%;margin-left:1.6rem;margin-top:-.1rem;";
457
- sub.textContent = c.label;
458
- li.appendChild(sub);
459
- }
460
  ul.appendChild(li);
461
  });
462
  }
463
-
464
  function flipDiff(before, after){
465
  if(before==null || after==null) return esc(String(after));
466
  var b=String(before), a=String(after), i=0;
@@ -468,11 +866,9 @@
468
  var pre=esc(a.slice(0,i)), ch=esc(a.slice(i,i+1)), post=esc(a.slice(i+1));
469
  return '<span class="b4">before: '+esc(b)+'</span><br><span class="af">after:&nbsp; '+pre+'<span class="hl">'+ch+'</span>'+post+'</span>';
470
  }
471
-
472
  function renderChainSelf(key, cs){
473
- var box = $("chainself-"+key);
474
- if(!box) return;
475
- if(!cs){ box.innerHTML = ""; return; }
476
  var intact = (cs.chain_intact===true) && (cs.merkle_root_matches===true);
477
  box.innerHTML =
478
  '<div class="tamper-grid">'+
@@ -482,9 +878,8 @@
482
  '<div class="tamper-cell"><div class="tk">chain depth</div><div class="tv">'+esc(cs.depth)+'</div></div>'+
483
  '</div>';
484
  }
485
-
486
- function renderTamper(key, tt, problem){
487
- var box = $("tamper-"+key);
488
  if(!tt){ box.innerHTML = '<div class="empty">— no tamper data —</div>'; return false; }
489
  var incl = tt.inclusion || {};
490
  var caught = (tt.chain_intact===false) || (tt.merkle_root_matches===false) || (incl.inclusion_valid===false);
@@ -493,10 +888,10 @@
493
  ? ('TAMPER CAUGHT — flipped 1 byte in receipt #'+(t.tampered_seq!=null?t.tampered_seq:"?")+' field "'+esc(t.field||"?")+'". '+
494
  'Chain intact: false (break at receipt #'+(tt.chain_break_at_seq!=null?tt.chain_break_at_seq:"?")+'). '+
495
  'Merkle root match: false. Inclusion proof valid: false.')
496
- : 'No break detected (unexpected).';
497
  box.innerHTML =
498
  '<div class="tamper-summary">'+summary+'</div>'+
499
- (t.before!=null ? '<div class="tamper-cell"><div class="tk">single-byte flip ('+esc(t.bytes_changed||1)+' byte) in '+esc(t.field||"")+'</div><div class="bytediff">'+flipDiff(t.before,t.after)+'</div></div>' : '')+
500
  '<div class="tamper-grid" style="margin-top:.5rem">'+
501
  '<div class="tamper-cell"><div class="tk">chain intact</div><div class="tv '+(tt.chain_intact?"good":"bad")+'">'+esc(tt.chain_intact)+'</div></div>'+
502
  '<div class="tamper-cell"><div class="tk">chain break at receipt</div><div class="tv '+(tt.chain_break_at_seq!=null?"bad":"good")+'">#'+esc(tt.chain_break_at_seq)+'</div></div>'+
@@ -507,18 +902,35 @@
507
  '</div>';
508
  return caught;
509
  }
510
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
  function renderFormula(key, panel){
512
- var ul = $("fp-"+key); ul.innerHTML="";
513
  if(!panel || !panel.length){ ul.innerHTML='<li class="empty">— no formula panel —</li>'; return; }
514
  panel.forEach(function(f){
515
  var li=document.createElement("li");
516
- var st = String(f.status||"");
517
- // bold the proven/roadmap keywords honestly
518
- var stHtml = esc(st)
519
  .replace(/PROVEN/g,'<b class="proven">PROVEN</b>')
520
  .replace(/roadmap/gi,'<b class="roadmap">roadmap</b>')
521
- .replace(/Conjecture 1/g,'<b class="roadmap">Conjecture 1</b>');
 
522
  li.innerHTML =
523
  '<div class="fn">'+esc(f.formula)+(f.role?'<span class="role">'+esc(f.role)+'</span>':'')+'</div>'+
524
  (f.expr?'<div class="expr">'+esc(f.expr)+'</div>':'')+
@@ -527,87 +939,190 @@
527
  ul.appendChild(li);
528
  });
529
  }
530
-
531
  function setLabel(key, realOrRoadmap){
532
  var el=$("lbl-"+key); if(!el) return;
533
  var isReal = /REAL TODAY/i.test(realOrRoadmap||"");
534
  el.className = isReal ? "label-real" : "label-roadmap";
535
  el.textContent = isReal ? "REAL TODAY" : "ROADMAP · substrate real";
536
- var note=$("rmnote-"+key);
537
- if(note){
538
- if(isReal){ note.innerHTML=""; }
539
- else { note.className="roadmap-note"; note.textContent = realOrRoadmap; }
540
- }
541
  }
542
 
543
- // ---- run one demo ----
544
- function runDemo(key, mode){
545
- setStatus(key, "loading", mode==="tamper"?"tamper run…":"running…");
546
- setTabDot(key, "loading");
547
- // clear timeline to pending state quickly
548
- return postJSON("/wh-demo/run/"+key, {mode:mode}).then(function(r){
549
  var d = r.json;
 
550
  if(!d || d.ok===false){
551
- setStatus(key,"err","error"); setTabDot(key,"err");
552
- $("raw-"+key).textContent = pretty(d); $("raw-"+key).classList.add("show");
 
553
  return d;
554
  }
555
- setLabel(key, d.real_or_roadmap);
556
- // verdict
557
- var v=$("verdict-"+key);
558
  var ok = !!d.authorized;
559
- v.className = "verdict show " + (ok?"ok":"err");
560
  v.innerHTML = '<span>'+(ok?"✓":"✕")+'</span><span>'+esc(d.decision)+' — '+esc(d.headline)+'</span>';
561
- // timeline (animated)
562
- var anim = renderTimeline(key, d.timeline, true);
563
- // catch tree
564
- renderCatchTree(key, d.catch_tree, d.first_failing_node);
565
- // live-run chain self-verification (this run's OWN chain, intact)
566
- renderChainSelf(key, d.chain_self);
567
- // tamper test (always present — the negative test)
568
- var caught = renderTamper(key, d.tamper_test, key);
569
- // formula panel
570
- renderFormula(key, d.formula_panel);
571
- // raw
572
- $("raw-"+key).textContent = pretty(d);
573
- // honesty
574
- $("honesty-"+key).textContent = d.honesty || "";
575
  // stats
576
- if(!state.run[key]) state.run[key]=true;
577
- if(d.sealed && d.sealed.signed) state.signed++;
578
  state.tamperRun++; if(caught) state.tamperCaught++;
579
  refreshStats();
580
  anim.then(function(){
581
- setStatus(key, ok?"ok":"err", ok?"sealed + signed":"line crossed · caught");
582
- setTabDot(key, ok?"ok":"err");
583
  });
584
  return d;
585
  }).catch(function(e){
586
- setStatus(key,"err","error"); setTabDot(key,"err");
587
- $("raw-"+key).textContent = String(e); $("raw-"+key).classList.add("show");
588
  });
589
  }
590
 
591
- // ---- bind buttons ----
592
- document.querySelectorAll(".r-btn[data-act]").forEach(function(b){
593
- b.addEventListener("click", function(){ runDemo(b.dataset.k, b.dataset.act); });
594
- });
 
 
 
 
 
 
 
595
 
596
- // ---- run all ----
597
- $("runAll").addEventListener("click", function(){
598
- var btn=$("runAll"); btn.disabled=true; btn.textContent="running all five…";
599
- var seq = PROBLEMS.map(function(p){ return p.key; });
600
- var chain = Promise.resolve();
601
- seq.forEach(function(k){ chain = chain.then(function(){ return runDemo(k,"nominal"); }); });
602
- chain.then(function(){ btn.disabled=false; btn.textContent="✓ all five ran — open any tab, then try a tamper test"; })
603
- .catch(function(){ btn.disabled=false; btn.textContent="▶ Run all five (nominal)"; });
604
- });
605
 
606
- // ---- backend probe ----
607
- getJSON("/wh-demo/index").then(function(r){
608
- if(r.json && r.json.ok){ $("stBackend").textContent="LIVE"; }
609
- else { $("stBackend").textContent="—"; }
610
- }).catch(function(){ $("stBackend").textContent="—"; });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
611
 
612
  refreshStats();
613
  })();
 
3
  <head>
4
  <meta charset="UTF-8"/>
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>a11oy — Warhacker · 25 demos · SZL Holdings</title>
7
+ <meta name="description" content="Twenty-five step-by-step Warhacker demos across five official problems (CANNONICO, TYCHEE, HANGAR2APPS, CYBER-RTS, RAVEN). Each runs a real mechanism in-image: step timeline with real perf_counter durations and computed values, a single-byte tamper test that breaks the signed SHA-256 Merkle chain, an honest formula-proof panel, a signed-receipt verify, and a unique 3D/graph visualization. CANNONICO is real today; the other four run the proven horizontal substrate on clearly labelled sample data."/>
8
+ <!-- SOVEREIGN: zero runtime CDN. System font stack only. Viz libs vendored at /vendor/*. -->
9
  <style>
10
  :root{
11
  --ground:#0a0a0a; --panel:#0c0c0c; --panel2:#070707;
 
20
  --display:'Segoe UI','Helvetica Neue',Arial,system-ui,-apple-system,sans-serif;
21
  }
22
  *{box-sizing:border-box;}
23
+ html,body{margin:0;padding:0;background:var(--ground);color:var(--cream);overflow-x:hidden;
24
  font-family:var(--display);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;}
25
  .mono{font-family:var(--mono);}
26
  a{color:inherit;}
27
  :focus-visible{outline:2px solid var(--gold);outline-offset:3px;border-radius:3px;}
28
+ .ribbon{position:sticky;top:0;z-index:60;display:flex;align-items:center;gap:1.1rem;
29
  flex-wrap:wrap;padding:0.5rem 1.25rem;font-family:var(--mono);font-size:10px;
30
  letter-spacing:0.12em;text-transform:uppercase;color:var(--gold);
31
  background:rgba(10,10,10,0.92);backdrop-filter:blur(10px);
 
38
  .live-dot{width:6px;height:6px;border-radius:50%;background:var(--live);
39
  box-shadow:0 0 6px var(--live);animation:pulseDot 2.2s ease-in-out infinite;}
40
  @keyframes pulseDot{0%,100%{opacity:1;}50%{opacity:.35;}}
41
+ .hero{position:relative;overflow:hidden;padding:2.6rem 1.5rem 1.6rem;text-align:center;}
42
  .grid-bg{position:absolute;inset:0;pointer-events:none;
43
  background-image:
44
  linear-gradient(rgba(255,255,255,.025) 1px,transparent 1px),
 
48
  mask-image:radial-gradient(ellipse at center,#000 30%,transparent 75%);}
49
  .hero-inner{position:relative;z-index:2;max-width:64rem;margin:0 auto;}
50
  .eyebrow{display:inline-flex;align-items:center;gap:.5rem;padding:.3rem .85rem;
51
+ border-radius:999px;border:1px solid var(--gold-line);background:var(--gold-soft);margin-bottom:1.2rem;}
52
  .eyebrow-dot{width:6px;height:6px;border-radius:50%;background:var(--gold);
53
  animation:pulseDot 2.2s ease-in-out infinite;}
54
  .eyebrow-text{font-family:var(--mono);font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--gold);}
55
+ .headline{font-size:clamp(2rem,5vw,3.5rem);font-weight:300;line-height:1.05;
56
+ letter-spacing:-.03em;margin:0 0 1rem;color:var(--cream);}
57
  .headline .accent{background:linear-gradient(120deg,var(--cream) 20%,var(--gold) 90%);
58
  -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent;}
59
+ .support{font-size:15px;line-height:1.62;color:var(--paragraph);max-width:46rem;margin:0 auto 1.4rem;}
60
+ .stats{display:flex;flex-wrap:wrap;justify-content:center;gap:.7rem;max-width:62rem;margin:1.4rem auto 0;}
61
+ .stat{flex:1 1 140px;min-width:130px;border:1px solid var(--gold-line);border-radius:8px;
62
+ background:var(--panel);padding:.8rem 1rem;text-align:left;}
 
 
 
 
 
63
  .stat .k{font-family:var(--mono);font-size:9px;letter-spacing:.16em;text-transform:uppercase;color:var(--muted);}
64
+ .stat .v{font-size:1.25rem;font-weight:600;color:var(--gold);margin-top:.25rem;}
65
  .stat .v.teal{color:var(--teal);}
 
66
 
67
+ /* problem tabs */
68
+ .tabs-bar{position:sticky;top:33px;z-index:50;display:flex;gap:.35rem;flex-wrap:wrap;justify-content:center;
69
+ padding:.55rem 1rem;background:rgba(10,10,10,.96);backdrop-filter:blur(10px);border-bottom:1px solid var(--gold-line);}
70
+ .tab-btn{font-family:var(--mono);font-size:11px;letter-spacing:.02em;padding:.45rem .8rem;border-radius:7px;
71
  border:1px solid var(--gold-line);background:transparent;color:var(--paragraph);cursor:pointer;transition:all .18s ease;}
72
  .tab-btn:hover{color:var(--gold);border-color:rgba(201,183,135,.35);}
73
  .tab-btn.active{background:var(--gold);color:var(--ground);border-color:var(--gold);font-weight:600;}
 
 
 
 
74
 
75
+ .panel-wrap{max-width:64rem;margin:1.4rem auto 3rem;padding:0 1.25rem;}
76
  .panel{display:none;}
77
  .panel.active{display:block;animation:fade .3s ease;}
78
  @keyframes fade{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:none;}}
79
  .p-head{margin-bottom:.9rem;}
80
  .p-titlerow{display:flex;align-items:center;gap:.7rem;flex-wrap:wrap;margin-bottom:.4rem;}
81
+ .p-title{font-size:1.45rem;font-weight:400;letter-spacing:-.02em;margin:0;color:var(--cream);}
82
  .label-real,.label-roadmap{font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;
83
  padding:.2rem .55rem;border-radius:5px;font-weight:600;}
84
  .label-real{color:var(--live-bright);border:1px solid rgba(121,199,154,.45);background:rgba(90,138,110,.12);}
85
  .label-roadmap{color:var(--amber);border:1px solid rgba(214,178,74,.4);background:rgba(214,178,74,.08);}
86
+ .p-blurb{font-size:12.5px;line-height:1.6;color:var(--paragraph);max-width:48rem;margin:0;}
87
  .roadmap-note{font-size:11.5px;line-height:1.55;color:var(--amber);background:rgba(214,178,74,.06);
88
  border:1px solid rgba(214,178,74,.25);border-radius:7px;padding:.55rem .8rem;margin:.7rem 0 0;}
89
 
90
+ /* demo picker (5 per problem) */
91
+ .demo-pick{display:flex;gap:.4rem;flex-wrap:wrap;margin:.9rem 0 .2rem;}
92
+ .demo-chip{flex:1 1 auto;min-width:0;font-family:var(--mono);font-size:10.5px;letter-spacing:.02em;
93
+ padding:.5rem .55rem;border-radius:7px;border:1px solid var(--gold-line);background:transparent;
94
+ color:var(--paragraph);cursor:pointer;transition:all .16s ease;text-align:left;line-height:1.3;}
95
+ .demo-chip:hover{color:var(--gold);border-color:rgba(201,183,135,.35);}
96
+ .demo-chip.sel{background:rgba(201,183,135,.1);border-color:var(--gold);color:var(--gold);font-weight:600;}
97
+ .demo-chip .cid{display:inline-block;color:var(--teal);font-weight:700;margin-right:.35rem;}
98
+ .demo-chip .cdot{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--dim);margin-left:.35rem;vertical-align:middle;}
99
+ .demo-chip .cdot.ok{background:var(--live-bright);box-shadow:0 0 5px var(--live);}
100
+ .demo-chip .cdot.err{background:var(--err-bright);box-shadow:0 0 5px var(--err);}
101
+
102
+ .demo-title{font-size:1.05rem;font-weight:500;color:var(--cream);margin:1.1rem 0 .15rem;letter-spacing:-.01em;}
103
+ .card-controls{display:flex;flex-wrap:wrap;gap:.5rem;margin:.55rem 0 .9rem;}
104
  .r-btn{display:inline-flex;align-items:center;gap:.4rem;padding:.5rem 1rem;font-size:11.5px;
105
  font-weight:500;font-family:var(--mono);border-radius:6px;border:1px solid var(--gold-line);
106
  background:transparent;color:var(--gold);cursor:pointer;letter-spacing:.04em;
107
  transition:background .18s ease,border-color .18s ease;}
108
  .r-btn:hover{background:rgba(201,183,135,.08);border-color:rgba(201,183,135,.35);}
 
109
  .r-btn:disabled{opacity:.4;cursor:not-allowed;}
110
  .r-btn-primary{background:var(--gold);color:var(--ground);border-color:var(--gold);}
111
  .r-btn-primary:hover{background:var(--gold-bright);border-color:var(--gold-bright);}
112
  .r-btn-danger{color:var(--err-bright);border-color:rgba(176,106,90,.45);}
113
  .r-btn-danger:hover{background:rgba(176,106,90,.1);}
 
114
 
115
  .verdict{display:flex;align-items:center;gap:.5rem;font-family:var(--mono);font-size:12.5px;
116
+ padding:.5rem .8rem;border-radius:7px;margin-bottom:.8rem;font-weight:600;line-height:1.45;}
 
117
  .verdict.ok{color:var(--live-bright);border:1px solid rgba(90,138,110,.45);background:rgba(90,138,110,.1);}
118
  .verdict.err{color:var(--err-bright);border:1px solid rgba(176,106,90,.5);background:rgba(176,106,90,.1);}
119
 
 
120
  .block{border:1px solid var(--gold-line);border-radius:10px;background:var(--panel);
121
  padding:.9rem 1rem 1rem;margin-bottom:1rem;}
122
  .block-h{font-family:var(--mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;
123
+ color:var(--muted);margin:0 0 .65rem;display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;}
124
  .block-h .pillc{color:var(--teal);border:1px solid var(--teal-line);background:var(--teal-soft);
125
  border-radius:4px;padding:.05rem .35rem;font-size:9px;}
126
 
127
+ /* viz */
128
+ .viz-title{font-family:var(--mono);font-size:11px;color:var(--gold);margin:0 0 .55rem;line-height:1.45;}
129
+ .viz-canvas{width:100%;height:300px;max-width:100%;border-radius:8px;background:var(--panel2);
130
+ border:1px solid var(--gold-soft);overflow:hidden;position:relative;}
131
+ @media(max-width:560px){.viz-canvas{height:240px;}}
132
+ .viz-fallback{font-family:var(--mono);font-size:11px;color:var(--paragraph);line-height:1.6;padding:.6rem;}
133
+ .viz-legend{font-family:var(--mono);font-size:9.5px;color:var(--muted);margin-top:.45rem;display:flex;gap:1rem;flex-wrap:wrap;}
134
+ .viz-legend .sw{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:.3rem;vertical-align:middle;}
135
+
136
  /* step timeline */
137
  .tl{list-style:none;margin:0;padding:0;}
138
  .tl li{display:flex;gap:.65rem;align-items:flex-start;padding:.5rem 0;border-bottom:1px solid var(--gold-soft);}
139
  .tl li:last-child{border-bottom:none;}
140
  .tl .ic{flex:0 0 auto;width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;
141
  font-family:var(--mono);font-size:10px;margin-top:1px;border:1px solid var(--dim);color:var(--dim);background:transparent;}
 
142
  .tl li.running .ic{border-color:var(--gold);color:var(--gold);animation:pulseDot 1s ease-in-out infinite;}
143
  .tl li.complete .ic{background:rgba(90,138,110,.18);color:var(--live-bright);border-color:rgba(90,138,110,.5);}
144
  .tl li.failed .ic{background:rgba(176,106,90,.18);color:var(--err-bright);border-color:rgba(176,106,90,.55);}
 
153
  /* catch tree */
154
  .ct{list-style:none;margin:0;padding:0;}
155
  .ct li{font-family:var(--mono);font-size:11.5px;padding:.32rem .6rem;border-radius:5px;margin-bottom:.25rem;
156
+ display:flex;align-items:flex-start;gap:.5rem;border:1px solid transparent;flex-wrap:wrap;}
157
  .ct li.pass{color:var(--paragraph);}
158
  .ct li.pass .mark{color:var(--live-bright);}
159
  .ct li.fail{color:var(--err-bright);background:rgba(176,106,90,.1);border-color:rgba(176,106,90,.4);}
 
166
  .ct .firstbadge{font-size:8.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--err-bright);
167
  border:1px solid rgba(224,138,118,.5);border-radius:3px;padding:.02rem .3rem;margin-left:.45rem;}
168
 
169
+ /* tamper / chain cells */
170
  .tamper-grid{display:grid;grid-template-columns:1fr 1fr;gap:.5rem;}
171
  @media(max-width:560px){.tamper-grid{grid-template-columns:1fr;}}
172
+ .tamper-cell{border:1px solid var(--gold-soft);border-radius:6px;padding:.5rem .65rem;min-width:0;}
173
  .tamper-cell .tk{font-family:var(--mono);font-size:8.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);}
174
  .tamper-cell .tv{font-family:var(--mono);font-size:11px;margin-top:.2rem;word-break:break-all;}
175
  .tv.bad{color:var(--err-bright);} .tv.good{color:var(--live-bright);}
 
180
  .bytediff .b4{color:var(--paragraph);} .bytediff .af{color:var(--err-bright);}
181
  .bytediff .hl{background:rgba(224,138,118,.35);color:#fff;border-radius:2px;padding:0 1px;}
182
 
183
+ /* receipt verify */
184
+ .receipt{display:flex;align-items:center;gap:.6rem;font-family:var(--mono);font-size:11.5px;flex-wrap:wrap;}
185
+ .receipt .badge{padding:.25rem .6rem;border-radius:5px;font-weight:600;letter-spacing:.05em;}
186
+ .receipt .badge.pass{color:var(--live-bright);border:1px solid rgba(90,138,110,.5);background:rgba(90,138,110,.12);}
187
+ .receipt .badge.fail{color:var(--err-bright);border:1px solid rgba(176,106,90,.5);background:rgba(176,106,90,.12);}
188
+ .receipt .kv{color:var(--muted);}
189
+
190
  /* formula panel */
191
  .fp{list-style:none;margin:0;padding:0;}
192
  .fp li{border:1px solid var(--gold-soft);border-radius:7px;padding:.55rem .7rem;margin-bottom:.5rem;}
 
199
  .fp .status b.roadmap{color:var(--amber);}
200
  .fp .where{font-family:var(--mono);font-size:9.5px;color:var(--muted);margin-top:.3rem;}
201
 
 
202
  .out{position:relative;border:1px solid var(--gold-line);border-radius:8px;background:var(--panel2);overflow:hidden;}
203
  .out-label{display:flex;align-items:center;justify-content:space-between;padding:.45rem .85rem;
204
  border-bottom:1px solid var(--gold-line);font-family:var(--mono);font-size:9px;
205
  letter-spacing:.1em;text-transform:uppercase;color:var(--muted);cursor:pointer;}
 
 
 
 
 
206
  .out-pre{margin:0;padding:.85rem;font-family:var(--mono);font-size:10px;line-height:1.55;
207
  color:var(--paragraph);white-space:pre-wrap;word-break:break-word;max-height:340px;overflow-y:auto;
208
  scrollbar-width:thin;scrollbar-color:var(--dim) transparent;display:none;}
209
  .out-pre.show{display:block;}
 
210
  .honesty-note{font-family:var(--mono);font-size:10.5px;color:var(--teal);line-height:1.55;
211
  margin-top:.8rem;border-left:2px solid var(--teal-line);padding-left:.7rem;}
212
 
213
  .footer{border-top:1px solid var(--gold-line);padding:2rem 1.5rem;text-align:center;}
214
  .footer .note{font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--dim);}
215
+ .honest{max-width:64rem;margin:0 auto 2rem;padding:0 1.25rem;}
216
  .honest-box{border:1px solid var(--teal-line);border-radius:8px;background:var(--teal-soft);
217
  padding:.9rem 1.1rem;font-size:12px;color:var(--paragraph);line-height:1.6;}
218
  .honest-box b{color:var(--teal);}
 
223
  <body>
224
  <div class="ribbon">
225
  <span class="live"><span class="live-dot"></span>a11oy</span>
226
+ <span class="sep">·</span><span>Warhacker · 25 demos</span>
227
+ <span class="sep">·</span><span class="teal">in-image · real mechanism · signed · re-verifiable · zero CDN</span>
228
  <a href="/console">console →</a>
229
  </div>
230
 
231
  <section class="hero">
232
  <div class="grid-bg"></div>
233
  <div class="hero-inner">
234
+ <span class="eyebrow"><span class="eyebrow-dot"></span><span class="eyebrow-text">5 problems × 5 demos · live on this Space</span></span>
235
+ <h1 class="headline">Twenty-five proofs.<br/><span class="accent">Step by step. No hollow PASS.</span></h1>
236
+ <p class="support">Pick any of the 25 demos. Each runs a <b>real mechanism</b> inside this container and shows its work:
237
+ an ordered step timeline with real wall-clock durations and computed values, the catch tree that caught the exact
238
+ line-crossing, a single-byte tamper test that visibly breaks the signed SHA-256 Merkle chain, the proven formulas
239
+ behind the fix, a signed-receipt verify, and a unique visualization. CANNONICO is real today; the other four run
240
+ the proven horizontal substrate on clearly labelled sample data.</p>
 
241
  <div class="stats">
242
+ <div class="stat"><div class="k">Problems</div><div class="v" id="stProblems">5</div></div>
243
+ <div class="stat"><div class="k">Demos</div><div class="v" id="stDemos">25</div></div>
244
+ <div class="stat"><div class="k">Demos run</div><div class="v teal" id="stRun">0</div></div>
245
  <div class="stat"><div class="k">Tamper caught</div><div class="v teal" id="stTamper">0 / 0</div></div>
246
+ <div class="stat"><div class="k">Backend</div><div class="v teal" id="stBackend">…</div></div>
247
  </div>
248
  </div>
249
  </section>
250
 
251
+ <div class="tabs-bar" id="tabsBar"></div>
252
+ <div class="panel-wrap" id="panelWrap"></div>
 
 
 
 
 
 
 
 
 
 
 
253
 
254
  <div class="honest">
255
  <div class="honest-box">
256
+ <b>What is real here.</b> Every demo runs live in this container. <b>CANNONICO is real today</b>: STL robustness,
257
+ PolyCARP-style geofence containment, A* safe-return and a real SHA-256 Merkle/Khipu hash chain with a Rekor-style
258
+ inclusion proof — all computed live, sealed into a DSSE envelope signed by an in-image ECDSA-P256 key (verifiable
259
+ against <a href="/cosign.pub" style="color:var(--teal)">/cosign.pub</a>). Change one byte and the chain breaks on
260
+ screen. The other four problems run the <b>proven horizontal substrate</b> — SHA-256/Merkle integrity, conjunctive
261
+ admission, conformal coverage, reimplemented SGP4 propagation, Byzantine quorum — on clearly labelled sample data;
262
+ labelled ROADMAP because the operational vertical (real registry, real feed, real TPM hardware, production ATO) is
263
+ a fast stand-up, not claimed as done. Locked-proven (zero-sorry Lean): F11 STL, M2 Hash-Chain, CP1 Conformal,
264
+ G1 CPA, S2 Simplex, B1 Byzantine. The trust score (&Lambda;) uniqueness is <b>Conjecture 1</b>
265
+ (conditional/CI-green; unconditional is false). SLSA L1 honest; L2 roadmap. GIN-GRU / PEGASUS are external,
266
+ experimental predictors shown on sample data.
267
  </div>
268
  </div>
269
 
270
+ <footer class="footer"><span class="note">a11oy · SZL Holdings · Warhacker · 25 demos · in-image · signed · re-verifiable · zero CDN</span></footer>
271
 
272
+ <!-- SOVEREIGN: viz libraries vendored locally (NO CDN). Loaded lazily on first use. -->
273
  <script>
274
  (function(){
275
  "use strict";
 
276
  var BASES = ["/api/a11oy/v1", "/v1"];
277
  function api(path, opts){
278
  var i = 0;
 
280
  return fetch(BASES[i] + path, opts).then(function(r){
281
  if(!r.ok && r.status === 404 && i < BASES.length-1){ i++; return attempt(); }
282
  return r.json().then(function(j){ return {ok:r.ok, status:r.status, json:j}; });
283
+ }).catch(function(e){ if(i < BASES.length-1){ i++; return attempt(); } throw e; });
 
 
 
284
  }
285
  return attempt();
286
  }
287
  function getJSON(p){ return api(p, {method:"GET"}); }
288
+ function postJSON(p, body){ return api(p, {method:"POST", headers:{"Content-Type":"application/json"}, body:JSON.stringify(body||{})}); }
 
 
289
  var $ = function(id){ return document.getElementById(id); };
290
  function esc(s){ return String(s==null?"":s).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"); }
291
  function pretty(o){ try{ return JSON.stringify(o, null, 2); }catch(e){ return String(o); } }
292
  function compactVal(v){
293
  if(v==null) return "";
294
  if(typeof v !== "object") return String(v);
 
295
  var s = JSON.stringify(v);
296
+ return s.length>240 ? s.slice(0,237)+"…" : s;
297
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
 
299
+ /* ---------- lazy vendored-script loader (zero CDN; from /vendor/*) ---------- */
300
+ var _loaded = {};
301
+ function loadScript(file){
302
+ if(_loaded[file]) return _loaded[file];
303
+ _loaded[file] = new Promise(function(res, rej){
304
+ var s = document.createElement("script");
305
+ s.src = "/vendor/" + file; s.async = true;
306
+ s.onload = function(){ res(true); };
307
+ s.onerror = function(){ rej(new Error("vendor load failed: "+file)); };
308
+ document.head.appendChild(s);
309
  });
310
+ return _loaded[file];
311
+ }
312
+ // which vendor files each viz kind needs
313
+ var VIZ_DEPS = {
314
+ vectorfield:["echarts.min.js"], geofence:["echarts.min.js"], scatter_trails:["echarts.min.js"],
315
+ thermal_field:["echarts.min.js"], byzantine:["echarts.min.js"], fusion_ellipse:["echarts.min.js"],
316
+ iq_scatter:["echarts.min.js"], vital_band:["echarts.min.js"], scatter_simplex:["echarts.min.js"],
317
+ httpvfs_console:["echarts.min.js"],
318
+ tamper_dag:["cytoscape.min.js"], cascade_tree:["cytoscape.min.js"],
319
+ globe_conjunction:["three.min.js","globe.gl.min.js"], globe_tube:["three.min.js","globe.gl.min.js"],
320
+ killchain_globe:["three.min.js","globe.gl.min.js"],
321
+ sankey_stl:["d3.min.js","d3-sankey.min.js"], sankey_info:["d3.min.js","d3-sankey.min.js"]
322
+ // boids -> canvas (no dep); waterfall -> svg (no dep);
323
+ // pathgraph/galaxy/mesh_graph/tactical_galaxy -> built-in canvas graph (no dep)
324
+ };
325
+ function ensureDeps(kind){
326
+ var deps = VIZ_DEPS[kind] || [];
327
+ var chain = Promise.resolve();
328
+ deps.forEach(function(f){ chain = chain.then(function(){ return loadScript(f); }); });
329
+ return chain;
330
+ }
331
 
332
+ var GOLD="#c9b787", TEAL="#5fb3a3", ERR="#e08a76", LIVE="#79c79a", DIM="#555", CREAM="#f5f5f5", PANEL="#070707";
333
+
334
+ /* ---------- the 5 problems / 25 demos (filled from /warhacker/index) ---------- */
335
+ var PROBLEMS = []; // [{key,title,blurb,real_or_roadmap,demos:[{id,title,...}]}]
336
+ var state = { run:{}, tamperCaught:0, tamperRun:0 };
337
+ var sel = {}; // problemKey -> selected demo id
338
+
339
+ /* ============================ VIZ RENDERERS ============================ */
340
+ var _charts = {}; // containerId -> echarts instance / cleanup
341
+ function clearViz(cid){
342
+ var c = $(cid); if(!c) return;
343
+ if(_charts[cid]){ try{ _charts[cid](); }catch(e){} delete _charts[cid]; }
344
+ c.innerHTML = "";
345
+ }
346
+ function vizFallback(cid, viz){
347
+ var c=$(cid); if(!c) return;
348
+ var rows = Object.keys(viz).filter(function(k){return k!=="kind"&&k!=="lib"&&k!=="title";})
349
+ .map(function(k){ return '<div><span style="color:'+TEAL+'">'+esc(k)+'</span>: '+esc(compactVal(viz[k]))+'</div>'; }).join("");
350
+ c.innerHTML = '<div class="viz-fallback">'+rows+'</div>';
351
+ }
352
+ function echartInit(cid){
353
+ var c=$(cid); if(!c||!window.echarts) return null;
354
+ var inst = window.echarts.init(c, null, {renderer:"canvas"});
355
+ _charts[cid] = function(){ try{ inst.dispose(); }catch(e){} window.removeEventListener("resize", rs); };
356
+ var rs = function(){ try{ inst.resize(); }catch(e){} };
357
+ window.addEventListener("resize", rs);
358
+ return inst;
359
+ }
360
+ var ECBASE = { backgroundColor:"transparent", textStyle:{color:"#9a9a9a",fontFamily:"monospace"},
361
+ grid:{left:48,right:18,top:30,bottom:38}, animationDuration:600 };
362
 
363
+ function renderViz(cid, viz){
364
+ clearViz(cid);
365
+ if(!viz){ return; }
366
+ var kind = viz.kind;
367
+ ensureDeps(kind).then(function(){
368
+ try{ _renderVizImpl(cid, viz); }
369
+ catch(e){ vizFallback(cid, viz); }
370
+ }).catch(function(){ vizFallback(cid, viz); });
371
  }
 
372
 
373
+ function _renderVizImpl(cid, viz){
374
+ var k = viz.kind;
375
+ switch(k){
376
+ case "vectorfield": return vizVectorField(cid, viz);
377
+ case "geofence": return vizGeofence(cid, viz);
378
+ case "scatter_trails": return vizTrails(cid, viz, "AI confidence", "floor");
379
+ case "scatter_simplex": return vizTrails(cid, viz, "deterioration score", "thr", true);
380
+ case "thermal_field": return vizThermal(cid, viz);
381
+ case "vital_band": return vizVitalBand(cid, viz);
382
+ case "byzantine": return vizByzantine(cid, viz);
383
+ case "fusion_ellipse": return vizFusion(cid, viz);
384
+ case "iq_scatter": return vizIQ(cid, viz);
385
+ case "httpvfs_console": return vizHttpvfs(cid, viz);
386
+ case "tamper_dag": return vizDag(cid, viz);
387
+ case "cascade_tree": return vizCascade(cid, viz);
388
+ case "globe_conjunction":
389
+ case "killchain_globe":
390
+ case "globe_tube": return vizGlobe(cid, viz);
391
+ case "sankey_stl": return vizSankeySTL(cid, viz);
392
+ case "sankey_info": return vizSankeyInfo(cid, viz);
393
+ case "boids": return vizBoids(cid, viz);
394
+ case "waterfall": return vizWaterfall(cid, viz);
395
+ case "pathgraph":
396
+ case "galaxy":
397
+ case "mesh_graph":
398
+ case "tactical_galaxy": return vizGraph(cid, viz);
399
+ default: return vizFallback(cid, viz);
400
+ }
401
+ }
402
+
403
+ /* --- ECharts-based --- */
404
+ function vizVectorField(cid, v){
405
+ var ec = echartInit(cid); if(!ec) return vizFallback(cid, v);
406
+ var pts = (v.series||[]).map(function(s){ return [s.t, s.alt]; });
407
+ var danger = (v.series||[]).filter(function(s){return s.alt>(v.threshold_x||1e9);}).map(function(s){return [s.t,s.alt];});
408
+ ec.setOption(Object.assign({}, ECBASE, {
409
+ xAxis:{name:"t (10Hz)", type:"value", axisLine:{lineStyle:{color:DIM}}, splitLine:{show:false}},
410
+ yAxis:{name:v.y_label||"alt", type:"value", axisLine:{lineStyle:{color:DIM}}, splitLine:{lineStyle:{color:"rgba(255,255,255,.04)"}}},
411
+ series:[
412
+ {type:"line", data:pts, smooth:true, symbol:"circle", symbolSize:7, lineStyle:{color:GOLD,width:2},
413
+ itemStyle:{color:GOLD}, areaStyle:{color:"rgba(201,183,135,.06)"},
414
+ markLine:{silent:true, data:[{yAxis:v.threshold_x, lineStyle:{color:ERR,type:"dashed"}, label:{formatter:"ceiling "+v.threshold_x, color:ERR}}]}},
415
+ {type:"scatter", data:danger, symbolSize:12, itemStyle:{color:ERR}}
416
+ ]
417
+ }));
418
+ }
419
+ function vizGeofence(cid, v){
420
+ var ec = echartInit(cid); if(!ec) return vizFallback(cid, v);
421
+ var poly = (v.polygon||[]).map(function(p){return [p[1],p[0]];}); // [lon,lat]
422
+ if(poly.length) poly.push(poly[0]);
423
+ var d = v.drone||{};
424
+ ec.setOption(Object.assign({}, ECBASE, {
425
+ xAxis:{name:"lon", type:"value", scale:true, axisLine:{lineStyle:{color:DIM}}, splitLine:{show:false}},
426
+ yAxis:{name:"lat", type:"value", scale:true, axisLine:{lineStyle:{color:DIM}}, splitLine:{show:false}},
427
+ series:[
428
+ {type:"line", data:poly, lineStyle:{color:ERR,width:2}, areaStyle:{color:"rgba(176,106,90,.12)"}, symbol:"none", name:"keep-out"},
429
+ {type:"scatter", data:[[d.lon,d.lat]], symbolSize:16, symbol:"diamond",
430
+ itemStyle:{color:v.inside?ERR:LIVE}, label:{show:true, formatter:v.inside?"INSIDE (incursion)":"clear", color:v.inside?ERR:LIVE, position:"right"}}
431
+ ]
432
+ }));
433
+ }
434
+ function vizTrails(cid, v, yname, thrKey, rising){
435
+ var ec = echartInit(cid); if(!ec) return vizFallback(cid, v);
436
+ var arr = v.conf || v.score || [];
437
+ var data = arr.map(function(y,i){ return [i,y]; });
438
+ var thr = v[thrKey];
439
+ var marks = [];
440
+ if(v.warn_idx!=null) marks.push({xAxis:v.warn_idx, lineStyle:{color:TEAL,type:"dashed"}, label:{formatter:"warn τ", color:TEAL}});
441
+ if(v.actual_idx!=null) marks.push({xAxis:v.actual_idx, lineStyle:{color:ERR}, label:{formatter:"crossing", color:ERR}});
442
+ ec.setOption(Object.assign({}, ECBASE, {
443
+ xAxis:{name:"t", type:"value", axisLine:{lineStyle:{color:DIM}}, splitLine:{show:false}},
444
+ yAxis:{name:yname, type:"value", scale:true, axisLine:{lineStyle:{color:DIM}}, splitLine:{lineStyle:{color:"rgba(255,255,255,.04)"}}},
445
+ series:[{type:"line", data:data, smooth:true, symbol:"circle", symbolSize:6, lineStyle:{color:GOLD,width:2}, itemStyle:{color:GOLD},
446
+ markLine:{silent:true, data:[{yAxis:thr, lineStyle:{color:ERR,type:"dashed"}, label:{formatter:(rising?"face ":"floor ")+thr, color:ERR}}].concat(marks)}}]
447
+ }));
448
+ }
449
+ function vizThermal(cid, v){
450
+ var ec = echartInit(cid); if(!ec) return vizFallback(cid, v);
451
+ var data = (v.temp||[]).map(function(y,i){return [i,y];});
452
+ var marks=[{yAxis:v.limit, lineStyle:{color:ERR,type:"dashed"}, label:{formatter:"limit "+v.limit, color:ERR}}];
453
+ if(v.warn_idx!=null) marks.push({xAxis:v.warn_idx, lineStyle:{color:TEAL,type:"dashed"}, label:{formatter:"W5-3 warn", color:TEAL}});
454
+ ec.setOption(Object.assign({}, ECBASE, {
455
+ xAxis:{name:"t", type:"value", axisLine:{lineStyle:{color:DIM}}, splitLine:{show:false}},
456
+ yAxis:{name:"°C", type:"value", scale:true, axisLine:{lineStyle:{color:DIM}}, splitLine:{lineStyle:{color:"rgba(255,255,255,.04)"}}},
457
+ series:[{type:"line", data:data, smooth:true, symbol:"circle", symbolSize:6, lineStyle:{color:GOLD,width:2}, itemStyle:{color:GOLD},
458
+ areaStyle:{color:"rgba(214,178,74,.06)"}, markLine:{silent:true, data:marks}}]
459
+ }));
460
+ }
461
+ function vizVitalBand(cid, v){
462
+ var ec = echartInit(cid); if(!ec) return vizFallback(cid, v);
463
+ var data=(v.spo2||[]).map(function(y,i){return [i,y];});
464
+ var lo=(v.band||[])[0], hi=(v.band||[])[1];
465
+ ec.setOption(Object.assign({}, ECBASE, {
466
+ xAxis:{name:"session", type:"value", axisLine:{lineStyle:{color:DIM}}, splitLine:{show:false}},
467
+ yAxis:{name:"SpO₂ %", type:"value", scale:true, axisLine:{lineStyle:{color:DIM}}, splitLine:{lineStyle:{color:"rgba(255,255,255,.04)"}}},
468
+ series:[{type:"line", data:data, smooth:true, symbol:"circle", symbolSize:7,
469
+ lineStyle:{color:v.in_interval?GOLD:ERR,width:2}, itemStyle:{color:v.in_interval?GOLD:ERR},
470
+ markLine:{silent:true, data:[
471
+ {yAxis:hi, lineStyle:{color:TEAL,type:"dashed"}, label:{formatter:"CP1 hi "+hi, color:TEAL}},
472
+ {yAxis:lo, lineStyle:{color:TEAL,type:"dashed"}, label:{formatter:"CP1 lo "+lo, color:TEAL}}]}}]
473
+ }));
474
+ }
475
+ function vizByzantine(cid, v){
476
+ var ec = echartInit(cid); if(!ec) return vizFallback(cid, v);
477
+ var w=v.witnesses||[];
478
+ var data=w.map(function(x){return {value:1, name:x.id, itemStyle:{color:x.valid?LIVE:ERR}}; });
479
+ ec.setOption({backgroundColor:"transparent", textStyle:{color:"#9a9a9a",fontFamily:"monospace"},
480
+ grid:{left:90,right:20,top:20,bottom:30},
481
+ xAxis:{type:"value", max:1.4, axisLine:{lineStyle:{color:DIM}}, splitLine:{show:false},
482
+ markLine:{}},
483
+ yAxis:{type:"category", data:w.map(function(x){return x.id;}), axisLine:{lineStyle:{color:DIM}}},
484
+ series:[{type:"bar", data:data, barWidth:"50%",
485
+ markLine:{silent:true, symbol:"none", data:[{xAxis:0}]} }],
486
+ graphic:[{type:"text", right:18, top:8, style:{text:"quorum "+v.quorum+"/"+w.length+" · valid "+v.valid+" · "+(v.reached?"REACHED":"NOT REACHED"),
487
+ fill:v.reached?LIVE:ERR, font:"11px monospace"}}]
488
+ });
489
+ }
490
+ function vizFusion(cid, v){
491
+ var ec = echartInit(cid); if(!ec) return vizFallback(cid, v);
492
+ var sens=v.sensors||[]; var f=v.fused||[0,0]; var a=(v.semi_axes||[1,1]);
493
+ // covariance ellipse (2-sigma) as a polyline
494
+ var ell=[]; for(var th=0; th<=360; th+=12){ var r=th*Math.PI/180;
495
+ ell.push([f[0]+2*a[0]*Math.cos(r), f[1]+2*a[1]*Math.sin(r)]); }
496
+ var pts=sens.map(function(s){ return {value:[s.x,s.y], itemStyle:{color:(s.maha>2?ERR:TEAL)},
497
+ label:{show:true, formatter:s.id+" (m="+s.maha+")", color:(s.maha>2?ERR:"#9a9a9a"), position:"top", fontSize:9}}; });
498
+ ec.setOption(Object.assign({}, ECBASE, {
499
+ xAxis:{type:"value", scale:true, axisLine:{lineStyle:{color:DIM}}, splitLine:{show:false}},
500
+ yAxis:{type:"value", scale:true, axisLine:{lineStyle:{color:DIM}}, splitLine:{show:false}},
501
+ series:[
502
+ {type:"line", data:ell, symbol:"none", lineStyle:{color:GOLD,type:"dashed",width:1.5}, name:"2σ ellipse"},
503
+ {type:"scatter", data:[{value:f, itemStyle:{color:GOLD}, symbolSize:14, symbol:"diamond", label:{show:true,formatter:"fused",color:GOLD,position:"bottom",fontSize:9}}], symbolSize:14},
504
+ {type:"scatter", data:pts, symbolSize:12}
505
+ ]
506
+ }));
507
+ }
508
+ function vizIQ(cid, v){
509
+ var ec = echartInit(cid); if(!ec) return vizFallback(cid, v);
510
+ function map(arr){ return (arr||[]).map(function(p){ return Array.isArray(p)?p:[p.i,p.q]; }); }
511
+ ec.setOption(Object.assign({}, ECBASE, {
512
+ legend:{top:4, textStyle:{color:"#9a9a9a"}, data:[v.labelA||"A", v.labelB||"B"]},
513
+ xAxis:{name:"I", type:"value", scale:true, axisLine:{lineStyle:{color:DIM}}, splitLine:{show:false}},
514
+ yAxis:{name:"Q", type:"value", scale:true, axisLine:{lineStyle:{color:DIM}}, splitLine:{show:false}},
515
+ series:[
516
+ {name:v.labelA||"A", type:"scatter", data:map(v.iqA), symbolSize:7, itemStyle:{color:TEAL}},
517
+ {name:v.labelB||"B", type:"scatter", data:map(v.iqB), symbolSize:7, itemStyle:{color:ERR}}
518
+ ]
519
+ }));
520
+ }
521
+ function vizHttpvfs(cid, v){
522
+ var ec = echartInit(cid); if(!ec) return vizFallback(cid, v);
523
+ ec.setOption({backgroundColor:"transparent", textStyle:{color:"#9a9a9a",fontFamily:"monospace"},
524
+ grid:{left:50,right:20,top:30,bottom:34},
525
+ xAxis:{type:"category", data:["DB pages","range reqs (O(log n))"], axisLine:{lineStyle:{color:DIM}}},
526
+ yAxis:{type:"value", axisLine:{lineStyle:{color:DIM}}, splitLine:{lineStyle:{color:"rgba(255,255,255,.04)"}}},
527
+ series:[{type:"bar", barWidth:"45%", data:[
528
+ {value:v.db_pages, itemStyle:{color:TEAL}}, {value:v.range_requests, itemStyle:{color:GOLD}}]}],
529
+ graphic:[{type:"text", right:18, top:8, style:{text:"integrity "+(v.integrity?"OK":"FAIL"),
530
+ fill:v.integrity?LIVE:ERR, font:"11px monospace"}}]
531
+ });
532
+ }
533
+
534
+ /* --- cytoscape DAGs --- */
535
+ function vizDag(cid, v){
536
+ var c=$(cid); if(!c||!window.cytoscape) return vizFallback(cid, v);
537
+ var els=[];
538
+ (v.nodes||[]).forEach(function(n){ els.push({data:{id:n.id, label:n.label||n.id,
539
+ cls:(n.tampered?"tamp":(n.cascade?"casc":"ok"))}}); });
540
+ (v.edges||[]).forEach(function(e){ els.push({data:{source:e.s, target:e.t}}); });
541
+ var cy = window.cytoscape({ container:c, elements:els, userZoomingEnabled:false, autoungrabify:true,
542
+ layout:{name:"breadthfirst", directed:true, spacingFactor:1.15, padding:14},
543
+ style:[
544
+ {selector:"node", style:{"background-color":TEAL,"label":"data(label)","color":CREAM,"font-size":"9px",
545
+ "font-family":"monospace","text-valign":"bottom","text-margin-y":4,"width":22,"height":22}},
546
+ {selector:'node[cls="tamp"]', style:{"background-color":ERR,"border-width":3,"border-color":"#fff","width":28,"height":28}},
547
+ {selector:'node[cls="casc"]', style:{"background-color":"#b06a5a"}},
548
+ {selector:"edge", style:{"width":2,"line-color":"#444","target-arrow-color":"#444","target-arrow-shape":"triangle","curve-style":"bezier"}}
549
+ ]});
550
+ cy.fit(undefined, 18);
551
+ _charts[cid]=function(){ try{cy.destroy();}catch(e){} };
552
+ }
553
+ function vizCascade(cid, v){
554
+ var c=$(cid); if(!c||!window.cytoscape) return vizFallback(cid, v);
555
+ var gates=v.gates||[]; var halt=v.halted_at;
556
+ var els=[]; var prev=null;
557
+ gates.forEach(function(g,i){
558
+ var id="g"+i; var failed = (g.pass===false)||(halt!=null && (g.name===halt||i===halt));
559
+ els.push({data:{id:id, label:(g.name||("gate"+i)), cls:(failed?"fail":"pass")}});
560
+ if(prev) els.push({data:{source:prev, target:id}});
561
+ prev=id;
562
+ });
563
+ var cy=window.cytoscape({container:c, elements:els, userZoomingEnabled:false, autoungrabify:true,
564
+ layout:{name:"breadthfirst", directed:true, spacingFactor:1.1, padding:14},
565
+ style:[
566
+ {selector:"node", style:{"background-color":LIVE,"label":"data(label)","color":CREAM,"font-size":"9px",
567
+ "font-family":"monospace","text-valign":"bottom","text-margin-y":4,"width":22,"height":22}},
568
+ {selector:'node[cls="fail"]', style:{"background-color":ERR,"border-width":3,"border-color":"#fff","width":28,"height":28}},
569
+ {selector:"edge", style:{"width":2,"line-color":"#444","target-arrow-color":"#444","target-arrow-shape":"triangle","curve-style":"bezier"}}
570
+ ]});
571
+ cy.fit(undefined,18);
572
+ _charts[cid]=function(){ try{cy.destroy();}catch(e){} };
573
+ }
574
+
575
+ /* --- globe.gl globes --- */
576
+ function vizGlobe(cid, v){
577
+ var c=$(cid); if(!c||!window.Globe) return vizGlobeCanvas(cid, v);
578
+ var w=c.clientWidth||300, h=c.clientHeight||300;
579
+ var g;
580
+ try{
581
+ g = window.Globe()(c)
582
+ .width(w).height(h)
583
+ .backgroundColor("rgba(0,0,0,0)")
584
+ .showGlobe(true).showAtmosphere(true).atmosphereColor(TEAL).atmosphereAltitude(0.18)
585
+ .globeImageUrl("/vendor/earth-night.jpg");
586
+ }catch(e){ try{c.innerHTML="";}catch(_){}; return vizGlobeCanvas(cid, v); }
587
+ try{ g.controls().autoRotate = true; g.controls().autoRotateSpeed = 0.6; g.controls().enableZoom=false; }catch(e){}
588
+ try{
589
+ function ring(seed, color){
590
+ var pts=[]; for(var i=0;i<=40;i++){ var t=i/40*Math.PI*2;
591
+ pts.push({lat: 45*Math.sin(t+seed), lng: (i*9 - 180), color:color}); }
592
+ return pts;
593
+ }
594
+ var pathsData = [ring(0.3, TEAL), ring(1.1, ERR)];
595
+ g.pathsData(pathsData).pathPointLat("lat").pathPointLng("lng")
596
+ .pathColor(function(p){ return p[0]&&p[0].color?p[0].color:TEAL; })
597
+ .pathStroke(1.5).pathDashLength(0.3).pathDashGap(0.1).pathDashAnimateTime(6000);
598
+ var label = (v.d_cpa_km!=null) ? ("CPA "+v.d_cpa_km+" km @ t+"+(v.t_cpa_s||0)+"s") : (v.title||"");
599
+ var alert = (v.conjunction===true) || (v.blocked && v.blocked.length>0);
600
+ g.pointsData([{lat:20, lng:-30, size:0.6}]).pointColor(function(){return alert?ERR:GOLD;})
601
+ .pointAltitude(0.12).pointRadius(0.6);
602
+ c.setAttribute("data-cpa", label);
603
+ }catch(e){ try{c.innerHTML="";}catch(_){}; return vizGlobeCanvas(cid, v); }
604
+ var rs=function(){ try{ g.width(c.clientWidth).height(c.clientHeight); }catch(e){} };
605
+ window.addEventListener("resize", rs);
606
+ _charts[cid]=function(){ window.removeEventListener("resize",rs); try{ c.innerHTML=""; }catch(e){} };
607
+ }
608
+
609
+ /* --- 2D canvas orbital fallback (sovereign, no WebGL/globe.gl dependency) --- */
610
+ function vizGlobeCanvas(cid, v){
611
+ var c=$(cid); if(!c) return;
612
+ c.innerHTML="";
613
+ var w=c.clientWidth||300, h=c.clientHeight||300;
614
+ var cv=document.createElement("canvas"); cv.width=w; cv.height=h;
615
+ cv.style.width="100%"; cv.style.height="100%"; cv.style.display="block";
616
+ c.appendChild(cv);
617
+ var ctx=cv.getContext("2d");
618
+ var cx=w/2, cy=h/2, R=Math.min(w,h)*0.32;
619
+ var alert = (v.conjunction===true) || (v.blocked && v.blocked.length>0);
620
+ var label = (v.d_cpa_km!=null) ? ("CPA "+v.d_cpa_km+" km @ t+"+(v.t_cpa_s||0)+"s")
621
+ : (v.title||"orbital geometry");
622
+ var ang=0, raf=null;
623
+ function draw(){
624
+ ctx.clearRect(0,0,w,h);
625
+ // earth disc
626
+ var grd=ctx.createRadialGradient(cx-R*0.3,cy-R*0.3,R*0.2,cx,cy,R);
627
+ grd.addColorStop(0,"#13343a"); grd.addColorStop(1,"#05131a");
628
+ ctx.beginPath(); ctx.arc(cx,cy,R,0,Math.PI*2); ctx.fillStyle=grd; ctx.fill();
629
+ ctx.strokeStyle="rgba(95,179,163,0.5)"; ctx.lineWidth=1; ctx.stroke();
630
+ // atmosphere
631
+ ctx.beginPath(); ctx.arc(cx,cy,R+5,0,Math.PI*2);
632
+ ctx.strokeStyle="rgba(95,179,163,0.18)"; ctx.lineWidth=6; ctx.stroke();
633
+ // graticule
634
+ ctx.strokeStyle="rgba(95,179,163,0.12)"; ctx.lineWidth=0.6;
635
+ for(var k=1;k<3;k++){ ctx.beginPath(); ctx.ellipse(cx,cy,R*(k/3),R,0,0,Math.PI*2); ctx.stroke(); }
636
+ ctx.beginPath(); ctx.ellipse(cx,cy,R,R*0.45,0,0,Math.PI*2); ctx.stroke();
637
+ // two orbit rings (tilted ellipses)
638
+ function orbit(rx,ry,rot,color,off){
639
+ ctx.save(); ctx.translate(cx,cy); ctx.rotate(rot);
640
+ ctx.beginPath(); ctx.ellipse(0,0,rx,ry,0,0,Math.PI*2);
641
+ ctx.strokeStyle=color; ctx.lineWidth=1.4; ctx.setLineDash([5,4]); ctx.stroke();
642
+ ctx.setLineDash([]);
643
+ // moving satellite
644
+ var a=ang+off; var sx=Math.cos(a)*rx, sy=Math.sin(a)*ry;
645
+ ctx.beginPath(); ctx.arc(sx,sy,3.2,0,Math.PI*2); ctx.fillStyle=color; ctx.fill();
646
+ ctx.restore();
647
+ }
648
+ orbit(R*1.45,R*0.7,0.4,TEAL,0);
649
+ orbit(R*1.25,R*0.95,-0.6,(alert?ERR:GOLD),Math.PI*0.7);
650
+ // CPA marker
651
+ ctx.fillStyle=alert?ERR:GOLD;
652
+ ctx.font="11px ui-monospace,monospace"; ctx.textAlign="center";
653
+ ctx.fillText(label, cx, h-12);
654
+ if(alert){ ctx.beginPath(); ctx.arc(cx+R*0.5,cy-R*0.2,5,0,Math.PI*2); ctx.fillStyle=ERR; ctx.fill(); }
655
+ ang+=0.012; raf=requestAnimationFrame(draw);
656
+ }
657
+ draw();
658
+ c.setAttribute("data-cpa", label);
659
+ _charts[cid]=function(){ if(raf) cancelAnimationFrame(raf); try{c.innerHTML="";}catch(e){} };
660
+ }
661
+
662
+ /* --- d3-sankey --- */
663
+ function vizSankeySTL(cid, v){
664
+ var c=$(cid); if(!c||!window.d3||!window.d3.sankey) return vizFallback(cid, v);
665
+ var stages=v.stages||[];
666
+ var nodes=stages.map(function(s){return {name:s.name};});
667
+ var links=[]; for(var i=0;i<stages.length-1;i++){
668
+ links.push({source:i, target:i+1, value:Math.max(0.4, Math.abs(stages[i].rho)||1), violated:(stages[i].rho<0)}); }
669
+ drawSankey(c, nodes, links, function(l){ return l.violated?ERR:TEAL; });
670
+ }
671
+ function vizSankeyInfo(cid, v){
672
+ var c=$(cid); if(!c||!window.d3||!window.d3.sankey) return vizFallback(cid, v);
673
+ var fnd=v.findings||[]; var summ=v.summary||[];
674
+ var nodes=[{name:"raw_vitals"},{name:"extractor"},{name:"summary"}];
675
+ var links=[{source:0,target:1,value:Math.max(1,fnd.length)},{source:1,target:2,value:Math.max(1,summ.length)}];
676
+ drawSankey(c, nodes, links, function(){ return TEAL; });
677
+ }
678
+ function drawSankey(c, nodes, links, colorFn){
679
+ var w=c.clientWidth||300, h=c.clientHeight||280;
680
+ var d3=window.d3;
681
+ c.innerHTML="";
682
+ var svg=d3.select(c).append("svg").attr("width",w).attr("height",h).style("display","block");
683
+ var sankey=d3.sankey().nodeWidth(14).nodePadding(16).extent([[8,12],[w-8,h-12]]);
684
+ var graph=sankey({nodes:nodes.map(function(d){return Object.assign({},d);}),
685
+ links:links.map(function(d){return Object.assign({},d);})});
686
+ svg.append("g").selectAll("path").data(graph.links).join("path")
687
+ .attr("d", d3.sankeyLinkHorizontal())
688
+ .attr("stroke", colorFn).attr("stroke-opacity",0.4)
689
+ .attr("stroke-width", function(d){return Math.max(2,d.width);}).attr("fill","none");
690
+ var node=svg.append("g").selectAll("g").data(graph.nodes).join("g");
691
+ node.append("rect").attr("x",function(d){return d.x0;}).attr("y",function(d){return d.y0;})
692
+ .attr("height",function(d){return Math.max(2,d.y1-d.y0);}).attr("width",function(d){return d.x1-d.x0;})
693
+ .attr("fill",GOLD).attr("rx",2);
694
+ node.append("text").attr("x",function(d){return d.x0<w/2?d.x1+5:d.x0-5;})
695
+ .attr("y",function(d){return (d.y0+d.y1)/2;}).attr("dy","0.35em")
696
+ .attr("text-anchor",function(d){return d.x0<w/2?"start":"end";})
697
+ .attr("fill",CREAM).attr("font-size","9px").attr("font-family","monospace")
698
+ .text(function(d){return d.name;});
699
+ }
700
+
701
+ /* --- built-in canvas force graph (pathgraph / galaxy / mesh / tactical) --- */
702
+ function vizGraph(cid, v){
703
+ var c=$(cid); if(!c) return;
704
+ var w=c.clientWidth||300, h=c.clientHeight||300;
705
+ var cv=document.createElement("canvas"); cv.width=w; cv.height=h; cv.style.width="100%"; cv.style.height="100%";
706
+ c.appendChild(cv); var ctx=cv.getContext("2d");
707
+ // normalize nodes/edges from various shapes
708
+ var rawNodes = v.nodes || v.points || v.files || [];
709
+ var nodes = rawNodes.map(function(n,i){
710
+ if(typeof n==="string") return {id:n};
711
+ return Object.assign({id:(n.id||n.name||("n"+i))}, n);
712
+ });
713
+ var idIndex={}; nodes.forEach(function(n,i){ idIndex[n.id]=i; });
714
+ var rawEdges = v.edges || v.links || [];
715
+ var edges = rawEdges.map(function(e){ return {s:(e.s||e.source||e.from), t:(e.t||e.target||e.to)}; })
716
+ .filter(function(e){ return idIndex[e.s]!=null && idIndex[e.t]!=null; });
717
+ var pathSet={}; (v.path||[]).forEach(function(p,i,arr){ if(i<arr.length-1){ pathSet[p+"->"+arr[i+1]]=1; pathSet[arr[i+1]+"->"+p]=1; } });
718
+ var pathNodes={}; (v.path||[]).forEach(function(p){ pathNodes[p]=1; });
719
+ var outlier = v.outlier;
720
+ // circular / seeded layout
721
+ nodes.forEach(function(n,i){
722
+ if(n.lat!=null && n.lon!=null){ n._x=n.lon; n._y=n.lat; }
723
+ else { var a=i/nodes.length*Math.PI*2; n._x=Math.cos(a); n._y=Math.sin(a); }
724
+ });
725
+ // scale to canvas
726
+ var xs=nodes.map(function(n){return n._x;}), ys=nodes.map(function(n){return n._y;});
727
+ var minx=Math.min.apply(null,xs), maxx=Math.max.apply(null,xs);
728
+ var miny=Math.min.apply(null,ys), maxy=Math.max.apply(null,ys);
729
+ var pad=36;
730
+ function sx(x){ return pad + (maxx-minx? (x-minx)/(maxx-minx):0.5)*(w-2*pad); }
731
+ function sy(y){ return (h-pad) - (maxy-miny? (y-miny)/(maxy-miny):0.5)*(h-2*pad); }
732
+ ctx.clearRect(0,0,w,h);
733
+ // edges
734
+ edges.forEach(function(e){
735
+ var a=nodes[idIndex[e.s]], b=nodes[idIndex[e.t]];
736
+ var onPath = pathSet[e.s+"->"+e.t];
737
+ ctx.beginPath(); ctx.moveTo(sx(a._x),sy(a._y)); ctx.lineTo(sx(b._x),sy(b._y));
738
+ ctx.strokeStyle = onPath?GOLD:"rgba(120,120,120,.35)"; ctx.lineWidth = onPath?2.4:1; ctx.stroke();
739
+ });
740
+ // nodes
741
+ nodes.forEach(function(n){
742
+ var col = (n.id===outlier)?ERR : (pathNodes[n.id]?GOLD : TEAL);
743
+ var r = (n.id===outlier||pathNodes[n.id])?6:4;
744
+ ctx.beginPath(); ctx.arc(sx(n._x),sy(n._y),r,0,Math.PI*2); ctx.fillStyle=col; ctx.fill();
745
+ ctx.fillStyle="#9a9a9a"; ctx.font="8px monospace"; ctx.fillText(String(n.id).slice(0,8), sx(n._x)+7, sy(n._y)+3);
746
+ });
747
  }
748
 
749
+ /* --- boids canvas animation --- */
750
+ function vizBoids(cid, v){
751
+ var c=$(cid); if(!c) return;
752
+ var w=c.clientWidth||300, h=c.clientHeight||300;
753
+ var cv=document.createElement("canvas"); cv.width=w; cv.height=h; cv.style.width="100%"; cv.style.height="100%";
754
+ c.appendChild(cv); var ctx=cv.getContext("2d");
755
+ var drones=(v.drones||[]).map(function(d,i){
756
+ return {x:(d.x!=null?d.x:Math.random()*w), y:(d.y!=null?d.y:Math.random()*h),
757
+ vx:(Math.random()-0.5)*1.2, vy:(Math.random()-0.5)*1.2, id:(d.id!=null?d.id:i)};
758
+ });
759
+ if(!drones.length){ for(var i=0;i<8;i++) drones.push({x:Math.random()*w,y:Math.random()*h,vx:(Math.random()-0.5),vy:(Math.random()-0.5),id:i}); }
760
+ // place inside canvas if coords look like world units
761
+ var maxc=Math.max.apply(null, drones.map(function(d){return Math.max(Math.abs(d.x),Math.abs(d.y));}).concat([1]));
762
+ if(maxc>1) drones.forEach(function(d){ d.x=(d.x/maxc)*0.7*w+0.15*w; d.y=(d.y/maxc)*0.7*h+0.15*h; });
763
+ var sep = v.rho_sep||30; var raf;
764
+ function step(){
765
+ ctx.fillStyle="rgba(7,7,7,.35)"; ctx.fillRect(0,0,w,h);
766
+ // boids: cohesion + separation + alignment (light)
767
+ var cx=0,cy=0; drones.forEach(function(d){cx+=d.x;cy+=d.y;}); cx/=drones.length; cy/=drones.length;
768
+ var minD=1e9, pair=null;
769
+ for(var i=0;i<drones.length;i++){ for(var j=i+1;j<drones.length;j++){
770
+ var dx=drones[i].x-drones[j].x, dy=drones[i].y-drones[j].y; var dd=Math.sqrt(dx*dx+dy*dy);
771
+ if(dd<minD){minD=dd;pair=[i,j];}
772
+ }}
773
+ drones.forEach(function(d){
774
+ d.vx += (cx-d.x)*0.0006; d.vy += (cy-d.y)*0.0006;
775
+ drones.forEach(function(o){ if(o!==d){ var dx=d.x-o.x, dy=d.y-o.y; var dd=Math.sqrt(dx*dx+dy*dy)||1;
776
+ if(dd<28){ d.vx += dx/dd*0.06; d.vy += dy/dd*0.06; } }});
777
+ var sp=Math.sqrt(d.vx*d.vx+d.vy*d.vy)||1; var max=1.6; if(sp>max){ d.vx=d.vx/sp*max; d.vy=d.vy/sp*max; }
778
+ d.x+=d.vx; d.y+=d.vy;
779
+ if(d.x<6||d.x>w-6) d.vx*=-1; if(d.y<6||d.y>h-6) d.vy*=-1;
780
+ d.x=Math.max(6,Math.min(w-6,d.x)); d.y=Math.max(6,Math.min(h-6,d.y));
781
+ });
782
+ // draw closest-pair link
783
+ if(pair){ var a=drones[pair[0]], b=drones[pair[1]]; ctx.beginPath(); ctx.moveTo(a.x,a.y); ctx.lineTo(b.x,b.y);
784
+ ctx.strokeStyle = minD<28?ERR:"rgba(95,179,163,.4)"; ctx.lineWidth=1.5; ctx.stroke(); }
785
+ drones.forEach(function(d){ ctx.beginPath(); ctx.arc(d.x,d.y,4,0,Math.PI*2);
786
+ ctx.fillStyle=GOLD; ctx.fill(); });
787
+ raf=requestAnimationFrame(step);
788
+ }
789
+ step();
790
+ _charts[cid]=function(){ if(raf) cancelAnimationFrame(raf); };
791
+ }
792
+
793
+ /* --- waterfall SVG --- */
794
+ function vizWaterfall(cid, v){
795
+ var c=$(cid); if(!c) return;
796
+ var steps=v.steps||[]; var dv=v.diverge_at;
797
+ var rows = steps.map(function(s,i){
798
+ var bad = (s.match===false) || (dv!=null && (s.name===dv||i>=dv));
799
+ return '<div style="display:flex;align-items:center;gap:.5rem;margin-bottom:.4rem;font-family:monospace;font-size:11px;">'+
800
+ '<span style="width:110px;color:'+CREAM+'">'+esc(s.name)+'</span>'+
801
+ '<span style="flex:1;height:10px;border-radius:3px;background:'+(bad?ERR:TEAL)+';opacity:.7;"></span>'+
802
+ '<span style="width:70px;color:'+(bad?ERR:DIM)+'">'+esc(s.orig||"")+'</span>'+
803
+ '<span style="width:70px;color:'+(bad?ERR:DIM)+'">'+esc(s.replay||"")+'</span>'+
804
+ '<span style="width:16px;color:'+(bad?ERR:LIVE)+'">'+(bad?"✕":"✓")+'</span>'+
805
+ '</div>';
806
+ }).join("");
807
+ c.innerHTML = '<div style="padding:.7rem;">'+
808
+ '<div style="display:flex;gap:.5rem;font-family:monospace;font-size:8.5px;color:'+DIM+';text-transform:uppercase;letter-spacing:.08em;margin-bottom:.5rem;">'+
809
+ '<span style="width:110px;">span</span><span style="flex:1;">replay overlay</span><span style="width:70px;">orig</span><span style="width:70px;">replay</span><span style="width:16px;">=</span></div>'+
810
+ rows + '</div>';
811
+ }
812
+ /* ============================ end VIZ ============================ */
813
+
814
+ /* ---------- renderers (timeline / catch / chain / tamper / formula / receipt) ---------- */
815
  function renderTimeline(key, timeline, animate){
816
+ var ul = $("tl-"+key); if(!ul) return Promise.resolve(); ul.innerHTML = "";
817
  var lis = [];
818
  (timeline||[]).forEach(function(t){
819
  var li = document.createElement("li");
820
+ li.className = animate ? "running" : (t.ok ? "complete" : "failed");
 
821
  var icon = animate ? "•" : (t.ok ? "✓" : "✕");
822
  li.innerHTML =
823
  '<span class="ic">'+icon+'</span>'+
 
828
  ul.appendChild(li); lis.push({li:li, t:t});
829
  });
830
  if(!animate) return Promise.resolve();
 
831
  return new Promise(function(resolve){
832
  var i = 0;
833
  function tick(){
834
  if(i >= lis.length){ resolve(); return; }
835
+ var o = lis[i]; o.li.className = "running"; o.li.querySelector(".ic").textContent="•";
836
+ setTimeout(function(){ var ok=o.t.ok; o.li.className= ok?"complete":"failed";
837
+ o.li.querySelector(".ic").textContent= ok?"✓":"✕"; i++; tick(); },
838
+ Math.min(260, 90 + (o.t.duration_ms||0)*3));
 
 
 
 
 
839
  }
840
  tick();
841
  });
842
  }
 
843
  function renderCatchTree(key, tree, firstFail){
844
+ var ul = $("ct-"+key); if(!ul) return; ul.innerHTML = "";
845
  if(!tree || !tree.length){ ul.innerHTML = '<li class="empty">— no catch tree —</li>'; return; }
846
  tree.forEach(function(c){
847
  var pass = !!c.pass;
848
  var li = document.createElement("li");
849
+ li.className = pass ? "pass" : ("fail" + (c.node===firstFail ? " firstfail" : ""));
850
  var marginTxt = (c.margin!=null) ? ("margin "+c.margin) : (c.detail||"");
851
  li.innerHTML =
852
  '<span class="mark">'+(pass?"✓":"✕")+'</span>'+
853
  '<span class="nm">'+esc(c.node)+'</span>'+
854
  (c.node===firstFail ? '<span class="firstbadge">caught here</span>' : '')+
855
  '<span class="margin">'+esc(marginTxt)+'</span>';
856
+ if(c.label){ var sub=document.createElement("div");
 
 
857
  sub.style.cssText="font-size:10px;color:var(--muted);width:100%;margin-left:1.6rem;margin-top:-.1rem;";
858
+ sub.textContent=c.label; li.appendChild(sub); }
 
 
859
  ul.appendChild(li);
860
  });
861
  }
 
862
  function flipDiff(before, after){
863
  if(before==null || after==null) return esc(String(after));
864
  var b=String(before), a=String(after), i=0;
 
866
  var pre=esc(a.slice(0,i)), ch=esc(a.slice(i,i+1)), post=esc(a.slice(i+1));
867
  return '<span class="b4">before: '+esc(b)+'</span><br><span class="af">after:&nbsp; '+pre+'<span class="hl">'+ch+'</span>'+post+'</span>';
868
  }
 
869
  function renderChainSelf(key, cs){
870
+ var box = $("chainself-"+key); if(!box) return;
871
+ if(!cs){ box.innerHTML = '<div class="empty">— run a demo —</div>'; return; }
 
872
  var intact = (cs.chain_intact===true) && (cs.merkle_root_matches===true);
873
  box.innerHTML =
874
  '<div class="tamper-grid">'+
 
878
  '<div class="tamper-cell"><div class="tk">chain depth</div><div class="tv">'+esc(cs.depth)+'</div></div>'+
879
  '</div>';
880
  }
881
+ function renderTamper(key, tt){
882
+ var box = $("tamper-"+key); if(!box) return false;
 
883
  if(!tt){ box.innerHTML = '<div class="empty">— no tamper data —</div>'; return false; }
884
  var incl = tt.inclusion || {};
885
  var caught = (tt.chain_intact===false) || (tt.merkle_root_matches===false) || (incl.inclusion_valid===false);
 
888
  ? ('TAMPER CAUGHT — flipped 1 byte in receipt #'+(t.tampered_seq!=null?t.tampered_seq:"?")+' field "'+esc(t.field||"?")+'". '+
889
  'Chain intact: false (break at receipt #'+(tt.chain_break_at_seq!=null?tt.chain_break_at_seq:"?")+'). '+
890
  'Merkle root match: false. Inclusion proof valid: false.')
891
+ : 'No break detected.';
892
  box.innerHTML =
893
  '<div class="tamper-summary">'+summary+'</div>'+
894
+ (t.before!=null ? '<div class="tamper-cell"><div class="tk">single-byte flip in '+esc(t.field||"")+'</div><div class="bytediff">'+flipDiff(t.before,t.after)+'</div></div>' : '')+
895
  '<div class="tamper-grid" style="margin-top:.5rem">'+
896
  '<div class="tamper-cell"><div class="tk">chain intact</div><div class="tv '+(tt.chain_intact?"good":"bad")+'">'+esc(tt.chain_intact)+'</div></div>'+
897
  '<div class="tamper-cell"><div class="tk">chain break at receipt</div><div class="tv '+(tt.chain_break_at_seq!=null?"bad":"good")+'">#'+esc(tt.chain_break_at_seq)+'</div></div>'+
 
902
  '</div>';
903
  return caught;
904
  }
905
+ function renderReceipt(key, d){
906
+ var box=$("receipt-"+key); if(!box) return;
907
+ var sealed = d.sealed||{}; var env = sealed.envelope||{};
908
+ var signed = !!sealed.signed;
909
+ var mode = d.mode;
910
+ // honest receipt verification: nominal -> chain self intact = verifiable PASS;
911
+ // tamper of a tamper_dag demo (the demo IS the tamper) -> FAIL by design.
912
+ var cs = d.chain_self||{}; var live = (cs.chain_intact===true && cs.merkle_root_matches===true);
913
+ var pass = signed && live;
914
+ box.innerHTML =
915
+ '<div class="receipt">'+
916
+ '<span class="badge '+(pass?"pass":"fail")+'">'+(pass?"RECEIPT VERIFIED":"RECEIPT INVALID")+'</span>'+
917
+ '<span class="kv">DSSE signed: '+esc(signed)+'</span>'+
918
+ '<span class="kv">payloadType: '+esc(env.payloadType||sealed.payloadType||"in-toto")+'</span>'+
919
+ '<span class="kv">seq #'+esc(sealed.chain_seq!=null?sealed.chain_seq:"–")+'</span>'+
920
+ '<span class="kv">merkle: '+esc(String(sealed.merkle_root||"").slice(0,18))+'…</span>'+
921
+ '</div>';
922
+ }
923
  function renderFormula(key, panel){
924
+ var ul = $("fp-"+key); if(!ul) return; ul.innerHTML="";
925
  if(!panel || !panel.length){ ul.innerHTML='<li class="empty">— no formula panel —</li>'; return; }
926
  panel.forEach(function(f){
927
  var li=document.createElement("li");
928
+ var stHtml = esc(String(f.status||""))
929
+ .replace(/LOCKED-PROVEN/g,'<b class="proven">LOCKED-PROVEN</b>')
 
930
  .replace(/PROVEN/g,'<b class="proven">PROVEN</b>')
931
  .replace(/roadmap/gi,'<b class="roadmap">roadmap</b>')
932
+ .replace(/Conjecture 1/g,'<b class="roadmap">Conjecture 1</b>')
933
+ .replace(/CI-green/g,'<b class="roadmap">CI-green</b>');
934
  li.innerHTML =
935
  '<div class="fn">'+esc(f.formula)+(f.role?'<span class="role">'+esc(f.role)+'</span>':'')+'</div>'+
936
  (f.expr?'<div class="expr">'+esc(f.expr)+'</div>':'')+
 
939
  ul.appendChild(li);
940
  });
941
  }
 
942
  function setLabel(key, realOrRoadmap){
943
  var el=$("lbl-"+key); if(!el) return;
944
  var isReal = /REAL TODAY/i.test(realOrRoadmap||"");
945
  el.className = isReal ? "label-real" : "label-roadmap";
946
  el.textContent = isReal ? "REAL TODAY" : "ROADMAP · substrate real";
947
+ }
948
+ function setDemoDot(pkey, demoId, st){
949
+ var d=$("cdot-"+pkey+"-"+demoId); if(d) d.className="cdot"+(st?(" "+st):"");
 
 
950
  }
951
 
952
+ /* ---------- run one demo ---------- */
953
+ function runDemo(pkey, demoId, mode){
954
+ var statusEl=$("status-"+pkey);
955
+ if(statusEl) statusEl.textContent = mode==="tamper"?"tamper run…":"running…";
956
+ setDemoDot(pkey, demoId, "loading");
957
+ return postJSON("/warhacker/run/"+pkey+"/"+demoId, {mode:mode}).then(function(r){
958
  var d = r.json;
959
+ var raw=$("raw-"+pkey); if(raw) raw.textContent = pretty(d);
960
  if(!d || d.ok===false){
961
+ setDemoDot(pkey, demoId, "err");
962
+ var v=$("verdict-"+pkey); if(v){ v.className="verdict err"; v.innerHTML='<span>✕</span><span>backend error — see raw record</span>'; }
963
+ if(raw) raw.classList.add("show");
964
  return d;
965
  }
966
+ setLabel(pkey, d.real_or_roadmap);
967
+ $("demo-title-"+pkey).textContent = d.title || demoId;
968
+ var v=$("verdict-"+pkey);
969
  var ok = !!d.authorized;
970
+ v.className = "verdict " + (ok?"ok":"err");
971
  v.innerHTML = '<span>'+(ok?"✓":"✕")+'</span><span>'+esc(d.decision)+' — '+esc(d.headline)+'</span>';
972
+ var anim = renderTimeline(pkey, d.timeline, true);
973
+ renderCatchTree(pkey, d.catch_tree, d.first_failing_node);
974
+ renderChainSelf(pkey, d.chain_self);
975
+ var caught = renderTamper(pkey, d.tamper_test);
976
+ renderReceipt(pkey, d);
977
+ renderFormula(pkey, d.formula_panel);
978
+ $("honesty-"+pkey).textContent = d.honesty || "";
979
+ // viz
980
+ var vt=$("viztitle-"+pkey); if(vt) vt.textContent = (d.viz&&d.viz.title)||"";
981
+ renderViz("viz-"+pkey, d.viz);
 
 
 
 
982
  // stats
983
+ state.run[pkey+"/"+demoId]=true;
 
984
  state.tamperRun++; if(caught) state.tamperCaught++;
985
  refreshStats();
986
  anim.then(function(){
987
+ if(statusEl) statusEl.textContent = ok?"sealed + signed":"line crossed · caught";
988
+ setDemoDot(pkey, demoId, ok?"ok":"err");
989
  });
990
  return d;
991
  }).catch(function(e){
992
+ setDemoDot(pkey, demoId, "err");
993
+ var raw=$("raw-"+pkey); if(raw){ raw.textContent = String(e); raw.classList.add("show"); }
994
  });
995
  }
996
 
997
+ function selectDemo(pkey, demoId){
998
+ sel[pkey]=demoId;
999
+ document.querySelectorAll('.demo-chip[data-p="'+pkey+'"]').forEach(function(ch){
1000
+ ch.classList.toggle("sel", ch.dataset.d===demoId);
1001
+ });
1002
+ var prob = PROBLEMS.filter(function(p){return p.key===pkey;})[0];
1003
+ var dm = (prob.demos||[]).filter(function(x){return x.id===demoId;})[0] || {};
1004
+ $("demo-title-"+pkey).textContent = dm.title || demoId;
1005
+ // reset run panels for the newly selected demo
1006
+ var v=$("verdict-"+pkey); if(v){ v.className="verdict"; v.style.display="none"; }
1007
+ }
1008
 
1009
+ function refreshStats(){
1010
+ $("stRun").textContent = Object.keys(state.run).length;
1011
+ $("stTamper").textContent = state.tamperCaught + " / " + state.tamperRun;
1012
+ }
 
 
 
 
 
1013
 
1014
+ /* ---------- build UI from index ---------- */
1015
+ function buildUI(){
1016
+ var bar=$("tabsBar"); var wrap=$("panelWrap");
1017
+ bar.innerHTML=""; wrap.innerHTML="";
1018
+ PROBLEMS.forEach(function(p, idx){
1019
+ var b=document.createElement("button");
1020
+ b.className="tab-btn"+(idx===0?" active":""); b.dataset.tab=p.key;
1021
+ b.textContent=(idx+1)+" · "+p.shortName;
1022
+ b.addEventListener("click", function(){
1023
+ document.querySelectorAll(".tab-btn").forEach(function(x){x.classList.remove("active");});
1024
+ b.classList.add("active");
1025
+ document.querySelectorAll(".panel").forEach(function(pp){pp.classList.remove("active");});
1026
+ $("panel-"+p.key).classList.add("active");
1027
+ });
1028
+ bar.appendChild(b);
1029
+
1030
+ var sec=document.createElement("section");
1031
+ sec.className="panel"+(idx===0?" active":""); sec.id="panel-"+p.key;
1032
+ var chips = (p.demos||[]).map(function(dm){
1033
+ return '<button class="demo-chip" data-p="'+p.key+'" data-d="'+dm.id+'">'+
1034
+ '<span class="cid">'+esc(dm.id)+'</span>'+esc(dm.short||dm.title||dm.id)+
1035
+ '<span class="cdot" id="cdot-'+p.key+'-'+dm.id+'"></span></button>';
1036
+ }).join("");
1037
+ sec.innerHTML =
1038
+ '<div class="p-head">'+
1039
+ '<div class="p-titlerow"><h2 class="p-title">'+esc(p.title)+'</h2><span id="lbl-'+p.key+'" class="'+(/REAL TODAY/i.test(p.real_or_roadmap)?"label-real":"label-roadmap")+'">'+(/REAL TODAY/i.test(p.real_or_roadmap)?"REAL TODAY":"ROADMAP · substrate real")+'</span></div>'+
1040
+ '<p class="p-blurb">'+esc(p.blurb||"")+'</p>'+
1041
+ (/REAL TODAY/i.test(p.real_or_roadmap)?'':'<div class="roadmap-note">'+esc(p.real_or_roadmap)+'</div>')+
1042
+ '</div>'+
1043
+ '<div class="block-h" style="margin-top:.4rem"><span class="pillc">pick a demo</span> 5 demos · click one, then run nominal or tamper</div>'+
1044
+ '<div class="demo-pick">'+chips+'</div>'+
1045
+ '<div class="demo-title" id="demo-title-'+p.key+'"></div>'+
1046
+ '<div class="card-controls">'+
1047
+ '<button class="r-btn r-btn-primary" data-act="nominal" data-p="'+p.key+'">▶ Run nominal</button>'+
1048
+ '<button class="r-btn r-btn-danger" data-act="tamper" data-p="'+p.key+'">⚠ Run tamper test</button>'+
1049
+ '<span style="font-family:var(--mono);font-size:10px;color:var(--muted);align-self:center" id="status-'+p.key+'">idle</span>'+
1050
+ '</div>'+
1051
+ '<div id="verdict-'+p.key+'" class="verdict" style="display:none"></div>'+
1052
+ '<div class="block"><div class="block-h"><span class="pillc">visualization</span> unique per demo · vendored libs · zero CDN</div>'+
1053
+ '<div class="viz-title" id="viztitle-'+p.key+'"></div>'+
1054
+ '<div class="viz-canvas" id="viz-'+p.key+'"></div></div>'+
1055
+ '<div class="block"><div class="block-h"><span class="pillc">step timeline</span> real perf_counter durations · computed values</div>'+
1056
+ '<ul class="tl" id="tl-'+p.key+'"><li class="empty">— run a demo to populate the timeline —</li></ul></div>'+
1057
+ '<div class="block"><div class="block-h"><span class="pillc">catch tree</span> first failing condition auto-flagged</div>'+
1058
+ '<ul class="ct" id="ct-'+p.key+'"><li class="empty">— run a demo —</li></ul></div>'+
1059
+ '<div class="block"><div class="block-h"><span class="pillc">live chain</span> this run\u2019s own signed chain — re-verified intact</div>'+
1060
+ '<div id="chainself-'+p.key+'"><div class="empty">— run a demo —</div></div></div>'+
1061
+ '<div class="block"><div class="block-h"><span class="pillc">tamper test</span> one byte flipped → chain breaks</div>'+
1062
+ '<div id="tamper-'+p.key+'"><div class="empty">— run a demo; the always-on negative test flips one byte —</div></div></div>'+
1063
+ '<div class="block"><div class="block-h"><span class="pillc">signed receipt</span> DSSE / ECDSA-P256 · verify PASS/FAIL</div>'+
1064
+ '<div id="receipt-'+p.key+'"><div class="empty">— run a demo —</div></div></div>'+
1065
+ '<div class="block"><div class="block-h"><span class="pillc">formula-proof panel</span> exact formulas + honest status</div>'+
1066
+ '<ul class="fp" id="fp-'+p.key+'"><li class="empty">— run a demo —</li></ul></div>'+
1067
+ '<div class="out">'+
1068
+ '<div class="out-label" data-toggle="raw-'+p.key+'"><span>raw signed record (click to expand)</span></div>'+
1069
+ '<pre class="out-pre" id="raw-'+p.key+'"></pre></div>'+
1070
+ '<div class="honesty-note" id="honesty-'+p.key+'"></div>';
1071
+ wrap.appendChild(sec);
1072
+ });
1073
+
1074
+ // bind chip selection
1075
+ document.querySelectorAll(".demo-chip").forEach(function(ch){
1076
+ ch.addEventListener("click", function(){ selectDemo(ch.dataset.p, ch.dataset.d); });
1077
+ });
1078
+ // bind run buttons
1079
+ document.querySelectorAll(".r-btn[data-act]").forEach(function(b){
1080
+ b.addEventListener("click", function(){
1081
+ var pkey=b.dataset.p; var demoId=sel[pkey];
1082
+ if(!demoId){ // default to first demo
1083
+ var prob=PROBLEMS.filter(function(p){return p.key===pkey;})[0];
1084
+ demoId=(prob.demos[0]||{}).id; selectDemo(pkey, demoId);
1085
+ }
1086
+ var vd=$("verdict-"+pkey); if(vd) vd.style.display="flex";
1087
+ runDemo(pkey, demoId, b.dataset.act);
1088
+ });
1089
+ });
1090
+ // raw toggles
1091
+ document.querySelectorAll(".out-label[data-toggle]").forEach(function(el){
1092
+ el.addEventListener("click", function(){ var t=$(el.dataset.toggle); if(t) t.classList.toggle("show"); });
1093
+ });
1094
+ // pre-select first demo per problem
1095
+ PROBLEMS.forEach(function(p){ if(p.demos&&p.demos.length) selectDemo(p.key, p.demos[0].id); });
1096
+ }
1097
+
1098
+ // short tab names + short demo labels (fallbacks; index may already provide)
1099
+ var SHORT = {cannonico:"CANNONICO", tychee:"TYCHEE", hangar2apps:"HANGAR2APPS", cyber_rts:"CYBER-RTS", raven:"RAVEN"};
1100
+
1101
+ /* ---------- load index ---------- */
1102
+ getJSON("/warhacker/index").then(function(r){
1103
+ var j=r.json||{};
1104
+ var probs = j.problems || [];
1105
+ if(!probs.length){ $("stBackend").textContent="—"; return; }
1106
+ PROBLEMS = probs.map(function(p){
1107
+ return {
1108
+ key: p.key, title: p.title || p.key,
1109
+ shortName: SHORT[p.key] || (p.title||p.key).split(/[ \-—]/)[0].toUpperCase(),
1110
+ blurb: p.blurb || p.summary || p.description || "",
1111
+ real_or_roadmap: p.real_or_roadmap || "",
1112
+ demos: (p.demos||[]).map(function(d){
1113
+ return {id:d.id, title:d.title||d.id, short:d.short||d.subtitle||d.title||d.id};
1114
+ })
1115
+ };
1116
+ });
1117
+ var totDemos = PROBLEMS.reduce(function(a,p){return a+(p.demos?p.demos.length:0);},0);
1118
+ $("stProblems").textContent = PROBLEMS.length;
1119
+ $("stDemos").textContent = totDemos;
1120
+ $("stBackend").textContent = (j.ok!==false)?"LIVE":"—";
1121
+ buildUI();
1122
+ }).catch(function(e){
1123
+ $("stBackend").textContent="—";
1124
+ $("panelWrap").innerHTML = '<div class="empty" style="text-align:center;padding:2rem">Could not load the demo index. The backend may still be starting — refresh in a moment.</div>';
1125
+ });
1126
 
1127
  refreshStats();
1128
  })();
szl_warhacker_demos.py CHANGED
The diff for this file is too large to render. See raw diff