@idlekit/core 0.2.0 → 0.3.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.
Files changed (82) hide show
  1. package/dist/index.d.ts +12 -0
  2. package/dist/index.js +12 -0
  3. package/dist/index.js.map +1 -1
  4. package/dist/scenario/compile.d.ts +25 -0
  5. package/dist/scenario/compile.js +60 -0
  6. package/dist/scenario/compile.js.map +1 -1
  7. package/dist/scenario/scenarioContract.d.ts +152 -0
  8. package/dist/scenario/scenarioContract.js +2815 -0
  9. package/dist/scenario/scenarioContract.js.map +1 -0
  10. package/dist/sim/analysis/analyzers.d.ts +245 -0
  11. package/dist/sim/analysis/analyzers.js +1076 -0
  12. package/dist/sim/analysis/analyzers.js.map +1 -0
  13. package/dist/sim/analysis/contracts.d.ts +147 -0
  14. package/dist/sim/analysis/contracts.js +1230 -0
  15. package/dist/sim/analysis/contracts.js.map +1 -0
  16. package/dist/sim/analysis/distribution.d.ts +199 -0
  17. package/dist/sim/analysis/distribution.js +1246 -0
  18. package/dist/sim/analysis/distribution.js.map +1 -0
  19. package/dist/sim/analysis/eta.d.ts +118 -0
  20. package/dist/sim/analysis/eta.js +968 -70
  21. package/dist/sim/analysis/eta.js.map +1 -1
  22. package/dist/sim/analysis/experiment.d.ts +300 -0
  23. package/dist/sim/analysis/experiment.js +2206 -0
  24. package/dist/sim/analysis/experiment.js.map +1 -0
  25. package/dist/sim/analysis/growth.d.ts +2 -0
  26. package/dist/sim/analysis/growth.js +5 -1
  27. package/dist/sim/analysis/growth.js.map +1 -1
  28. package/dist/sim/analysis/growthFit.d.ts +183 -0
  29. package/dist/sim/analysis/growthFit.js +1798 -0
  30. package/dist/sim/analysis/growthFit.js.map +1 -0
  31. package/dist/sim/analysis/inspect.d.ts +236 -0
  32. package/dist/sim/analysis/inspect.js +388 -0
  33. package/dist/sim/analysis/inspect.js.map +1 -0
  34. package/dist/sim/analysis/metrics.d.ts +91 -0
  35. package/dist/sim/analysis/metrics.js +266 -0
  36. package/dist/sim/analysis/metrics.js.map +1 -0
  37. package/dist/sim/analysis/prestigeCycle.d.ts +6 -0
  38. package/dist/sim/analysis/prestigeCycle.js +10 -1
  39. package/dist/sim/analysis/prestigeCycle.js.map +1 -1
  40. package/dist/sim/analysis/registry.d.ts +41 -0
  41. package/dist/sim/analysis/registry.js +54 -0
  42. package/dist/sim/analysis/registry.js.map +1 -0
  43. package/dist/sim/analysis/resetActions.d.ts +7 -0
  44. package/dist/sim/analysis/resetActions.js +45 -0
  45. package/dist/sim/analysis/resetActions.js.map +1 -0
  46. package/dist/sim/analysis/resetCycle.d.ts +175 -0
  47. package/dist/sim/analysis/resetCycle.js +1009 -0
  48. package/dist/sim/analysis/resetCycle.js.map +1 -0
  49. package/dist/sim/analysis/resetOptimization.d.ts +198 -0
  50. package/dist/sim/analysis/resetOptimization.js +2187 -0
  51. package/dist/sim/analysis/resetOptimization.js.map +1 -0
  52. package/dist/sim/analysis/sensitivity.d.ts +73 -0
  53. package/dist/sim/analysis/sensitivity.js +160 -0
  54. package/dist/sim/analysis/sensitivity.js.map +1 -0
  55. package/dist/sim/monteCarlo.d.ts +12 -0
  56. package/dist/sim/monteCarlo.js +33 -4
  57. package/dist/sim/monteCarlo.js.map +1 -1
  58. package/dist/sim/observation.d.ts +22 -0
  59. package/dist/sim/observation.js +33 -0
  60. package/dist/sim/observation.js.map +1 -1
  61. package/dist/sim/offline.js +9 -2
  62. package/dist/sim/offline.js.map +1 -1
  63. package/dist/sim/random.d.ts +15 -0
  64. package/dist/sim/random.js +28 -0
  65. package/dist/sim/random.js.map +1 -1
  66. package/dist/sim/session.d.ts +7 -0
  67. package/dist/sim/session.js +13 -0
  68. package/dist/sim/session.js.map +1 -1
  69. package/dist/sim/simulator.js +7 -1
  70. package/dist/sim/simulator.js.map +1 -1
  71. package/dist/sim/step.d.ts +67 -1
  72. package/dist/sim/step.js +100 -18
  73. package/dist/sim/step.js.map +1 -1
  74. package/dist/sim/strategy/opt/freshRun.d.ts +3 -0
  75. package/dist/sim/strategy/opt/freshRun.js +6 -0
  76. package/dist/sim/strategy/opt/freshRun.js.map +1 -0
  77. package/dist/sim/strategy/opt/runner.js +2 -2
  78. package/dist/sim/strategy/opt/runner.js.map +1 -1
  79. package/dist/sim/strategy/planner.d.ts +26 -0
  80. package/dist/sim/strategy/planner.js +30 -0
  81. package/dist/sim/strategy/planner.js.map +1 -1
  82. package/package.json +2 -2
