@neuroverseos/governance 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +20 -0
  2. package/package.json +16 -3
  3. package/policies/content-moderation-rules.txt +8 -0
  4. package/policies/marketing-rules.txt +8 -0
  5. package/policies/science-research-rules.txt +11 -0
  6. package/policies/social-media-rules.txt +7 -0
  7. package/policies/strict-rules.txt +8 -0
  8. package/policies/trading-rules.txt +8 -0
  9. package/simulate.html +1899 -0
  10. package/dist/adapters/autoresearch.cjs +0 -196
  11. package/dist/adapters/autoresearch.d.cts +0 -103
  12. package/dist/adapters/autoresearch.d.ts +0 -103
  13. package/dist/adapters/autoresearch.js +0 -7
  14. package/dist/adapters/express.cjs +0 -1114
  15. package/dist/adapters/express.d.cts +0 -66
  16. package/dist/adapters/express.d.ts +0 -66
  17. package/dist/adapters/express.js +0 -12
  18. package/dist/adapters/index.cjs +0 -1669
  19. package/dist/adapters/index.d.cts +0 -6
  20. package/dist/adapters/index.d.ts +0 -6
  21. package/dist/adapters/index.js +0 -46
  22. package/dist/adapters/langchain.cjs +0 -1155
  23. package/dist/adapters/langchain.d.cts +0 -89
  24. package/dist/adapters/langchain.d.ts +0 -89
  25. package/dist/adapters/langchain.js +0 -16
  26. package/dist/adapters/openai.cjs +0 -1185
  27. package/dist/adapters/openai.d.cts +0 -99
  28. package/dist/adapters/openai.d.ts +0 -99
  29. package/dist/adapters/openai.js +0 -16
  30. package/dist/adapters/openclaw.cjs +0 -1177
  31. package/dist/adapters/openclaw.d.cts +0 -99
  32. package/dist/adapters/openclaw.d.ts +0 -99
  33. package/dist/adapters/openclaw.js +0 -16
  34. package/dist/bootstrap-GXVDZNF7.js +0 -114
  35. package/dist/build-P42YFKQV.js +0 -339
  36. package/dist/chunk-2NICNKOM.js +0 -100
  37. package/dist/chunk-2PQU3VAN.js +0 -131
  38. package/dist/chunk-4A7LISES.js +0 -324
  39. package/dist/chunk-4JRYGIO7.js +0 -727
  40. package/dist/chunk-4NGDRRQH.js +0 -10
  41. package/dist/chunk-4QXB6PEO.js +0 -232
  42. package/dist/chunk-6CZSKEY5.js +0 -164
  43. package/dist/chunk-7P3S7MAY.js +0 -1090
  44. package/dist/chunk-A5W4GNQO.js +0 -130
  45. package/dist/chunk-AKW5YVCE.js +0 -96
  46. package/dist/chunk-BUWWN2NX.js +0 -192
  47. package/dist/chunk-COT5XS4V.js +0 -109
  48. package/dist/chunk-ER62HNGF.js +0 -139
  49. package/dist/chunk-FYS2CBUW.js +0 -304
  50. package/dist/chunk-GR6DGCZ2.js +0 -340
  51. package/dist/chunk-I3RRAYK2.js +0 -11
  52. package/dist/chunk-JZPQGIKR.js +0 -79
  53. package/dist/chunk-MWDQ4MJB.js +0 -11
  54. package/dist/chunk-NF5POFCI.js +0 -622
  55. package/dist/chunk-OGL7QXZS.js +0 -608
  56. package/dist/chunk-OT6PXH54.js +0 -61
  57. package/dist/chunk-PDOZHZWL.js +0 -225
  58. package/dist/chunk-Q6O7ZLO2.js +0 -62
  59. package/dist/chunk-QPASI2BR.js +0 -187
  60. package/dist/chunk-T5EUJQE5.js +0 -172
  61. package/dist/chunk-XPDMYECO.js +0 -642
  62. package/dist/chunk-YZFATT7X.js +0 -9
  63. package/dist/cli/neuroverse.cjs +0 -11448
  64. package/dist/cli/neuroverse.d.cts +0 -1
  65. package/dist/cli/neuroverse.d.ts +0 -1
  66. package/dist/cli/neuroverse.js +0 -196
  67. package/dist/cli/plan.cjs +0 -1599
  68. package/dist/cli/plan.d.cts +0 -20
  69. package/dist/cli/plan.d.ts +0 -20
  70. package/dist/cli/plan.js +0 -361
  71. package/dist/cli/run.cjs +0 -1746
  72. package/dist/cli/run.d.cts +0 -20
  73. package/dist/cli/run.d.ts +0 -20
  74. package/dist/cli/run.js +0 -143
  75. package/dist/configure-ai-TK67ZWZL.js +0 -132
  76. package/dist/derive-TLIV4OOU.js +0 -152
  77. package/dist/doctor-XPDLEYXN.js +0 -171
  78. package/dist/explain-IDCRWMPX.js +0 -70
  79. package/dist/guard-RV65TT4L.js +0 -96
  80. package/dist/guard-contract-WZx__PmU.d.cts +0 -709
  81. package/dist/guard-contract-WZx__PmU.d.ts +0 -709
  82. package/dist/guard-engine-JLTUARGU.js +0 -10
  83. package/dist/impact-XPECYRLH.js +0 -59
  84. package/dist/improve-GPUBKTEA.js +0 -85
  85. package/dist/index.cjs +0 -6273
  86. package/dist/index.d.cts +0 -1616
  87. package/dist/index.d.ts +0 -1616
  88. package/dist/index.js +0 -379
  89. package/dist/infer-world-7GVZWFX4.js +0 -543
  90. package/dist/init-PKPIYHYE.js +0 -144
  91. package/dist/init-world-VWMQZQC7.js +0 -223
  92. package/dist/mcp-server-FPVSU32Z.js +0 -13
  93. package/dist/model-adapter-BB7G4MFI.js +0 -11
  94. package/dist/playground-E664U4T6.js +0 -550
  95. package/dist/redteam-Z7WREJ44.js +0 -357
  96. package/dist/session-EKTRSR7C.js +0 -14
  97. package/dist/simulate-VDOYQFRO.js +0 -108
  98. package/dist/test-OGXJK4QU.js +0 -217
  99. package/dist/trace-JVF67VR3.js +0 -166
  100. package/dist/validate-LLBWVPGV.js +0 -81
  101. package/dist/validate-engine-UIABSIHD.js +0 -7
  102. package/dist/world-LAXO6DOX.js +0 -378
  103. package/dist/world-loader-HMPTOEA2.js +0 -9
  104. package/dist/worlds/autoresearch.nv-world.md +0 -230
  105. package/dist/worlds/derivation-world.nv-world.md +0 -278
