jssm 5.153.0 → 5.155.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.
package/README.md CHANGED
@@ -18,10 +18,10 @@ Please edit the file it's derived from, instead: `./src/md/readme_base.md`
18
18
 
19
19
 
20
20
 
21
- * Generated for version 5.153.0 at 6/28/2026, 6:29:10 PM
21
+ * Generated for version 5.155.0 at 6/28/2026, 7:41:44 PM
22
22
 
23
23
  -->
24
- # jssm 5.153.0
24
+ # jssm 5.155.0
25
25
 
26
26
  [**Try the live editor**](https://stonecypher.github.io/jssm-viz-demo/graph_explorer.html) ·
27
27
  [Documentation](https://stonecypher.github.io/jssm/docs/) ·
@@ -312,7 +312,7 @@ That decision shows up everywhere downstream:
312
312
  or run `npm run benny` against your own machine.
313
313
 
314
314
  - **More thoroughly tested than any other JavaScript state-machine
315
- library.** 7,769 tests at 100.0% line coverage
315
+ library.** 7,811 tests at 100.0% line coverage
316
316
  ([report](https://coveralls.io/github/StoneCypher/jssm)), plus
317
317
  fuzz testing via `fast-check`, with parser test data across ten natural
318
318
  languages and Emoji.
@@ -445,11 +445,11 @@ If your contribution is missing here, please open an issue.
445
445
 
446
446
  <br/>
447
447
 
448
- ***7,769 tests***, run 82,415 times.
448
+ ***7,811 tests***, run 82,853 times.
449
449
 
450
- - 7,015 specs with 100.0% coverage
451
- - 754 fuzz tests with 55.5% coverage
452
- - 9,374 TypeScript lines - 0.8 tests per line, 8.8 generated tests per line
450
+ - 7,053 specs with 100.0% coverage
451
+ - 758 fuzz tests with 54.8% coverage
452
+ - 9,666 TypeScript lines - 0.8 tests per line, 8.6 generated tests per line
453
453
 
454
454
  [![Actions Status](https://github.com/StoneCypher/jssm/workflows/Node%20CI/badge.svg)](https://github.com/StoneCypher/jssm/actions)
455
455
  [![NPM version](https://img.shields.io/npm/v/jssm.svg)](https://www.npmjs.com/package/jssm)
@@ -444,7 +444,7 @@
444
444
  "kind": "variable",
445
445
  "name": "FSL_COLOR_KEYS",
446
446
  "default": "new Set([ 'color', 'text-color', 'background-color', 'border-color', 'edge-color', ])",
447
- "description": "Attribute keys whose value is a color, mirroring the editor's `COLOR_KEYS`\n(language_service). The value token following one of these (plus its `:`) is\ntagged `color` and rendered with a swatch, matching the editor overlay."
447
+ "description": "Attribute keys whose value is a color, mirroring the editor's `COLOR_KEYS`\r\n(language_service). The value token following one of these (plus its `:`) is\r\ntagged `color` and rendered with a swatch, matching the editor overlay."
448
448
  },
449
449
  {
450
450
  "kind": "function",
@@ -462,7 +462,7 @@
462
462
  }
463
463
  }
464
464
  ],
465
- "description": "Tokenize FSL source into `{cls, text}` runs for syntax highlighting. A pure,\nregex-driven scanner — never parses, so it cannot throw on malformed input.\n`cls` is null for uncategorized text (punctuation, identifiers, whitespace).\n\nBeyond the lexical classes it tracks one bit of structural context: an\nidentifier immediately before a `:` is retro-tagged `key` (an attribute key,\nunless it is already a `keyword`), and the value token after a color key's\ncolon — or any hex literal — is tagged `color`. The context never spans a\n`;`, so a value can't leak past its statement."
465
+ "description": "Tokenize FSL source into `{cls, text}` runs for syntax highlighting. A pure,\r\nregex-driven scanner — never parses, so it cannot throw on malformed input.\r\n`cls` is null for uncategorized text (punctuation, identifiers, whitespace).\r\n\r\nBeyond the lexical classes it tracks one bit of structural context: an\r\nidentifier immediately before a `:` is retro-tagged `key` (an attribute key,\r\nunless it is already a `keyword`), and the value token after a color key's\r\ncolon — or any hex literal — is tagged `color`. The context never spans a\r\n`;`, so a value can't leak past its statement."
466
466
  },
467
467
  {
468
468
  "kind": "function",
@@ -480,7 +480,7 @@
480
480
  }
481
481
  }
482
482
  ],
483
- "description": "Highlight FSL source to an HTML string of `<span class=\"fsl-tok-…\">` runs.\nA `color` token is preceded by an inline `<span class=\"fsl-swatch\">` whose\nbackground is the literal color text (a CSS-valid named color or hex), giving\nthe docs the same swatch the editor overlay shows. Color text is a hex or\nidentifier run, so it is a safe `background:` value."
483
+ "description": "Highlight FSL source to an HTML string of `<span class=\"fsl-tok-…\">` runs.\r\nA `color` token is preceded by an inline `<span class=\"fsl-swatch\">` whose\r\nbackground is the literal color text (a CSS-valid named color or hex), giving\r\nthe docs the same swatch the editor overlay shows. Color text is a hex or\r\nidentifier run, so it is a safe `background:` value."
484
484
  },
485
485
  {
486
486
  "kind": "function",
@@ -558,7 +558,7 @@
558
558
  "declarations": [
559
559
  {
560
560
  "kind": "class",
561
- "description": "`<fsl-docs>` — the language-docs content engine: drill-in nav over the bundled\ncurriculum (Getting Started / About State Machines / Tutorials / Example\nMachines / Index / Search), a markdown page renderer, and \"load into editor\"\nfor tagged FSL examples. Content-only; slot it into `<fsl-help>`.",
561
+ "description": "`<fsl-docs>` — the language-docs content engine: drill-in nav over the bundled\r\ncurriculum (Getting Started / About State Machines / Tutorials / Example\r\nMachines / Index / Search), a markdown page renderer, and \"load into editor\"\r\nfor tagged FSL examples. Content-only; slot it into `<fsl-help>`.",
562
562
  "name": "FslDocs",
563
563
  "members": [
564
564
  {
@@ -2138,6 +2138,10 @@
2138
2138
  "description": "Slot for the random-walk simulation (`<fsl-simulation>`, #668).",
2139
2139
  "name": "simulation"
2140
2140
  },
2141
+ {
2142
+ "description": "Slot for the stochastic analysis panel (`<fsl-stochastic>`, fsl#1384).",
2143
+ "name": "stochastic"
2144
+ },
2141
2145
  {
2142
2146
  "description": "Slot for the export menu (`<fsl-export>`, #667).",
2143
2147
  "name": "export"
@@ -3653,6 +3657,280 @@
3653
3657
  }
3654
3658
  ]
3655
3659
  },
3660
+ {
3661
+ "kind": "javascript-module",
3662
+ "path": "src/ts/wc/fsl_stochastic_wc.ts",
3663
+ "declarations": [
3664
+ {
3665
+ "kind": "class",
3666
+ "description": "`<fsl-stochastic>` — a statistical/Monte-Carlo explorer for a parent\r\n`<fsl-instance>` (fsl#1384). Builds its own throwaway machine from the\r\nhost's `.fsl` source (never touching the live machine) and renders\r\naggregate run statistics in-panel. Standalone (no host) the controls are\r\ndisabled.",
3667
+ "name": "FslStochastic",
3668
+ "cssParts": [
3669
+ {
3670
+ "description": "The control row.",
3671
+ "name": "controls"
3672
+ }
3673
+ ],
3674
+ "members": [
3675
+ {
3676
+ "kind": "field",
3677
+ "name": "runs",
3678
+ "default": "STOCHASTIC_DEFAULT_RUNS",
3679
+ "description": "Run-count (montecarlo). Defaults from editor_config().stochastic_run_count.",
3680
+ "attribute": "runs"
3681
+ },
3682
+ {
3683
+ "kind": "field",
3684
+ "name": "maxSteps",
3685
+ "default": "STOCHASTIC_DEFAULT_MAX_STEPS",
3686
+ "description": "Per-run step cap (montecarlo) / walk length (steady_state).",
3687
+ "attribute": "maxSteps"
3688
+ },
3689
+ {
3690
+ "kind": "field",
3691
+ "name": "seed",
3692
+ "type": {
3693
+ "text": "string"
3694
+ },
3695
+ "default": "''",
3696
+ "description": "Seed string ('' = time-based). Kept as string so the field can be blank.",
3697
+ "attribute": "seed"
3698
+ },
3699
+ {
3700
+ "kind": "field",
3701
+ "name": "mode",
3702
+ "type": {
3703
+ "text": "JssmStochasticMode"
3704
+ },
3705
+ "default": "'montecarlo'",
3706
+ "description": "Run mode.",
3707
+ "attribute": "mode"
3708
+ },
3709
+ {
3710
+ "kind": "field",
3711
+ "name": "_summary",
3712
+ "type": {
3713
+ "text": "JssmStochasticSummary | null"
3714
+ },
3715
+ "privacy": "private",
3716
+ "default": "null"
3717
+ },
3718
+ {
3719
+ "kind": "field",
3720
+ "name": "_error",
3721
+ "type": {
3722
+ "text": "string | null"
3723
+ },
3724
+ "privacy": "private",
3725
+ "default": "null"
3726
+ },
3727
+ {
3728
+ "kind": "field",
3729
+ "name": "_host",
3730
+ "type": {
3731
+ "text": "StochHost | null"
3732
+ },
3733
+ "privacy": "private",
3734
+ "default": "null"
3735
+ },
3736
+ {
3737
+ "kind": "field",
3738
+ "name": "_playing",
3739
+ "type": {
3740
+ "text": "boolean"
3741
+ },
3742
+ "privacy": "private",
3743
+ "default": "false"
3744
+ },
3745
+ {
3746
+ "kind": "field",
3747
+ "name": "run",
3748
+ "description": "Execute a batch synchronously and render the aggregates."
3749
+ },
3750
+ {
3751
+ "kind": "field",
3752
+ "name": "play",
3753
+ "description": "Animate the batch: accumulate runs incrementally via `requestAnimationFrame`,\r\nredrawing as they land. Resolves when the batch completes or is paused-to-stop.\r\n\r\nFalls back to immediate (synchronous chunk) scheduling under jsdom where\r\n`requestAnimationFrame` is undefined."
3754
+ },
3755
+ {
3756
+ "kind": "method",
3757
+ "name": "_commit",
3758
+ "privacy": "private",
3759
+ "return": {
3760
+ "type": {
3761
+ "text": "void"
3762
+ }
3763
+ },
3764
+ "parameters": [
3765
+ {
3766
+ "name": "state_visits",
3767
+ "type": {
3768
+ "text": "Map<string, number>"
3769
+ },
3770
+ "description": "Accumulated visit counts per state name."
3771
+ },
3772
+ {
3773
+ "name": "edge_traversals",
3774
+ "type": {
3775
+ "text": "Map<string, number>"
3776
+ },
3777
+ "description": "Accumulated traversal counts per edge key."
3778
+ },
3779
+ {
3780
+ "name": "path_lengths",
3781
+ "type": {
3782
+ "text": "number[]"
3783
+ },
3784
+ "description": "Lengths of completed (terminated) paths."
3785
+ },
3786
+ {
3787
+ "name": "terminal_reached",
3788
+ "type": {
3789
+ "text": "number"
3790
+ },
3791
+ "description": "Count of runs that reached a terminal state."
3792
+ },
3793
+ {
3794
+ "name": "capped",
3795
+ "type": {
3796
+ "text": "number"
3797
+ },
3798
+ "description": "Count of runs that hit the step cap."
3799
+ },
3800
+ {
3801
+ "name": "runs",
3802
+ "type": {
3803
+ "text": "number"
3804
+ },
3805
+ "description": "Total runs processed so far."
3806
+ },
3807
+ {
3808
+ "name": "seed",
3809
+ "type": {
3810
+ "text": "number"
3811
+ },
3812
+ "description": "The effective RNG seed used for this batch."
3813
+ }
3814
+ ],
3815
+ "description": "Fold accumulated counters into a rendered summary. Shared by play\r\nfor incremental rendering during animation."
3816
+ },
3817
+ {
3818
+ "kind": "field",
3819
+ "name": "_togglePlay",
3820
+ "privacy": "private",
3821
+ "description": "Toggle between playing and paused. Starts a new play batch when idle."
3822
+ },
3823
+ {
3824
+ "kind": "field",
3825
+ "name": "_onMode",
3826
+ "privacy": "private"
3827
+ },
3828
+ {
3829
+ "kind": "field",
3830
+ "name": "_onRuns",
3831
+ "privacy": "private"
3832
+ },
3833
+ {
3834
+ "kind": "field",
3835
+ "name": "_onMax",
3836
+ "privacy": "private"
3837
+ },
3838
+ {
3839
+ "kind": "field",
3840
+ "name": "_onSeed",
3841
+ "privacy": "private"
3842
+ },
3843
+ {
3844
+ "kind": "method",
3845
+ "name": "_bars",
3846
+ "privacy": "private",
3847
+ "return": {
3848
+ "type": {
3849
+ "text": "TemplateResult"
3850
+ }
3851
+ }
3852
+ },
3853
+ {
3854
+ "kind": "method",
3855
+ "name": "_panes",
3856
+ "privacy": "private",
3857
+ "return": {
3858
+ "type": {
3859
+ "text": "TemplateResult"
3860
+ }
3861
+ }
3862
+ }
3863
+ ],
3864
+ "events": [
3865
+ {
3866
+ "description": "CustomEvent<JssmStochasticSummary> after a run.",
3867
+ "name": "fsl-stochastic-complete"
3868
+ }
3869
+ ],
3870
+ "attributes": [
3871
+ {
3872
+ "name": "runs",
3873
+ "default": "STOCHASTIC_DEFAULT_RUNS",
3874
+ "description": "Run-count (montecarlo). Defaults from editor_config().stochastic_run_count.",
3875
+ "fieldName": "runs"
3876
+ },
3877
+ {
3878
+ "name": "maxSteps",
3879
+ "default": "STOCHASTIC_DEFAULT_MAX_STEPS",
3880
+ "description": "Per-run step cap (montecarlo) / walk length (steady_state).",
3881
+ "fieldName": "maxSteps"
3882
+ },
3883
+ {
3884
+ "name": "seed",
3885
+ "type": {
3886
+ "text": "string"
3887
+ },
3888
+ "default": "''",
3889
+ "description": "Seed string ('' = time-based). Kept as string so the field can be blank.",
3890
+ "fieldName": "seed"
3891
+ },
3892
+ {
3893
+ "name": "mode",
3894
+ "type": {
3895
+ "text": "JssmStochasticMode"
3896
+ },
3897
+ "default": "'montecarlo'",
3898
+ "description": "Run mode.",
3899
+ "fieldName": "mode"
3900
+ }
3901
+ ],
3902
+ "superclass": {
3903
+ "name": "LitElement",
3904
+ "package": "lit"
3905
+ },
3906
+ "tagName": "fsl-stochastic",
3907
+ "customElement": true
3908
+ },
3909
+ {
3910
+ "kind": "variable",
3911
+ "name": "state_visits",
3912
+ "default": "new Map<string, number>()"
3913
+ },
3914
+ {
3915
+ "kind": "variable",
3916
+ "name": "runs",
3917
+ "type": {
3918
+ "text": "number"
3919
+ },
3920
+ "default": "0"
3921
+ }
3922
+ ],
3923
+ "exports": [
3924
+ {
3925
+ "kind": "js",
3926
+ "name": "FslStochastic",
3927
+ "declaration": {
3928
+ "name": "FslStochastic",
3929
+ "module": "src/ts/wc/fsl_stochastic_wc.ts"
3930
+ }
3931
+ }
3932
+ ]
3933
+ },
3656
3934
  {
3657
3935
  "kind": "javascript-module",
3658
3936
  "path": "src/ts/wc/fsl_themes.ts",
@@ -4603,6 +4881,14 @@
4603
4881
  "name": "FslExport",
4604
4882
  "module": "./fsl_export_wc.js"
4605
4883
  }
4884
+ },
4885
+ {
4886
+ "kind": "js",
4887
+ "name": "FslStochastic",
4888
+ "declaration": {
4889
+ "name": "FslStochastic",
4890
+ "module": "./fsl_stochastic_wc.js"
4891
+ }
4606
4892
  }
4607
4893
  ]
4608
4894
  }