@@ -0,0 +1,2187 @@
1
+ import * as _validateReport_1 from "typia/lib/internal/_validateReport";
2
+ import * as _createStandardSchema_1 from "typia/lib/internal/_createStandardSchema";
3
+ import typia, {} from "typia";
4
+ import { recordPrestigeReset } from "../constraints";
5
+ import { shareObservation } from "../goalRead";
6
+ import { emitActionFacts, emitCommittedResets } from "../observation";
7
+ import { offlineAbsenceForCredit } from "../offlineCredit";
8
+ import { applyOfflineSeconds } from "../offline";
9
+ import { sessionActiveBlocks } from "../session";
10
+ import { runScenario } from "../simulator";
11
+ import { applyDecisions } from "../step";
12
+ import { runElapsedSec, } from "../types";
13
+ import { executeResetCycles, resetAdapterFor, } from "./resetCycle";
14
+ import { withoutResetActions } from "./resetActions";
15
+ import { deepClonePreservingPrototype } from "../../utils/deepClone";
16
+ /**
17
+ * Reset policy comparison v1.
18
+ * `analyzePrestigeCycle` stays the legacy interval scan.
19
+ * This artifact does not copy `breakEvenSec` or `stability`.
20
+ */
21
+ const RESET_POLICY_ALGORITHM_VERSION = "reset-policy-v1";
22
+ const RESET_POLICY_SCHEMA_ID = "https://idlekit.dev/schemas/reset.policy.v1.schema.json";
23
+ const MAX_CANDIDATES = 10000;
24
+ /** The runner treats a duration at or below this as already complete, as `executeResetCycles` rejects it. */
25
+ const RUNNER_MIN_DURATION_SEC = 1e-12;
26
+ /** Rounding slack in ULPs of the magnitudes involved, as `executeResetCycles` admits an interval at the horizon. */
27
+ const HORIZON_ULPS = 64;
28
+ /** Rounding dust of a wall or elapsed sum near the horizon: a few ULPs, never a share of the horizon. */
29
+ function horizonDust(horizonSec, reached) {
30
+ return HORIZON_ULPS * Number.EPSILON * Math.max(Math.abs(horizonSec), Math.abs(reached));
31
+ }
32
+ /**
33
+ * @evidence docs/requirements/active/reset-cycle-analysis.md#req-pr11-reward-rate Realized reward is the committed deltas. The rate uses completed cycle time.
34
+ * @evidenceReview docs/requirements/active/reset-cycle-analysis.md#req-pr11-reward-rate #9ef48a3 Re-read the section: opening points stay out of the delta, and the 5 second tail is not realized reward.
35
+ */
36
+ export const resetPolicyRewardRate = "completed-cycle-time-denominator";
37
+ /**
38
+ * @evidence docs/requirements/active/reset-cycle-analysis.md#req-pr11-empirical-stability Stable requires the requested windows and both tolerances. A cycle count is not enough.
39
+ * @evidenceReview docs/requirements/active/reset-cycle-analysis.md#req-pr11-empirical-stability #3acf06e Re-read the section: a rising multiplier is drifting, an alternating reward is oscillating, and two cycles stay insufficient.
40
+ */
41
+ export const resetPolicyEmpiricalStability = "window-tolerance-not-cycle-count";
42
+ /**
43
+ * @evidence docs/requirements/active/reset-cycle-analysis.md#req-pr11-recovery-crossing Recovery time is the threshold crossing. A miss inside the horizon is censored.
44
+ * @evidenceReview docs/requirements/active/reset-cycle-analysis.md#req-pr11-recovery-crossing #28b60db Re-read the section: the climb crosses at 25 seconds, and the short horizon does not substitute min(interval, horizon).
45
+ */
46
+ export const resetPolicyRecoveryCrossing = "threshold-crossing-or-censored";
47
+ /**
48
+ * @evidence docs/requirements/active/reset-cycle-analysis.md#req-pr11-session-reset A manual reset is committed only inside an active session block.
49
+ * @evidenceReview docs/requirements/active/reset-cycle-analysis.md#req-pr11-session-reset #0ea6f15 Re-read the section: wall 2000 is not a reset, and the caller delay moves the later morning resets.
50
+ */
51
+ export const resetPolicySessionReset = "manual-reset-only-while-active";
52
+ /**
53
+ * @evidence docs/requirements/active/reset-cycle-analysis.md#req-pr11-scan-rank Candidates are ranked with Engine.cmp inside the scanned range only.
54
+ * @evidenceReview docs/requirements/active/reset-cycle-analysis.md#req-pr11-scan-rank #468d46d Re-read the section: infinite toNumber values still rank the larger reward, and a missed goal stays null.
55
+ */
56
+ export const resetPolicyScanRank = "engine-compare-scan-range";
57
+ export function resetPolicySchemaId() {
58
+ return RESET_POLICY_SCHEMA_ID;
59
+ }
60
+ export function resetPolicyAlgorithmVersion() {
61
+ return RESET_POLICY_ALGORITHM_VERSION;
62
+ }
63
+ export class ResetPolicyRejected extends Error {
64
+ constructor(message) {
65
+ super(message);
66
+ this.name = "ResetPolicyRejected";
67
+ }
68
+ }
69
+ /*
70
+ * typia runs on first call, not at module load. A plugin that the bundled CLI loads
71
+ * imports core source without the transform, and a module-load call would throw there.
72
+ */
73
+ let validateResetPolicyArtifactValidator;
74
+ /**
75
+ * @evidence docs/requirements/active/reset-cycle-analysis.md#req-pr11-scan-rank The generated validator accepts this artifact and rejects a missing scope.
76
+ * @evidenceReview docs/requirements/active/reset-cycle-analysis.md#req-pr11-scan-rank #468d46d Re-read the section: the wire object is reset policy v1 and globalOptimum stays false.
77
+ */
78
+ export const validateResetPolicyArtifact = (input) => (validateResetPolicyArtifactValidator ??= (() => {
79
+ const _ip0 = input => "scan-range" === input["scope"];
80
+ const _ip1 = input => false === input["globalOptimum"];
81
+ const _ip2 = input => "number" === typeof input["intervalSec"];
82
+ const _ip3 = input => "number" === typeof input["horizonSec"];
83
+ const _ip4 = input => "drifting" === input["status"] || "insufficient-data" === input["status"] || "non-stationary" === input["status"] || "oscillating" === input["status"] || "stable" === input["status"];
84
+ const _ip5 = input => false === input["provedSteadyState"];
85
+ const _ip6 = input => "censored" === input["status"] || "incomplete" === input["status"] || "ok" === input["status"] || "unsupported" === input["status"];
86
+ const _ip7 = input => "number" === typeof input["completedCycles"];
87
+ const _ip8 = input => "string" === typeof input["realizedReward"];
88
+ const _ip9 = input => "string" === typeof input["unrealizedReward"];
89
+ const _ip10 = input => "string" === typeof input["finalPoints"];
90
+ const _ip11 = input => "string" === typeof input["finalNetWorth"];
91
+ const _ip12 = input => null === input["rewardRate"] || "string" === typeof input["rewardRate"];
92
+ const _ip13 = input => "completed-cycle-time" === input["rewardRateDenominator"];
93
+ const _ip14 = input => null === input["incomplete"] || "object" === typeof input["incomplete"] && null !== input["incomplete"] && _io7(input["incomplete"]);
94
+ const _ip15 = input => null === input["goalReachSec"] || "number" === typeof input["goalReachSec"];
95
+ const _ip16 = input => "censored" === input["goalStatus"] || "not-requested" === input["goalStatus"] || "observed" === input["goalStatus"];
96
+ const _ip17 = input => "object" === typeof input["recovery"] && null !== input["recovery"] && _io8(input["recovery"]);
97
+ const _ip18 = input => "object" === typeof input["stability"] && null !== input["stability"] && _io9(input["stability"]);
98
+ const _ip19 = input => "string" === typeof input["objectiveValue"];
99
+ const _ip20 = input => "flow" === input["objectiveReading"] || "stock" === input["objectiveReading"] || "time" === input["objectiveReading"];
100
+ const _ip21 = input => Array.isArray(input["resetWallTimes"]) && input["resetWallTimes"].every(elem => "number" === typeof elem);
101
+ const _ve0 = "Readonly<{ schemaVersion: 1; algorithmVersion: \"reset-policy-v1\"; status: \"censored\" | \"incomplete\" | \"insufficient-data\" | \"ok\" | \"unsupported\"; objective: ResetObjectiveKind; ... 6 more ...; warnings: readonly string[]; }>";
102
+ const _vp0 = (input, _path, _exceptionable = true) => "scan-range" === input["scope"] || _report(_exceptionable, {
103
+ path: _path + ".scope",
104
+ expected: "\"scan-range\"",
105
+ value: input["scope"]
106
+ });
107
+ const _vp1 = (input, _path, _exceptionable = true) => false === input["globalOptimum"] || _report(_exceptionable, {
108
+ path: _path + ".globalOptimum",
109
+ expected: "false",
110
+ value: input["globalOptimum"]
111
+ });
112
+ const _ve1 = "Readonly<{ intervalSec: number; objective: Readonly<{ kind: ResetObjectiveKind; reading: ObjectiveReading; value: string; }>; comparison: Readonly<{ ...; }>; session: Readonly<...>; observation: Readonly<...>; }>";
113
+ const _ve2 = "Readonly<{ intervalSec: number; status: \"censored\" | \"incomplete\" | \"ok\" | \"unsupported\"; completedCycles: number; realizedReward: string; unrealizedReward: string; finalPoints: string; finalNetWorth: string; ... 9 more ...; resetWallTimes: readonly number[]; }>";
114
+ const _ve3 = "ReadonlyArray<Readonly<{ intervalSec: number; status: \"censored\" | \"incomplete\" | \"ok\" | \"unsupported\"; completedCycles: number; realizedReward: string; unrealizedReward: string; finalPoints: string; finalNetWorth: string; ... 9 more ...; resetWallTimes: readonly number[]; }>>";
115
+ const _vp2 = (input, _path, _exceptionable = true) => "number" === typeof input["intervalSec"] || _report(_exceptionable, {
116
+ path: _path + ".intervalSec",
117
+ expected: "number",
118
+ value: input["intervalSec"]
119
+ });
120
+ const _ve4 = "Readonly<{ fromSec: number; toSec: number; stepSec: number; horizonSec: number; candidateCount: number; scope: \"scan-range\"; globalOptimum: false; }>";
121
+ const _ve5 = "Readonly<{ status: EmpiricalStability; stability: EmpiricalStability; provedSteadyState: false; recovery: \"censored\" | \"observed\" | \"unsupported\"; }>";
122
+ const _vp3 = (input, _path, _exceptionable = true) => "number" === typeof input["horizonSec"] || _report(_exceptionable, {
123
+ path: _path + ".horizonSec",
124
+ expected: "number",
125
+ value: input["horizonSec"]
126
+ });
127
+ const _vp4 = (input, _path, _exceptionable = true) => "drifting" === input["status"] || "insufficient-data" === input["status"] || "non-stationary" === input["status"] || "oscillating" === input["status"] || "stable" === input["status"] || _report(_exceptionable, {
128
+ path: _path + ".status",
129
+ expected: "(\"drifting\" | \"insufficient-data\" | \"non-stationary\" | \"oscillating\" | \"stable\")",
130
+ value: input["status"]
131
+ });
132
+ const _vp5 = (input, _path, _exceptionable = true) => false === input["provedSteadyState"] || _report(_exceptionable, {
133
+ path: _path + ".provedSteadyState",
134
+ expected: "false",
135
+ value: input["provedSteadyState"]
136
+ });
137
+ const _vp6 = (input, _path, _exceptionable = true) => "censored" === input["status"] || "incomplete" === input["status"] || "ok" === input["status"] || "unsupported" === input["status"] || _report(_exceptionable, {
138
+ path: _path + ".status",
139
+ expected: "(\"censored\" | \"incomplete\" | \"ok\" | \"unsupported\")",
140
+ value: input["status"]
141
+ });
142
+ const _vp7 = (input, _path, _exceptionable = true) => "number" === typeof input["completedCycles"] || _report(_exceptionable, {
143
+ path: _path + ".completedCycles",
144
+ expected: "number",
145
+ value: input["completedCycles"]
146
+ });
147
+ const _vp8 = (input, _path, _exceptionable = true) => "string" === typeof input["realizedReward"] || _report(_exceptionable, {
148
+ path: _path + ".realizedReward",
149
+ expected: "string",
150
+ value: input["realizedReward"]
151
+ });
152
+ const _vp9 = (input, _path, _exceptionable = true) => "string" === typeof input["unrealizedReward"] || _report(_exceptionable, {
153
+ path: _path + ".unrealizedReward",
154
+ expected: "string",
155
+ value: input["unrealizedReward"]
156
+ });
157
+ const _vp10 = (input, _path, _exceptionable = true) => "string" === typeof input["finalPoints"] || _report(_exceptionable, {
158
+ path: _path + ".finalPoints",
159
+ expected: "string",
160
+ value: input["finalPoints"]
161
+ });
162
+ const _vp11 = (input, _path, _exceptionable = true) => "string" === typeof input["finalNetWorth"] || _report(_exceptionable, {
163
+ path: _path + ".finalNetWorth",
164
+ expected: "string",
165
+ value: input["finalNetWorth"]
166
+ });
167
+ const _vp12 = (input, _path, _exceptionable = true) => null === input["rewardRate"] || "string" === typeof input["rewardRate"] || _report(_exceptionable, {
168
+ path: _path + ".rewardRate",
169
+ expected: "(null | string)",
170
+ value: input["rewardRate"]
171
+ });
172
+ const _vp13 = (input, _path, _exceptionable = true) => "completed-cycle-time" === input["rewardRateDenominator"] || _report(_exceptionable, {
173
+ path: _path + ".rewardRateDenominator",
174
+ expected: "\"completed-cycle-time\"",
175
+ value: input["rewardRateDenominator"]
176
+ });
177
+ const _vp14 = (input, _path, _exceptionable = true) => null === input["incomplete"] || ("object" === typeof input["incomplete"] && null !== input["incomplete"] || _report(_exceptionable, {
178
+ path: _path + ".incomplete",
179
+ expected: "(Readonly<{ startSec: number; endSec: number; durationSec: number; }> | null)",
180
+ value: input["incomplete"]
181
+ })) && _vo7(input["incomplete"], _path + ".incomplete", true && _exceptionable) || _report(_exceptionable, {
182
+ path: _path + ".incomplete",
183
+ expected: "(Readonly<{ startSec: number; endSec: number; durationSec: number; }> | null)",
184
+ value: input["incomplete"]
185
+ });
186
+ const _vp15 = (input, _path, _exceptionable = true) => null === input["goalReachSec"] || "number" === typeof input["goalReachSec"] || _report(_exceptionable, {
187
+ path: _path + ".goalReachSec",
188
+ expected: "(null | number)",
189
+ value: input["goalReachSec"]
190
+ });
191
+ const _vp16 = (input, _path, _exceptionable = true) => "censored" === input["goalStatus"] || "not-requested" === input["goalStatus"] || "observed" === input["goalStatus"] || _report(_exceptionable, {
192
+ path: _path + ".goalStatus",
193
+ expected: "(\"censored\" | \"not-requested\" | \"observed\")",
194
+ value: input["goalStatus"]
195
+ });
196
+ const _vp17 = (input, _path, _exceptionable = true) => ("object" === typeof input["recovery"] && null !== input["recovery"] || _report(_exceptionable, {
197
+ path: _path + ".recovery",
198
+ expected: _ve6,
199
+ value: input["recovery"]
200
+ })) && _vo8(input["recovery"], _path + ".recovery", true && _exceptionable) || _report(_exceptionable, {
201
+ path: _path + ".recovery",
202
+ expected: _ve6,
203
+ value: input["recovery"]
204
+ });
205
+ const _ve6 = "Readonly<{ metric: RecoveryMetricKind; status: \"censored\" | \"observed\" | \"unsupported\"; seconds: number | null; threshold: string; }>";
206
+ const _vp18 = (input, _path, _exceptionable = true) => ("object" === typeof input["stability"] && null !== input["stability"] || _report(_exceptionable, {
207
+ path: _path + ".stability",
208
+ expected: _ve7,
209
+ value: input["stability"]
210
+ })) && _vo9(input["stability"], _path + ".stability", true && _exceptionable) || _report(_exceptionable, {
211
+ path: _path + ".stability",
212
+ expected: _ve7,
213
+ value: input["stability"]
214
+ });
215
+ const _ve7 = "Readonly<{ kind: \"empirical\"; provedSteadyState: false; status: EmpiricalStability; observedCycles: number; minObservedCycles: number; windows: readonly number[]; absoluteTolerance: string; relativeTolerance: number; horizonSec: number; }>";
216
+ const _vp19 = (input, _path, _exceptionable = true) => "string" === typeof input["objectiveValue"] || _report(_exceptionable, {
217
+ path: _path + ".objectiveValue",
218
+ expected: "string",
219
+ value: input["objectiveValue"]
220
+ });
221
+ const _vp20 = (input, _path, _exceptionable = true) => "flow" === input["objectiveReading"] || "stock" === input["objectiveReading"] || "time" === input["objectiveReading"] || _report(_exceptionable, {
222
+ path: _path + ".objectiveReading",
223
+ expected: "(\"flow\" | \"stock\" | \"time\")",
224
+ value: input["objectiveReading"]
225
+ });
226
+ const _vp21 = (input, _path, _exceptionable = true) => (Array.isArray(input["resetWallTimes"]) || _report(_exceptionable, {
227
+ path: _path + ".resetWallTimes",
228
+ expected: "ReadonlyArray<number>",
229
+ value: input["resetWallTimes"]
230
+ })) && input["resetWallTimes"].map((elem, _index9) => "number" === typeof elem || _report(_exceptionable, {
231
+ path: _path + ".resetWallTimes[" + _index9 + "]",
232
+ expected: "number",
233
+ value: elem
234
+ })).every(flag => flag) || _report(_exceptionable, {
235
+ path: _path + ".resetWallTimes",
236
+ expected: "ReadonlyArray<number>",
237
+ value: input["resetWallTimes"]
238
+ });
239
+ const _io0 = input => 1 === input.schemaVersion && "reset-policy-v1" === input.algorithmVersion && ("censored" === input.status || "incomplete" === input.status || "insufficient-data" === input.status || "ok" === input.status || "unsupported" === input.status) && ("cumulative-reset-reward" === input.objective || "final-net-worth" === input.objective || "goal-reach-sec" === input.objective) && _ip0(input) && _ip1(input) && ("object" === typeof input.best && null !== input.best && _io1(input.best)) && (Array.isArray(input.candidates) && input.candidates.every(elem => "object" === typeof elem && null !== elem && _io6(elem))) && (null === input.knownSolution || "object" === typeof input.knownSolution && null !== input.knownSolution && _io10(input.knownSolution)) && (Array.isArray(input.assumptions) && input.assumptions.every(elem => "string" === typeof elem)) && (Array.isArray(input.warnings) && input.warnings.every(elem => "string" === typeof elem));
240
+ const _io1 = input => _ip2(input) && ("object" === typeof input.objective && null !== input.objective && _io2(input.objective)) && ("object" === typeof input.comparison && null !== input.comparison && _io3(input.comparison)) && ("object" === typeof input.session && null !== input.session && _io4(input.session)) && ("object" === typeof input.observation && null !== input.observation && _io5(input.observation));
241
+ const _io10 = input => "string" === typeof input.fixtureId && "string" === typeof input.expectedRate && "string" === typeof input.observedRate && "boolean" === typeof input.matches;
242
+ const _io2 = input => ("cumulative-reset-reward" === input.kind || "final-net-worth" === input.kind || "goal-reach-sec" === input.kind) && ("flow" === input.reading || "stock" === input.reading || "time" === input.reading) && "string" === typeof input.value;
243
+ const _io3 = input => "number" === typeof input.fromSec && "number" === typeof input.toSec && "number" === typeof input.stepSec && _ip3(input) && "number" === typeof input.candidateCount && _ip0(input) && _ip1(input);
244
+ const _io4 = input => ("always-active" === input.assumed || "scheduled" === input.assumed) && (null === input.patternId || "string" === typeof input.patternId) && (null === input.resetDelaySec || "number" === typeof input.resetDelaySec);
245
+ const _io5 = input => _ip4(input) && ("drifting" === input.stability || "insufficient-data" === input.stability || "non-stationary" === input.stability || "oscillating" === input.stability || "stable" === input.stability) && _ip5(input) && ("censored" === input.recovery || "observed" === input.recovery || "unsupported" === input.recovery);
246
+ const _io6 = input => _ip2(input) && _ip6(input) && _ip7(input) && _ip8(input) && _ip9(input) && _ip10(input) && _ip11(input) && _ip12(input) && _ip13(input) && _ip14(input) && _ip15(input) && _ip16(input) && _ip17(input) && _ip18(input) && _ip19(input) && _ip20(input) && _ip21(input);
247
+ const _io7 = input => "number" === typeof input.startSec && "number" === typeof input.endSec && "number" === typeof input.durationSec;
248
+ const _io8 = input => ("pre-reset-income" === input.metric || "pre-reset-net-worth" === input.metric) && ("censored" === input.status || "observed" === input.status || "unsupported" === input.status) && (null === input.seconds || "number" === typeof input.seconds) && "string" === typeof input.threshold;
249
+ const _io9 = input => "empirical" === input.kind && _ip5(input) && _ip4(input) && "number" === typeof input.observedCycles && "number" === typeof input.minObservedCycles && (Array.isArray(input.windows) && input.windows.every(elem => "number" === typeof elem)) && "string" === typeof input.absoluteTolerance && "number" === typeof input.relativeTolerance && _ip3(input);
250
+ const _vo0 = (input, _path, _exceptionable = true) => [1 === input.schemaVersion || _report(_exceptionable, {
251
+ path: _path + ".schemaVersion",
252
+ expected: "1",
253
+ value: input.schemaVersion
254
+ }), "reset-policy-v1" === input.algorithmVersion || _report(_exceptionable, {
255
+ path: _path + ".algorithmVersion",
256
+ expected: "\"reset-policy-v1\"",
257
+ value: input.algorithmVersion
258
+ }), "censored" === input.status || "incomplete" === input.status || "insufficient-data" === input.status || "ok" === input.status || "unsupported" === input.status || _report(_exceptionable, {
259
+ path: _path + ".status",
260
+ expected: "(\"censored\" | \"incomplete\" | \"insufficient-data\" | \"ok\" | \"unsupported\")",
261
+ value: input.status
262
+ }), "cumulative-reset-reward" === input.objective || "final-net-worth" === input.objective || "goal-reach-sec" === input.objective || _report(_exceptionable, {
263
+ path: _path + ".objective",
264
+ expected: "(\"cumulative-reset-reward\" | \"final-net-worth\" | \"goal-reach-sec\")",
265
+ value: input.objective
266
+ }), _vp0(input, _path, true && _exceptionable), _vp1(input, _path, true && _exceptionable), ("object" === typeof input.best && null !== input.best || _report(_exceptionable, {
267
+ path: _path + ".best",
268
+ expected: _ve1,
269
+ value: input.best
270
+ })) && _vo1(input.best, _path + ".best", true && _exceptionable) || _report(_exceptionable, {
271
+ path: _path + ".best",
272
+ expected: _ve1,
273
+ value: input.best
274
+ }), (Array.isArray(input.candidates) || _report(_exceptionable, {
275
+ path: _path + ".candidates",
276
+ expected: _ve3,
277
+ value: input.candidates
278
+ })) && input.candidates.map((elem, _index6) => ("object" === typeof elem && null !== elem || _report(_exceptionable, {
279
+ path: _path + ".candidates[" + _index6 + "]",
280
+ expected: _ve2,
281
+ value: elem
282
+ })) && _vo6(elem, _path + ".candidates[" + _index6 + "]", true && _exceptionable) || _report(_exceptionable, {
283
+ path: _path + ".candidates[" + _index6 + "]",
284
+ expected: _ve2,
285
+ value: elem
286
+ })).every(flag => flag) || _report(_exceptionable, {
287
+ path: _path + ".candidates",
288
+ expected: _ve3,
289
+ value: input.candidates
290
+ }), null === input.knownSolution || ("object" === typeof input.knownSolution && null !== input.knownSolution || _report(_exceptionable, {
291
+ path: _path + ".knownSolution",
292
+ expected: "(Readonly<{ fixtureId: string; expectedRate: string; observedRate: string; matches: boolean; }> | null)",
293
+ value: input.knownSolution
294
+ })) && _vo10(input.knownSolution, _path + ".knownSolution", true && _exceptionable) || _report(_exceptionable, {
295
+ path: _path + ".knownSolution",
296
+ expected: "(Readonly<{ fixtureId: string; expectedRate: string; observedRate: string; matches: boolean; }> | null)",
297
+ value: input.knownSolution
298
+ }), (Array.isArray(input.assumptions) || _report(_exceptionable, {
299
+ path: _path + ".assumptions",
300
+ expected: "ReadonlyArray<string>",
301
+ value: input.assumptions
302
+ })) && input.assumptions.map((elem, _index7) => "string" === typeof elem || _report(_exceptionable, {
303
+ path: _path + ".assumptions[" + _index7 + "]",
304
+ expected: "string",
305
+ value: elem
306
+ })).every(flag => flag) || _report(_exceptionable, {
307
+ path: _path + ".assumptions",
308
+ expected: "ReadonlyArray<string>",
309
+ value: input.assumptions
310
+ }), (Array.isArray(input.warnings) || _report(_exceptionable, {
311
+ path: _path + ".warnings",
312
+ expected: "ReadonlyArray<string>",
313
+ value: input.warnings
314
+ })) && input.warnings.map((elem, _index8) => "string" === typeof elem || _report(_exceptionable, {
315
+ path: _path + ".warnings[" + _index8 + "]",
316
+ expected: "string",
317
+ value: elem
318
+ })).every(flag => flag) || _report(_exceptionable, {
319
+ path: _path + ".warnings",
320
+ expected: "ReadonlyArray<string>",
321
+ value: input.warnings
322
+ })].every(flag => flag);
323
+ const _vo1 = (input, _path, _exceptionable = true) => [_vp2(input, _path, true && _exceptionable), ("object" === typeof input.objective && null !== input.objective || _report(_exceptionable, {
324
+ path: _path + ".objective",
325
+ expected: "Readonly<{ kind: ResetObjectiveKind; reading: ObjectiveReading; value: string; }>",
326
+ value: input.objective
327
+ })) && _vo2(input.objective, _path + ".objective", true && _exceptionable) || _report(_exceptionable, {
328
+ path: _path + ".objective",
329
+ expected: "Readonly<{ kind: ResetObjectiveKind; reading: ObjectiveReading; value: string; }>",
330
+ value: input.objective
331
+ }), ("object" === typeof input.comparison && null !== input.comparison || _report(_exceptionable, {
332
+ path: _path + ".comparison",
333
+ expected: _ve4,
334
+ value: input.comparison
335
+ })) && _vo3(input.comparison, _path + ".comparison", true && _exceptionable) || _report(_exceptionable, {
336
+ path: _path + ".comparison",
337
+ expected: _ve4,
338
+ value: input.comparison
339
+ }), ("object" === typeof input.session && null !== input.session || _report(_exceptionable, {
340
+ path: _path + ".session",
341
+ expected: "Readonly<{ assumed: \"always-active\" | \"scheduled\"; patternId: string | null; resetDelaySec: number | null; }>",
342
+ value: input.session
343
+ })) && _vo4(input.session, _path + ".session", true && _exceptionable) || _report(_exceptionable, {
344
+ path: _path + ".session",
345
+ expected: "Readonly<{ assumed: \"always-active\" | \"scheduled\"; patternId: string | null; resetDelaySec: number | null; }>",
346
+ value: input.session
347
+ }), ("object" === typeof input.observation && null !== input.observation || _report(_exceptionable, {
348
+ path: _path + ".observation",
349
+ expected: _ve5,
350
+ value: input.observation
351
+ })) && _vo5(input.observation, _path + ".observation", true && _exceptionable) || _report(_exceptionable, {
352
+ path: _path + ".observation",
353
+ expected: _ve5,
354
+ value: input.observation
355
+ })].every(flag => flag);
356
+ const _vo10 = (input, _path, _exceptionable = true) => ["string" === typeof input.fixtureId || _report(_exceptionable, {
357
+ path: _path + ".fixtureId",
358
+ expected: "string",
359
+ value: input.fixtureId
360
+ }), "string" === typeof input.expectedRate || _report(_exceptionable, {
361
+ path: _path + ".expectedRate",
362
+ expected: "string",
363
+ value: input.expectedRate
364
+ }), "string" === typeof input.observedRate || _report(_exceptionable, {
365
+ path: _path + ".observedRate",
366
+ expected: "string",
367
+ value: input.observedRate
368
+ }), "boolean" === typeof input.matches || _report(_exceptionable, {
369
+ path: _path + ".matches",
370
+ expected: "boolean",
371
+ value: input.matches
372
+ })].every(flag => flag);
373
+ const _vo2 = (input, _path, _exceptionable = true) => ["cumulative-reset-reward" === input.kind || "final-net-worth" === input.kind || "goal-reach-sec" === input.kind || _report(_exceptionable, {
374
+ path: _path + ".kind",
375
+ expected: "(\"cumulative-reset-reward\" | \"final-net-worth\" | \"goal-reach-sec\")",
376
+ value: input.kind
377
+ }), "flow" === input.reading || "stock" === input.reading || "time" === input.reading || _report(_exceptionable, {
378
+ path: _path + ".reading",
379
+ expected: "(\"flow\" | \"stock\" | \"time\")",
380
+ value: input.reading
381
+ }), "string" === typeof input.value || _report(_exceptionable, {
382
+ path: _path + ".value",
383
+ expected: "string",
384
+ value: input.value
385
+ })].every(flag => flag);
386
+ const _vo3 = (input, _path, _exceptionable = true) => ["number" === typeof input.fromSec || _report(_exceptionable, {
387
+ path: _path + ".fromSec",
388
+ expected: "number",
389
+ value: input.fromSec
390
+ }), "number" === typeof input.toSec || _report(_exceptionable, {
391
+ path: _path + ".toSec",
392
+ expected: "number",
393
+ value: input.toSec
394
+ }), "number" === typeof input.stepSec || _report(_exceptionable, {
395
+ path: _path + ".stepSec",
396
+ expected: "number",
397
+ value: input.stepSec
398
+ }), _vp3(input, _path, true && _exceptionable), "number" === typeof input.candidateCount || _report(_exceptionable, {
399
+ path: _path + ".candidateCount",
400
+ expected: "number",
401
+ value: input.candidateCount
402
+ }), _vp0(input, _path, true && _exceptionable), _vp1(input, _path, true && _exceptionable)].every(flag => flag);
403
+ const _vo4 = (input, _path, _exceptionable = true) => ["always-active" === input.assumed || "scheduled" === input.assumed || _report(_exceptionable, {
404
+ path: _path + ".assumed",
405
+ expected: "(\"always-active\" | \"scheduled\")",
406
+ value: input.assumed
407
+ }), null === input.patternId || "string" === typeof input.patternId || _report(_exceptionable, {
408
+ path: _path + ".patternId",
409
+ expected: "(null | string)",
410
+ value: input.patternId
411
+ }), null === input.resetDelaySec || "number" === typeof input.resetDelaySec || _report(_exceptionable, {
412
+ path: _path + ".resetDelaySec",
413
+ expected: "(null | number)",
414
+ value: input.resetDelaySec
415
+ })].every(flag => flag);
416
+ const _vo5 = (input, _path, _exceptionable = true) => [_vp4(input, _path, true && _exceptionable), "drifting" === input.stability || "insufficient-data" === input.stability || "non-stationary" === input.stability || "oscillating" === input.stability || "stable" === input.stability || _report(_exceptionable, {
417
+ path: _path + ".stability",
418
+ expected: "(\"drifting\" | \"insufficient-data\" | \"non-stationary\" | \"oscillating\" | \"stable\")",
419
+ value: input.stability
420
+ }), _vp5(input, _path, true && _exceptionable), "censored" === input.recovery || "observed" === input.recovery || "unsupported" === input.recovery || _report(_exceptionable, {
421
+ path: _path + ".recovery",
422
+ expected: "(\"censored\" | \"observed\" | \"unsupported\")",
423
+ value: input.recovery
424
+ })].every(flag => flag);
425
+ const _vo6 = (input, _path, _exceptionable = true) => [_vp2(input, _path, true && _exceptionable), _vp6(input, _path, true && _exceptionable), _vp7(input, _path, true && _exceptionable), _vp8(input, _path, true && _exceptionable), _vp9(input, _path, true && _exceptionable), _vp10(input, _path, true && _exceptionable), _vp11(input, _path, true && _exceptionable), _vp12(input, _path, true && _exceptionable), _vp13(input, _path, true && _exceptionable), _vp14(input, _path, true && _exceptionable), _vp15(input, _path, true && _exceptionable), _vp16(input, _path, true && _exceptionable), _vp17(input, _path, true && _exceptionable), _vp18(input, _path, true && _exceptionable), _vp19(input, _path, true && _exceptionable), _vp20(input, _path, true && _exceptionable), _vp21(input, _path, true && _exceptionable)].every(flag => flag);
426
+ const _vo7 = (input, _path, _exceptionable = true) => ["number" === typeof input.startSec || _report(_exceptionable, {
427
+ path: _path + ".startSec",
428
+ expected: "number",
429
+ value: input.startSec
430
+ }), "number" === typeof input.endSec || _report(_exceptionable, {
431
+ path: _path + ".endSec",
432
+ expected: "number",
433
+ value: input.endSec
434
+ }), "number" === typeof input.durationSec || _report(_exceptionable, {
435
+ path: _path + ".durationSec",
436
+ expected: "number",
437
+ value: input.durationSec
438
+ })].every(flag => flag);
439
+ const _vo8 = (input, _path, _exceptionable = true) => ["pre-reset-income" === input.metric || "pre-reset-net-worth" === input.metric || _report(_exceptionable, {
440
+ path: _path + ".metric",
441
+ expected: "(\"pre-reset-income\" | \"pre-reset-net-worth\")",
442
+ value: input.metric
443
+ }), "censored" === input.status || "observed" === input.status || "unsupported" === input.status || _report(_exceptionable, {
444
+ path: _path + ".status",
445
+ expected: "(\"censored\" | \"observed\" | \"unsupported\")",
446
+ value: input.status
447
+ }), null === input.seconds || "number" === typeof input.seconds || _report(_exceptionable, {
448
+ path: _path + ".seconds",
449
+ expected: "(null | number)",
450
+ value: input.seconds
451
+ }), "string" === typeof input.threshold || _report(_exceptionable, {
452
+ path: _path + ".threshold",
453
+ expected: "string",
454
+ value: input.threshold
455
+ })].every(flag => flag);
456
+ const _vo9 = (input, _path, _exceptionable = true) => ["empirical" === input.kind || _report(_exceptionable, {
457
+ path: _path + ".kind",
458
+ expected: "\"empirical\"",
459
+ value: input.kind
460
+ }), _vp5(input, _path, true && _exceptionable), _vp4(input, _path, true && _exceptionable), "number" === typeof input.observedCycles || _report(_exceptionable, {
461
+ path: _path + ".observedCycles",
462
+ expected: "number",
463
+ value: input.observedCycles
464
+ }), "number" === typeof input.minObservedCycles || _report(_exceptionable, {
465
+ path: _path + ".minObservedCycles",
466
+ expected: "number",
467
+ value: input.minObservedCycles
468
+ }), (Array.isArray(input.windows) || _report(_exceptionable, {
469
+ path: _path + ".windows",
470
+ expected: "ReadonlyArray<number>",
471
+ value: input.windows
472
+ })) && input.windows.map((elem, _index10) => "number" === typeof elem || _report(_exceptionable, {
473
+ path: _path + ".windows[" + _index10 + "]",
474
+ expected: "number",
475
+ value: elem
476
+ })).every(flag => flag) || _report(_exceptionable, {
477
+ path: _path + ".windows",
478
+ expected: "ReadonlyArray<number>",
479
+ value: input.windows
480
+ }), "string" === typeof input.absoluteTolerance || _report(_exceptionable, {
481
+ path: _path + ".absoluteTolerance",
482
+ expected: "string",
483
+ value: input.absoluteTolerance
484
+ }), "number" === typeof input.relativeTolerance || _report(_exceptionable, {
485
+ path: _path + ".relativeTolerance",
486
+ expected: "number",
487
+ value: input.relativeTolerance
488
+ }), _vp3(input, _path, true && _exceptionable)].every(flag => flag);
489
+ const __is = input => "object" === typeof input && null !== input && _io0(input);
490
+ let errors;
491
+ let _report;
492
+ return _createStandardSchema_1._createStandardSchema(input => {
493
+ if (false === __is(input)) {
494
+ errors = [];
495
+ _report = _validateReport_1._validateReport(errors);
496
+ ((input, _path, _exceptionable = true) => ("object" === typeof input && null !== input || _report(true, {
497
+ path: _path + "",
498
+ expected: _ve0,
499
+ value: input
500
+ })) && _vo0(input, _path + "", true) || _report(true, {
501
+ path: _path + "",
502
+ expected: _ve0,
503
+ value: input
504
+ }))(input, "$input", true);
505
+ const success = 0 === errors.length;
506
+ return success ? {
507
+ success,
508
+ data: input
509
+ } : {
510
+ success,
511
+ errors,
512
+ data: input
513
+ };
514
+ }
515
+ return {
516
+ success: true,
517
+ data: input
518
+ };
519
+ });
520
+ })())(input);
521
+ function buildResetPolicyJsonSchema() {
522
+ return {
523
+ components: {
524
+ schemas: {
525
+ "ReadonlyschemaVersion1algorithmVersionreset-policy-v1statuscensoredincompleteinsufficient-dataokunsupportedobjectiveResetObjectiveKind---6more---warningsreadonlystring": {
526
+ type: "object",
527
+ properties: {
528
+ schemaVersion: {
529
+ "const": 1
530
+ },
531
+ algorithmVersion: {
532
+ "const": "reset-policy-v1"
533
+ },
534
+ status: {
535
+ oneOf: [
536
+ {
537
+ "const": "censored"
538
+ },
539
+ {
540
+ "const": "incomplete"
541
+ },
542
+ {
543
+ "const": "insufficient-data"
544
+ },
545
+ {
546
+ "const": "ok"
547
+ },
548
+ {
549
+ "const": "unsupported"
550
+ }
551
+ ]
552
+ },
553
+ objective: {
554
+ $ref: "#/components/schemas/ResetObjectiveKind"
555
+ },
556
+ scope: {
557
+ "const": "scan-range"
558
+ },
559
+ globalOptimum: {
560
+ "const": false
561
+ },
562
+ best: {
563
+ $ref: "#/components/schemas/ReadonlyintervalSecnumberobjectiveReadonlykindResetObjectiveKindreadingObjectiveReadingvaluestringcomparisonReadonly---sessionReadonly---observationReadonly---"
564
+ },
565
+ candidates: {
566
+ type: "array",
567
+ items: {
568
+ $ref: "#/components/schemas/ReadonlyintervalSecnumberstatuscensoredincompleteokunsupportedcompletedCyclesnumberrealizedRewardstringunrealizedRewardstringfinalPointsstringfinalNetWorthstring---9more---resetWallTimesreadonlynumber"
569
+ }
570
+ },
571
+ knownSolution: {
572
+ oneOf: [
573
+ {
574
+ type: "null"
575
+ },
576
+ {
577
+ $ref: "#/components/schemas/ReadonlyfixtureIdstringexpectedRatestringobservedRatestringmatchesboolean"
578
+ }
579
+ ]
580
+ },
581
+ assumptions: {
582
+ type: "array",
583
+ items: {
584
+ type: "string"
585
+ }
586
+ },
587
+ warnings: {
588
+ type: "array",
589
+ items: {
590
+ type: "string"
591
+ }
592
+ }
593
+ },
594
+ required: [
595
+ "schemaVersion",
596
+ "algorithmVersion",
597
+ "status",
598
+ "objective",
599
+ "scope",
600
+ "globalOptimum",
601
+ "best",
602
+ "candidates",
603
+ "knownSolution",
604
+ "assumptions",
605
+ "warnings"
606
+ ],
607
+ additionalProperties: false
608
+ },
609
+ ResetObjectiveKind: {
610
+ oneOf: [
611
+ {
612
+ "const": "cumulative-reset-reward"
613
+ },
614
+ {
615
+ "const": "final-net-worth"
616
+ },
617
+ {
618
+ "const": "goal-reach-sec"
619
+ }
620
+ ]
621
+ },
622
+ "ReadonlyintervalSecnumberobjectiveReadonlykindResetObjectiveKindreadingObjectiveReadingvaluestringcomparisonReadonly---sessionReadonly---observationReadonly---": {
623
+ type: "object",
624
+ properties: {
625
+ intervalSec: {
626
+ type: "number"
627
+ },
628
+ objective: {
629
+ $ref: "#/components/schemas/ReadonlykindResetObjectiveKindreadingObjectiveReadingvaluestring"
630
+ },
631
+ comparison: {
632
+ $ref: "#/components/schemas/ReadonlyfromSecnumbertoSecnumberstepSecnumberhorizonSecnumbercandidateCountnumberscopescan-rangeglobalOptimumfalse"
633
+ },
634
+ session: {
635
+ $ref: "#/components/schemas/Readonlyassumedalways-activescheduledpatternIdstringnullresetDelaySecnumbernull"
636
+ },
637
+ observation: {
638
+ $ref: "#/components/schemas/ReadonlystatusEmpiricalStabilitystabilityEmpiricalStabilityprovedSteadyStatefalserecoverycensoredobservedunsupported"
639
+ }
640
+ },
641
+ required: [
642
+ "intervalSec",
643
+ "objective",
644
+ "comparison",
645
+ "session",
646
+ "observation"
647
+ ],
648
+ additionalProperties: false
649
+ },
650
+ ReadonlykindResetObjectiveKindreadingObjectiveReadingvaluestring: {
651
+ type: "object",
652
+ properties: {
653
+ kind: {
654
+ $ref: "#/components/schemas/ResetObjectiveKind"
655
+ },
656
+ reading: {
657
+ $ref: "#/components/schemas/ObjectiveReading"
658
+ },
659
+ value: {
660
+ type: "string"
661
+ }
662
+ },
663
+ required: [
664
+ "kind",
665
+ "reading",
666
+ "value"
667
+ ],
668
+ additionalProperties: false
669
+ },
670
+ ObjectiveReading: {
671
+ oneOf: [
672
+ {
673
+ "const": "flow"
674
+ },
675
+ {
676
+ "const": "stock"
677
+ },
678
+ {
679
+ "const": "time"
680
+ }
681
+ ]
682
+ },
683
+ "ReadonlyfromSecnumbertoSecnumberstepSecnumberhorizonSecnumbercandidateCountnumberscopescan-rangeglobalOptimumfalse": {
684
+ type: "object",
685
+ properties: {
686
+ fromSec: {
687
+ type: "number"
688
+ },
689
+ toSec: {
690
+ type: "number"
691
+ },
692
+ stepSec: {
693
+ type: "number"
694
+ },
695
+ horizonSec: {
696
+ type: "number"
697
+ },
698
+ candidateCount: {
699
+ type: "number"
700
+ },
701
+ scope: {
702
+ "const": "scan-range"
703
+ },
704
+ globalOptimum: {
705
+ "const": false
706
+ }
707
+ },
708
+ required: [
709
+ "fromSec",
710
+ "toSec",
711
+ "stepSec",
712
+ "horizonSec",
713
+ "candidateCount",
714
+ "scope",
715
+ "globalOptimum"
716
+ ],
717
+ additionalProperties: false
718
+ },
719
+ "Readonlyassumedalways-activescheduledpatternIdstringnullresetDelaySecnumbernull": {
720
+ type: "object",
721
+ properties: {
722
+ assumed: {
723
+ oneOf: [
724
+ {
725
+ "const": "always-active"
726
+ },
727
+ {
728
+ "const": "scheduled"
729
+ }
730
+ ]
731
+ },
732
+ patternId: {
733
+ oneOf: [
734
+ {
735
+ type: "null"
736
+ },
737
+ {
738
+ type: "string"
739
+ }
740
+ ]
741
+ },
742
+ resetDelaySec: {
743
+ oneOf: [
744
+ {
745
+ type: "null"
746
+ },
747
+ {
748
+ type: "number"
749
+ }
750
+ ]
751
+ }
752
+ },
753
+ required: [
754
+ "assumed",
755
+ "patternId",
756
+ "resetDelaySec"
757
+ ],
758
+ additionalProperties: false
759
+ },
760
+ ReadonlystatusEmpiricalStabilitystabilityEmpiricalStabilityprovedSteadyStatefalserecoverycensoredobservedunsupported: {
761
+ type: "object",
762
+ properties: {
763
+ status: {
764
+ $ref: "#/components/schemas/EmpiricalStability"
765
+ },
766
+ stability: {
767
+ $ref: "#/components/schemas/EmpiricalStability"
768
+ },
769
+ provedSteadyState: {
770
+ "const": false
771
+ },
772
+ recovery: {
773
+ oneOf: [
774
+ {
775
+ "const": "censored"
776
+ },
777
+ {
778
+ "const": "observed"
779
+ },
780
+ {
781
+ "const": "unsupported"
782
+ }
783
+ ]
784
+ }
785
+ },
786
+ required: [
787
+ "status",
788
+ "stability",
789
+ "provedSteadyState",
790
+ "recovery"
791
+ ],
792
+ additionalProperties: false
793
+ },
794
+ EmpiricalStability: {
795
+ oneOf: [
796
+ {
797
+ "const": "drifting"
798
+ },
799
+ {
800
+ "const": "insufficient-data"
801
+ },
802
+ {
803
+ "const": "non-stationary"
804
+ },
805
+ {
806
+ "const": "oscillating"
807
+ },
808
+ {
809
+ "const": "stable"
810
+ }
811
+ ]
812
+ },
813
+ "ReadonlyintervalSecnumberstatuscensoredincompleteokunsupportedcompletedCyclesnumberrealizedRewardstringunrealizedRewardstringfinalPointsstringfinalNetWorthstring---9more---resetWallTimesreadonlynumber": {
814
+ type: "object",
815
+ properties: {
816
+ intervalSec: {
817
+ type: "number"
818
+ },
819
+ status: {
820
+ oneOf: [
821
+ {
822
+ "const": "censored"
823
+ },
824
+ {
825
+ "const": "incomplete"
826
+ },
827
+ {
828
+ "const": "ok"
829
+ },
830
+ {
831
+ "const": "unsupported"
832
+ }
833
+ ]
834
+ },
835
+ completedCycles: {
836
+ type: "number"
837
+ },
838
+ realizedReward: {
839
+ type: "string"
840
+ },
841
+ unrealizedReward: {
842
+ type: "string"
843
+ },
844
+ finalPoints: {
845
+ type: "string"
846
+ },
847
+ finalNetWorth: {
848
+ type: "string"
849
+ },
850
+ rewardRate: {
851
+ oneOf: [
852
+ {
853
+ type: "null"
854
+ },
855
+ {
856
+ type: "string"
857
+ }
858
+ ]
859
+ },
860
+ rewardRateDenominator: {
861
+ "const": "completed-cycle-time"
862
+ },
863
+ incomplete: {
864
+ oneOf: [
865
+ {
866
+ type: "null"
867
+ },
868
+ {
869
+ $ref: "#/components/schemas/ReadonlystartSecnumberendSecnumberdurationSecnumber"
870
+ }
871
+ ]
872
+ },
873
+ goalReachSec: {
874
+ oneOf: [
875
+ {
876
+ type: "null"
877
+ },
878
+ {
879
+ type: "number"
880
+ }
881
+ ]
882
+ },
883
+ goalStatus: {
884
+ oneOf: [
885
+ {
886
+ "const": "censored"
887
+ },
888
+ {
889
+ "const": "not-requested"
890
+ },
891
+ {
892
+ "const": "observed"
893
+ }
894
+ ]
895
+ },
896
+ recovery: {
897
+ $ref: "#/components/schemas/ReadonlymetricRecoveryMetricKindstatuscensoredobservedunsupportedsecondsnumbernullthresholdstring"
898
+ },
899
+ stability: {
900
+ $ref: "#/components/schemas/ReadonlykindempiricalprovedSteadyStatefalsestatusEmpiricalStabilityobservedCyclesnumberminObservedCyclesnumberwindowsreadonlynumberabsoluteTolerancestringrelativeTolerancenumberhorizonSecnumber"
901
+ },
902
+ objectiveValue: {
903
+ type: "string"
904
+ },
905
+ objectiveReading: {
906
+ $ref: "#/components/schemas/ObjectiveReading"
907
+ },
908
+ resetWallTimes: {
909
+ type: "array",
910
+ items: {
911
+ type: "number"
912
+ }
913
+ }
914
+ },
915
+ required: [
916
+ "intervalSec",
917
+ "status",
918
+ "completedCycles",
919
+ "realizedReward",
920
+ "unrealizedReward",
921
+ "finalPoints",
922
+ "finalNetWorth",
923
+ "rewardRate",
924
+ "rewardRateDenominator",
925
+ "incomplete",
926
+ "goalReachSec",
927
+ "goalStatus",
928
+ "recovery",
929
+ "stability",
930
+ "objectiveValue",
931
+ "objectiveReading",
932
+ "resetWallTimes"
933
+ ],
934
+ additionalProperties: false
935
+ },
936
+ ReadonlystartSecnumberendSecnumberdurationSecnumber: {
937
+ type: "object",
938
+ properties: {
939
+ startSec: {
940
+ type: "number"
941
+ },
942
+ endSec: {
943
+ type: "number"
944
+ },
945
+ durationSec: {
946
+ type: "number"
947
+ }
948
+ },
949
+ required: [
950
+ "startSec",
951
+ "endSec",
952
+ "durationSec"
953
+ ],
954
+ additionalProperties: false
955
+ },
956
+ ReadonlymetricRecoveryMetricKindstatuscensoredobservedunsupportedsecondsnumbernullthresholdstring: {
957
+ type: "object",
958
+ properties: {
959
+ metric: {
960
+ $ref: "#/components/schemas/RecoveryMetricKind"
961
+ },
962
+ status: {
963
+ oneOf: [
964
+ {
965
+ "const": "censored"
966
+ },
967
+ {
968
+ "const": "observed"
969
+ },
970
+ {
971
+ "const": "unsupported"
972
+ }
973
+ ]
974
+ },
975
+ seconds: {
976
+ oneOf: [
977
+ {
978
+ type: "null"
979
+ },
980
+ {
981
+ type: "number"
982
+ }
983
+ ]
984
+ },
985
+ threshold: {
986
+ type: "string"
987
+ }
988
+ },
989
+ required: [
990
+ "metric",
991
+ "status",
992
+ "seconds",
993
+ "threshold"
994
+ ],
995
+ additionalProperties: false
996
+ },
997
+ RecoveryMetricKind: {
998
+ oneOf: [
999
+ {
1000
+ "const": "pre-reset-income"
1001
+ },
1002
+ {
1003
+ "const": "pre-reset-net-worth"
1004
+ }
1005
+ ]
1006
+ },
1007
+ ReadonlykindempiricalprovedSteadyStatefalsestatusEmpiricalStabilityobservedCyclesnumberminObservedCyclesnumberwindowsreadonlynumberabsoluteTolerancestringrelativeTolerancenumberhorizonSecnumber: {
1008
+ type: "object",
1009
+ properties: {
1010
+ kind: {
1011
+ "const": "empirical"
1012
+ },
1013
+ provedSteadyState: {
1014
+ "const": false
1015
+ },
1016
+ status: {
1017
+ $ref: "#/components/schemas/EmpiricalStability"
1018
+ },
1019
+ observedCycles: {
1020
+ type: "number"
1021
+ },
1022
+ minObservedCycles: {
1023
+ type: "number"
1024
+ },
1025
+ windows: {
1026
+ type: "array",
1027
+ items: {
1028
+ type: "number"
1029
+ }
1030
+ },
1031
+ absoluteTolerance: {
1032
+ type: "string"
1033
+ },
1034
+ relativeTolerance: {
1035
+ type: "number"
1036
+ },
1037
+ horizonSec: {
1038
+ type: "number"
1039
+ }
1040
+ },
1041
+ required: [
1042
+ "kind",
1043
+ "provedSteadyState",
1044
+ "status",
1045
+ "observedCycles",
1046
+ "minObservedCycles",
1047
+ "windows",
1048
+ "absoluteTolerance",
1049
+ "relativeTolerance",
1050
+ "horizonSec"
1051
+ ],
1052
+ additionalProperties: false
1053
+ },
1054
+ ReadonlyfixtureIdstringexpectedRatestringobservedRatestringmatchesboolean: {
1055
+ type: "object",
1056
+ properties: {
1057
+ fixtureId: {
1058
+ type: "string"
1059
+ },
1060
+ expectedRate: {
1061
+ type: "string"
1062
+ },
1063
+ observedRate: {
1064
+ type: "string"
1065
+ },
1066
+ matches: {
1067
+ type: "boolean"
1068
+ }
1069
+ },
1070
+ required: [
1071
+ "fixtureId",
1072
+ "expectedRate",
1073
+ "observedRate",
1074
+ "matches"
1075
+ ],
1076
+ additionalProperties: false
1077
+ }
1078
+ }
1079
+ },
1080
+ schema: {
1081
+ type: "object",
1082
+ properties: {
1083
+ schemaVersion: {
1084
+ "const": 1
1085
+ },
1086
+ algorithmVersion: {
1087
+ "const": "reset-policy-v1"
1088
+ },
1089
+ status: {
1090
+ oneOf: [
1091
+ {
1092
+ "const": "censored"
1093
+ },
1094
+ {
1095
+ "const": "incomplete"
1096
+ },
1097
+ {
1098
+ "const": "insufficient-data"
1099
+ },
1100
+ {
1101
+ "const": "ok"
1102
+ },
1103
+ {
1104
+ "const": "unsupported"
1105
+ }
1106
+ ]
1107
+ },
1108
+ objective: {
1109
+ $ref: "#/components/schemas/ResetObjectiveKind"
1110
+ },
1111
+ scope: {
1112
+ "const": "scan-range"
1113
+ },
1114
+ globalOptimum: {
1115
+ "const": false
1116
+ },
1117
+ best: {
1118
+ $ref: "#/components/schemas/ReadonlyintervalSecnumberobjectiveReadonlykindResetObjectiveKindreadingObjectiveReadingvaluestringcomparisonReadonly---sessionReadonly---observationReadonly---"
1119
+ },
1120
+ candidates: {
1121
+ type: "array",
1122
+ items: {
1123
+ $ref: "#/components/schemas/ReadonlyintervalSecnumberstatuscensoredincompleteokunsupportedcompletedCyclesnumberrealizedRewardstringunrealizedRewardstringfinalPointsstringfinalNetWorthstring---9more---resetWallTimesreadonlynumber"
1124
+ }
1125
+ },
1126
+ knownSolution: {
1127
+ oneOf: [
1128
+ {
1129
+ type: "null"
1130
+ },
1131
+ {
1132
+ $ref: "#/components/schemas/ReadonlyfixtureIdstringexpectedRatestringobservedRatestringmatchesboolean"
1133
+ }
1134
+ ]
1135
+ },
1136
+ assumptions: {
1137
+ type: "array",
1138
+ items: {
1139
+ type: "string"
1140
+ }
1141
+ },
1142
+ warnings: {
1143
+ type: "array",
1144
+ items: {
1145
+ type: "string"
1146
+ }
1147
+ }
1148
+ },
1149
+ required: [
1150
+ "schemaVersion",
1151
+ "algorithmVersion",
1152
+ "status",
1153
+ "objective",
1154
+ "scope",
1155
+ "globalOptimum",
1156
+ "best",
1157
+ "candidates",
1158
+ "knownSolution",
1159
+ "assumptions",
1160
+ "warnings"
1161
+ ],
1162
+ additionalProperties: false
1163
+ },
1164
+ version: "3.1"
1165
+ };
1166
+ }
1167
+ let resetPolicyJsonSchemaUnit;
1168
+ /**
1169
+ * @evidence docs/requirements/active/reset-cycle-analysis.md#req-pr11-scan-rank The structural schema is OpenAPI 3.1 from typia.
1170
+ * @evidenceReview docs/requirements/active/reset-cycle-analysis.md#req-pr11-scan-rank #468d46d Re-read the section: schema version is 3.1.
1171
+ */
1172
+ export function resetPolicyJsonSchema() {
1173
+ resetPolicyJsonSchemaUnit ??= buildResetPolicyJsonSchema();
1174
+ return resetPolicyJsonSchemaUnit;
1175
+ }
1176
+ /** Goal ids this analysis adds to a run's goals, so the runner reports the first committed state that meets them. */
1177
+ const GOAL_WATCH_ID = "reset-policy.goal";
1178
+ const RECOVERY_WATCH_ID = "reset-policy.recovery";
1179
+ function assertFinite(label, value) {
1180
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1181
+ throw new ResetPolicyRejected(`reset policy ${label} must be a finite number`);
1182
+ }
1183
+ }
1184
+ function assertStep(value) {
1185
+ assertFinite("scan.stepSec", value);
1186
+ if (!(value > 0))
1187
+ throw new ResetPolicyRejected("reset policy scan.stepSec must be a finite number > 0");
1188
+ }
1189
+ function scanIntervals(scan) {
1190
+ assertFinite("scan.fromSec", scan.fromSec);
1191
+ assertFinite("scan.toSec", scan.toSec);
1192
+ assertStep(scan.stepSec);
1193
+ if (!(scan.fromSec > 0) || scan.toSec < scan.fromSec) {
1194
+ throw new ResetPolicyRejected("reset policy scan range must be finite and fromSec > 0");
1195
+ }
1196
+ // Intervals ascend from fromSec. One at or below the runner resolution would commit resets without playing time.
1197
+ if (scan.fromSec <= RUNNER_MIN_DURATION_SEC) {
1198
+ throw new ResetPolicyRejected(`reset policy scan.fromSec must be greater than the runner's ${RUNNER_MIN_DURATION_SEC} second resolution`);
1199
+ }
1200
+ const steps = (scan.toSec - scan.fromSec) / scan.stepSec;
1201
+ if (!Number.isFinite(steps))
1202
+ throw new ResetPolicyRejected("reset policy scan range is not finite");
1203
+ // A provisional count bounds the loop. The limit applies to the intervals that survive the upper-bound check.
1204
+ const provisional = Math.floor(steps + 1e-9) + 1;
1205
+ if (!Number.isSafeInteger(provisional) || provisional < 1 || provisional > MAX_CANDIDATES + 1) {
1206
+ throw new ResetPolicyRejected("reset policy scan candidate count must be a safe integer from 1 to 10000");
1207
+ }
1208
+ const intervals = [];
1209
+ for (let index = 0; index < provisional; index += 1) {
1210
+ const interval = scan.fromSec + index * scan.stepSec;
1211
+ // Rounding dust only: a share of a large step could admit an interval well past toSec.
1212
+ const dust = 8 * Number.EPSILON * Math.max(Math.abs(scan.toSec), Math.abs(interval));
1213
+ if (interval > scan.toSec + dust)
1214
+ break;
1215
+ // A step below the spacing of floats at this interval repeats a candidate instead of scanning a new one.
1216
+ const previous = intervals[intervals.length - 1];
1217
+ if (previous !== undefined && !(interval > previous)) {
1218
+ throw new ResetPolicyRejected("reset policy scan.stepSec must advance every scanned interval");
1219
+ }
1220
+ intervals.push(interval);
1221
+ }
1222
+ if (intervals.length === 0)
1223
+ throw new ResetPolicyRejected("reset policy scan produced no candidates");
1224
+ if (intervals.length > MAX_CANDIDATES) {
1225
+ throw new ResetPolicyRejected("reset policy scan candidate count must be a safe integer from 1 to 10000");
1226
+ }
1227
+ return intervals;
1228
+ }
1229
+ const OBJECTIVE_KINDS = ["cumulative-reset-reward", "final-net-worth", "goal-reach-sec"];
1230
+ const RECOVERY_METRICS = ["pre-reset-income", "pre-reset-net-worth"];
1231
+ function assertRequest(request) {
1232
+ if (typeof request.createScenario !== "function") {
1233
+ throw new ResetPolicyRejected("reset policy createScenario must be a function");
1234
+ }
1235
+ // An unchecked caller can pass any string. An unknown kind would fall back to another ranking or metric.
1236
+ if (!OBJECTIVE_KINDS.includes(request.objective)) {
1237
+ throw new ResetPolicyRejected(`reset policy objective must be one of ${OBJECTIVE_KINDS.join(", ")}`);
1238
+ }
1239
+ if (!RECOVERY_METRICS.includes(request.recovery?.metric)) {
1240
+ throw new ResetPolicyRejected(`reset policy recovery.metric must be one of ${RECOVERY_METRICS.join(", ")}`);
1241
+ }
1242
+ scanIntervals(request.scan);
1243
+ assertFinite("horizonSec", request.horizonSec);
1244
+ if (!(request.horizonSec > 0))
1245
+ throw new ResetPolicyRejected("reset policy horizonSec must be a finite number > 0");
1246
+ // Every wall or elapsed time is at most the horizon. When the shortest interval still advances the horizon, it
1247
+ // advances every earlier time too, so no two resets share an instant.
1248
+ if (!(request.horizonSec + request.scan.fromSec > request.horizonSec)) {
1249
+ throw new ResetPolicyRejected("reset policy scan.fromSec must advance a time at horizonSec");
1250
+ }
1251
+ if (!Number.isInteger(request.cycleBudget) || request.cycleBudget < 1) {
1252
+ throw new ResetPolicyRejected("reset policy cycleBudget must be a positive integer");
1253
+ }
1254
+ // A goal-time objective without a goal has nothing to rank.
1255
+ if (request.objective === "goal-reach-sec" && typeof request.goal !== "function") {
1256
+ throw new ResetPolicyRejected("reset policy goal-reach-sec needs a goal predicate");
1257
+ }
1258
+ if (!Number.isInteger(request.stability.minObservedCycles) || request.stability.minObservedCycles < 1) {
1259
+ throw new ResetPolicyRejected("reset policy minObservedCycles must be a positive integer");
1260
+ }
1261
+ if (request.stability.windows.length < 2) {
1262
+ throw new ResetPolicyRejected("reset policy stability requires at least two trailing windows");
1263
+ }
1264
+ for (const window of request.stability.windows) {
1265
+ if (!Number.isInteger(window) || window < 2) {
1266
+ throw new ResetPolicyRejected("reset policy stability windows must be integers >= 2");
1267
+ }
1268
+ }
1269
+ // Two copies of one window inspect the same samples twice. Agreement needs two distinct windows.
1270
+ if (new Set(request.stability.windows).size !== request.stability.windows.length) {
1271
+ throw new ResetPolicyRejected("reset policy stability windows must be distinct");
1272
+ }
1273
+ if (request.stability.absoluteTolerance.trim() === "") {
1274
+ throw new ResetPolicyRejected("reset policy absoluteTolerance must be a non-empty amount");
1275
+ }
1276
+ assertFinite("relativeTolerance", request.stability.relativeTolerance);
1277
+ if (request.stability.relativeTolerance < 0) {
1278
+ throw new ResetPolicyRejected("reset policy relativeTolerance must be >= 0");
1279
+ }
1280
+ if (request.session) {
1281
+ assertFinite("resetDelaySec", request.session.resetDelaySec);
1282
+ // An unknown preset id from an unchecked caller builds no block. That is not an all-offline schedule.
1283
+ if (sessionActiveBlocks(horizonPattern(request)).length === 0) {
1284
+ throw new ResetPolicyRejected(`reset policy session pattern ${String(request.session.pattern.id)} has no active block`);
1285
+ }
1286
+ if (request.session.resetDelaySec < 0) {
1287
+ throw new ResetPolicyRejected("reset policy resetDelaySec must be a finite number >= 0");
1288
+ }
1289
+ }
1290
+ }
1291
+ /**
1292
+ * The pattern's days that can reach the horizon. Every preset has a block on each day, and days past the horizon
1293
+ * play nothing, so a long day count does not expand blocks the analysis never reaches. A schedule is used as given.
1294
+ */
1295
+ function horizonPattern(request) {
1296
+ const pattern = request.session?.pattern;
1297
+ if (!pattern)
1298
+ throw new ResetPolicyRejected("reset policy session is missing");
1299
+ if (pattern.schedule || !Number.isInteger(pattern.days) || pattern.days <= 0)
1300
+ return pattern;
1301
+ return { ...pattern, days: Math.min(pattern.days, Math.floor(request.horizonSec / 86400) + 1) };
1302
+ }
1303
+ /** Blocks inside the horizon, built once per request and shared by every candidate and probe. */
1304
+ const horizonBlockCache = new WeakMap();
1305
+ function horizonBlocks(request) {
1306
+ const cached = horizonBlockCache.get(request);
1307
+ if (cached)
1308
+ return cached;
1309
+ const blocks = sessionActiveBlocks(horizonPattern(request))
1310
+ .map((block) => {
1311
+ const start = block.day * 86400 + block.startOffsetSec;
1312
+ // Playback stops at the horizon, but a reset at the horizon is still inside the block's natural span.
1313
+ const natural = start + block.durationSec;
1314
+ return { start, natural, end: Math.min(natural, request.horizonSec) };
1315
+ })
1316
+ // A block that starts exactly at the horizon still admits a reset at the horizon, inside its natural span.
1317
+ .filter((block) => block.end > block.start || (block.start === request.horizonSec && block.natural > block.start));
1318
+ horizonBlockCache.set(request, blocks);
1319
+ return blocks;
1320
+ }
1321
+ /** The absolute tolerance in the scenario's engine. It must be a finite amount >= 0. */
1322
+ function absoluteToleranceOf(E, text) {
1323
+ let amount;
1324
+ try {
1325
+ amount = E.from(text);
1326
+ }
1327
+ catch {
1328
+ throw new ResetPolicyRejected("reset policy absoluteTolerance must be a finite amount >= 0");
1329
+ }
1330
+ if (!E.isFinite(amount) || E.cmp(amount, E.zero()) < 0) {
1331
+ throw new ResetPolicyRejected("reset policy absoluteTolerance must be a finite amount >= 0");
1332
+ }
1333
+ return amount;
1334
+ }
1335
+ function magnitude(E, value) {
1336
+ return E.cmp(value, E.zero()) < 0 ? E.sub(E.zero(), value) : value;
1337
+ }
1338
+ function within(E, left, right, absolute, relative) {
1339
+ const delta = magnitude(E, E.sub(left, right));
1340
+ if (E.cmp(delta, absolute) <= 0)
1341
+ return true;
1342
+ const leftMag = magnitude(E, left);
1343
+ const rightMag = magnitude(E, right);
1344
+ const scale = E.cmp(leftMag, rightMag) >= 0 ? leftMag : rightMag;
1345
+ return E.cmp(delta, E.mul(scale, relative)) <= 0;
1346
+ }
1347
+ function diffs(E, values) {
1348
+ const out = [];
1349
+ for (let index = 1; index < values.length; index += 1) {
1350
+ const current = values[index];
1351
+ const previous = values[index - 1];
1352
+ if (current === undefined || previous === undefined)
1353
+ continue;
1354
+ out.push(E.sub(current, previous));
1355
+ }
1356
+ return out;
1357
+ }
1358
+ function moves(E, step, base, absolute, relative) {
1359
+ return !within(E, E.add(base, step), base, absolute, relative);
1360
+ }
1361
+ function isOscillating(E, values, absolute, relative) {
1362
+ const steps = diffs(E, values);
1363
+ if (steps.length < 3)
1364
+ return false;
1365
+ let sign = 0;
1366
+ for (let index = 0; index < steps.length; index += 1) {
1367
+ const step = steps[index];
1368
+ const base = values[index];
1369
+ if (step === undefined || base === undefined)
1370
+ return false;
1371
+ if (!moves(E, step, base, absolute, relative))
1372
+ return false;
1373
+ const next = E.cmp(step, E.zero());
1374
+ if (next === 0 || (sign !== 0 && next === sign))
1375
+ return false;
1376
+ sign = next;
1377
+ }
1378
+ return true;
1379
+ }
1380
+ function isDrifting(E, values, absolute, relative) {
1381
+ const steps = diffs(E, values);
1382
+ if (steps.length < 2)
1383
+ return false;
1384
+ let sign = 0;
1385
+ for (let index = 0; index < steps.length; index += 1) {
1386
+ const step = steps[index];
1387
+ const base = values[index];
1388
+ if (step === undefined || base === undefined)
1389
+ return false;
1390
+ if (!moves(E, step, base, absolute, relative))
1391
+ return false;
1392
+ const next = E.cmp(step, E.zero());
1393
+ if (next === 0 || (sign !== 0 && next !== sign))
1394
+ return false;
1395
+ sign = next;
1396
+ }
1397
+ return true;
1398
+ }
1399
+ function stationary(E, values, absolute, relative) {
1400
+ const anchor = values[values.length - 1];
1401
+ if (anchor === undefined)
1402
+ return false;
1403
+ return values.every((value) => within(E, value, anchor, absolute, relative));
1404
+ }
1405
+ function classify(E, reward, condition, spec, absolute) {
1406
+ if (reward.length < spec.minObservedCycles || condition.length < spec.minObservedCycles)
1407
+ return "insufficient-data";
1408
+ const usable = spec.windows.filter((window) => window <= reward.length && window <= condition.length);
1409
+ if (usable.length < 2)
1410
+ return "insufficient-data";
1411
+ const rewardStable = usable.every((window) => stationary(E, reward.slice(-window), absolute, spec.relativeTolerance));
1412
+ const conditionStable = usable.every((window) => stationary(E, condition.slice(-window), absolute, spec.relativeTolerance));
1413
+ if (rewardStable && conditionStable)
1414
+ return "stable";
1415
+ if (isOscillating(E, reward, absolute, spec.relativeTolerance) ||
1416
+ isOscillating(E, condition, absolute, spec.relativeTolerance)) {
1417
+ return "oscillating";
1418
+ }
1419
+ if (isDrifting(E, reward, absolute, spec.relativeTolerance) || isDrifting(E, condition, absolute, spec.relativeTolerance)) {
1420
+ return "drifting";
1421
+ }
1422
+ return "non-stationary";
1423
+ }
1424
+ /** The scenario context with the live constraints, as runScenario builds it for a step. */
1425
+ function liveCtx(scenario, constraints) {
1426
+ return { ...scenario.ctx, ...(constraints ? { constraints } : {}) };
1427
+ }
1428
+ /**
1429
+ * A measurement-only copy: no observer, reset hook, event emitter, or caller goal. A probe run is not the candidate
1430
+ * run, so none of its steps, actions, goals, milestones, or resets reach the caller, and no caller predicate runs.
1431
+ */
1432
+ function silent(scenario) {
1433
+ const { observer: _observer, onPrestigeReset: _onPrestigeReset, goals: _goals, ...run } = scenario.run;
1434
+ const { emit: _emit, ...ctx } = scenario.ctx;
1435
+ return { ...scenario, ctx, run };
1436
+ }
1437
+ /** Net worth read with the context the simulation used: live constraints, and clocks on a schedule. */
1438
+ function netWorth(scenario, state, ctx) {
1439
+ return scenario.model.netWorth?.(ctx, state)?.amount ?? state.wallet.money.amount;
1440
+ }
1441
+ function incomeOf(scenario, state, ctx) {
1442
+ return scenario.model.income(ctx, state).amount;
1443
+ }
1444
+ function recoveryMetric(request, scenario, state, ctx) {
1445
+ return request.recovery.metric === "pre-reset-income" ? incomeOf(scenario, state, ctx) : netWorth(scenario, state, ctx);
1446
+ }
1447
+ /** The adapter where a reset would happen: the request's, or one discovered at this state and context. */
1448
+ function adapterAt(request, scenario, state, constraints, clocks) {
1449
+ const ctx = liveCtx(scenario, constraints);
1450
+ return request.adapter ?? resetAdapterFor(scenario, undefined, state, clocks ? { ...ctx, clocks } : ctx);
1451
+ }
1452
+ function observationShare(scenario) {
1453
+ return {
1454
+ observation: shareObservation({ ...scenario.run.observation, enabled: true }).observation,
1455
+ reachedGoals: new Set(),
1456
+ reachedWatchers: new Set(),
1457
+ };
1458
+ }
1459
+ /**
1460
+ * The scenario a policy run plays: the caller's open goals plus this analysis' open watchers, read by the runner on
1461
+ * every committed state, and an observer that routes a watcher's first crossing back here. Recording is on while a
1462
+ * watcher is open, since the runner reads goals only while it records. A caller that disabled observation keeps it
1463
+ * disabled when no watcher is open, and hears no fact while a watcher forces recording on.
1464
+ */
1465
+ function watched(scenario, watchers, share) {
1466
+ const open = watchers.filter((watcher) => !share.reachedWatchers.has(watcher));
1467
+ const callerDisabled = scenario.run.observation?.enabled === false;
1468
+ if (callerDisabled && open.length === 0)
1469
+ return scenario;
1470
+ // A disabled caller has no goal the runner would read and no observer that hears facts.
1471
+ const callerGoals = callerDisabled ? [] : (scenario.run.goals ?? []).filter((goal) => !share.reachedGoals.has(goal.id));
1472
+ const observer = callerDisabled ? undefined : scenario.run.observer;
1473
+ // A caller goal can use any id. Each watcher takes an id no caller goal uses, so a caller goal is never
1474
+ // routed to a watcher and a watcher is never marked reached by a caller goal.
1475
+ const taken = new Set((scenario.run.goals ?? []).map((goal) => goal.id));
1476
+ const byId = new Map();
1477
+ for (const watcher of open) {
1478
+ let id = watcher.id;
1479
+ for (let suffix = 1; taken.has(id); suffix += 1)
1480
+ id = `${watcher.id}#${suffix}`;
1481
+ taken.add(id);
1482
+ byId.set(id, watcher);
1483
+ }
1484
+ const { observer: _callerObserver, ...run } = scenario.run;
1485
+ return {
1486
+ ...scenario,
1487
+ run: {
1488
+ ...run,
1489
+ observation: share.observation,
1490
+ goals: [...callerGoals, ...[...byId].map(([id, watcher]) => ({ id, met: watcher.met }))],
1491
+ observer: {
1492
+ ...observer,
1493
+ onGoal: (fact) => {
1494
+ const watcher = byId.get(fact.goalId);
1495
+ if (watcher) {
1496
+ // The first crossing is the answer. Later runs of this candidate do not read the predicate again.
1497
+ share.reachedWatchers.add(watcher);
1498
+ watcher.onMet(fact.t);
1499
+ return;
1500
+ }
1501
+ share.reachedGoals.add(fact.goalId);
1502
+ observer?.onGoal?.(fact);
1503
+ },
1504
+ },
1505
+ },
1506
+ };
1507
+ }
1508
+ /**
1509
+ * Read the caller's open goals on a committed boundary reset state, which no runner records, and tell the observer
1510
+ * about each first crossing once. A caller that disabled observation reads no goal.
1511
+ */
1512
+ function readCallerGoals(scenario, state, share) {
1513
+ if (scenario.run.observation?.enabled === false)
1514
+ return;
1515
+ for (const goal of scenario.run.goals ?? []) {
1516
+ if (share.reachedGoals.has(goal.id))
1517
+ continue;
1518
+ if (!goal.met(deepClonePreservingPrototype(state)))
1519
+ continue;
1520
+ share.reachedGoals.add(goal.id);
1521
+ scenario.run.observer?.onGoal?.({ t: state.t, goalId: goal.id });
1522
+ }
1523
+ }
1524
+ /**
1525
+ * Play without a reset. The reset action is hidden from the strategy, as `executeResetCycles` hides it, so only
1526
+ * this analysis commits resets. The run sees the live constraints and, when the model reads them, session clocks.
1527
+ */
1528
+ function play(scenario, state, constraints, adapter, durationSec, until, clocks) {
1529
+ const { maxSteps: _maxSteps, until: _until, trace: _trace, ...runRest } = scenario.run;
1530
+ return runScenario({
1531
+ ...scenario,
1532
+ ...(clocks ? { ctx: { ...scenario.ctx, clocks } } : {}),
1533
+ model: withoutResetActions(scenario.model, adapter),
1534
+ initial: state,
1535
+ ...(constraints ? { constraints } : {}),
1536
+ run: {
1537
+ ...runRest,
1538
+ durationSec,
1539
+ ...(until ? { until } : {}),
1540
+ },
1541
+ });
1542
+ }
1543
+ function commitReset(scenario, state, constraints, adapter, emit, clocks) {
1544
+ const { E } = scenario.ctx;
1545
+ // The adapter and the action see the live constraints, as the reset-cycle executor passes them, and on a
1546
+ // schedule the session clocks of the block the reset happens in.
1547
+ const live = liveCtx(scenario, constraints);
1548
+ const ctx = clocks ? { ...live, clocks } : live;
1549
+ const before = adapter.reward(ctx, state);
1550
+ const action = adapter.action(ctx, state);
1551
+ const kinds = new Set();
1552
+ if (action && (action.kind === "prestige" || action.kind === "custom"))
1553
+ kinds.add(action.kind);
1554
+ const applied = applyDecisions({
1555
+ ctx,
1556
+ model: scenario.model,
1557
+ state,
1558
+ decisions: action ? [{ action }] : [],
1559
+ ...(constraints ? { constraints } : {}),
1560
+ resetKinds: kinds,
1561
+ // The adapter built this action for this state. A spec or custom adapter action is not in model.actions.
1562
+ resolveAgainstModel: false,
1563
+ });
1564
+ if (emit) {
1565
+ // The boundary decision, applied or refused, is an action fact and an emitted event, as in the executor.
1566
+ emitActionFacts(scenario.run.observer, state.t, applied.events);
1567
+ if (applied.events.length > 0)
1568
+ ctx.emit?.(applied.events);
1569
+ }
1570
+ const skipped = applied.events.find((event) => event.type === "action.skipped");
1571
+ const fact = applied.committedResets[0];
1572
+ if (!fact || skipped)
1573
+ return { ok: false, state };
1574
+ let next = applied.state;
1575
+ // stepOnce keeps maxMoneyEver at or above the wallet. The boundary action does not pass through it.
1576
+ if (E.cmp(next.wallet.money.amount, next.maxMoneyEver.amount) > 0)
1577
+ next = { ...next, maxMoneyEver: next.wallet.money };
1578
+ const delta = E.sub(adapter.reward(ctx, next), before);
1579
+ const nextConstraints = action?.kind === "prestige"
1580
+ ? recordPrestigeReset(constraints, fact.t, emit ? scenario.run.onPrestigeReset : undefined)
1581
+ : constraints;
1582
+ if (emit)
1583
+ emitCommittedResets(scenario.run.observer, [{ ...fact, rewardDelta: E.toString(delta) }]);
1584
+ return { ok: true, state: next, ...(nextConstraints ? { constraints: nextConstraints } : {}), delta, t: fact.t };
1585
+ }
1586
+ function sumAmounts(E, amounts) {
1587
+ return amounts.reduce((total, amount) => E.add(total, amount), E.zero());
1588
+ }
1589
+ function stabilityReport(scenario, snaps, request) {
1590
+ const { E } = scenario.ctx;
1591
+ const absolute = absoluteToleranceOf(E, request.stability.absoluteTolerance);
1592
+ const status = classify(E, snaps.map((snap) => E.from(snap.reward)), snaps.map((snap) => E.from(snap.multiplier)), request.stability, absolute);
1593
+ return {
1594
+ kind: "empirical",
1595
+ provedSteadyState: false,
1596
+ status,
1597
+ observedCycles: snaps.length,
1598
+ minObservedCycles: request.stability.minObservedCycles,
1599
+ windows: request.stability.windows,
1600
+ absoluteTolerance: request.stability.absoluteTolerance,
1601
+ relativeTolerance: request.stability.relativeTolerance,
1602
+ horizonSec: request.horizonSec,
1603
+ };
1604
+ }
1605
+ /** Recovery for an always-active candidate: one reset at intervalSec, then play without resets to the horizon. */
1606
+ function recover(request, intervalSec) {
1607
+ // A probe: its runs are measurement only and emit nothing to the caller's observer.
1608
+ const scenario = silent(request.createScenario());
1609
+ const { E } = scenario.ctx;
1610
+ const blank = E.toString(E.zero());
1611
+ // horizonSec is elapsed time from the scenario origin, as executeResetCycles reads it.
1612
+ if (!(intervalSec <= request.horizonSec)) {
1613
+ return { metric: request.recovery.metric, status: "censored", seconds: null, threshold: blank };
1614
+ }
1615
+ const constraints = scenario.constraints;
1616
+ const lead = play(scenario, scenario.initial, constraints, adapterAt(request, scenario, scenario.initial, constraints), intervalSec);
1617
+ const threshold = recoveryMetric(request, scenario, lead.end, liveCtx(scenario, constraints));
1618
+ // Resolve the adapter where the reset happens. A prestige action can appear only after the lead.
1619
+ const adapter = adapterAt(request, scenario, lead.end, constraints);
1620
+ if (!adapter)
1621
+ return { metric: request.recovery.metric, status: "unsupported", seconds: null, threshold: blank };
1622
+ const committed = commitReset(scenario, lead.end, constraints, adapter, false);
1623
+ if (!committed.ok) {
1624
+ return { metric: request.recovery.metric, status: "unsupported", seconds: null, threshold: E.toString(threshold) };
1625
+ }
1626
+ const remaining = request.horizonSec - runElapsedSec(lead);
1627
+ const afterCtx = liveCtx(scenario, committed.constraints);
1628
+ if (!(remaining > 0)) {
1629
+ if (E.cmp(recoveryMetric(request, scenario, committed.state, afterCtx), threshold) >= 0) {
1630
+ return { metric: request.recovery.metric, status: "observed", seconds: 0, threshold: E.toString(threshold) };
1631
+ }
1632
+ return { metric: request.recovery.metric, status: "censored", seconds: null, threshold: E.toString(threshold) };
1633
+ }
1634
+ const search = play(scenario, committed.state, committed.constraints, adapter, remaining, (state) => E.cmp(recoveryMetric(request, scenario, state, afterCtx), threshold) >= 0);
1635
+ if (E.cmp(recoveryMetric(request, scenario, search.end, afterCtx), threshold) < 0) {
1636
+ return { metric: request.recovery.metric, status: "censored", seconds: null, threshold: E.toString(threshold) };
1637
+ }
1638
+ return {
1639
+ metric: request.recovery.metric,
1640
+ status: "observed",
1641
+ // The runner's accumulated duration. Absolute clocks round each addition at a large origin.
1642
+ seconds: runElapsedSec(search),
1643
+ threshold: E.toString(threshold),
1644
+ };
1645
+ }
1646
+ function unrealizedAt(request, scenario, state, constraints, clocks) {
1647
+ // The dry run sees the constraints the committed resets left, so a cooldown still blocks it.
1648
+ const adapter = adapterAt(request, scenario, state, constraints, clocks);
1649
+ if (!adapter)
1650
+ return scenario.ctx.E.zero();
1651
+ const committed = commitReset(scenario, state, constraints, adapter, false, clocks);
1652
+ return committed.ok ? committed.delta : scenario.ctx.E.zero();
1653
+ }
1654
+ function objectiveOf(kind, realized, worth, goalReachSec, goalStatus) {
1655
+ if (kind === "final-net-worth")
1656
+ return { value: worth, reading: "stock" };
1657
+ if (kind === "goal-reach-sec") {
1658
+ return goalStatus === "observed" && goalReachSec !== null
1659
+ ? { value: String(goalReachSec), reading: "time" }
1660
+ : { value: "censored", reading: "time" };
1661
+ }
1662
+ return { value: realized, reading: "flow" };
1663
+ }
1664
+ function candidateFrom(request, scenario, intervalSec, status, snaps, end, incomplete, unrealized, goalReachSec, walls, recovery, endCtx) {
1665
+ const { E } = scenario.ctx;
1666
+ const completed = snaps;
1667
+ const realized = sumAmounts(E, completed.map((snap) => E.from(snap.reward)));
1668
+ const cycleTime = completed.reduce((total, snap) => total + snap.durationSec, 0);
1669
+ const rewardRate = cycleTime > 0 ? E.toString(E.div(realized, cycleTime)) : null;
1670
+ const goalStatus = request.goal
1671
+ ? goalReachSec === null
1672
+ ? "censored"
1673
+ : "observed"
1674
+ : "not-requested";
1675
+ const worth = netWorth(scenario, end, endCtx);
1676
+ const objective = objectiveOf(request.objective, E.toString(realized), E.toString(worth), goalReachSec, goalStatus);
1677
+ return {
1678
+ intervalSec,
1679
+ status,
1680
+ completedCycles: completed.length,
1681
+ realizedReward: E.toString(realized),
1682
+ unrealizedReward: E.toString(unrealized),
1683
+ finalPoints: E.toString(end.prestige.points),
1684
+ finalNetWorth: E.toString(worth),
1685
+ rewardRate,
1686
+ rewardRateDenominator: "completed-cycle-time",
1687
+ incomplete,
1688
+ goalReachSec,
1689
+ goalStatus,
1690
+ recovery,
1691
+ stability: stabilityReport(scenario, completed, request),
1692
+ objectiveValue: objective.value,
1693
+ objectiveReading: objective.reading,
1694
+ resetWallTimes: walls,
1695
+ };
1696
+ }
1697
+ /** A goal watcher that keeps the first reward time the goal holds on a committed state. */
1698
+ function goalWatcher(request, ctxNow, onMet) {
1699
+ const goal = request.goal;
1700
+ if (!goal)
1701
+ return [];
1702
+ return [{ id: GOAL_WATCH_ID, met: (state) => goal(ctxNow(), state), onMet }];
1703
+ }
1704
+ function evaluateAlwaysActive(request, intervalSec) {
1705
+ const scenario = request.createScenario();
1706
+ const { E } = scenario.ctx;
1707
+ absoluteToleranceOf(E, request.stability.absoluteTolerance);
1708
+ const recovery = recover(request, intervalSec);
1709
+ // Goal times are seconds from the scenario origin, as horizonSec is.
1710
+ const originT = scenario.initial.t;
1711
+ const reached = { t: null };
1712
+ let constraints = scenario.constraints;
1713
+ // Constraints change only at a boundary, so the context of the next interval is known before it runs.
1714
+ let metricCtx = liveCtx(scenario, constraints);
1715
+ // The runner reads the goal on every committed state, so a crossing between resets keeps its own time.
1716
+ const watchers = goalWatcher(request, () => metricCtx, (t) => {
1717
+ if (reached.t === null)
1718
+ reached.t = Math.min(t - originT, request.horizonSec);
1719
+ });
1720
+ // Every run of this candidate shares one ledger and the caller goals already reached.
1721
+ const share = observationShare(scenario);
1722
+ const observed = () => watched(scenario, watchers, share);
1723
+ const snaps = [];
1724
+ let resume;
1725
+ let state = scenario.initial;
1726
+ let elapsed = 0;
1727
+ // The span after the last committed reset is not a completed cycle, refused intervals included.
1728
+ // Reset times are elapsed seconds from the scenario origin, as horizonSec and scheduled reset walls are.
1729
+ const resetTimes = [];
1730
+ let elapsedAtLastReset = 0;
1731
+ let refused = false;
1732
+ let unsupported = false;
1733
+ while (snaps.length < request.cycleBudget) {
1734
+ metricCtx = liveCtx(scenario, constraints);
1735
+ // The executor resolves the adapter at each boundary, so a prestige action unlocked during the interval counts.
1736
+ const step = executeResetCycles({
1737
+ scenario: observed(),
1738
+ policy: { kind: "fixed-interval", intervalSec, cycles: 1 },
1739
+ horizonSec: request.horizonSec,
1740
+ ...(request.adapter ? { adapter: request.adapter } : {}),
1741
+ ...(resume ? { resume } : {}),
1742
+ });
1743
+ const row = step.artifact.cycles[0];
1744
+ state = step.state;
1745
+ constraints = step.constraints;
1746
+ resume = step.checkpoint;
1747
+ elapsed = step.artifact.globalElapsedSec;
1748
+ if (step.artifact.status === "unsupported") {
1749
+ unsupported = true;
1750
+ break;
1751
+ }
1752
+ if (!row || row.resetAtSec === null) {
1753
+ refused = row?.resetSkippedReason !== undefined;
1754
+ break;
1755
+ }
1756
+ snaps.push({ reward: row.rewardDelta, durationSec: row.durationSec, multiplier: row.carried.multiplier });
1757
+ // The executor admits a boundary a few ULPs past the horizon. Its position is the horizon.
1758
+ elapsedAtLastReset = Math.min(elapsed, request.horizonSec);
1759
+ resetTimes.push(elapsedAtLastReset);
1760
+ // The executor's interval run recorded the state before the reset. The reset state can meet a caller goal first.
1761
+ readCallerGoals(scenario, state, share);
1762
+ }
1763
+ metricCtx = liveCtx(scenario, constraints);
1764
+ // horizonSec is elapsed time from the scenario origin. The tail is what the cycles did not play.
1765
+ // Only rounding dust of the elapsed sum is skipped, so a long horizon still plays a real remainder.
1766
+ const tail = request.horizonSec - elapsed;
1767
+ if (tail > Math.max(RUNNER_MIN_DURATION_SEC, horizonDust(request.horizonSec, elapsed))) {
1768
+ const played = play(observed(), state, constraints, adapterAt(request, scenario, state, constraints), tail);
1769
+ state = played.end;
1770
+ elapsed += runElapsedSec(played);
1771
+ }
1772
+ const openSec = elapsed - elapsedAtLastReset;
1773
+ const incomplete = openSec > Math.max(RUNNER_MIN_DURATION_SEC, horizonDust(request.horizonSec, elapsed)) ? { startSec: elapsedAtLastReset, endSec: elapsed, durationSec: openSec } : null;
1774
+ // A goal met only by the last committed reset has no later run to read it.
1775
+ // Read on a copy, as the runner reads goals, so a predicate that writes its argument cannot change the result.
1776
+ if (request.goal && reached.t === null && request.goal(metricCtx, deepClonePreservingPrototype(state))) {
1777
+ reached.t = Math.min(state.t - originT, request.horizonSec);
1778
+ }
1779
+ const unrealized = incomplete ? unrealizedAt(request, scenario, state, constraints) : E.zero();
1780
+ const status = unsupported && snaps.length === 0
1781
+ ? "unsupported"
1782
+ : refused || unsupported
1783
+ ? "incomplete"
1784
+ : snaps.length === 0
1785
+ ? "censored"
1786
+ : "ok";
1787
+ return {
1788
+ engine: E,
1789
+ report: candidateFrom(request, scenario, intervalSec, status, snaps, state, incomplete, unrealized, reached.t, resetTimes, recovery, metricCtx),
1790
+ };
1791
+ }
1792
+ /**
1793
+ * Play the session schedule once. Active blocks run with clocks when the model reads them, a manual reset commits
1794
+ * only inside a block, and the gaps between blocks apply the scenario's offline progression, as
1795
+ * `simulateSessionPattern` does. Reaching the cycle budget, a refusal, or a missing adapter stops further resets,
1796
+ * not the schedule, so every candidate is played to the same horizon.
1797
+ */
1798
+ function playSchedule(request, scenario, intervalSec, options) {
1799
+ const session = request.session;
1800
+ if (!session)
1801
+ throw new ResetPolicyRejected("reset policy session is missing");
1802
+ const { E } = scenario.ctx;
1803
+ const originT = scenario.initial.t;
1804
+ const blocks = horizonBlocks(request);
1805
+ let state = scenario.initial;
1806
+ let constraints = scenario.constraints;
1807
+ let wall = 0;
1808
+ let activeSec = 0;
1809
+ let lastResetWall = 0;
1810
+ // Where active play last reached. A reset delay advances wall time but is not active play.
1811
+ let activeReach = 0;
1812
+ let resetsOpen = true;
1813
+ let refused = false;
1814
+ let unsupported = false;
1815
+ const snaps = [];
1816
+ const walls = [];
1817
+ const reached = { t: null };
1818
+ let recovery = null;
1819
+ // The running segment, so a watcher's reward time maps back to wall time. Active play advances both clocks
1820
+ // together. An offline gap credits reward by its cap and decay, so its wall position is the absence that earned it.
1821
+ let segment = {
1822
+ wallStart: 0,
1823
+ rewardStart: state.t,
1824
+ offlineRequested: null,
1825
+ };
1826
+ const wallOf = (t) => {
1827
+ const credited = Math.max(0, t - segment.rewardStart);
1828
+ return (segment.wallStart +
1829
+ (segment.offlineRequested === null
1830
+ ? credited
1831
+ : offlineAbsenceForCredit(credited, segment.offlineRequested, scenario.run.offline)));
1832
+ };
1833
+ const clocksFor = (wallStart, wallEnd) => (scenario.model.clocks?.respondsTo?.length ?? 0) > 0
1834
+ ? { wallT: originT + wallStart, wallEndT: originT + wallEnd, rewardT: state.t, activeT: activeSec }
1835
+ : undefined;
1836
+ // The context a segment starting at this wall time would see. Watchers read the context of the running segment.
1837
+ // Inside an active block the model sees that block's clocks, as simulateSessionPattern plays a block as one segment.
1838
+ // A reset or a delay splits the block for this analysis only, so it does not move the clocks.
1839
+ let blockClocks = null;
1840
+ const ctxAt = (wallEnd) => {
1841
+ const clocks = blockClocks ? blockClocks.clocks : clocksFor(wall, wallEnd);
1842
+ return { ...liveCtx(scenario, constraints), ...(clocks ? { clocks } : {}) };
1843
+ };
1844
+ let segmentCtx = ctxAt(wall);
1845
+ // Segments share one ledger and the caller goals already reached, as simulateSessionPattern's segments do.
1846
+ const share = observationShare(scenario);
1847
+ const goalWatchers = options.watchGoal
1848
+ ? goalWatcher(request, () => segmentCtx, (t) => {
1849
+ if (reached.t === null)
1850
+ reached.t = wallOf(t);
1851
+ })
1852
+ : [];
1853
+ const watchers = [...goalWatchers];
1854
+ // A goal first met on a boundary read closes its watcher too, so later runs do not read the predicate again.
1855
+ const reachGoalAt = (wallAt) => {
1856
+ reached.t = wallAt;
1857
+ for (const watcher of goalWatchers)
1858
+ share.reachedWatchers.add(watcher);
1859
+ };
1860
+ const runActive = (durationSec) => {
1861
+ segmentCtx = ctxAt(wall + durationSec);
1862
+ segment = { wallStart: wall, rewardStart: state.t, offlineRequested: null };
1863
+ const played = play(watched(scenario, watchers, share), state, constraints, adapterAt(request, scenario, state, constraints), durationSec, undefined, blockClocks ? blockClocks.clocks : clocksFor(wall, wall + durationSec));
1864
+ activeSec += runElapsedSec(played);
1865
+ state = played.end;
1866
+ wall += durationSec;
1867
+ activeReach = wall;
1868
+ };
1869
+ const runOffline = (toWall) => {
1870
+ if (!(wall < toWall))
1871
+ return;
1872
+ segmentCtx = ctxAt(toWall);
1873
+ segment = { wallStart: wall, rewardStart: state.t, offlineRequested: toWall - wall };
1874
+ const clocks = clocksFor(wall, toWall);
1875
+ const base = watched(scenario, watchers, share);
1876
+ const offline = applyOfflineSeconds({
1877
+ scenario: {
1878
+ ...base,
1879
+ ...(clocks ? { ctx: { ...base.ctx, clocks } } : {}),
1880
+ model: withoutResetActions(base.model, adapterAt(request, scenario, state, constraints)),
1881
+ ...(constraints ? { constraints } : {}),
1882
+ },
1883
+ seconds: toWall - wall,
1884
+ options: {
1885
+ fromState: state,
1886
+ actions: scenario.run.offline?.actions ?? { mode: "legacy-all" },
1887
+ fast: scenario.run.fast,
1888
+ policy: scenario.run.offline,
1889
+ },
1890
+ });
1891
+ state = offline.end;
1892
+ wall = toWall;
1893
+ };
1894
+ for (const block of blocks) {
1895
+ runOffline(block.start);
1896
+ if (wall < block.start)
1897
+ wall = block.start;
1898
+ blockClocks = { clocks: clocksFor(block.start, block.end) };
1899
+ while (resetsOpen && snaps.length < options.budget) {
1900
+ const next = Math.max(lastResetWall + intervalSec, wall);
1901
+ // A sum of intervals can round a few ULPs past the horizon. That boundary is the horizon, as the executor admits it.
1902
+ if (!(next < block.natural && next <= request.horizonSec + horizonDust(request.horizonSec, next)))
1903
+ break;
1904
+ const at = Math.min(next, request.horizonSec);
1905
+ if (at > wall)
1906
+ runActive(at - wall);
1907
+ const boundaryCtx = ctxAt(block.end);
1908
+ const adapter = adapterAt(request, scenario, state, constraints, boundaryCtx.clocks);
1909
+ if (!adapter) {
1910
+ unsupported = true;
1911
+ resetsOpen = false;
1912
+ break;
1913
+ }
1914
+ const threshold = recoveryMetric(request, scenario, state, boundaryCtx);
1915
+ // With no runner since the previous reset, as after a delay that covers the interval, the boundary state has
1916
+ // not been read at this wall time. A clock-aware goal can first hold here, before the reset changes the state.
1917
+ const boundaryGoal = request.goal;
1918
+ if (options.watchGoal && boundaryGoal && reached.t === null && boundaryGoal(boundaryCtx, deepClonePreservingPrototype(state))) {
1919
+ reachGoalAt(at);
1920
+ }
1921
+ const committed = commitReset(scenario, state, constraints, adapter, options.emit, boundaryCtx.clocks);
1922
+ if (!committed.ok) {
1923
+ refused = true;
1924
+ resetsOpen = false;
1925
+ break;
1926
+ }
1927
+ // Reset eligibility and the horizon are wall time, so a cycle lasts from the previous reset wall to this one,
1928
+ // reset delay and capped offline time included.
1929
+ snaps.push({
1930
+ reward: E.toString(committed.delta),
1931
+ durationSec: at - lastResetWall,
1932
+ multiplier: E.toString(committed.state.prestige.multiplier),
1933
+ });
1934
+ state = committed.state;
1935
+ constraints = committed.constraints;
1936
+ walls.push(at);
1937
+ activeReach = at;
1938
+ // The reset is the latest segment of the block. With no runner after it, as at a horizon-start block, the final
1939
+ // reads keep the block's clocks.
1940
+ segmentCtx = ctxAt(block.end);
1941
+ // The reset state can be the first to meet a caller goal. A reset at the horizon has no later run to read it.
1942
+ if (options.emit)
1943
+ readCallerGoals(scenario, state, share);
1944
+ // Two resets can follow each other with no runner in between when the delay covers the interval, so the
1945
+ // goal is read on the committed reset state too, on a copy as the runner reads it.
1946
+ const goal = request.goal;
1947
+ if (options.watchGoal && goal && reached.t === null && goal(ctxAt(block.end), deepClonePreservingPrototype(state))) {
1948
+ reachGoalAt(at);
1949
+ }
1950
+ if (options.watchRecovery && recovery === null) {
1951
+ const found = { threshold, resetWall: at, crossedWall: null };
1952
+ recovery = found;
1953
+ if (E.cmp(recoveryMetric(request, scenario, state, ctxAt(block.end)), threshold) >= 0)
1954
+ found.crossedWall = at;
1955
+ else {
1956
+ watchers.push({
1957
+ id: RECOVERY_WATCH_ID,
1958
+ met: (next) => E.cmp(recoveryMetric(request, scenario, next, segmentCtx), threshold) >= 0,
1959
+ onMet: (t) => {
1960
+ if (found.crossedWall === null)
1961
+ found.crossedWall = wallOf(t);
1962
+ },
1963
+ });
1964
+ }
1965
+ }
1966
+ lastResetWall = at;
1967
+ // The caller's delay advances wall time and grants no income.
1968
+ wall = at + session.resetDelaySec;
1969
+ }
1970
+ if (wall < block.end)
1971
+ runActive(block.end - wall);
1972
+ blockClocks = null;
1973
+ }
1974
+ const activeWallEnd = activeReach;
1975
+ // The rest of the horizon is away time and applies the offline progression.
1976
+ runOffline(request.horizonSec);
1977
+ // A reset delay can carry the wall cursor past the horizon. The final reads happen at the horizon.
1978
+ if (wall > request.horizonSec)
1979
+ wall = request.horizonSec;
1980
+ // The final reads see the clocks of the last segment played, the active block's when the horizon falls inside one,
1981
+ // with the constraints the resets left.
1982
+ const endClocks = segmentCtx.clocks;
1983
+ const endCtx = { ...liveCtx(scenario, constraints), ...(endClocks ? { clocks: endClocks } : {}) };
1984
+ const goal = request.goal;
1985
+ if (options.watchGoal && goal && reached.t === null && goal(endCtx, deepClonePreservingPrototype(state))) {
1986
+ reachGoalAt(wall);
1987
+ }
1988
+ // A delay past the horizon leaves no runner after the reset, so an open recovery crossing is read here too.
1989
+ const openRecovery = recovery;
1990
+ if (openRecovery &&
1991
+ openRecovery.crossedWall === null &&
1992
+ E.cmp(recoveryMetric(request, scenario, state, endCtx), openRecovery.threshold) >= 0) {
1993
+ openRecovery.crossedWall = wall;
1994
+ }
1995
+ return {
1996
+ snaps,
1997
+ walls,
1998
+ state,
1999
+ constraints,
2000
+ refused,
2001
+ unsupported,
2002
+ activeWallEnd,
2003
+ lastResetWall: walls.length > 0 ? lastResetWall : 0,
2004
+ endCtx,
2005
+ goalReachSec: reached.t,
2006
+ recovery,
2007
+ };
2008
+ }
2009
+ function evaluateSession(request, intervalSec) {
2010
+ const scenario = request.createScenario();
2011
+ const { E } = scenario.ctx;
2012
+ absoluteToleranceOf(E, request.stability.absoluteTolerance);
2013
+ // Recovery is measured on this candidate's own schedule: its first committed reset, then no further resets.
2014
+ // The probe is measurement only: its runs emit nothing to the caller's observer.
2015
+ const probe = playSchedule(request, silent(request.createScenario()), intervalSec, {
2016
+ budget: 1,
2017
+ emit: false,
2018
+ watchRecovery: true,
2019
+ watchGoal: false,
2020
+ });
2021
+ const blank = E.toString(E.zero());
2022
+ const recovery = probe.recovery
2023
+ ? {
2024
+ metric: request.recovery.metric,
2025
+ status: probe.recovery.crossedWall === null ? "censored" : "observed",
2026
+ // Wall seconds, as the interval and horizon are, so a reset delay or a capped absence counts in full.
2027
+ seconds: probe.recovery.crossedWall === null ? null : probe.recovery.crossedWall - probe.recovery.resetWall,
2028
+ threshold: E.toString(probe.recovery.threshold),
2029
+ }
2030
+ : {
2031
+ metric: request.recovery.metric,
2032
+ status: probe.refused || probe.unsupported ? "unsupported" : "censored",
2033
+ seconds: null,
2034
+ threshold: blank,
2035
+ };
2036
+ const outcome = playSchedule(request, scenario, intervalSec, {
2037
+ budget: request.cycleBudget,
2038
+ emit: true,
2039
+ watchRecovery: false,
2040
+ watchGoal: true,
2041
+ });
2042
+ // The span after the last committed reset is not a completed cycle, as on the always-active path.
2043
+ const incomplete = outcome.lastResetWall + horizonDust(request.horizonSec, outcome.lastResetWall) < request.horizonSec
2044
+ ? { startSec: outcome.lastResetWall, endSec: request.horizonSec, durationSec: request.horizonSec - outcome.lastResetWall }
2045
+ : null;
2046
+ // A manual reset needs active play at the horizon. An offline span or a reset delay there has nothing to realize,
2047
+ // and a reset committed at the horizon leaves no incomplete span to realize after.
2048
+ const activeAtHorizon = outcome.activeWallEnd + horizonDust(request.horizonSec, outcome.activeWallEnd) >= request.horizonSec;
2049
+ const unrealized = incomplete && activeAtHorizon
2050
+ ? unrealizedAt(request, scenario, outcome.state, outcome.constraints, outcome.endCtx.clocks)
2051
+ : E.zero();
2052
+ const status = outcome.unsupported && outcome.snaps.length === 0
2053
+ ? "unsupported"
2054
+ : outcome.refused || outcome.unsupported
2055
+ ? "incomplete"
2056
+ : outcome.snaps.length === 0
2057
+ ? "censored"
2058
+ : "ok";
2059
+ return {
2060
+ engine: E,
2061
+ report: candidateFrom(request, scenario, intervalSec, status, outcome.snaps, outcome.state, incomplete, unrealized, outcome.goalReachSec, outcome.walls, recovery, outcome.endCtx),
2062
+ };
2063
+ }
2064
+ function better(kind, left, right, E) {
2065
+ if (left.status === "unsupported")
2066
+ return false;
2067
+ if (right.status === "unsupported")
2068
+ return true;
2069
+ if (kind === "goal-reach-sec") {
2070
+ if (left.goalStatus !== "observed" || left.goalReachSec === null)
2071
+ return false;
2072
+ if (right.goalStatus !== "observed" || right.goalReachSec === null)
2073
+ return true;
2074
+ return left.goalReachSec < right.goalReachSec;
2075
+ }
2076
+ if (left.objectiveValue === "censored")
2077
+ return false;
2078
+ if (right.objectiveValue === "censored")
2079
+ return true;
2080
+ return E.cmp(E.from(left.objectiveValue), E.from(right.objectiveValue)) > 0;
2081
+ }
2082
+ /**
2083
+ * Compare reset intervals on one horizon.
2084
+ * Each candidate starts from `createScenario`. Completed resets stay separate from the trailing uncommitted span.
2085
+ *
2086
+ * @evidence docs/requirements/active/reset-cycle-analysis.md#req-pr11-reward-rate The comparison ranks committed reward and leaves the tail unrealized.
2087
+ * @evidenceReview docs/requirements/active/reset-cycle-analysis.md#req-pr11-reward-rate #9ef48a3 Re-read the section: four resets realize 4, and the unfinished span does not join that total.
2088
+ */
2089
+ export function analyzeResetPolicies(request) {
2090
+ assertRequest(request);
2091
+ const intervals = scanIntervals(request.scan);
2092
+ const evaluated = intervals.map((interval) => request.session ? evaluateSession(request, interval) : evaluateAlwaysActive(request, interval));
2093
+ const first = evaluated[0];
2094
+ if (!first)
2095
+ throw new ResetPolicyRejected("reset policy scan produced no candidates");
2096
+ let bestIndex = 0;
2097
+ for (let index = 1; index < evaluated.length; index += 1) {
2098
+ const candidate = evaluated[index];
2099
+ const winner = evaluated[bestIndex];
2100
+ if (!candidate || !winner)
2101
+ continue;
2102
+ if (better(request.objective, candidate.report, winner.report, first.engine))
2103
+ bestIndex = index;
2104
+ }
2105
+ const bestEval = evaluated[bestIndex] ?? first;
2106
+ const bestReport = bestEval.report;
2107
+ const known = request.knownSolution
2108
+ ? {
2109
+ fixtureId: request.knownSolution.fixtureId,
2110
+ expectedRate: request.knownSolution.expectedRate,
2111
+ observedRate: bestReport.rewardRate ?? "",
2112
+ matches: bestReport.rewardRate !== null &&
2113
+ bestEval.engine.cmp(bestEval.engine.from(bestReport.rewardRate), bestEval.engine.from(request.knownSolution.expectedRate)) ===
2114
+ 0,
2115
+ }
2116
+ : null;
2117
+ const assumptions = [
2118
+ "realized reward sums committed reset deltas and excludes stock already held",
2119
+ "reward rate divides that sum by completed cycle time, not by stock or the horizon",
2120
+ "unrealized reward is the uncommitted reset after the incomplete span and is not ranked",
2121
+ "recovery seconds are the first threshold crossing inside the horizon, otherwise censored",
2122
+ "each candidate starts from createScenario",
2123
+ "empirical window agreement is not a proof of a steady state",
2124
+ ];
2125
+ if (request.session) {
2126
+ assumptions.push("manual reset is committed only inside an active session block");
2127
+ assumptions.push("resetDelaySec is the caller input, advances wall time, and grants no income");
2128
+ assumptions.push("offline gaps apply the scenario's offline progression and do not commit a reset");
2129
+ assumptions.push("the cycle budget or a refused reset stops further resets, not the schedule");
2130
+ }
2131
+ const censoredGoal = request.objective === "goal-reach-sec" && bestReport.goalStatus === "censored";
2132
+ // A winner that stopped on a refusal did not finish its requested evaluation, so the artifact is not ok.
2133
+ const status = evaluated.every((entry) => entry.report.status === "unsupported")
2134
+ ? "unsupported"
2135
+ : bestReport.status === "censored" || censoredGoal
2136
+ ? "censored"
2137
+ : bestReport.status === "incomplete"
2138
+ ? "incomplete"
2139
+ : bestReport.stability.status === "insufficient-data"
2140
+ ? "insufficient-data"
2141
+ : "ok";
2142
+ return {
2143
+ schemaVersion: 1,
2144
+ algorithmVersion: RESET_POLICY_ALGORITHM_VERSION,
2145
+ status,
2146
+ objective: request.objective,
2147
+ scope: "scan-range",
2148
+ globalOptimum: false,
2149
+ best: {
2150
+ intervalSec: bestReport.intervalSec,
2151
+ objective: {
2152
+ kind: request.objective,
2153
+ reading: bestReport.objectiveReading,
2154
+ value: bestReport.objectiveValue,
2155
+ },
2156
+ comparison: {
2157
+ fromSec: request.scan.fromSec,
2158
+ toSec: request.scan.toSec,
2159
+ stepSec: request.scan.stepSec,
2160
+ horizonSec: request.horizonSec,
2161
+ candidateCount: evaluated.length,
2162
+ scope: "scan-range",
2163
+ globalOptimum: false,
2164
+ },
2165
+ session: {
2166
+ assumed: request.session ? "scheduled" : "always-active",
2167
+ patternId: request.session?.pattern.id ?? null,
2168
+ resetDelaySec: request.session?.resetDelaySec ?? null,
2169
+ },
2170
+ observation: {
2171
+ status: bestReport.stability.status,
2172
+ stability: bestReport.stability.status,
2173
+ provedSteadyState: false,
2174
+ recovery: bestReport.recovery.status,
2175
+ },
2176
+ },
2177
+ candidates: evaluated.map((entry) => entry.report),
2178
+ knownSolution: known,
2179
+ assumptions,
2180
+ warnings: [
2181
+ "legacy breakEvenSec and stability are not this artifact",
2182
+ "empirical window agreement is not a mathematical steady state",
2183
+ "the best candidate is the scan-range winner, not a global optimum",
2184
+ ],
2185
+ };
2186
+ }
2187
+ //# sourceMappingURL=resetOptimization.js.map