package/simulate.html ADDED
@@ -0,0 +1,1899 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>NeuroVerse Simulation Engine</title>
7
+ <style>
8
+ @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;700&display=swap');
9
+
10
+ * { margin: 0; padding: 0; box-sizing: border-box; }
11
+
12
+ :root {
13
+ --green: #00ff41;
14
+ --green-dim: #00cc33;
15
+ --green-dark: #009922;
16
+ --green-glow: #00ff4180;
17
+ --bg: #0a0a0a;
18
+ --bg-panel: #0d1117;
19
+ --bg-card: #111820;
20
+ --border: #1a2332;
21
+ --text: #c9d1d9;
22
+ --text-dim: #6e7681;
23
+ --red: #ff4444;
24
+ --red-glow: #ff444460;
25
+ --amber: #ffaa00;
26
+ --cyan: #00d4ff;
27
+ }
28
+
29
+ body {
30
+ background: var(--bg);
31
+ color: var(--text);
32
+ font-family: 'JetBrains Mono', 'Courier New', monospace;
33
+ min-height: 100vh;
34
+ overflow-x: hidden;
35
+ }
36
+
37
+ /* ─── Matrix Rain Canvas ─── */
38
+ #matrix-canvas {
39
+ position: fixed;
40
+ top: 0; left: 0;
41
+ width: 100%; height: 100%;
42
+ z-index: 0;
43
+ opacity: 0.06;
44
+ pointer-events: none;
45
+ }
46
+
47
+ /* ─── Main Layout ─── */
48
+ .app {
49
+ position: relative;
50
+ z-index: 1;
51
+ max-width: 1400px;
52
+ margin: 0 auto;
53
+ padding: 20px;
54
+ }
55
+
56
+ /* ─── Header ─── */
57
+ .header {
58
+ text-align: center;
59
+ padding: 30px 0 20px;
60
+ border-bottom: 1px solid var(--border);
61
+ margin-bottom: 24px;
62
+ }
63
+
64
+ .header h1 {
65
+ font-size: 14px;
66
+ font-weight: 400;
67
+ letter-spacing: 8px;
68
+ text-transform: uppercase;
69
+ color: var(--green);
70
+ text-shadow: 0 0 20px var(--green-glow);
71
+ margin-bottom: 6px;
72
+ }
73
+
74
+ .header .subtitle {
75
+ font-size: 11px;
76
+ color: var(--text-dim);
77
+ letter-spacing: 3px;
78
+ }
79
+
80
+ .header .powered {
81
+ font-size: 10px;
82
+ color: var(--text-dim);
83
+ margin-top: 8px;
84
+ opacity: 0.6;
85
+ }
86
+
87
+ /* ─── Grid ─── */
88
+ .grid {
89
+ display: grid;
90
+ grid-template-columns: 320px 1fr 300px;
91
+ gap: 16px;
92
+ min-height: 600px;
93
+ }
94
+
95
+ @media (max-width: 1100px) {
96
+ .grid { grid-template-columns: 1fr; }
97
+ }
98
+
99
+ /* ─── Panels ─── */
100
+ .panel {
101
+ background: var(--bg-panel);
102
+ border: 1px solid var(--border);
103
+ border-radius: 4px;
104
+ overflow: hidden;
105
+ }
106
+
107
+ .panel-header {
108
+ padding: 12px 16px;
109
+ border-bottom: 1px solid var(--border);
110
+ font-size: 10px;
111
+ letter-spacing: 3px;
112
+ text-transform: uppercase;
113
+ color: var(--green-dim);
114
+ display: flex;
115
+ align-items: center;
116
+ gap: 8px;
117
+ }
118
+
119
+ .panel-header .dot {
120
+ width: 6px; height: 6px;
121
+ border-radius: 50%;
122
+ background: var(--green);
123
+ box-shadow: 0 0 6px var(--green-glow);
124
+ }
125
+
126
+ .panel-body {
127
+ padding: 16px;
128
+ }
129
+
130
+ /* ─── World Selector ─── */
131
+ .world-tabs {
132
+ display: flex;
133
+ flex-direction: column;
134
+ gap: 4px;
135
+ margin-bottom: 16px;
136
+ }
137
+
138
+ .world-tab {
139
+ padding: 10px 12px;
140
+ background: var(--bg-card);
141
+ border: 1px solid var(--border);
142
+ border-radius: 3px;
143
+ color: var(--text-dim);
144
+ font-size: 12px;
145
+ font-family: inherit;
146
+ cursor: pointer;
147
+ transition: all 0.15s;
148
+ text-align: left;
149
+ }
150
+
151
+ .world-tab:hover {
152
+ border-color: var(--green-dark);
153
+ color: var(--text);
154
+ }
155
+
156
+ .world-tab.active {
157
+ border-color: var(--green);
158
+ color: var(--green);
159
+ background: #00ff4108;
160
+ box-shadow: 0 0 10px #00ff4110;
161
+ }
162
+
163
+ .world-tab .tab-name {
164
+ display: block;
165
+ font-weight: 500;
166
+ }
167
+
168
+ .world-tab .tab-desc {
169
+ display: block;
170
+ font-size: 10px;
171
+ margin-top: 2px;
172
+ opacity: 0.6;
173
+ }
174
+
175
+ .custom-toggle {
176
+ margin-top: 8px;
177
+ padding: 8px 12px;
178
+ background: transparent;
179
+ border: 1px dashed var(--border);
180
+ border-radius: 3px;
181
+ color: var(--text-dim);
182
+ font-size: 11px;
183
+ font-family: inherit;
184
+ cursor: pointer;
185
+ width: 100%;
186
+ text-align: left;
187
+ }
188
+
189
+ .custom-toggle:hover { border-color: var(--green-dark); color: var(--text); }
190
+
191
+ .custom-textarea {
192
+ width: 100%;
193
+ height: 200px;
194
+ background: var(--bg);
195
+ border: 1px solid var(--border);
196
+ border-radius: 3px;
197
+ color: var(--green-dim);
198
+ font-family: inherit;
199
+ font-size: 11px;
200
+ padding: 10px;
201
+ resize: vertical;
202
+ margin-top: 8px;
203
+ display: none;
204
+ }
205
+
206
+ .custom-textarea:focus { outline: none; border-color: var(--green-dark); }
207
+ .custom-textarea.visible { display: block; }
208
+
209
+ /* ─── State Sliders ─── */
210
+ .state-section {
211
+ margin-top: 16px;
212
+ padding-top: 16px;
213
+ border-top: 1px solid var(--border);
214
+ }
215
+
216
+ .state-label-row {
217
+ display: flex;
218
+ justify-content: space-between;
219
+ align-items: center;
220
+ margin-bottom: 4px;
221
+ }
222
+
223
+ .state-label {
224
+ font-size: 11px;
225
+ color: var(--text-dim);
226
+ }
227
+
228
+ .state-value {
229
+ font-size: 12px;
230
+ color: var(--green);
231
+ font-weight: 500;
232
+ min-width: 40px;
233
+ text-align: right;
234
+ }
235
+
236
+ .slider-group {
237
+ margin-bottom: 14px;
238
+ }
239
+
240
+ input[type="range"] {
241
+ -webkit-appearance: none;
242
+ width: 100%;
243
+ height: 4px;
244
+ background: var(--border);
245
+ border-radius: 2px;
246
+ outline: none;
247
+ cursor: pointer;
248
+ }
249
+
250
+ input[type="range"]::-webkit-slider-thumb {
251
+ -webkit-appearance: none;
252
+ width: 14px; height: 14px;
253
+ border-radius: 50%;
254
+ background: var(--green);
255
+ box-shadow: 0 0 8px var(--green-glow);
256
+ cursor: pointer;
257
+ }
258
+
259
+ input[type="range"]::-moz-range-thumb {
260
+ width: 14px; height: 14px;
261
+ border-radius: 50%;
262
+ background: var(--green);
263
+ box-shadow: 0 0 8px var(--green-glow);
264
+ border: none;
265
+ cursor: pointer;
266
+ }
267
+
268
+ /* ─── Simulate Button ─── */
269
+ .simulate-btn {
270
+ width: 100%;
271
+ padding: 14px;
272
+ background: transparent;
273
+ border: 1px solid var(--green);
274
+ border-radius: 3px;
275
+ color: var(--green);
276
+ font-family: inherit;
277
+ font-size: 12px;
278
+ letter-spacing: 4px;
279
+ text-transform: uppercase;
280
+ cursor: pointer;
281
+ transition: all 0.2s;
282
+ margin-top: 16px;
283
+ }
284
+
285
+ .simulate-btn:hover {
286
+ background: #00ff4115;
287
+ box-shadow: 0 0 20px var(--green-glow);
288
+ }
289
+
290
+ .simulate-btn:active {
291
+ transform: scale(0.98);
292
+ }
293
+
294
+ .simulate-btn.running {
295
+ animation: pulse-border 1s infinite;
296
+ pointer-events: none;
297
+ }
298
+
299
+ @keyframes pulse-border {
300
+ 0%, 100% { box-shadow: 0 0 5px var(--green-glow); }
301
+ 50% { box-shadow: 0 0 25px var(--green-glow); }
302
+ }
303
+
304
+ /* ─── Simulation Output ─── */
305
+ .sim-output {
306
+ min-height: 500px;
307
+ }
308
+
309
+ .sim-placeholder {
310
+ display: flex;
311
+ align-items: center;
312
+ justify-content: center;
313
+ min-height: 400px;
314
+ color: var(--text-dim);
315
+ font-size: 11px;
316
+ letter-spacing: 2px;
317
+ text-align: center;
318
+ flex-direction: column;
319
+ gap: 12px;
320
+ }
321
+
322
+ .sim-placeholder .cursor {
323
+ display: inline-block;
324
+ width: 8px;
325
+ height: 16px;
326
+ background: var(--green);
327
+ animation: blink 1s infinite;
328
+ }
329
+
330
+ @keyframes blink {
331
+ 0%, 50% { opacity: 1; }
332
+ 51%, 100% { opacity: 0; }
333
+ }
334
+
335
+ /* ─── Step Cards ─── */
336
+ .step-card {
337
+ background: var(--bg-card);
338
+ border: 1px solid var(--border);
339
+ border-radius: 3px;
340
+ margin-bottom: 8px;
341
+ overflow: hidden;
342
+ opacity: 0;
343
+ transform: translateY(10px);
344
+ transition: all 0.3s ease;
345
+ }
346
+
347
+ .step-card.visible {
348
+ opacity: 1;
349
+ transform: translateY(0);
350
+ }
351
+
352
+ .step-header {
353
+ padding: 10px 14px;
354
+ display: flex;
355
+ justify-content: space-between;
356
+ align-items: center;
357
+ border-bottom: 1px solid var(--border);
358
+ font-size: 11px;
359
+ }
360
+
361
+ .step-number {
362
+ color: var(--green);
363
+ font-weight: 500;
364
+ letter-spacing: 2px;
365
+ }
366
+
367
+ .step-viability {
368
+ font-size: 10px;
369
+ letter-spacing: 1px;
370
+ padding: 2px 8px;
371
+ border-radius: 2px;
372
+ }
373
+
374
+ .step-viability.THRIVING,
375
+ .step-viability.TRUSTED { background: #00ff4120; color: var(--green); }
376
+ .step-viability.STABLE,
377
+ .step-viability.PRODUCTIVE { background: #00ff4115; color: var(--green-dim); }
378
+ .step-viability.COMPRESSED,
379
+ .step-viability.CAUTIOUS { background: #ffaa0020; color: var(--amber); }
380
+ .step-viability.CRITICAL,
381
+ .step-viability.RESTRICTED,
382
+ .step-viability.AT_RISK { background: #ff444420; color: var(--red); }
383
+ .step-viability.MODEL_COLLAPSES,
384
+ .step-viability.TERMINATED,
385
+ .step-viability.HALTED,
386
+ .step-viability.UNRELIABLE { background: #ff444430; color: var(--red); border: 1px solid var(--red); }
387
+
388
+ .step-rules {
389
+ padding: 8px 14px;
390
+ }
391
+
392
+ .rule-row {
393
+ display: flex;
394
+ align-items: center;
395
+ gap: 8px;
396
+ padding: 4px 0;
397
+ font-size: 11px;
398
+ }
399
+
400
+ .rule-icon {
401
+ width: 16px;
402
+ text-align: center;
403
+ flex-shrink: 0;
404
+ }
405
+
406
+ .rule-icon.fired { color: var(--amber); }
407
+ .rule-icon.pass { color: var(--green-dark); opacity: 0.4; }
408
+ .rule-icon.excluded { color: var(--text-dim); opacity: 0.3; }
409
+
410
+ .rule-label {
411
+ flex: 1;
412
+ color: var(--text-dim);
413
+ }
414
+
415
+ .rule-label.fired { color: var(--text); }
416
+
417
+ .rule-effect {
418
+ font-size: 10px;
419
+ color: var(--amber);
420
+ }
421
+
422
+ .rule-effect.advantage { color: var(--green); }
423
+ .rule-effect.structural { color: var(--red); }
424
+
425
+ .step-state {
426
+ padding: 8px 14px;
427
+ border-top: 1px solid var(--border);
428
+ display: flex;
429
+ flex-wrap: wrap;
430
+ gap: 12px;
431
+ font-size: 10px;
432
+ color: var(--text-dim);
433
+ }
434
+
435
+ .state-chip {
436
+ display: flex;
437
+ align-items: center;
438
+ gap: 4px;
439
+ }
440
+
441
+ .state-chip .val { color: var(--green-dim); font-weight: 500; }
442
+ .state-chip .val.changed { color: var(--amber); }
443
+
444
+ /* ─── Verdict Panel ─── */
445
+ .verdict-section {
446
+ text-align: center;
447
+ }
448
+
449
+ .viability-display {
450
+ margin: 20px 0;
451
+ }
452
+
453
+ .viability-ring {
454
+ width: 160px;
455
+ height: 160px;
456
+ margin: 0 auto 16px;
457
+ position: relative;
458
+ }
459
+
460
+ .viability-ring svg {
461
+ transform: rotate(-90deg);
462
+ width: 160px;
463
+ height: 160px;
464
+ }
465
+
466
+ .viability-ring .bg-ring {
467
+ fill: none;
468
+ stroke: var(--border);
469
+ stroke-width: 6;
470
+ }
471
+
472
+ .viability-ring .fg-ring {
473
+ fill: none;
474
+ stroke: var(--green);
475
+ stroke-width: 6;
476
+ stroke-linecap: round;
477
+ stroke-dasharray: 440;
478
+ stroke-dashoffset: 440;
479
+ transition: stroke-dashoffset 1.5s ease, stroke 0.5s;
480
+ filter: drop-shadow(0 0 6px var(--green-glow));
481
+ }
482
+
483
+ .viability-ring .value-text {
484
+ position: absolute;
485
+ top: 50%;
486
+ left: 50%;
487
+ transform: translate(-50%, -50%);
488
+ font-size: 32px;
489
+ font-weight: 700;
490
+ color: var(--green);
491
+ text-shadow: 0 0 20px var(--green-glow);
492
+ }
493
+
494
+ .gate-badge {
495
+ display: inline-block;
496
+ padding: 8px 24px;
497
+ border-radius: 3px;
498
+ font-size: 12px;
499
+ letter-spacing: 4px;
500
+ text-transform: uppercase;
501
+ font-weight: 500;
502
+ opacity: 0;
503
+ transition: opacity 0.5s;
504
+ }
505
+
506
+ .gate-badge.visible { opacity: 1; }
507
+
508
+ /* ─── Stats ─── */
509
+ .stats-grid {
510
+ display: grid;
511
+ grid-template-columns: 1fr 1fr;
512
+ gap: 8px;
513
+ margin-top: 24px;
514
+ text-align: left;
515
+ }
516
+
517
+ .stat-card {
518
+ background: var(--bg-card);
519
+ border: 1px solid var(--border);
520
+ border-radius: 3px;
521
+ padding: 12px;
522
+ }
523
+
524
+ .stat-label {
525
+ font-size: 9px;
526
+ color: var(--text-dim);
527
+ letter-spacing: 1px;
528
+ text-transform: uppercase;
529
+ margin-bottom: 4px;
530
+ }
531
+
532
+ .stat-value {
533
+ font-size: 18px;
534
+ font-weight: 700;
535
+ color: var(--green);
536
+ }
537
+
538
+ .stat-value.warning { color: var(--amber); }
539
+ .stat-value.danger { color: var(--red); }
540
+
541
+ /* ─── Collapse Banner ─── */
542
+ .collapse-banner {
543
+ background: #ff444415;
544
+ border: 1px solid #ff444440;
545
+ border-radius: 3px;
546
+ padding: 16px;
547
+ margin-top: 16px;
548
+ text-align: center;
549
+ display: none;
550
+ }
551
+
552
+ .collapse-banner.visible { display: block; }
553
+
554
+ .collapse-banner .collapse-title {
555
+ color: var(--red);
556
+ font-size: 12px;
557
+ letter-spacing: 3px;
558
+ text-transform: uppercase;
559
+ font-weight: 700;
560
+ text-shadow: 0 0 10px var(--red-glow);
561
+ }
562
+
563
+ .collapse-banner .collapse-detail {
564
+ color: var(--text-dim);
565
+ font-size: 10px;
566
+ margin-top: 6px;
567
+ }
568
+
569
+ /* ─── Info text ─── */
570
+ .info-block {
571
+ margin-top: 16px;
572
+ padding: 12px;
573
+ background: var(--bg-card);
574
+ border: 1px solid var(--border);
575
+ border-radius: 3px;
576
+ font-size: 10px;
577
+ color: var(--text-dim);
578
+ line-height: 1.6;
579
+ }
580
+
581
+ .invariant-list {
582
+ margin-top: 12px;
583
+ list-style: none;
584
+ font-size: 10px;
585
+ }
586
+
587
+ .invariant-list li {
588
+ padding: 4px 0;
589
+ color: var(--text-dim);
590
+ display: flex;
591
+ align-items: flex-start;
592
+ gap: 6px;
593
+ }
594
+
595
+ .invariant-list li::before {
596
+ content: '>';
597
+ color: var(--green-dark);
598
+ flex-shrink: 0;
599
+ }
600
+
601
+ /* Profile selector */
602
+ .profile-select {
603
+ width: 100%;
604
+ padding: 8px 12px;
605
+ background: var(--bg-card);
606
+ border: 1px solid var(--border);
607
+ border-radius: 3px;
608
+ color: var(--text);
609
+ font-family: inherit;
610
+ font-size: 11px;
611
+ cursor: pointer;
612
+ margin-top: 8px;
613
+ }
614
+
615
+ .profile-select:focus { outline: none; border-color: var(--green-dark); }
616
+
617
+ .section-divider {
618
+ font-size: 10px;
619
+ color: var(--text-dim);
620
+ letter-spacing: 2px;
621
+ text-transform: uppercase;
622
+ margin: 12px 0 8px;
623
+ }
624
+
625
+ /* Steps slider */
626
+ .steps-row {
627
+ display: flex;
628
+ align-items: center;
629
+ gap: 10px;
630
+ margin-top: 12px;
631
+ }
632
+
633
+ .steps-row label {
634
+ font-size: 11px;
635
+ color: var(--text-dim);
636
+ white-space: nowrap;
637
+ }
638
+
639
+ .steps-row input { flex: 1; }
640
+
641
+ .steps-row .steps-val {
642
+ font-size: 12px;
643
+ color: var(--green);
644
+ font-weight: 500;
645
+ min-width: 20px;
646
+ text-align: right;
647
+ }
648
+
649
+ </style>
650
+ </head>
651
+ <body>
652
+
653
+ <canvas id="matrix-canvas"></canvas>
654
+
655
+ <div class="app">
656
+ <div class="header">
657
+ <h1>NeuroVerse Simulation Engine</h1>
658
+ <div class="subtitle">Deterministic World Governance</div>
659
+ <div class="powered">neuroverseos.com</div>
660
+ </div>
661
+
662
+ <div class="grid">
663
+ <!-- LEFT: World + State -->
664
+ <div class="panel">
665
+ <div class="panel-header"><span class="dot"></span> World</div>
666
+ <div class="panel-body">
667
+ <div class="world-tabs" id="world-tabs"></div>
668
+ <button class="custom-toggle" id="custom-toggle">+ paste custom .nv-world.md</button>
669
+ <textarea class="custom-textarea" id="custom-textarea" placeholder="Paste your .nv-world.md here..."></textarea>
670
+
671
+ <div class="section-divider">Profile</div>
672
+ <select class="profile-select" id="profile-select"></select>
673
+
674
+ <div class="state-section" id="state-section">
675
+ <div class="section-divider">State Overrides</div>
676
+ <div id="state-sliders"></div>
677
+ </div>
678
+
679
+ <div class="steps-row">
680
+ <label>Steps</label>
681
+ <input type="range" id="steps-slider" min="1" max="20" value="5">
682
+ <span class="steps-val" id="steps-val">5</span>
683
+ </div>
684
+
685
+ <button class="simulate-btn" id="simulate-btn">Simulate</button>
686
+ </div>
687
+ </div>
688
+
689
+ <!-- CENTER: Simulation -->
690
+ <div class="panel">
691
+ <div class="panel-header"><span class="dot"></span> Simulation</div>
692
+ <div class="panel-body sim-output" id="sim-output">
693
+ <div class="sim-placeholder">
694
+ <div>Select a world and configure state</div>
695
+ <div>then press <span style="color:var(--green)">SIMULATE</span></div>
696
+ <span class="cursor"></span>
697
+ </div>
698
+ </div>
699
+ </div>
700
+
701
+ <!-- RIGHT: Verdict -->
702
+ <div class="panel">
703
+ <div class="panel-header"><span class="dot"></span> Verdict</div>
704
+ <div class="panel-body verdict-section" id="verdict-section">
705
+ <div class="viability-display">
706
+ <div class="viability-ring">
707
+ <svg viewBox="0 0 160 160">
708
+ <circle class="bg-ring" cx="80" cy="80" r="70"/>
709
+ <circle class="fg-ring" id="viability-ring" cx="80" cy="80" r="70"/>
710
+ </svg>
711
+ <div class="value-text" id="viability-value">--</div>
712
+ </div>
713
+ <div class="gate-badge" id="gate-badge">AWAITING</div>
714
+ </div>
715
+
716
+ <div class="stats-grid" id="stats-grid"></div>
717
+
718
+ <div class="collapse-banner" id="collapse-banner">
719
+ <div class="collapse-title">Model Collapsed</div>
720
+ <div class="collapse-detail" id="collapse-detail"></div>
721
+ </div>
722
+
723
+ <ul class="invariant-list" id="invariant-list"></ul>
724
+
725
+ </div>
726
+ </div>
727
+ </div>
728
+ </div>
729
+
730
+ <script>
731
+ // ═══════════════════════════════════════════════════════════════════════════
732
+ // MATRIX RAIN
733
+ // ═══════════════════════════════════════════════════════════════════════════
734
+
735
+ const canvas = document.getElementById('matrix-canvas');
736
+ const ctx = canvas.getContext('2d');
737
+
738
+ function resizeCanvas() {
739
+ canvas.width = window.innerWidth;
740
+ canvas.height = window.innerHeight;
741
+ }
742
+ resizeCanvas();
743
+ window.addEventListener('resize', resizeCanvas);
744
+
745
+ const chars = 'NeuroVerse01アイウエオカキクケコサシスセソ>|/\\=+-*';
746
+ const fontSize = 14;
747
+ let columns = Math.floor(canvas.width / fontSize);
748
+ let drops = Array(columns).fill(1);
749
+
750
+ function drawMatrix() {
751
+ ctx.fillStyle = 'rgba(10, 10, 10, 0.05)';
752
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
753
+ ctx.fillStyle = '#00ff41';
754
+ ctx.font = fontSize + 'px monospace';
755
+
756
+ for (let i = 0; i < drops.length; i++) {
757
+ const text = chars[Math.floor(Math.random() * chars.length)];
758
+ ctx.fillText(text, i * fontSize, drops[i] * fontSize);
759
+ if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) {
760
+ drops[i] = 0;
761
+ }
762
+ drops[i]++;
763
+ }
764
+ }
765
+
766
+ setInterval(drawMatrix, 50);
767
+
768
+ // ═══════════════════════════════════════════════════════════════════════════
769
+ // WORLD PARSER (Simplified .nv-world.md → WorldDefinition)
770
+ // ═══════════════════════════════════════════════════════════════════════════
771
+
772
+ function parseWorldMarkdown(md) {
773
+ const world = {
774
+ world: { world_id: '', name: '', thesis: '', version: '1.0.0', runtime_mode: 'SIMULATION',
775
+ default_assumption_profile: '', default_alternative_profile: '', modules: [],
776
+ players: { thinking_space: true, experience_space: true, action_space: true } },
777
+ invariants: [],
778
+ assumptions: { profiles: {}, parameter_definitions: {} },
779
+ stateSchema: { variables: {}, presets: {} },
780
+ rules: [],
781
+ gates: { viability_classification: [], structural_override: { description: 'Structural violations override all gates', enforcement: 'mandatory' },
782
+ sustainability_threshold: 50, collapse_visual: { background: '#1a0000', text: '#ff4444', border: '#ff0000', label: 'COLLAPSED' } },
783
+ outcomes: { computed_outcomes: [], comparison_layout: { primary_card: '', status_badge: '', structural_indicators: [] } },
784
+ metadata: { format_version: '1.0.0', created_at: new Date().toISOString(), last_modified: new Date().toISOString(), authoring_method: 'manual-authoring' }
785
+ };
786
+
787
+ // Parse frontmatter
788
+ const fmMatch = md.match(/^---\n([\s\S]*?)\n---/);
789
+ if (fmMatch) {
790
+ const fm = fmMatch[1];
791
+ const getVal = (key) => { const m = fm.match(new RegExp(key + ':\\s*(.+)')); return m ? m[1].trim() : ''; };
792
+ world.world.world_id = getVal('world_id');
793
+ world.world.name = getVal('name');
794
+ world.world.version = getVal('version') || '1.0.0';
795
+ world.world.runtime_mode = getVal('runtime_mode') || 'SIMULATION';
796
+ world.world.default_assumption_profile = getVal('default_profile');
797
+ world.world.default_alternative_profile = getVal('alternative_profile');
798
+ }
799
+
800
+ // Split sections
801
+ const sections = {};
802
+ let currentSection = null;
803
+ for (const line of md.split('\n')) {
804
+ const h1 = line.match(/^# (.+)/);
805
+ if (h1) { currentSection = h1[1].trim().toLowerCase(); sections[currentSection] = ''; continue; }
806
+ if (currentSection) sections[currentSection] += line + '\n';
807
+ }
808
+
809
+ // Thesis
810
+ world.world.thesis = (sections['thesis'] || '').trim();
811
+
812
+ // Invariants
813
+ if (sections['invariants']) {
814
+ for (const line of sections['invariants'].split('\n')) {
815
+ const m = line.match(/^- `([^`]+)` — (.+?) \((\w+), (\w+)\)/);
816
+ if (m) world.invariants.push({ id: m[1], label: m[2], enforcement: m[3], mutable: false });
817
+ }
818
+ }
819
+
820
+ // State
821
+ if (sections['state']) {
822
+ let varId = null;
823
+ const vars = {};
824
+ for (const line of sections['state'].split('\n')) {
825
+ const h2 = line.match(/^## (\w+)/);
826
+ if (h2) { varId = h2[1]; vars[varId] = { type: 'number', default: 0, mutable: true, label: varId, description: '' }; continue; }
827
+ if (!varId) continue;
828
+ const kv = line.match(/^- (\w+):\s*(.+)/);
829
+ if (kv) {
830
+ const [, k, v] = kv;
831
+ if (k === 'type') vars[varId].type = v.trim();
832
+ else if (k === 'min') vars[varId].min = parseFloat(v);
833
+ else if (k === 'max') vars[varId].max = parseFloat(v);
834
+ else if (k === 'step') vars[varId].step = parseFloat(v);
835
+ else if (k === 'default') vars[varId].default = isNaN(+v) ? v.trim() : parseFloat(v);
836
+ else if (k === 'label') vars[varId].label = v.trim();
837
+ else if (k === 'description') vars[varId].description = v.trim();
838
+ }
839
+ }
840
+ world.stateSchema.variables = vars;
841
+ }
842
+
843
+ // Assumptions
844
+ if (sections['assumptions']) {
845
+ let profId = null;
846
+ for (const line of sections['assumptions'].split('\n')) {
847
+ const h2 = line.match(/^## (\w+)/);
848
+ if (h2) { profId = h2[1]; world.assumptions.profiles[profId] = { name: '', description: '', parameters: {} }; continue; }
849
+ if (!profId) continue;
850
+ const kv = line.match(/^- (\w+):\s*(.+)/);
851
+ if (kv) {
852
+ const [, k, v] = kv;
853
+ if (k === 'name') world.assumptions.profiles[profId].name = v.trim();
854
+ else if (k === 'description') world.assumptions.profiles[profId].description = v.trim();
855
+ else world.assumptions.profiles[profId].parameters[k] = v.trim();
856
+ }
857
+ }
858
+ if (world.world.default_assumption_profile) {
859
+ const p = world.assumptions.profiles[world.world.default_assumption_profile];
860
+ if (p) p.is_default_baseline = true;
861
+ }
862
+ }
863
+
864
+ // Rules
865
+ if (sections['rules']) {
866
+ const ruleBlocks = sections['rules'].split(/^## /m).filter(Boolean);
867
+ let order = 0;
868
+ for (const block of ruleBlocks) {
869
+ const headerMatch = block.match(/^(rule-\d+):\s*(.+?)\s*\((\w+)\)/);
870
+ if (!headerMatch) continue;
871
+ const [, ruleId, label, severity] = headerMatch;
872
+ const lines = block.split('\n');
873
+ let description = '';
874
+ const triggers = [];
875
+ const effects = [];
876
+ let collapseCheck = null;
877
+ const causal = { trigger_text: '', rule_text: '', shift_text: '', effect_text: '' };
878
+
879
+ for (const line of lines) {
880
+ // Triggers: When field op value [source] (AND field op value [source])*
881
+ const whenMatch = line.match(/^When (.+)/);
882
+ if (whenMatch) {
883
+ const parts = whenMatch[1].split(/\s+AND\s+/);
884
+ for (const part of parts) {
885
+ const tm = part.match(/(\w+)\s*(==|!=|>=|<=|>|<)\s*([\w.]+)\s*\[(\w+)\]/);
886
+ if (tm) triggers.push({ field: tm[1], operator: tm[2], value: isNaN(+tm[3]) ? tm[3] : parseFloat(tm[3]), source: tm[4] });
887
+ }
888
+ }
889
+ // Effects: Then target *= value or target += value
890
+ const thenMatch = line.match(/^Then (\w+)\s*(\*=|\+=|-=|=)\s*([\w.]+)/);
891
+ if (thenMatch) {
892
+ const opMap = { '*=': 'multiply', '+=': 'add', '-=': 'subtract', '=': 'set' };
893
+ effects.push({ target: thenMatch[1], operation: opMap[thenMatch[2]] || 'set', value: isNaN(+thenMatch[3]) ? thenMatch[3] : parseFloat(thenMatch[3]) });
894
+ }
895
+ // Collapse
896
+ const collapseMatch = line.match(/^Collapse:\s*(\w+)\s*(==|!=|>=|<=|>|<)\s*([\w.]+)/);
897
+ if (collapseMatch) {
898
+ collapseCheck = { field: collapseMatch[1], operator: collapseMatch[2], value: parseFloat(collapseMatch[3]), result: 'MODEL_COLLAPSES' };
899
+ }
900
+ // Causal
901
+ const causalMatch = line.match(/^> (\w+):\s*(.+)/);
902
+ if (causalMatch) causal[causalMatch[1] + '_text'] = causalMatch[2];
903
+
904
+ // Description (first non-header, non-directive line)
905
+ if (!line.startsWith('When ') && !line.startsWith('Then ') && !line.startsWith('Collapse:') && !line.startsWith('>') && !line.match(/^rule-/) && line.trim() && !description) {
906
+ description = line.trim();
907
+ }
908
+ }
909
+
910
+ world.rules.push({ id: ruleId, severity, label, description, order: order++, triggers, effects, collapse_check: collapseCheck, causal_translation: causal });
911
+ world.world.modules.push(ruleId);
912
+ }
913
+ }
914
+
915
+ // Gates
916
+ if (sections['gates']) {
917
+ const gateColors = { 0: '#00ff41', 1: '#00cc33', 2: '#ffaa00', 3: '#ff6600', 4: '#ff4444' };
918
+ const gateIcons = { 0: '\u2726', 1: '\u25CF', 2: '\u25B2', 3: '\u26A0', 4: '\u2715' };
919
+ let gi = 0;
920
+ for (const line of sections['gates'].split('\n')) {
921
+ const gm = line.match(/^- (\w+):\s*(\w+)\s*(>=|<=|>|<|==)\s*([\d.]+)/);
922
+ if (gm) {
923
+ world.gates.viability_classification.push({
924
+ status: gm[1], field: gm[2], operator: gm[3], value: parseFloat(gm[4]),
925
+ color: gateColors[gi] || '#888', icon: gateIcons[gi] || '?'
926
+ });
927
+ gi++;
928
+ }
929
+ }
930
+ }
931
+
932
+ // Outcomes
933
+ if (sections['outcomes']) {
934
+ let outId = null;
935
+ for (const line of sections['outcomes'].split('\n')) {
936
+ const h2 = line.match(/^## (\w+)/);
937
+ if (h2) {
938
+ outId = h2[1];
939
+ world.outcomes.computed_outcomes.push({ id: outId, type: 'number', label: outId, show_in_comparison: true });
940
+ continue;
941
+ }
942
+ if (!outId) continue;
943
+ const out = world.outcomes.computed_outcomes.find(o => o.id === outId);
944
+ if (!out) continue;
945
+ const kv = line.match(/^- (\w+):\s*(.+)/);
946
+ if (kv) {
947
+ if (kv[1] === 'type') out.type = kv[2].trim();
948
+ if (kv[1] === 'range') { const r = kv[2].match(/([\d.-]+)-([\d.-]+)/); if (r) out.range = [parseFloat(r[1]), parseFloat(r[2])]; }
949
+ if (kv[1] === 'display') out.display_as = kv[2].trim();
950
+ if (kv[1] === 'label') out.label = kv[2].trim();
951
+ if (kv[1] === 'primary' && kv[2].trim() === 'true') { out.primary = true; world.outcomes.comparison_layout.primary_card = outId; }
952
+ }
953
+ }
954
+ }
955
+
956
+ return world;
957
+ }
958
+
959
+ // ═══════════════════════════════════════════════════════════════════════════
960
+ // SIMULATE ENGINE (Pure deterministic simulation)
961
+ // ═══════════════════════════════════════════════════════════════════════════
962
+
963
+ function evaluateOperator(fieldValue, operator, condValue) {
964
+ switch (operator) {
965
+ case '==': return fieldValue == condValue;
966
+ case '!=': return fieldValue != condValue;
967
+ case '>': return typeof fieldValue === 'number' && typeof condValue === 'number' && fieldValue > condValue;
968
+ case '<': return typeof fieldValue === 'number' && typeof condValue === 'number' && fieldValue < condValue;
969
+ case '>=': return typeof fieldValue === 'number' && typeof condValue === 'number' && fieldValue >= condValue;
970
+ case '<=': return typeof fieldValue === 'number' && typeof condValue === 'number' && fieldValue <= condValue;
971
+ default: return false;
972
+ }
973
+ }
974
+
975
+ function evaluateTriggers(triggers, state, assumptions) {
976
+ return triggers.every(t => {
977
+ const source = t.source === 'assumption' ? assumptions : state;
978
+ const val = source[t.field];
979
+ if (val === undefined) return false;
980
+ return evaluateOperator(val, t.operator, t.value);
981
+ });
982
+ }
983
+
984
+ function applyEffect(state, effect) {
985
+ const before = state[effect.target];
986
+ let after = before;
987
+ const val = typeof effect.value === 'number' ? effect.value : parseFloat(effect.value);
988
+
989
+ switch (effect.operation) {
990
+ case 'multiply': after = typeof before === 'number' ? before * val : before; break;
991
+ case 'add': after = typeof before === 'number' ? before + val : before; break;
992
+ case 'subtract': after = typeof before === 'number' ? before - val : before; break;
993
+ case 'set': after = val; break;
994
+ }
995
+
996
+ state[effect.target] = typeof after === 'number' ? Math.round(after * 100) / 100 : after;
997
+ return { target: effect.target, operation: effect.operation, value: effect.value, before, after: state[effect.target] };
998
+ }
999
+
1000
+ function classifyViability(state, world) {
1001
+ const gates = world.gates?.viability_classification ?? [];
1002
+ for (const gate of gates) {
1003
+ const val = state[gate.field];
1004
+ if (typeof val !== 'number') continue;
1005
+ if (evaluateOperator(val, gate.operator, gate.value)) return gate.status;
1006
+ }
1007
+ return 'MODEL_COLLAPSES';
1008
+ }
1009
+
1010
+ function simulateWorld(world, options = {}) {
1011
+ const steps = Math.min(50, Math.max(1, options.steps || 1));
1012
+ const profile = options.profile || world.world.default_assumption_profile;
1013
+
1014
+ // Build initial state from schema defaults
1015
+ const state = {};
1016
+ for (const [k, v] of Object.entries(world.stateSchema.variables)) {
1017
+ state[k] = v.default;
1018
+ }
1019
+
1020
+ // Initialize outcome fields (primary outcome starts at 100)
1021
+ for (const out of world.outcomes.computed_outcomes) {
1022
+ if (out.primary && !(out.id in state)) state[out.id] = 100;
1023
+ else if (!(out.id in state)) state[out.id] = out.default || 0;
1024
+ }
1025
+
1026
+ // Apply state overrides
1027
+ if (options.stateOverrides) {
1028
+ for (const [k, v] of Object.entries(options.stateOverrides)) {
1029
+ state[k] = v;
1030
+ }
1031
+ }
1032
+
1033
+ // Get assumption parameters
1034
+ const assumptions = world.assumptions?.profiles?.[profile]?.parameters || {};
1035
+
1036
+ const initialState = { ...state };
1037
+ const simSteps = [];
1038
+ let collapsed = false;
1039
+ let collapseStep, collapseRule;
1040
+
1041
+ for (let s = 0; s < steps; s++) {
1042
+ const ruleEvals = [];
1043
+ let firedThisStep = [];
1044
+ let stepCollapsed = false;
1045
+
1046
+ for (const rule of world.rules) {
1047
+ if (collapsed) {
1048
+ ruleEvals.push({ ruleId: rule.id, label: rule.label, triggered: false, excluded: true, effects: [], collapsed: false, severity: rule.severity });
1049
+ continue;
1050
+ }
1051
+
1052
+ // Check exclusive_with
1053
+ if (rule.exclusive_with && firedThisStep.includes(rule.exclusive_with)) {
1054
+ ruleEvals.push({ ruleId: rule.id, label: rule.label, triggered: false, excluded: true, effects: [], collapsed: false, severity: rule.severity });
1055
+ continue;
1056
+ }
1057
+
1058
+ const triggered = evaluateTriggers(rule.triggers, state, assumptions);
1059
+ const appliedEffects = [];
1060
+
1061
+ if (triggered) {
1062
+ firedThisStep.push(rule.id);
1063
+ for (const eff of (rule.effects || [])) {
1064
+ appliedEffects.push(applyEffect(state, eff));
1065
+ }
1066
+
1067
+ // Check collapse
1068
+ if (rule.collapse_check) {
1069
+ const cv = state[rule.collapse_check.field];
1070
+ if (typeof cv === 'number' && evaluateOperator(cv, rule.collapse_check.operator, rule.collapse_check.value)) {
1071
+ collapsed = true;
1072
+ stepCollapsed = true;
1073
+ collapseStep = s + 1;
1074
+ collapseRule = rule.id;
1075
+ }
1076
+ }
1077
+ }
1078
+
1079
+ ruleEvals.push({ ruleId: rule.id, label: rule.label, triggered, excluded: false, effects: appliedEffects, collapsed: stepCollapsed && triggered, severity: rule.severity });
1080
+ }
1081
+
1082
+ simSteps.push({
1083
+ step: s + 1,
1084
+ rulesEvaluated: ruleEvals,
1085
+ rulesFired: ruleEvals.filter(r => r.triggered).length,
1086
+ stateAfter: { ...state },
1087
+ viability: classifyViability(state, world),
1088
+ collapsed: stepCollapsed
1089
+ });
1090
+
1091
+ if (collapsed) break;
1092
+ }
1093
+
1094
+ return {
1095
+ worldId: world.world.world_id,
1096
+ worldName: world.world.name,
1097
+ profile,
1098
+ initialState,
1099
+ steps: simSteps,
1100
+ finalState: { ...state },
1101
+ finalViability: classifyViability(state, world),
1102
+ collapsed,
1103
+ collapseStep,
1104
+ collapseRule
1105
+ };
1106
+ }
1107
+
1108
+ // ═══════════════════════════════════════════════════════════════════════════
1109
+ // PRE-BUNDLED WORLDS
1110
+ // ═══════════════════════════════════════════════════════════════════════════
1111
+
1112
+ const BUNDLED_WORLDS = {
1113
+ 'coding-agent': {
1114
+ name: 'Coding Agent',
1115
+ desc: 'File writes, shell commands, git',
1116
+ md: `---
1117
+ world_id: coding-agent
1118
+ name: Coding Agent Governance
1119
+ version: 1.0.0
1120
+ runtime_mode: COMPLIANCE
1121
+ default_profile: standard
1122
+ alternative_profile: strict
1123
+ ---
1124
+
1125
+ # Thesis
1126
+
1127
+ Autonomous coding agents that can read files, write code, execute shell commands, and interact with version control must operate within explicit governance boundaries.
1128
+
1129
+ # Invariants
1130
+
1131
+ - \`no_destructive_shell_commands\` — Shell commands must not perform irreversible destructive operations (structural, immutable)
1132
+ - \`no_secret_exfiltration\` — Agent must never read or transmit secrets or API keys (structural, immutable)
1133
+ - \`no_system_file_modification\` — Agent must not modify files outside the project repository (structural, immutable)
1134
+ - \`no_direct_production_push\` — Code must never be pushed directly to main without approval (structural, immutable)
1135
+ - \`repository_boundary_enforced\` — All file operations scoped to project directory (structural, immutable)
1136
+
1137
+ # State
1138
+
1139
+ ## files_modified
1140
+ - type: number
1141
+ - min: 0
1142
+ - max: 200
1143
+ - step: 1
1144
+ - default: 0
1145
+ - label: Files Modified
1146
+ - description: Total files modified in session
1147
+
1148
+ ## shell_commands_run
1149
+ - type: number
1150
+ - min: 0
1151
+ - max: 500
1152
+ - step: 1
1153
+ - default: 0
1154
+ - label: Shell Commands Run
1155
+ - description: Total shell commands executed
1156
+
1157
+ ## destructive_attempts
1158
+ - type: number
1159
+ - min: 0
1160
+ - max: 20
1161
+ - step: 1
1162
+ - default: 0
1163
+ - label: Destructive Attempts
1164
+ - description: Blocked destructive actions
1165
+
1166
+ ## tests_passing
1167
+ - type: number
1168
+ - min: 0
1169
+ - max: 100
1170
+ - step: 1
1171
+ - default: 100
1172
+ - label: Tests Passing
1173
+ - description: Percentage of tests passing
1174
+
1175
+ ## files_outside_repo
1176
+ - type: number
1177
+ - min: 0
1178
+ - max: 20
1179
+ - step: 1
1180
+ - default: 0
1181
+ - label: Out-of-Scope Access
1182
+ - description: File ops outside repository
1183
+
1184
+ ## secrets_detected
1185
+ - type: number
1186
+ - min: 0
1187
+ - max: 20
1188
+ - step: 1
1189
+ - default: 0
1190
+ - label: Secrets Detected
1191
+ - description: Secrets found in agent output
1192
+
1193
+ # Assumptions
1194
+
1195
+ ## standard
1196
+ - name: Standard Development
1197
+ - description: Normal dev workflow. Safe commands allowed.
1198
+ - shell_access: safe_commands
1199
+ - file_access: project_only
1200
+
1201
+ ## strict
1202
+ - name: Strict Governance
1203
+ - description: All writes require approval. Shell limited to tests.
1204
+ - shell_access: test_only
1205
+ - file_access: read_mostly
1206
+
1207
+ # Rules
1208
+
1209
+ ## rule-001: Destructive Shell Command (structural)
1210
+ Destructive shell commands must be blocked immediately.
1211
+
1212
+ When destructive_attempts > 0 [state]
1213
+ Then agent_trust *= 0.00
1214
+ Collapse: agent_trust < 0.05
1215
+
1216
+ > trigger: Agent attempted a destructive shell command.
1217
+ > rule: Destructive commands are never acceptable.
1218
+ > shift: Agent trust drops to zero. Session terminated.
1219
+ > effect: Agent trust eliminated.
1220
+
1221
+ ## rule-002: Secret Exfiltration (structural)
1222
+ Secret access must be blocked and flagged.
1223
+
1224
+ When secrets_detected > 0 [state]
1225
+ Then agent_trust *= 0.10
1226
+ Collapse: agent_trust < 0.05
1227
+
1228
+ > trigger: Agent accessed credentials or secrets.
1229
+ > rule: Secrets require explicit authorization.
1230
+ > shift: Agent trust drops severely.
1231
+ > effect: Agent trust reduced to 10%.
1232
+
1233
+ ## rule-003: Boundary Violation (structural)
1234
+ File operations outside the repository are forbidden.
1235
+
1236
+ When files_outside_repo > 0 [state]
1237
+ Then agent_trust *= 0.20
1238
+ Collapse: agent_trust < 0.05
1239
+
1240
+ > trigger: Agent accessed files outside project directory.
1241
+ > rule: Agents operate within repository scope.
1242
+ > shift: Agent trust degrades significantly.
1243
+ > effect: Agent trust reduced to 20%.
1244
+
1245
+ ## rule-004: Test Regression (degradation)
1246
+ Test failures indicate the agent is creating regressions.
1247
+
1248
+ When tests_passing < 70 [state] AND files_modified > 0 [state]
1249
+ Then agent_trust *= 0.50
1250
+
1251
+ > trigger: Tests dropped below 70%.
1252
+ > rule: Agents that break tests create liability.
1253
+ > shift: Agent must fix tests before continuing.
1254
+ > effect: Agent trust reduced to 50%.
1255
+
1256
+ ## rule-005: Excessive Modifications (degradation)
1257
+ Too many modifications indicate scope creep.
1258
+
1259
+ When files_modified > 50 [state]
1260
+ Then agent_trust *= 0.70
1261
+
1262
+ > trigger: More than 50 files modified.
1263
+ > rule: Large changes are risky and hard to review.
1264
+ > shift: Changes should be reviewed.
1265
+ > effect: Agent trust reduced to 70%.
1266
+
1267
+ ## rule-006: Clean Session (advantage)
1268
+ No destructive attempts and passing tests.
1269
+
1270
+ When destructive_attempts == 0 [state] AND tests_passing > 90 [state] AND files_modified > 0 [state]
1271
+ Then agent_trust *= 1.20
1272
+
1273
+ > trigger: Zero violations with passing tests.
1274
+ > rule: Trustworthy agents are rewarded.
1275
+ > shift: Session is healthy.
1276
+ > effect: Agent trust boosted 20%.
1277
+
1278
+ ## rule-007: Productive Session (advantage)
1279
+ Focused changes with good test coverage.
1280
+
1281
+ When files_modified > 0 [state] AND files_modified < 30 [state] AND tests_passing > 85 [state]
1282
+ Then agent_trust *= 1.10
1283
+
1284
+ > trigger: Focused changes with quality.
1285
+ > rule: Ideal workflow.
1286
+ > shift: Sustainable pace.
1287
+ > effect: Agent trust boosted 10%.
1288
+
1289
+ # Gates
1290
+
1291
+ - TRUSTED: agent_trust >= 90
1292
+ - PRODUCTIVE: agent_trust >= 60
1293
+ - CAUTIOUS: agent_trust >= 35
1294
+ - RESTRICTED: agent_trust > 10
1295
+ - TERMINATED: agent_trust <= 10
1296
+
1297
+ # Outcomes
1298
+
1299
+ ## agent_trust
1300
+ - type: number
1301
+ - range: 0-100
1302
+ - display: percentage
1303
+ - label: Agent Trust
1304
+ - primary: true
1305
+ `
1306
+ },
1307
+ 'research-agent': {
1308
+ name: 'Research Agent',
1309
+ desc: 'Citations, API budgets, sources',
1310
+ md: `---
1311
+ world_id: research-agent
1312
+ name: Research Agent Governance
1313
+ version: 1.0.0
1314
+ runtime_mode: COMPLIANCE
1315
+ default_profile: conservative
1316
+ alternative_profile: exploratory
1317
+ ---
1318
+
1319
+ # Thesis
1320
+
1321
+ AI research agents must operate within governance boundaries ensuring rigor, attribution, and responsible resource usage.
1322
+
1323
+ # Invariants
1324
+
1325
+ - \`sources_must_be_cited\` — Every claim must be traceable to a source (structural, immutable)
1326
+ - \`no_fabricated_citations\` — Agent must never invent sources (structural, immutable)
1327
+ - \`api_rate_limits_respected\` — Must respect rate limits on external APIs (structural, immutable)
1328
+ - \`no_unauthorized_publication\` — Findings require human review before publishing (prompt, immutable)
1329
+
1330
+ # State
1331
+
1332
+ ## sources_consulted
1333
+ - type: number
1334
+ - min: 0
1335
+ - max: 100
1336
+ - step: 1
1337
+ - default: 0
1338
+ - label: Sources Consulted
1339
+ - description: Unique sources accessed
1340
+
1341
+ ## claims_made
1342
+ - type: number
1343
+ - min: 0
1344
+ - max: 100
1345
+ - step: 1
1346
+ - default: 0
1347
+ - label: Claims Made
1348
+ - description: Assertions produced
1349
+
1350
+ ## unsourced_claims
1351
+ - type: number
1352
+ - min: 0
1353
+ - max: 50
1354
+ - step: 1
1355
+ - default: 0
1356
+ - label: Unsourced Claims
1357
+ - description: Claims without source attribution
1358
+
1359
+ ## api_calls_made
1360
+ - type: number
1361
+ - min: 0
1362
+ - max: 10000
1363
+ - step: 10
1364
+ - default: 0
1365
+ - label: API Calls Made
1366
+ - description: External API calls made
1367
+
1368
+ ## api_budget
1369
+ - type: number
1370
+ - min: 100
1371
+ - max: 10000
1372
+ - step: 100
1373
+ - default: 5000
1374
+ - label: API Budget
1375
+ - description: Maximum API calls allowed
1376
+
1377
+ # Assumptions
1378
+
1379
+ ## conservative
1380
+ - name: Conservative Research
1381
+ - description: Multiple sources per claim. Strict API limits.
1382
+ - source_requirement: multiple_per_claim
1383
+ - api_strictness: hard_limit
1384
+
1385
+ ## exploratory
1386
+ - name: Exploratory Research
1387
+ - description: Broader exploration. Softer limits.
1388
+ - source_requirement: at_least_one
1389
+ - api_strictness: soft_warning
1390
+
1391
+ # Rules
1392
+
1393
+ ## rule-001: API Budget Exhausted (structural)
1394
+ No further external calls when budget exceeded.
1395
+
1396
+ When api_calls_made > api_budget [state]
1397
+ Then research_viability *= 0.00
1398
+ Collapse: research_viability < 0.05
1399
+
1400
+ > trigger: API budget exceeded.
1401
+ > rule: Budgets prevent abuse and cost overruns.
1402
+ > shift: Research halts for external calls.
1403
+ > effect: Viability set to zero.
1404
+
1405
+ ## rule-002: Unsourced Claims (degradation)
1406
+ Too many unsourced claims lack rigor.
1407
+
1408
+ When unsourced_claims > 3 [state] AND claims_made > 0 [state]
1409
+ Then research_viability *= 0.40
1410
+
1411
+ > trigger: More than 3 unsourced claims.
1412
+ > rule: Every assertion must be traceable.
1413
+ > shift: Viability drops. Agent must add citations.
1414
+ > effect: Viability reduced to 40%.
1415
+
1416
+ ## rule-003: Source Diversity (advantage)
1417
+ Many diverse sources produce quality research.
1418
+
1419
+ When sources_consulted > 10 [state] AND unsourced_claims == 0 [state]
1420
+ Then research_viability *= 1.25
1421
+
1422
+ > trigger: 10+ sources with zero unsourced claims.
1423
+ > rule: Diverse, cited research is gold standard.
1424
+ > shift: Viability improves.
1425
+ > effect: Viability boosted 25%.
1426
+
1427
+ ## rule-004: Shallow Research (degradation)
1428
+ Many claims from few sources.
1429
+
1430
+ When claims_made > 10 [state] AND sources_consulted < 3 [state]
1431
+ Then research_viability *= 0.50
1432
+
1433
+ > trigger: 10+ claims from fewer than 3 sources.
1434
+ > rule: Good research requires multiple perspectives.
1435
+ > shift: Agent should broaden sources.
1436
+ > effect: Viability reduced to 50%.
1437
+
1438
+ # Gates
1439
+
1440
+ - RIGOROUS: research_viability >= 85
1441
+ - SOLID: research_viability >= 60
1442
+ - DEVELOPING: research_viability >= 35
1443
+ - WEAK: research_viability > 10
1444
+ - UNRELIABLE: research_viability <= 10
1445
+
1446
+ # Outcomes
1447
+
1448
+ ## research_viability
1449
+ - type: number
1450
+ - range: 0-100
1451
+ - display: percentage
1452
+ - label: Research Viability
1453
+ - primary: true
1454
+ `
1455
+ },
1456
+ 'trading-agent': {
1457
+ name: 'Trading Agent',
1458
+ desc: 'Position limits, stop-losses, P&L',
1459
+ md: `---
1460
+ world_id: trading-agent
1461
+ name: Trading Agent Governance
1462
+ version: 1.0.0
1463
+ runtime_mode: COMPLIANCE
1464
+ default_profile: conservative
1465
+ alternative_profile: aggressive
1466
+ ---
1467
+
1468
+ # Thesis
1469
+
1470
+ Autonomous trading agents must operate within strict governance including position limits, stop-losses, and daily loss constraints.
1471
+
1472
+ # Invariants
1473
+
1474
+ - \`position_limits_enforced\` — No position may exceed maximum size (structural, immutable)
1475
+ - \`stop_loss_required\` — Every position must have a stop-loss (structural, immutable)
1476
+ - \`daily_loss_limit_enforced\` — Daily losses must not exceed the limit (structural, immutable)
1477
+ - \`audit_trail_maintained\` — Every order must be logged (structural, immutable)
1478
+
1479
+ # State
1480
+
1481
+ ## daily_pnl
1482
+ - type: number
1483
+ - min: -20000
1484
+ - max: 20000
1485
+ - step: 100
1486
+ - default: 0
1487
+ - label: Daily P&L
1488
+ - description: Realized + unrealized profit/loss
1489
+
1490
+ ## daily_loss_limit
1491
+ - type: number
1492
+ - min: 1000
1493
+ - max: 50000
1494
+ - step: 1000
1495
+ - default: 5000
1496
+ - label: Daily Loss Limit
1497
+ - description: Max allowable daily loss
1498
+
1499
+ ## open_positions
1500
+ - type: number
1501
+ - min: 0
1502
+ - max: 50
1503
+ - step: 1
1504
+ - default: 0
1505
+ - label: Open Positions
1506
+ - description: Current open positions
1507
+
1508
+ ## max_positions
1509
+ - type: number
1510
+ - min: 1
1511
+ - max: 50
1512
+ - step: 1
1513
+ - default: 10
1514
+ - label: Max Positions
1515
+ - description: Maximum concurrent positions
1516
+
1517
+ ## largest_position_pct
1518
+ - type: number
1519
+ - min: 0
1520
+ - max: 100
1521
+ - step: 1
1522
+ - default: 0
1523
+ - label: Largest Position %
1524
+ - description: Portfolio % in largest position
1525
+
1526
+ ## positions_without_stop
1527
+ - type: number
1528
+ - min: 0
1529
+ - max: 20
1530
+ - step: 1
1531
+ - default: 0
1532
+ - label: Without Stop-Loss
1533
+ - description: Positions with no stop-loss
1534
+
1535
+ # Assumptions
1536
+
1537
+ ## conservative
1538
+ - name: Conservative Trading
1539
+ - description: Small positions. Strict stops. Low loss tolerance.
1540
+ - max_position_pct: 5
1541
+ - daily_loss_tolerance: strict
1542
+
1543
+ ## aggressive
1544
+ - name: Aggressive Trading
1545
+ - description: Larger positions. Higher loss tolerance.
1546
+ - max_position_pct: 15
1547
+ - daily_loss_tolerance: moderate
1548
+
1549
+ # Rules
1550
+
1551
+ ## rule-001: Loss Limit Breached (structural)
1552
+ Trading must stop when daily loss limit exceeded.
1553
+
1554
+ When daily_pnl < 0 [state] AND daily_loss_limit > 0 [state]
1555
+ Then trading_viability *= 0.00
1556
+ Collapse: trading_viability < 0.05
1557
+
1558
+ > trigger: Daily loss exceeds limit.
1559
+ > rule: Loss limits are absolute constraints.
1560
+ > shift: Trading halts immediately.
1561
+ > effect: Viability set to zero.
1562
+
1563
+ ## rule-002: Concentration Risk (degradation)
1564
+ Single position too large.
1565
+
1566
+ When largest_position_pct > 20 [state]
1567
+ Then trading_viability *= 0.50
1568
+
1569
+ > trigger: Position exceeds 20% of portfolio.
1570
+ > rule: Concentration kills portfolios.
1571
+ > shift: Agent should reduce position.
1572
+ > effect: Viability reduced to 50%.
1573
+
1574
+ ## rule-003: Naked Positions (structural)
1575
+ Positions without stop-losses are forbidden.
1576
+
1577
+ When positions_without_stop > 0 [state]
1578
+ Then trading_viability *= 0.30
1579
+ Collapse: trading_viability < 0.05
1580
+
1581
+ > trigger: Positions have no stop-loss.
1582
+ > rule: Every position needs a defined exit.
1583
+ > shift: Viability drops severely.
1584
+ > effect: Viability reduced to 30%.
1585
+
1586
+ ## rule-004: Over-Trading (degradation)
1587
+ Too many open positions.
1588
+
1589
+ When open_positions > max_positions [state]
1590
+ Then trading_viability *= 0.60
1591
+
1592
+ > trigger: Positions exceed maximum.
1593
+ > rule: Limits prevent fragmentation.
1594
+ > shift: Close positions before opening new ones.
1595
+ > effect: Viability reduced to 60%.
1596
+
1597
+ ## rule-005: Disciplined Trading (advantage)
1598
+ Profitable with proper risk controls.
1599
+
1600
+ When daily_pnl > 0 [state] AND positions_without_stop == 0 [state] AND largest_position_pct < 15 [state]
1601
+ Then trading_viability *= 1.20
1602
+
1603
+ > trigger: Positive P&L with risk controls in place.
1604
+ > rule: Disciplined trading is rewarded.
1605
+ > shift: Agent can continue strategy.
1606
+ > effect: Viability boosted 20%.
1607
+
1608
+ ## rule-006: Good Diversification (advantage)
1609
+ Small, distributed positions with stops.
1610
+
1611
+ When open_positions > 3 [state] AND largest_position_pct < 10 [state] AND positions_without_stop == 0 [state]
1612
+ Then trading_viability *= 1.15
1613
+
1614
+ > trigger: Multiple small positions, all with stops.
1615
+ > rule: Textbook diversification.
1616
+ > shift: Portfolio well-structured.
1617
+ > effect: Viability boosted 15%.
1618
+
1619
+ # Gates
1620
+
1621
+ - OPTIMAL: trading_viability >= 90
1622
+ - HEALTHY: trading_viability >= 60
1623
+ - CAUTIOUS: trading_viability >= 35
1624
+ - AT_RISK: trading_viability > 10
1625
+ - HALTED: trading_viability <= 10
1626
+
1627
+ # Outcomes
1628
+
1629
+ ## trading_viability
1630
+ - type: number
1631
+ - range: 0-100
1632
+ - display: percentage
1633
+ - label: Trading Viability
1634
+ - primary: true
1635
+ `
1636
+ }
1637
+ };
1638
+
1639
+ // ═══════════════════════════════════════════════════════════════════════════
1640
+ // UI CONTROLLER
1641
+ // ═══════════════════════════════════════════════════════════════════════════
1642
+
1643
+ let currentWorld = null;
1644
+ let currentWorldDef = null;
1645
+ let customMode = false;
1646
+
1647
+ const worldTabsEl = document.getElementById('world-tabs');
1648
+ const customToggleEl = document.getElementById('custom-toggle');
1649
+ const customTextareaEl = document.getElementById('custom-textarea');
1650
+ const profileSelectEl = document.getElementById('profile-select');
1651
+ const stateSlidersEl = document.getElementById('state-sliders');
1652
+ const stepsSliderEl = document.getElementById('steps-slider');
1653
+ const stepsValEl = document.getElementById('steps-val');
1654
+ const simulateBtnEl = document.getElementById('simulate-btn');
1655
+ const simOutputEl = document.getElementById('sim-output');
1656
+ const viabilityRingEl = document.getElementById('viability-ring');
1657
+ const viabilityValueEl = document.getElementById('viability-value');
1658
+ const gateBadgeEl = document.getElementById('gate-badge');
1659
+ const statsGridEl = document.getElementById('stats-grid');
1660
+ const collapseBannerEl = document.getElementById('collapse-banner');
1661
+ const collapseDetailEl = document.getElementById('collapse-detail');
1662
+ const invariantListEl = document.getElementById('invariant-list');
1663
+
1664
+ // Build world tabs
1665
+ for (const [id, w] of Object.entries(BUNDLED_WORLDS)) {
1666
+ const btn = document.createElement('button');
1667
+ btn.className = 'world-tab';
1668
+ btn.dataset.world = id;
1669
+ btn.innerHTML = `<span class="tab-name">${w.name}</span><span class="tab-desc">${w.desc}</span>`;
1670
+ btn.onclick = () => selectWorld(id);
1671
+ worldTabsEl.appendChild(btn);
1672
+ }
1673
+
1674
+ customToggleEl.onclick = () => {
1675
+ customMode = !customMode;
1676
+ customTextareaEl.classList.toggle('visible', customMode);
1677
+ if (customMode) {
1678
+ document.querySelectorAll('.world-tab').forEach(t => t.classList.remove('active'));
1679
+ customToggleEl.textContent = '- hide custom editor';
1680
+ } else {
1681
+ customToggleEl.textContent = '+ paste custom .nv-world.md';
1682
+ if (currentWorld) selectWorld(currentWorld);
1683
+ }
1684
+ };
1685
+
1686
+ customTextareaEl.oninput = () => {
1687
+ if (customMode && customTextareaEl.value.trim()) {
1688
+ try {
1689
+ currentWorldDef = parseWorldMarkdown(customTextareaEl.value);
1690
+ currentWorld = 'custom';
1691
+ buildControls(currentWorldDef);
1692
+ } catch (e) { /* ignore parse errors while typing */ }
1693
+ }
1694
+ };
1695
+
1696
+ stepsSliderEl.oninput = () => {
1697
+ stepsValEl.textContent = stepsSliderEl.value;
1698
+ };
1699
+
1700
+ function selectWorld(id) {
1701
+ currentWorld = id;
1702
+ customMode = false;
1703
+ customTextareaEl.classList.remove('visible');
1704
+ customToggleEl.textContent = '+ paste custom .nv-world.md';
1705
+ document.querySelectorAll('.world-tab').forEach(t => t.classList.toggle('active', t.dataset.world === id));
1706
+ currentWorldDef = parseWorldMarkdown(BUNDLED_WORLDS[id].md);
1707
+ buildControls(currentWorldDef);
1708
+ }
1709
+
1710
+ function buildControls(world) {
1711
+ // Profiles
1712
+ profileSelectEl.innerHTML = '';
1713
+ for (const [pid, prof] of Object.entries(world.assumptions.profiles)) {
1714
+ const opt = document.createElement('option');
1715
+ opt.value = pid;
1716
+ opt.textContent = prof.name || pid;
1717
+ if (pid === world.world.default_assumption_profile) opt.selected = true;
1718
+ profileSelectEl.appendChild(opt);
1719
+ }
1720
+
1721
+ // State sliders
1722
+ stateSlidersEl.innerHTML = '';
1723
+ for (const [vid, v] of Object.entries(world.stateSchema.variables)) {
1724
+ const group = document.createElement('div');
1725
+ group.className = 'slider-group';
1726
+ const min = v.min ?? 0;
1727
+ const max = v.max ?? 100;
1728
+ const step = v.step ?? 1;
1729
+ const def = v.default ?? 0;
1730
+ group.innerHTML = `
1731
+ <div class="state-label-row">
1732
+ <span class="state-label">${v.label || vid}</span>
1733
+ <span class="state-value" id="sv-${vid}">${def}</span>
1734
+ </div>
1735
+ <input type="range" id="slider-${vid}" min="${min}" max="${max}" step="${step}" value="${def}" data-var="${vid}">
1736
+ `;
1737
+ stateSlidersEl.appendChild(group);
1738
+
1739
+ const slider = group.querySelector('input');
1740
+ const display = group.querySelector('.state-value');
1741
+ slider.oninput = () => { display.textContent = slider.value; };
1742
+ }
1743
+
1744
+ // Invariants
1745
+ invariantListEl.innerHTML = '';
1746
+ for (const inv of world.invariants) {
1747
+ const li = document.createElement('li');
1748
+ li.textContent = inv.label;
1749
+ invariantListEl.appendChild(li);
1750
+ }
1751
+ }
1752
+
1753
+ // ═══════════════════════════════════════════════════════════════════════════
1754
+ // SIMULATE + ANIMATE
1755
+ // ═══════════════════════════════════════════════════════════════════════════
1756
+
1757
+ simulateBtnEl.onclick = async () => {
1758
+ if (!currentWorldDef) return;
1759
+
1760
+ simulateBtnEl.classList.add('running');
1761
+ simulateBtnEl.textContent = 'Simulating...';
1762
+ simOutputEl.innerHTML = '';
1763
+ gateBadgeEl.classList.remove('visible');
1764
+ collapseBannerEl.classList.remove('visible');
1765
+ statsGridEl.innerHTML = '';
1766
+
1767
+ // Collect state overrides
1768
+ const overrides = {};
1769
+ for (const slider of stateSlidersEl.querySelectorAll('input[type="range"]')) {
1770
+ const vid = slider.dataset.var;
1771
+ const val = parseFloat(slider.value);
1772
+ overrides[vid] = val;
1773
+ }
1774
+
1775
+ const result = simulateWorld(currentWorldDef, {
1776
+ steps: parseInt(stepsSliderEl.value),
1777
+ stateOverrides: overrides,
1778
+ profile: profileSelectEl.value,
1779
+ });
1780
+
1781
+ // Animate steps one by one
1782
+ for (let i = 0; i < result.steps.length; i++) {
1783
+ const step = result.steps[i];
1784
+ const card = document.createElement('div');
1785
+ card.className = 'step-card';
1786
+
1787
+ // Find primary outcome
1788
+ const primaryOut = currentWorldDef.outcomes.computed_outcomes.find(o => o.primary);
1789
+ const primaryField = primaryOut ? primaryOut.id : null;
1790
+ const primaryVal = primaryField ? step.stateAfter[primaryField] : null;
1791
+
1792
+ card.innerHTML = `
1793
+ <div class="step-header">
1794
+ <span class="step-number">STEP ${step.step}</span>
1795
+ <span class="step-viability ${step.viability}">${step.viability}${primaryVal !== null ? ` (${Math.round(primaryVal)}%)` : ''}</span>
1796
+ </div>
1797
+ <div class="step-rules">
1798
+ ${step.rulesEvaluated.map(r => {
1799
+ const icon = r.excluded ? '<span class="rule-icon excluded">-</span>'
1800
+ : r.triggered ? '<span class="rule-icon fired">\u26A1</span>'
1801
+ : '<span class="rule-icon pass">\u00B7</span>';
1802
+ const cls = r.triggered ? 'fired' : '';
1803
+ const effectStr = r.effects.length > 0
1804
+ ? r.effects.map(e => {
1805
+ const pct = e.operation === 'multiply' ? `${Math.round(e.value * 100)}%` : `${e.operation} ${e.value}`;
1806
+ return `<span class="rule-effect ${r.severity}">${e.target} \u2192 ${pct}</span>`;
1807
+ }).join(' ')
1808
+ : '';
1809
+ return `<div class="rule-row">${icon}<span class="rule-label ${cls}">${r.label}</span>${effectStr}</div>`;
1810
+ }).join('')}
1811
+ </div>
1812
+ <div class="step-state">
1813
+ ${Object.entries(step.stateAfter)
1814
+ .filter(([k]) => k in currentWorldDef.stateSchema.variables || (primaryField && k === primaryField))
1815
+ .map(([k, v]) => {
1816
+ const prev = i > 0 ? result.steps[i-1].stateAfter[k] : result.initialState[k];
1817
+ const changed = prev !== undefined && prev !== v;
1818
+ const label = currentWorldDef.stateSchema.variables[k]?.label || k;
1819
+ return `<span class="state-chip">${label}: <span class="val ${changed ? 'changed' : ''}">${typeof v === 'number' ? Math.round(v * 100) / 100 : v}</span></span>`;
1820
+ }).join('')}
1821
+ </div>
1822
+ `;
1823
+
1824
+ simOutputEl.appendChild(card);
1825
+ await sleep(120);
1826
+ card.classList.add('visible');
1827
+ await sleep(80);
1828
+
1829
+ // Update ring progressively
1830
+ if (primaryField && primaryVal !== null) {
1831
+ updateRing(Math.round(primaryVal), step.viability);
1832
+ }
1833
+ }
1834
+
1835
+ // Final verdict
1836
+ await sleep(200);
1837
+
1838
+ const finalPrimary = currentWorldDef.outcomes.computed_outcomes.find(o => o.primary);
1839
+ const finalVal = finalPrimary ? Math.round(result.finalState[finalPrimary.id] ?? 0) : 0;
1840
+
1841
+ updateRing(finalVal, result.finalViability);
1842
+
1843
+ gateBadgeEl.textContent = result.finalViability;
1844
+ gateBadgeEl.className = `gate-badge ${result.finalViability} visible`;
1845
+
1846
+ // Stats
1847
+ const totalFired = result.steps.reduce((s, st) => s + st.rulesFired, 0);
1848
+ const totalRules = result.steps.reduce((s, st) => s + st.rulesEvaluated.length, 0);
1849
+
1850
+ statsGridEl.innerHTML = `
1851
+ <div class="stat-card">
1852
+ <div class="stat-label">Steps</div>
1853
+ <div class="stat-value">${result.steps.length}</div>
1854
+ </div>
1855
+ <div class="stat-card">
1856
+ <div class="stat-label">Rules Fired</div>
1857
+ <div class="stat-value ${totalFired > 5 ? 'warning' : ''}">${totalFired}</div>
1858
+ </div>
1859
+ <div class="stat-card">
1860
+ <div class="stat-label">Final Score</div>
1861
+ <div class="stat-value ${finalVal < 35 ? 'danger' : finalVal < 60 ? 'warning' : ''}">${finalVal}%</div>
1862
+ </div>
1863
+ <div class="stat-card">
1864
+ <div class="stat-label">Collapsed</div>
1865
+ <div class="stat-value ${result.collapsed ? 'danger' : ''}">${result.collapsed ? 'YES' : 'NO'}</div>
1866
+ </div>
1867
+ `;
1868
+
1869
+ if (result.collapsed) {
1870
+ collapseBannerEl.classList.add('visible');
1871
+ collapseDetailEl.textContent = `Collapsed at step ${result.collapseStep} by ${result.collapseRule}`;
1872
+ }
1873
+
1874
+ simulateBtnEl.classList.remove('running');
1875
+ simulateBtnEl.textContent = 'Simulate';
1876
+ };
1877
+
1878
+ function updateRing(value, viability) {
1879
+ const circumference = 2 * Math.PI * 70; // r=70
1880
+ const offset = circumference - (value / 100) * circumference;
1881
+ viabilityRingEl.style.strokeDashoffset = offset;
1882
+
1883
+ const color = value >= 60 ? 'var(--green)' : value >= 35 ? 'var(--amber)' : 'var(--red)';
1884
+ viabilityRingEl.style.stroke = color;
1885
+ viabilityRingEl.style.filter = `drop-shadow(0 0 6px ${color === 'var(--green)' ? 'var(--green-glow)' : color === 'var(--amber)' ? '#ffaa0060' : 'var(--red-glow)'})`;
1886
+
1887
+ viabilityValueEl.textContent = value + '%';
1888
+ viabilityValueEl.style.color = color;
1889
+ viabilityValueEl.style.textShadow = `0 0 20px ${color === 'var(--green)' ? 'var(--green-glow)' : color === 'var(--amber)' ? '#ffaa0060' : 'var(--red-glow)'}`;
1890
+ }
1891
+
1892
+ function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
1893
+
1894
+ // Auto-select first world
1895
+ selectWorld('coding-agent');
1896
+
1897
+ </script>
1898
+ </body>
1899
+ </html>