jssm 5.153.0 → 5.154.0

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.
@@ -1090,6 +1090,7 @@ function split_ratio(coord, start, size) {
1090
1090
  * @slot hook-log - Slot for the hook-firing log (`<fsl-hook-log>`, #664).
1091
1091
  * @slot effective-properties - Slot for the resolved-properties panel (`<fsl-effective-properties>`, #665).
1092
1092
  * @slot simulation - Slot for the random-walk simulation (`<fsl-simulation>`, #668).
1093
+ * @slot stochastic - Slot for the stochastic analysis panel (`<fsl-stochastic>`, fsl#1384).
1093
1094
  * @slot export - Slot for the export menu (`<fsl-export>`, #667).
1094
1095
  * @slot footer - Footer slot.
1095
1096
  */
@@ -1967,6 +1968,7 @@ class FslInstance extends LitElement {
1967
1968
  ${docked ? '' : html `<section class="hook-log" ?hidden=${h('hook-log')}><slot name="hook-log"></slot></section>`}
1968
1969
  <section class="effective-properties" ?hidden=${h('effective-properties')}><slot name="effective-properties"></slot></section>
1969
1970
  <section class="simulation" ?hidden=${h('simulation')}><slot name="simulation"></slot></section>
1971
+ <section class="stochastic" ?hidden=${h('stochastic')}><slot name="stochastic"></slot></section>
1970
1972
  <section class="export" ?hidden=${h('export')}><slot name="export"></slot></section>
1971
1973
  `;
1972
1974
  }
@@ -1,4 +1,4 @@
1
- import { FslToolbar, FslActions, FslFooter, FslHelp, FslHistory, FslDataInspector, FslHookLog, FslSimulation, FslExport } from './widgets.js';
1
+ import { FslToolbar, FslActions, FslFooter, FslHelp, FslHistory, FslDataInspector, FslHookLog, FslSimulation, FslExport, FslStochastic } from './widgets.js';
2
2
  export * from './widgets.js';
3
3
 
4
4
  /**
@@ -50,7 +50,7 @@ function define_canonical(canonical_tag, CanonicalClass) {
50
50
 
51
51
  /**
52
52
  * Bundle entry: registers the entire light fsl-* widget suite (toolbar, actions,
53
- * footer, help, history, data-inspector, hook-log, simulation, export) in one import.
53
+ * footer, help, history, data-inspector, hook-log, simulation, export, stochastic) in one import.
54
54
  * Canonical `fsl-*` tags only — no deprecated `jssm-*` synonyms. Registration
55
55
  * is idempotent, so this composes safely with the per-widget `*.define` modules.
56
56
  */
@@ -63,3 +63,4 @@ define_canonical('fsl-data-inspector', FslDataInspector);
63
63
  define_canonical('fsl-hook-log', FslHookLog);
64
64
  define_canonical('fsl-simulation', FslSimulation);
65
65
  define_canonical('fsl-export', FslExport);
66
+ define_canonical('fsl-stochastic', FslStochastic);
@@ -1,6 +1,7 @@
1
1
  import { css, html, LitElement } from 'lit';
2
2
  import { state, property } from 'lit/decorators.js';
3
3
  import { machine_to_dot, machine_to_svg_string } from 'jssm/viz';
4
+ import { STOCHASTIC_DEFAULT_RUNS, STOCHASTIC_DEFAULT_MAX_STEPS, sm } from 'jssm';
4
5
 
5
6
  /**
6
7
  * Shared FSL appearance contract — the `--fsl-*` design-token vocabulary.
@@ -193,7 +194,7 @@ async function permalink_for(fsl, key = DEFAULT_PERMALINK_KEY, href = location.h
193
194
  return `${href.split('#')[0]}#${fragment}`;
194
195
  }
195
196
 
196
- var __decorate$7 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
197
+ var __decorate$8 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
197
198
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
198
199
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
199
200
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -496,23 +497,23 @@ FslToolbar.styles = css `
496
497
  .spacer { flex: 1; }
497
498
  ${fslTokens}
498
499
  `;
499
- __decorate$7([
500
+ __decorate$8([
500
501
  state()
501
502
  ], FslToolbar.prototype, "_openMenu", void 0);
502
- __decorate$7([
503
+ __decorate$8([
503
504
  state()
504
505
  ], FslToolbar.prototype, "_dest", void 0);
505
- __decorate$7([
506
+ __decorate$8([
506
507
  property({ attribute: false })
507
508
  ], FslToolbar.prototype, "lastDirectory", void 0);
508
- __decorate$7([
509
+ __decorate$8([
509
510
  property({ type: Boolean, attribute: 'no-validate' })
510
511
  ], FslToolbar.prototype, "noValidate", void 0);
511
- __decorate$7([
512
+ __decorate$8([
512
513
  property({ type: Boolean, attribute: 'no-lint' })
513
514
  ], FslToolbar.prototype, "noLint", void 0);
514
515
 
515
- var __decorate$6 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
516
+ var __decorate$7 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
516
517
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
517
518
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
518
519
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -662,20 +663,20 @@ FslActions.styles = css `
662
663
  .empty { color: var(--_fsl-muted); font-style: italic; }
663
664
  ${fslTokens}
664
665
  `;
665
- __decorate$6([
666
+ __decorate$7([
666
667
  state()
667
668
  ], FslActions.prototype, "_actions", void 0);
668
- __decorate$6([
669
+ __decorate$7([
669
670
  state()
670
671
  ], FslActions.prototype, "_main", void 0);
671
- __decorate$6([
672
+ __decorate$7([
672
673
  state()
673
674
  ], FslActions.prototype, "_regular", void 0);
674
- __decorate$6([
675
+ __decorate$7([
675
676
  state()
676
677
  ], FslActions.prototype, "_forced", void 0);
677
678
 
678
- var __decorate$5 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
679
+ var __decorate$6 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
679
680
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
680
681
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
681
682
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -791,32 +792,32 @@ FslFooter.styles = css `
791
792
  .spacer { flex: 1; }
792
793
  ${fslTokens}
793
794
  `;
794
- __decorate$5([
795
+ __decorate$6([
795
796
  state()
796
797
  ], FslFooter.prototype, "_state", void 0);
797
- __decorate$5([
798
+ __decorate$6([
798
799
  state()
799
800
  ], FslFooter.prototype, "_actions", void 0);
800
- __decorate$5([
801
+ __decorate$6([
801
802
  state()
802
803
  ], FslFooter.prototype, "_transitions", void 0);
803
- __decorate$5([
804
+ __decorate$6([
804
805
  state()
805
806
  ], FslFooter.prototype, "_terminal", void 0);
806
- __decorate$5([
807
+ __decorate$6([
807
808
  state()
808
809
  ], FslFooter.prototype, "_complete", void 0);
809
- __decorate$5([
810
+ __decorate$6([
810
811
  state()
811
812
  ], FslFooter.prototype, "_gActions", void 0);
812
- __decorate$5([
813
+ __decorate$6([
813
814
  state()
814
815
  ], FslFooter.prototype, "_gStarts", void 0);
815
- __decorate$5([
816
+ __decorate$6([
816
817
  state()
817
818
  ], FslFooter.prototype, "_gTransitions", void 0);
818
819
 
819
- var __decorate$4 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
820
+ var __decorate$5 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
820
821
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
821
822
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
822
823
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -885,14 +886,14 @@ FslHelp.styles = css `
885
886
  .body { overflow-y: auto; padding: 0.5rem 0.9rem; font-size: 0.86rem; line-height: 1.5; }
886
887
  ${fslTokens}
887
888
  `;
888
- __decorate$4([
889
+ __decorate$5([
889
890
  property({ type: Boolean, reflect: true })
890
891
  ], FslHelp.prototype, "open", void 0);
891
- __decorate$4([
892
+ __decorate$5([
892
893
  property({ type: String })
893
894
  ], FslHelp.prototype, "heading", void 0);
894
895
 
895
- var __decorate$3 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
896
+ var __decorate$4 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
896
897
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
897
898
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
898
899
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -965,11 +966,11 @@ FslHistory.styles = css `
965
966
  .empty { color: var(--_fsl-muted); font-style: italic; padding: 0.5rem 0.7rem; }
966
967
  ${fslTokens}
967
968
  `;
968
- __decorate$3([
969
+ __decorate$4([
969
970
  state()
970
971
  ], FslHistory.prototype, "_history", void 0);
971
972
 
972
- var __decorate$2 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
973
+ var __decorate$3 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
973
974
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
974
975
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
975
976
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -1089,11 +1090,11 @@ FslDataInspector.styles = css `
1089
1090
  .tok-bool, .tok-null { color: var(--fsl-color-json-atom, #c2185b); }
1090
1091
  ${fslTokens}
1091
1092
  `;
1092
- __decorate$2([
1093
+ __decorate$3([
1093
1094
  state()
1094
1095
  ], FslDataInspector.prototype, "_data", void 0);
1095
1096
 
1096
- var __decorate$1 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1097
+ var __decorate$2 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1097
1098
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1098
1099
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1099
1100
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -1163,11 +1164,11 @@ FslHookLog.styles = css `
1163
1164
  .empty { color: var(--_fsl-muted); font-style: italic; }
1164
1165
  ${fslTokens}
1165
1166
  `;
1166
- __decorate$1([
1167
+ __decorate$2([
1167
1168
  state()
1168
1169
  ], FslHookLog.prototype, "_log", void 0);
1169
1170
 
1170
- var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1171
+ var __decorate$1 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1171
1172
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1172
1173
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1173
1174
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -1261,13 +1262,13 @@ FslSimulation.styles = css `
1261
1262
  .count.idle { font-style: italic; }
1262
1263
  ${fslTokens}
1263
1264
  `;
1264
- __decorate([
1265
+ __decorate$1([
1265
1266
  property({ type: Number })
1266
1267
  ], FslSimulation.prototype, "interval", void 0);
1267
- __decorate([
1268
+ __decorate$1([
1268
1269
  state()
1269
1270
  ], FslSimulation.prototype, "_running", void 0);
1270
- __decorate([
1271
+ __decorate$1([
1271
1272
  state()
1272
1273
  ], FslSimulation.prototype, "_steps", void 0);
1273
1274
 
@@ -1334,4 +1335,300 @@ FslExport.styles = css `
1334
1335
  ${fslTokens}
1335
1336
  `;
1336
1337
 
1337
- export { FslActions, FslDataInspector, FslExport, FslFooter, FslHelp, FslHistory, FslHookLog, FslSimulation, FslToolbar };
1338
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1339
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1340
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1341
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1342
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1343
+ };
1344
+ /**
1345
+ * `<fsl-stochastic>` — a statistical/Monte-Carlo explorer for a parent
1346
+ * `<fsl-instance>` (fsl#1384). Builds its own throwaway machine from the
1347
+ * host's `.fsl` source (never touching the live machine) and renders
1348
+ * aggregate run statistics in-panel. Standalone (no host) the controls are
1349
+ * disabled.
1350
+ *
1351
+ * @element fsl-stochastic
1352
+ * @csspart controls - The control row.
1353
+ * @fires fsl-stochastic-complete - CustomEvent<JssmStochasticSummary> after a run.
1354
+ */
1355
+ class FslStochastic extends LitElement {
1356
+ constructor() {
1357
+ super(...arguments);
1358
+ /** Run-count (montecarlo). Defaults from editor_config().stochastic_run_count. */
1359
+ this.runs = STOCHASTIC_DEFAULT_RUNS;
1360
+ /** Per-run step cap (montecarlo) / walk length (steady_state). */
1361
+ this.maxSteps = STOCHASTIC_DEFAULT_MAX_STEPS;
1362
+ /** Seed string ('' = time-based). Kept as string so the field can be blank. */
1363
+ this.seed = '';
1364
+ /** Run mode. */
1365
+ this.mode = 'montecarlo';
1366
+ this._summary = null;
1367
+ this._error = null;
1368
+ this._host = null;
1369
+ this._playing = false;
1370
+ /** Execute a batch synchronously and render the aggregates. */
1371
+ this.run = () => {
1372
+ if (this._host === null) {
1373
+ return;
1374
+ }
1375
+ let machine;
1376
+ try {
1377
+ machine = sm `${this._host.fsl}`;
1378
+ }
1379
+ catch (_a) {
1380
+ this._error = 'Cannot run on invalid FSL source.';
1381
+ this._summary = null;
1382
+ return;
1383
+ }
1384
+ this._error = null;
1385
+ const seedNum = this.seed.trim() === '' ? undefined : Number(this.seed);
1386
+ this._summary = machine.stochastic_summary({
1387
+ mode: this.mode, runs: this.runs, max_steps: this.maxSteps, seed: seedNum,
1388
+ });
1389
+ this.dispatchEvent(new CustomEvent('fsl-stochastic-complete', {
1390
+ detail: this._summary, bubbles: true, composed: true,
1391
+ }));
1392
+ };
1393
+ /**
1394
+ * Animate the batch: accumulate runs incrementally via `requestAnimationFrame`,
1395
+ * redrawing as they land. Resolves when the batch completes or is paused-to-stop.
1396
+ *
1397
+ * Falls back to immediate (synchronous chunk) scheduling under jsdom where
1398
+ * `requestAnimationFrame` is undefined.
1399
+ *
1400
+ * @example
1401
+ * panel.runs = 100;
1402
+ * await panel.play(); // resolves when all 100 runs are done
1403
+ */
1404
+ this.play = () => new Promise((resolve) => {
1405
+ if (this._host === null) {
1406
+ resolve();
1407
+ return;
1408
+ }
1409
+ let machine;
1410
+ try {
1411
+ machine = sm `${this._host.fsl}`;
1412
+ }
1413
+ catch (_a) {
1414
+ this._error = 'Cannot run on invalid FSL source.';
1415
+ this._summary = null;
1416
+ resolve();
1417
+ return;
1418
+ }
1419
+ this._error = null;
1420
+ if (this.seed.trim() !== '') {
1421
+ machine.rng_seed = Number(this.seed);
1422
+ }
1423
+ const effective_seed = machine.rng_seed;
1424
+ const state_visits = new Map();
1425
+ const edges = new Map();
1426
+ const path_lengths = [];
1427
+ let reached = 0;
1428
+ let capped = 0;
1429
+ let runs = 0;
1430
+ const iter = machine.stochastic_runs({ mode: this.mode, runs: this.runs, max_steps: this.maxSteps });
1431
+ this._playing = true;
1432
+ const CHUNK = 50;
1433
+ const schedule = (fn) => {
1434
+ if (typeof requestAnimationFrame === 'function') {
1435
+ requestAnimationFrame(fn);
1436
+ }
1437
+ else {
1438
+ fn();
1439
+ }
1440
+ };
1441
+ const tick = () => {
1442
+ var _a, _b;
1443
+ if (!this._playing) {
1444
+ resolve();
1445
+ return;
1446
+ }
1447
+ for (let i = 0; i < CHUNK; i++) {
1448
+ const next = iter.next();
1449
+ if (next.done) {
1450
+ this._commit(state_visits, edges, path_lengths, reached, capped, runs, effective_seed);
1451
+ this._playing = false;
1452
+ resolve();
1453
+ return;
1454
+ }
1455
+ const r = next.value;
1456
+ runs += 1;
1457
+ for (const s of r.states) {
1458
+ state_visits.set(s, ((_a = state_visits.get(s)) !== null && _a !== void 0 ? _a : 0) + 1);
1459
+ }
1460
+ for (const e of r.edges) {
1461
+ edges.set(e, ((_b = edges.get(e)) !== null && _b !== void 0 ? _b : 0) + 1);
1462
+ }
1463
+ if (this.mode === 'montecarlo') {
1464
+ if (r.terminated) {
1465
+ reached += 1;
1466
+ path_lengths.push(r.length);
1467
+ }
1468
+ else {
1469
+ capped += 1;
1470
+ }
1471
+ }
1472
+ }
1473
+ this._commit(state_visits, edges, path_lengths, reached, capped, runs, effective_seed);
1474
+ schedule(tick);
1475
+ };
1476
+ schedule(tick);
1477
+ });
1478
+ /** Toggle between playing and paused. Starts a new {@link play} batch when idle. */
1479
+ this._togglePlay = () => { if (this._playing) {
1480
+ this._playing = false;
1481
+ }
1482
+ else {
1483
+ void this.play();
1484
+ } };
1485
+ this._onMode = (e) => { this.mode = e.target.value; };
1486
+ this._onRuns = (e) => { this.runs = Number(e.target.value); };
1487
+ this._onMax = (e) => { this.maxSteps = Number(e.target.value); };
1488
+ this._onSeed = (e) => { this.seed = e.target.value; };
1489
+ }
1490
+ connectedCallback() {
1491
+ super.connectedCallback();
1492
+ this._host = closest_wc(this, 'instance');
1493
+ }
1494
+ disconnectedCallback() {
1495
+ super.disconnectedCallback();
1496
+ this._host = null;
1497
+ }
1498
+ /**
1499
+ * Read `editor_config().stochastic_run_count` after the first render so the
1500
+ * host's machine is guaranteed to be built (machine construction happens in
1501
+ * the host's connectedCallback, which may fire after the panel's own
1502
+ * connectedCallback in some environments).
1503
+ */
1504
+ firstUpdated(_changedProperties) {
1505
+ var _a, _b, _c, _d;
1506
+ super.firstUpdated(_changedProperties);
1507
+ try {
1508
+ const declared = (_d = (_c = (_b = (_a = this._host) === null || _a === void 0 ? void 0 : _a.machine) === null || _b === void 0 ? void 0 : _b.editor_config) === null || _c === void 0 ? void 0 : _c.call(_b)) === null || _d === void 0 ? void 0 : _d.stochastic_run_count;
1509
+ if (typeof declared === 'number') {
1510
+ this.runs = declared;
1511
+ }
1512
+ }
1513
+ catch ( /* machine not ready — leave default */_e) { /* machine not ready — leave default */ }
1514
+ }
1515
+ /**
1516
+ * Fold accumulated counters into a rendered summary. Shared by {@link play}
1517
+ * for incremental rendering during animation.
1518
+ *
1519
+ * @param state_visits - Accumulated visit counts per state name.
1520
+ * @param edge_traversals - Accumulated traversal counts per edge key.
1521
+ * @param path_lengths - Lengths of completed (terminated) paths.
1522
+ * @param terminal_reached - Count of runs that reached a terminal state.
1523
+ * @param capped - Count of runs that hit the step cap.
1524
+ * @param runs - Total runs processed so far.
1525
+ * @param seed - The effective RNG seed used for this batch.
1526
+ */
1527
+ _commit(state_visits, edge_traversals, path_lengths, terminal_reached, capped, runs, seed) {
1528
+ const total = [...state_visits.values()].reduce((a, b) => a + b, 0);
1529
+ const frac = new Map();
1530
+ for (const [s, c] of state_visits) {
1531
+ frac.set(s, c / total);
1532
+ }
1533
+ const summary = {
1534
+ mode: this.mode, runs, seed,
1535
+ state_visits, state_visit_fraction: frac, edge_traversals,
1536
+ };
1537
+ if (this.mode === 'montecarlo') {
1538
+ summary.path_lengths = path_lengths;
1539
+ summary.terminal_reached = terminal_reached;
1540
+ summary.capped = capped;
1541
+ }
1542
+ this._summary = summary;
1543
+ }
1544
+ _bars() {
1545
+ const frac = this._summary.state_visit_fraction;
1546
+ const rows = [...frac.entries()].sort((a, b) => b[1] - a[1]);
1547
+ return html `${rows.map(([name, f]) => html `
1548
+ <div class="bar-row">
1549
+ <span>${name}</span>
1550
+ <span class="track"><span class="bar" style="width:${(f * 100).toFixed(1)}%"></span></span>
1551
+ <span>${(f * 100).toFixed(1)}%</span>
1552
+ </div>`)}`;
1553
+ }
1554
+ _panes() {
1555
+ const s = this._summary;
1556
+ const mc = s.mode === 'montecarlo';
1557
+ const reached = mc && s.runs > 0 ? Math.round((s.terminal_reached / s.runs) * 100) : 0;
1558
+ const capped = mc && s.runs > 0 ? Math.round((s.capped / s.runs) * 100) : 0;
1559
+ return html `
1560
+ <div class="panes">
1561
+ <div><strong>State visits</strong></div>
1562
+ ${this._bars()}
1563
+ ${mc ? html `<div>Reached terminal: ${reached}% · Hit cap: ${capped}%</div>` : html `<div class="muted">steady-state distribution</div>`}
1564
+ </div>`;
1565
+ }
1566
+ render() {
1567
+ const disabled = this._host === null;
1568
+ return html `
1569
+ <div class="controls" part="controls">
1570
+ <select @change=${this._onMode} .value=${this.mode}>
1571
+ <option value="montecarlo">Monte Carlo</option>
1572
+ <option value="steady_state">Steady-state</option>
1573
+ </select>
1574
+ ${this.mode === 'montecarlo'
1575
+ ? html `<label>runs <input type="number" .value=${String(this.runs)} @change=${this._onRuns}></label>`
1576
+ : ''}
1577
+ <label>max steps <input type="number" .value=${String(this.maxSteps)} @change=${this._onMax}></label>
1578
+ <label>seed <input type="text" .value=${this.seed} @change=${this._onSeed}></label>
1579
+ <button class="run" ?disabled=${disabled} @click=${this.run}>Run</button>
1580
+ <button class="btn" ?disabled=${disabled} @click=${this._togglePlay}>${this._playing ? 'Pause' : 'Play'}</button>
1581
+ </div>
1582
+ ${this._error ? html `<div class="error">${this._error}</div>` : ''}
1583
+ ${this._summary && !this._error ? this._panes() : html `<div class="panes muted">No run yet.</div>`}`;
1584
+ }
1585
+ }
1586
+ FslStochastic.styles = css `
1587
+ :host { display: block; font: 0.8rem var(--_fsl-font); color: var(--_fsl-text); }
1588
+ .controls {
1589
+ display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
1590
+ padding: 0.4rem 0.6rem; background: var(--_fsl-surface);
1591
+ border-top: 1px solid var(--_fsl-border);
1592
+ }
1593
+ .btn, .run {
1594
+ height: 1.8rem; padding: 0 0.7rem; cursor: pointer; border-radius: 4px;
1595
+ border: 1px solid var(--_fsl-border); background: var(--_fsl-surface);
1596
+ color: var(--_fsl-text); font: 600 0.8rem var(--_fsl-font);
1597
+ }
1598
+ .btn:disabled, .run:disabled { opacity: 0.5; cursor: not-allowed; }
1599
+ input { width: 4.5rem; height: 1.6rem; background: var(--_fsl-surface);
1600
+ color: var(--_fsl-text); border: 1px solid var(--_fsl-border); border-radius: 4px; }
1601
+ .panes { padding: 0.4rem 0.6rem; display: grid; gap: 0.5rem; }
1602
+ .bar-row { display: grid; grid-template-columns: 4rem 1fr 3rem; align-items: center; gap: 0.4rem; }
1603
+ .track { background: color-mix(in srgb, var(--_fsl-text) 8%, var(--_fsl-surface)); border-radius: 3px; height: 0.9rem; }
1604
+ .bar { background: var(--_fsl-accent); height: 0.9rem; border-radius: 3px; }
1605
+ .muted { color: var(--_fsl-muted); }
1606
+ .error { color: var(--_fsl-danger, #c0392b); padding: 0.4rem 0.6rem; }
1607
+ ${fslTokens}
1608
+ `;
1609
+ __decorate([
1610
+ property({ type: Number })
1611
+ ], FslStochastic.prototype, "runs", void 0);
1612
+ __decorate([
1613
+ property({ type: Number })
1614
+ ], FslStochastic.prototype, "maxSteps", void 0);
1615
+ __decorate([
1616
+ property({ type: String })
1617
+ ], FslStochastic.prototype, "seed", void 0);
1618
+ __decorate([
1619
+ property({ type: String })
1620
+ ], FslStochastic.prototype, "mode", void 0);
1621
+ __decorate([
1622
+ state()
1623
+ ], FslStochastic.prototype, "_summary", void 0);
1624
+ __decorate([
1625
+ state()
1626
+ ], FslStochastic.prototype, "_error", void 0);
1627
+ __decorate([
1628
+ state()
1629
+ ], FslStochastic.prototype, "_host", void 0);
1630
+ __decorate([
1631
+ state()
1632
+ ], FslStochastic.prototype, "_playing", void 0);
1633
+
1634
+ export { FslActions, FslDataInspector, FslExport, FslFooter, FslHelp, FslHistory, FslHookLog, FslSimulation, FslStochastic, FslToolbar };
package/jssm.es5.d.cts CHANGED
@@ -524,6 +524,34 @@ declare type JssmEditorConfig = {
524
524
  stochastic_run_count?: number;
525
525
  panels?: Array<string>;
526
526
  };
527
+ /** Which stochastic view a run batch produces. */
528
+ declare type JssmStochasticMode = 'montecarlo' | 'steady_state';
529
+ /** Options for {@link Machine.stochastic_summary} / {@link Machine.stochastic_runs}. */
530
+ declare type JssmStochasticOptions = {
531
+ mode?: JssmStochasticMode;
532
+ runs?: number;
533
+ max_steps?: number;
534
+ seed?: number;
535
+ };
536
+ /** One walk's result, yielded by {@link Machine.stochastic_runs}. */
537
+ declare type JssmStochasticRun = {
538
+ states: Array<string>;
539
+ edges: Array<string>;
540
+ length: number;
541
+ terminated: boolean;
542
+ };
543
+ /** Aggregate statistics over a stochastic run batch. */
544
+ declare type JssmStochasticSummary = {
545
+ mode: JssmStochasticMode;
546
+ runs: number;
547
+ seed: number;
548
+ state_visits: Map<string, number>;
549
+ state_visit_fraction: Map<string, number>;
550
+ edge_traversals: Map<string, number>;
551
+ path_lengths?: Array<number>;
552
+ terminal_reached?: number;
553
+ capped?: number;
554
+ };
527
555
  declare type JssmGenericConfig<StateType, DataType> = {
528
556
  graph_layout?: JssmLayout;
529
557
  complete?: Array<StateType>;
@@ -1991,6 +2019,10 @@ declare function transfer_state_properties(state_decl: JssmStateDeclaration): Js
1991
2019
  *
1992
2020
  */
1993
2021
  declare function state_style_condense(jssk: JssmStateStyleKeyList, machine?: any): JssmStateConfig;
2022
+ /** Default number of independent Monte-Carlo runs when none is declared. */
2023
+ declare const STOCHASTIC_DEFAULT_RUNS = 1000;
2024
+ /** Default per-run step cap (montecarlo) / walk length (steady_state). */
2025
+ declare const STOCHASTIC_DEFAULT_MAX_STEPS = 1000;
1994
2026
  declare class Machine<mDT> {
1995
2027
  _state: StateType;
1996
2028
  _states: Map<StateType, JssmGenericState>;
@@ -2919,6 +2951,65 @@ declare class Machine<mDT> {
2919
2951
  * @returns A `Map` from state name to visit count.
2920
2952
  */
2921
2953
  probabilistic_histo_walk(n: number): Map<StateType, number>;
2954
+ /** One non-destructive weighted-random walk over the graph from `start`.
2955
+ *
2956
+ * Reads the graph and advances the PRNG only — it never calls
2957
+ * {@link Machine.transition}, so it fires no hooks, mutates no machine
2958
+ * state, and touches no `data`. A state with no probabilistic exits
2959
+ * (a terminal, or a forced-only `~>` state) ends the walk.
2960
+ *
2961
+ * @param start - State to begin the walk from.
2962
+ * @param max_steps - Maximum transitions before the walk is step-capped.
2963
+ * @returns The {@link JssmStochasticRun} for this walk.
2964
+ */
2965
+ private _stochastic_one_walk;
2966
+ /** Lazily yield one {@link JssmStochasticRun} at a time.
2967
+ *
2968
+ * In `montecarlo` mode (default) yields `runs` independent walks from the
2969
+ * current state, each ending at a terminal or after `max_steps`. In
2970
+ * `steady_state` mode yields exactly one walk of `max_steps` steps. This
2971
+ * is the lazy engine behind {@link Machine.stochastic_summary}; the
2972
+ * fsl-stochastic panel drives it across animation frames.
2973
+ *
2974
+ * Passing `seed` reseeds the machine for reproducible runs. Unlike
2975
+ * {@link Machine.stochastic_summary}, the generator does NOT restore the
2976
+ * prior seed afterward — a direct caller's machine is left reseeded.
2977
+ *
2978
+ * @param opts - {@link JssmStochasticOptions}.
2979
+ * @returns A generator of per-run results.
2980
+ *
2981
+ * @example
2982
+ * const m = sm`a 'go' -> b 'go' -> c;`;
2983
+ * [...m.stochastic_runs({ runs: 2, seed: 1 })].length; // => 2
2984
+ */
2985
+ stochastic_runs(opts?: JssmStochasticOptions): Generator<JssmStochasticRun>;
2986
+ /** Run many weighted-random walks and return aggregate statistics.
2987
+ *
2988
+ * Honors `%` transition probabilities (via the existing probabilistic
2989
+ * machinery). Non-destructive: the machine's current state and
2990
+ * {@link Machine.rng_seed} are restored before returning, so calling this
2991
+ * never perturbs the live machine. `montecarlo` mode (default) reports
2992
+ * per-run `path_lengths`, `terminal_reached`, and `capped`; `steady_state`
2993
+ * mode runs one long walk and omits those fields.
2994
+ *
2995
+ * Timing (`after`) decorations and data-guard conditions are not modeled
2996
+ * by this sampler; it walks the probabilistic graph topology.
2997
+ *
2998
+ * @param opts - {@link JssmStochasticOptions}. `runs` defaults to the
2999
+ * machine's declared `editor: { stochastic_run_count }` (fsl#1334) when
3000
+ * present, otherwise {@link STOCHASTIC_DEFAULT_RUNS}.
3001
+ * @returns A {@link JssmStochasticSummary}.
3002
+ *
3003
+ * @see Machine.stochastic_runs
3004
+ * @see Machine.probabilistic_walk
3005
+ * @see Machine.editor_config
3006
+ *
3007
+ * @example
3008
+ * const m = sm`a 'go' -> b 'go' -> c;`;
3009
+ * const s = m.stochastic_summary({ runs: 100, seed: 1 });
3010
+ * s.terminal_reached; // => 100
3011
+ */
3012
+ stochastic_summary(opts?: JssmStochasticOptions): JssmStochasticSummary;
2922
3013
  /********
2923
3014
  *
2924
3015
  * List all actions available from this state. Please note that the order of
@@ -4691,4 +4782,4 @@ declare function compareVersions(v1: string, v2: string): number;
4691
4782
  */
4692
4783
  declare function deserialize<mDT>(machine_string: string, ser: JssmSerialization<mDT>): Machine<mDT>;
4693
4784
 
4694
- export { FslDirections, Machine, abstract_everything_hook_step, abstract_hook_step, action_label_chars, arrow_direction, arrow_left_kind, arrow_right_kind, build_time, compareVersions, compile, jssm_constants_d as constants, deserialize, find_repeated, from, fslCompletions, fslDiagnostics, fslSemanticSpans, gen_splitmix32, gviz_shapes, histograph, is_hook_complex_result, is_hook_rejection, make, named_colors, wrap_parse as parse, seq, shapes, sleep, sm, state_name_chars, state_name_first_chars, state_style_condense, transfer_state_properties, unique, version, weighted_histo_key, weighted_rand_select, weighted_sample_select };
4785
+ export { FslDirections, Machine, STOCHASTIC_DEFAULT_MAX_STEPS, STOCHASTIC_DEFAULT_RUNS, abstract_everything_hook_step, abstract_hook_step, action_label_chars, arrow_direction, arrow_left_kind, arrow_right_kind, build_time, compareVersions, compile, jssm_constants_d as constants, deserialize, find_repeated, from, fslCompletions, fslDiagnostics, fslSemanticSpans, gen_splitmix32, gviz_shapes, histograph, is_hook_complex_result, is_hook_rejection, make, named_colors, wrap_parse as parse, seq, shapes, sleep, sm, state_name_chars, state_name_first_chars, state_style_condense, transfer_state_properties, unique, version, weighted_histo_key, weighted_rand_select, weighted_sample_select };