@@ -23563,7 +23563,7 @@ var constants = /*#__PURE__*/Object.freeze({
23563
23563
  * Useful for runtime diagnostics and for embedding in serialized machine
23564
23564
  * snapshots so that deserializers can detect version-skew.
23565
23565
  */
23566
- const version = "5.153.0";
23566
+ const version = "5.155.0";
23567
23567
 
23568
23568
  // whargarbl lots of these return arrays could/should be sets
23569
23569
  const { state_name_chars, state_name_first_chars, action_label_chars } = constants;
@@ -23878,6 +23878,10 @@ function find_connected_components(states, edges) {
23878
23878
  }
23879
23879
  return result;
23880
23880
  }
23881
+ /** Default number of independent Monte-Carlo runs when none is declared. */
23882
+ const STOCHASTIC_DEFAULT_RUNS = 1000;
23883
+ /** Default per-run step cap (montecarlo) / walk length (steady_state). */
23884
+ const STOCHASTIC_DEFAULT_MAX_STEPS = 1000;
23881
23885
  class Machine {
23882
23886
  // whargarbl this badly needs to be broken up, monolith master
23883
23887
  constructor({ start_states, end_states = [], failed_outputs = [], initial_state, start_states_no_enforce, complete = [], transitions, machine_author, machine_comment, machine_contributor, machine_definition, machine_language, machine_license, machine_name, machine_version, npm_name, default_size, state_declaration, property_definition, state_property, fsl_version, dot_preamble = undefined, arrange_declaration = [], arrange_start_declaration = [], arrange_end_declaration = [], oarrange_declaration = [], farrange_declaration = [], theme = ['default'], flow = 'down', graph_layout = 'dot', instance_name, history, boundary_depth_limit, data, default_state_config, default_active_state_config, default_hooked_state_config, default_terminal_state_config, default_start_state_config, default_end_state_config, default_transition_config, default_graph_config, group_registry, group_metadata, group_hooks, state_hooks, allows_override, config_allows_override, allow_islands, editor_config, rng_seed, time_source, timeout_source, clear_timeout_source }) {
@@ -25394,6 +25398,147 @@ class Machine {
25394
25398
  probabilistic_histo_walk(n) {
25395
25399
  return histograph(this.probabilistic_walk(n));
25396
25400
  }
25401
+ /** One non-destructive weighted-random walk over the graph from `start`.
25402
+ *
25403
+ * Reads the graph and advances the PRNG only — it never calls
25404
+ * {@link Machine.transition}, so it fires no hooks, mutates no machine
25405
+ * state, and touches no `data`. A state with no probabilistic exits
25406
+ * (a terminal, or a forced-only `~>` state) ends the walk.
25407
+ *
25408
+ * @param start - State to begin the walk from.
25409
+ * @param max_steps - Maximum transitions before the walk is step-capped.
25410
+ * @returns The {@link JssmStochasticRun} for this walk.
25411
+ */
25412
+ _stochastic_one_walk(start, max_steps) {
25413
+ const states = [start];
25414
+ const edges = [];
25415
+ let cur = start;
25416
+ let terminated = false;
25417
+ for (let step = 0; step < max_steps; step++) {
25418
+ const exits = this.probable_exits_for(cur);
25419
+ if (exits.length === 0) {
25420
+ terminated = true;
25421
+ break;
25422
+ }
25423
+ const selected = weighted_rand_select(exits, undefined, this._rng);
25424
+ edges.push(`${cur}→${selected.to}`);
25425
+ cur = selected.to;
25426
+ states.push(cur);
25427
+ }
25428
+ return { states, edges, length: states.length - 1, terminated };
25429
+ }
25430
+ /** Lazily yield one {@link JssmStochasticRun} at a time.
25431
+ *
25432
+ * In `montecarlo` mode (default) yields `runs` independent walks from the
25433
+ * current state, each ending at a terminal or after `max_steps`. In
25434
+ * `steady_state` mode yields exactly one walk of `max_steps` steps. This
25435
+ * is the lazy engine behind {@link Machine.stochastic_summary}; the
25436
+ * fsl-stochastic panel drives it across animation frames.
25437
+ *
25438
+ * Passing `seed` reseeds the machine for reproducible runs. Unlike
25439
+ * {@link Machine.stochastic_summary}, the generator does NOT restore the
25440
+ * prior seed afterward — a direct caller's machine is left reseeded.
25441
+ *
25442
+ * @param opts - {@link JssmStochasticOptions}.
25443
+ * @returns A generator of per-run results.
25444
+ *
25445
+ * @example
25446
+ * const m = sm`a 'go' -> b 'go' -> c;`;
25447
+ * [...m.stochastic_runs({ runs: 2, seed: 1 })].length; // => 2
25448
+ */
25449
+ *stochastic_runs(opts = {}) {
25450
+ var _a, _b, _c, _d, _e;
25451
+ if (opts.seed !== undefined) {
25452
+ this.rng_seed = opts.seed;
25453
+ }
25454
+ const mode = (_a = opts.mode) !== null && _a !== void 0 ? _a : 'montecarlo';
25455
+ const max_steps = (_b = opts.max_steps) !== null && _b !== void 0 ? _b : STOCHASTIC_DEFAULT_MAX_STEPS;
25456
+ const runs = (mode === 'steady_state')
25457
+ ? 1
25458
+ : ((_e = (_c = opts.runs) !== null && _c !== void 0 ? _c : (_d = this.editor_config()) === null || _d === void 0 ? void 0 : _d.stochastic_run_count) !== null && _e !== void 0 ? _e : STOCHASTIC_DEFAULT_RUNS);
25459
+ const start = this.state();
25460
+ for (let i = 0; i < runs; i++) {
25461
+ yield this._stochastic_one_walk(start, max_steps);
25462
+ }
25463
+ }
25464
+ /** Run many weighted-random walks and return aggregate statistics.
25465
+ *
25466
+ * Honors `%` transition probabilities (via the existing probabilistic
25467
+ * machinery). Non-destructive: the machine's current state and
25468
+ * {@link Machine.rng_seed} are restored before returning, so calling this
25469
+ * never perturbs the live machine. `montecarlo` mode (default) reports
25470
+ * per-run `path_lengths`, `terminal_reached`, and `capped`; `steady_state`
25471
+ * mode runs one long walk and omits those fields.
25472
+ *
25473
+ * Timing (`after`) decorations and data-guard conditions are not modeled
25474
+ * by this sampler; it walks the probabilistic graph topology.
25475
+ *
25476
+ * @param opts - {@link JssmStochasticOptions}. `runs` defaults to the
25477
+ * machine's declared `editor: { stochastic_run_count }` (fsl#1334) when
25478
+ * present, otherwise {@link STOCHASTIC_DEFAULT_RUNS}.
25479
+ * @returns A {@link JssmStochasticSummary}.
25480
+ *
25481
+ * @see Machine.stochastic_runs
25482
+ * @see Machine.probabilistic_walk
25483
+ * @see Machine.editor_config
25484
+ *
25485
+ * @example
25486
+ * const m = sm`a 'go' -> b 'go' -> c;`;
25487
+ * const s = m.stochastic_summary({ runs: 100, seed: 1 });
25488
+ * s.terminal_reached; // => 100
25489
+ */
25490
+ stochastic_summary(opts = {}) {
25491
+ var _a, _b, _c;
25492
+ const mode = (_a = opts.mode) !== null && _a !== void 0 ? _a : 'montecarlo';
25493
+ const saved_seed = this._rng_seed;
25494
+ if (opts.seed !== undefined) {
25495
+ this.rng_seed = opts.seed;
25496
+ }
25497
+ const effective_seed = this._rng_seed;
25498
+ const state_visits = new Map();
25499
+ const edge_traversals = new Map();
25500
+ const path_lengths = [];
25501
+ let terminal_reached = 0, capped = 0, runs = 0;
25502
+ try {
25503
+ for (const run of this.stochastic_runs(Object.assign(Object.assign({}, opts), { mode }))) {
25504
+ runs += 1;
25505
+ for (const s of run.states) {
25506
+ state_visits.set(s, ((_b = state_visits.get(s)) !== null && _b !== void 0 ? _b : 0) + 1);
25507
+ }
25508
+ for (const e of run.edges) {
25509
+ edge_traversals.set(e, ((_c = edge_traversals.get(e)) !== null && _c !== void 0 ? _c : 0) + 1);
25510
+ }
25511
+ if (mode === 'montecarlo') {
25512
+ if (run.terminated) {
25513
+ terminal_reached += 1;
25514
+ path_lengths.push(run.length);
25515
+ }
25516
+ else {
25517
+ capped += 1;
25518
+ }
25519
+ }
25520
+ }
25521
+ }
25522
+ finally {
25523
+ // restore the PRNG so the call is non-destructive even when the loop throws
25524
+ this.rng_seed = saved_seed;
25525
+ }
25526
+ const total_visits = [...state_visits.values()].reduce((a, b) => a + b, 0);
25527
+ const state_visit_fraction = new Map();
25528
+ for (const [s, c] of state_visits) {
25529
+ state_visit_fraction.set(s, c / total_visits);
25530
+ }
25531
+ const summary = {
25532
+ mode, runs, seed: effective_seed,
25533
+ state_visits, state_visit_fraction, edge_traversals,
25534
+ };
25535
+ if (mode === 'montecarlo') {
25536
+ summary.path_lengths = path_lengths;
25537
+ summary.terminal_reached = terminal_reached;
25538
+ summary.capped = capped;
25539
+ }
25540
+ return summary;
25541
+ }
25397
25542
  /********
25398
25543
  *
25399
25544
  * List all actions available from this state. Please note that the order of
@@ -29860,6 +30005,7 @@ function split_ratio(coord, start, size) {
29860
30005
  * @slot hook-log - Slot for the hook-firing log (`<fsl-hook-log>`, #664).
29861
30006
  * @slot effective-properties - Slot for the resolved-properties panel (`<fsl-effective-properties>`, #665).
29862
30007
  * @slot simulation - Slot for the random-walk simulation (`<fsl-simulation>`, #668).
30008
+ * @slot stochastic - Slot for the stochastic analysis panel (`<fsl-stochastic>`, fsl#1384).
29863
30009
  * @slot export - Slot for the export menu (`<fsl-export>`, #667).
29864
30010
  * @slot footer - Footer slot.
29865
30011
  */
@@ -30737,6 +30883,7 @@ class FslInstance extends i {
30737
30883
  ${docked ? '' : b `<section class="hook-log" ?hidden=${h('hook-log')}><slot name="hook-log"></slot></section>`}
30738
30884
  <section class="effective-properties" ?hidden=${h('effective-properties')}><slot name="effective-properties"></slot></section>
30739
30885
  <section class="simulation" ?hidden=${h('simulation')}><slot name="simulation"></slot></section>
30886
+ <section class="stochastic" ?hidden=${h('stochastic')}><slot name="stochastic"></slot></section>
30740
30887
  <section class="export" ?hidden=${h('export')}><slot name="export"></slot></section>
30741
30888
  `;
30742
30889
  }