@series-inc/rundot-syncplay 5.23.0-beta.7

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 (264) hide show
  1. package/README.md +610 -0
  2. package/dist/animation.d.ts +119 -0
  3. package/dist/animation.js +488 -0
  4. package/dist/asset-cooking.d.ts +82 -0
  5. package/dist/asset-cooking.js +364 -0
  6. package/dist/authority-room.d.ts +217 -0
  7. package/dist/authority-room.js +701 -0
  8. package/dist/bot-documents.d.ts +115 -0
  9. package/dist/bot-documents.js +549 -0
  10. package/dist/bot-primitives.d.ts +152 -0
  11. package/dist/bot-primitives.js +472 -0
  12. package/dist/browser.d.ts +57 -0
  13. package/dist/browser.js +53 -0
  14. package/dist/canonical.d.ts +12 -0
  15. package/dist/canonical.js +288 -0
  16. package/dist/certification.d.ts +455 -0
  17. package/dist/certification.js +1855 -0
  18. package/dist/chaos-certification.d.ts +56 -0
  19. package/dist/chaos-certification.js +1397 -0
  20. package/dist/cjs/animation.js +495 -0
  21. package/dist/cjs/asset-cooking.js +372 -0
  22. package/dist/cjs/authority-room.js +704 -0
  23. package/dist/cjs/bot-documents.js +556 -0
  24. package/dist/cjs/bot-primitives.js +482 -0
  25. package/dist/cjs/browser.js +96 -0
  26. package/dist/cjs/canonical.js +299 -0
  27. package/dist/cjs/certification.js +1860 -0
  28. package/dist/cjs/chaos-certification.js +1402 -0
  29. package/dist/cjs/collections.js +36 -0
  30. package/dist/cjs/collision.js +96 -0
  31. package/dist/cjs/commands.js +987 -0
  32. package/dist/cjs/config-bundle.js +491 -0
  33. package/dist/cjs/creator.js +30 -0
  34. package/dist/cjs/deterministic-primitives.js +134 -0
  35. package/dist/cjs/deterministic-session.js +1410 -0
  36. package/dist/cjs/ecs-lite.js +155 -0
  37. package/dist/cjs/effects-adapter.js +97 -0
  38. package/dist/cjs/engine-complete.js +2297 -0
  39. package/dist/cjs/engine-core-proof.js +70 -0
  40. package/dist/cjs/engine-parity-matrix.js +874 -0
  41. package/dist/cjs/engine-parity.js +3183 -0
  42. package/dist/cjs/engine-runtime.js +3141 -0
  43. package/dist/cjs/errors.js +22 -0
  44. package/dist/cjs/events.js +247 -0
  45. package/dist/cjs/examples/counter-duel.js +157 -0
  46. package/dist/cjs/examples/fixed-arena.js +368 -0
  47. package/dist/cjs/index.js +229 -0
  48. package/dist/cjs/input-authority.js +150 -0
  49. package/dist/cjs/input-button.js +256 -0
  50. package/dist/cjs/input-timeline.js +418 -0
  51. package/dist/cjs/instant-replay.js +66 -0
  52. package/dist/cjs/lag-compensation3d.js +837 -0
  53. package/dist/cjs/local-authority-server-runner.js +1121 -0
  54. package/dist/cjs/local-authority-wire.js +1497 -0
  55. package/dist/cjs/local-authority.js +817 -0
  56. package/dist/cjs/local-ws-adapter.js +150 -0
  57. package/dist/cjs/match-log.js +61 -0
  58. package/dist/cjs/math.js +157 -0
  59. package/dist/cjs/mock-network-adapter.js +26 -0
  60. package/dist/cjs/movement.js +703 -0
  61. package/dist/cjs/movement3d.js +1246 -0
  62. package/dist/cjs/multiclient-authority.js +308 -0
  63. package/dist/cjs/multiplayer-service.js +434 -0
  64. package/dist/cjs/navigation.js +347 -0
  65. package/dist/cjs/navmesh.js +1437 -0
  66. package/dist/cjs/networked-client.js +691 -0
  67. package/dist/cjs/networked-input-authority.js +443 -0
  68. package/dist/cjs/node.js +47 -0
  69. package/dist/cjs/number-lab.js +78 -0
  70. package/dist/cjs/offline-session.js +370 -0
  71. package/dist/cjs/package.json +1 -0
  72. package/dist/cjs/pause-resume.js +43 -0
  73. package/dist/cjs/physics-cert.js +426 -0
  74. package/dist/cjs/physics2d.js +751 -0
  75. package/dist/cjs/physics3d.js +1996 -0
  76. package/dist/cjs/prediction-culling.js +203 -0
  77. package/dist/cjs/protocol-lab.js +1299 -0
  78. package/dist/cjs/r3f-render-adapter.js +209 -0
  79. package/dist/cjs/random.js +59 -0
  80. package/dist/cjs/replay-bundle.js +1598 -0
  81. package/dist/cjs/replay-inspect.js +236 -0
  82. package/dist/cjs/replay-scoring.js +68 -0
  83. package/dist/cjs/replay.js +356 -0
  84. package/dist/cjs/reports.js +181 -0
  85. package/dist/cjs/rollback-history.js +251 -0
  86. package/dist/cjs/runtime-guards.js +111 -0
  87. package/dist/cjs/sample-lab.js +145 -0
  88. package/dist/cjs/sample-scenes.js +752 -0
  89. package/dist/cjs/schema-artifacts.js +359 -0
  90. package/dist/cjs/schema-codegen.js +544 -0
  91. package/dist/cjs/schema-dsl-binary.js +655 -0
  92. package/dist/cjs/schema.js +319 -0
  93. package/dist/cjs/sdk-offline.js +99 -0
  94. package/dist/cjs/session-promotion.js +74 -0
  95. package/dist/cjs/session-snapshot.js +265 -0
  96. package/dist/cjs/session-wire.js +216 -0
  97. package/dist/cjs/signals.js +103 -0
  98. package/dist/cjs/sparse-set.js +169 -0
  99. package/dist/cjs/synctest.js +136 -0
  100. package/dist/cjs/system-lifecycle.js +286 -0
  101. package/dist/cjs/timers.js +61 -0
  102. package/dist/cjs/tools/static-checker.js +1468 -0
  103. package/dist/cjs/types.js +2 -0
  104. package/dist/cjs/venus-mp-budget.js +125 -0
  105. package/dist/cjs/ws-frame.js +255 -0
  106. package/dist/cli.d.ts +2 -0
  107. package/dist/cli.js +11379 -0
  108. package/dist/collections.d.ts +9 -0
  109. package/dist/collections.js +30 -0
  110. package/dist/collision.d.ts +25 -0
  111. package/dist/collision.js +85 -0
  112. package/dist/commands.d.ts +137 -0
  113. package/dist/commands.js +977 -0
  114. package/dist/config-bundle.d.ts +89 -0
  115. package/dist/config-bundle.js +483 -0
  116. package/dist/creator.d.ts +20 -0
  117. package/dist/creator.js +11 -0
  118. package/dist/deterministic-primitives.d.ts +59 -0
  119. package/dist/deterministic-primitives.js +126 -0
  120. package/dist/deterministic-session.d.ts +197 -0
  121. package/dist/deterministic-session.js +1401 -0
  122. package/dist/ecs-lite.d.ts +26 -0
  123. package/dist/ecs-lite.js +152 -0
  124. package/dist/effects-adapter.d.ts +21 -0
  125. package/dist/effects-adapter.js +94 -0
  126. package/dist/engine-complete.d.ts +45 -0
  127. package/dist/engine-complete.js +2292 -0
  128. package/dist/engine-core-proof.d.ts +26 -0
  129. package/dist/engine-core-proof.js +66 -0
  130. package/dist/engine-parity-matrix.d.ts +300 -0
  131. package/dist/engine-parity-matrix.js +869 -0
  132. package/dist/engine-parity.d.ts +74 -0
  133. package/dist/engine-parity.js +3178 -0
  134. package/dist/engine-runtime.d.ts +351 -0
  135. package/dist/engine-runtime.js +3123 -0
  136. package/dist/errors.d.ts +11 -0
  137. package/dist/errors.js +17 -0
  138. package/dist/events.d.ts +45 -0
  139. package/dist/events.js +242 -0
  140. package/dist/examples/counter-duel.d.ts +43 -0
  141. package/dist/examples/counter-duel.js +150 -0
  142. package/dist/examples/fixed-arena.d.ts +110 -0
  143. package/dist/examples/fixed-arena.js +356 -0
  144. package/dist/index.d.ts +101 -0
  145. package/dist/index.js +66 -0
  146. package/dist/input-authority.d.ts +82 -0
  147. package/dist/input-authority.js +147 -0
  148. package/dist/input-button.d.ts +36 -0
  149. package/dist/input-button.js +249 -0
  150. package/dist/input-timeline.d.ts +65 -0
  151. package/dist/input-timeline.js +412 -0
  152. package/dist/instant-replay.d.ts +59 -0
  153. package/dist/instant-replay.js +62 -0
  154. package/dist/lag-compensation3d.d.ts +146 -0
  155. package/dist/lag-compensation3d.js +827 -0
  156. package/dist/local-authority-server-runner.d.ts +79 -0
  157. package/dist/local-authority-server-runner.js +1118 -0
  158. package/dist/local-authority-wire.d.ts +142 -0
  159. package/dist/local-authority-wire.js +1477 -0
  160. package/dist/local-authority.d.ts +160 -0
  161. package/dist/local-authority.js +810 -0
  162. package/dist/local-ws-adapter.d.ts +14 -0
  163. package/dist/local-ws-adapter.js +146 -0
  164. package/dist/match-log.d.ts +74 -0
  165. package/dist/match-log.js +57 -0
  166. package/dist/math.d.ts +41 -0
  167. package/dist/math.js +154 -0
  168. package/dist/mock-network-adapter.d.ts +13 -0
  169. package/dist/mock-network-adapter.js +23 -0
  170. package/dist/movement.d.ts +155 -0
  171. package/dist/movement.js +694 -0
  172. package/dist/movement3d.d.ts +195 -0
  173. package/dist/movement3d.js +1238 -0
  174. package/dist/multiclient-authority.d.ts +30 -0
  175. package/dist/multiclient-authority.js +305 -0
  176. package/dist/multiplayer-service.d.ts +153 -0
  177. package/dist/multiplayer-service.js +429 -0
  178. package/dist/navigation.d.ts +91 -0
  179. package/dist/navigation.js +338 -0
  180. package/dist/navmesh.d.ts +236 -0
  181. package/dist/navmesh.js +1423 -0
  182. package/dist/networked-client.d.ts +124 -0
  183. package/dist/networked-client.js +688 -0
  184. package/dist/networked-input-authority.d.ts +51 -0
  185. package/dist/networked-input-authority.js +439 -0
  186. package/dist/node.d.ts +22 -0
  187. package/dist/node.js +19 -0
  188. package/dist/number-lab.d.ts +15 -0
  189. package/dist/number-lab.js +75 -0
  190. package/dist/offline-session.d.ts +2 -0
  191. package/dist/offline-session.js +367 -0
  192. package/dist/pause-resume.d.ts +48 -0
  193. package/dist/pause-resume.js +40 -0
  194. package/dist/physics-cert.d.ts +40 -0
  195. package/dist/physics-cert.js +389 -0
  196. package/dist/physics2d.d.ts +142 -0
  197. package/dist/physics2d.js +741 -0
  198. package/dist/physics3d.d.ts +285 -0
  199. package/dist/physics3d.js +1979 -0
  200. package/dist/prediction-culling.d.ts +34 -0
  201. package/dist/prediction-culling.js +200 -0
  202. package/dist/protocol-lab.d.ts +115 -0
  203. package/dist/protocol-lab.js +1295 -0
  204. package/dist/r3f-render-adapter.d.ts +61 -0
  205. package/dist/r3f-render-adapter.js +203 -0
  206. package/dist/random.d.ts +13 -0
  207. package/dist/random.js +55 -0
  208. package/dist/replay-bundle.d.ts +150 -0
  209. package/dist/replay-bundle.js +1586 -0
  210. package/dist/replay-inspect.d.ts +60 -0
  211. package/dist/replay-inspect.js +228 -0
  212. package/dist/replay-scoring.d.ts +28 -0
  213. package/dist/replay-scoring.js +65 -0
  214. package/dist/replay.d.ts +17 -0
  215. package/dist/replay.js +351 -0
  216. package/dist/reports.d.ts +176 -0
  217. package/dist/reports.js +174 -0
  218. package/dist/rollback-history.d.ts +46 -0
  219. package/dist/rollback-history.js +247 -0
  220. package/dist/runtime-guards.d.ts +1 -0
  221. package/dist/runtime-guards.js +108 -0
  222. package/dist/sample-lab.d.ts +32 -0
  223. package/dist/sample-lab.js +134 -0
  224. package/dist/sample-scenes.d.ts +86 -0
  225. package/dist/sample-scenes.js +745 -0
  226. package/dist/schema-artifacts.d.ts +49 -0
  227. package/dist/schema-artifacts.js +356 -0
  228. package/dist/schema-codegen.d.ts +21 -0
  229. package/dist/schema-codegen.js +538 -0
  230. package/dist/schema-dsl-binary.d.ts +75 -0
  231. package/dist/schema-dsl-binary.js +648 -0
  232. package/dist/schema.d.ts +87 -0
  233. package/dist/schema.js +315 -0
  234. package/dist/sdk-offline.d.ts +34 -0
  235. package/dist/sdk-offline.js +96 -0
  236. package/dist/session-promotion.d.ts +26 -0
  237. package/dist/session-promotion.js +70 -0
  238. package/dist/session-snapshot.d.ts +95 -0
  239. package/dist/session-snapshot.js +255 -0
  240. package/dist/session-wire.d.ts +89 -0
  241. package/dist/session-wire.js +211 -0
  242. package/dist/signals.d.ts +31 -0
  243. package/dist/signals.js +99 -0
  244. package/dist/sparse-set.d.ts +34 -0
  245. package/dist/sparse-set.js +165 -0
  246. package/dist/synctest.d.ts +31 -0
  247. package/dist/synctest.js +133 -0
  248. package/dist/system-lifecycle.d.ts +72 -0
  249. package/dist/system-lifecycle.js +281 -0
  250. package/dist/timers.d.ts +17 -0
  251. package/dist/timers.js +53 -0
  252. package/dist/tools/index.d.ts +4 -0
  253. package/dist/tools/index.js +4 -0
  254. package/dist/tools/static-checker.d.ts +31 -0
  255. package/dist/tools/static-checker.js +1462 -0
  256. package/dist/tools/vite-plugin.d.ts +69 -0
  257. package/dist/tools/vite-plugin.js +203 -0
  258. package/dist/types.d.ts +148 -0
  259. package/dist/types.js +1 -0
  260. package/dist/venus-mp-budget.d.ts +29 -0
  261. package/dist/venus-mp-budget.js +122 -0
  262. package/dist/ws-frame.d.ts +49 -0
  263. package/dist/ws-frame.js +247 -0
  264. package/package.json +169 -0
@@ -0,0 +1,115 @@
1
+ import { type DeterministicBehaviorTreeNode, type DeterministicBotBlackboard, type DeterministicHfsmDefinition, type DeterministicUtilityConsideration } from './bot-primitives.js';
2
+ export interface DeterministicBotDocument {
3
+ readonly id: string;
4
+ readonly version: 1;
5
+ readonly blackboard?: readonly {
6
+ readonly key: string;
7
+ readonly value: boolean | number | string;
8
+ }[];
9
+ readonly hfsm?: DeterministicHfsmDefinition;
10
+ readonly behaviorTree?: DeterministicBehaviorTreeNode;
11
+ readonly utility?: readonly DeterministicUtilityConsideration[];
12
+ readonly assetRefs?: readonly string[];
13
+ }
14
+ export interface CompiledDeterministicBotDocument {
15
+ readonly id: string;
16
+ readonly hash: string;
17
+ readonly bytes: string;
18
+ readonly blackboard: DeterministicBotBlackboard;
19
+ readonly debugMetadata: {
20
+ readonly stateCount: number;
21
+ readonly behaviorNodeCount: number;
22
+ readonly utilityCount: number;
23
+ readonly assetRefs: readonly string[];
24
+ };
25
+ }
26
+ export interface DeterministicBotDocumentTrace {
27
+ readonly frame: number;
28
+ readonly slot: number;
29
+ readonly hfsmState?: string;
30
+ readonly hfsmTransitionKey?: string;
31
+ readonly behaviorStatus?: string;
32
+ readonly behaviorActions: readonly string[];
33
+ readonly behaviorServiceActions: readonly string[];
34
+ readonly behaviorServiceNodeIds: readonly string[];
35
+ readonly visitedBehaviorNodes: readonly string[];
36
+ readonly resumedBehaviorNodes: readonly string[];
37
+ readonly behaviorRunningNodes: readonly string[];
38
+ readonly utilityAction?: string;
39
+ readonly utilityConsiderationId?: string;
40
+ readonly utilityScoredConsiderationIds: readonly string[];
41
+ readonly utilityScores: readonly {
42
+ readonly id: string;
43
+ readonly score: number;
44
+ readonly baseScore: number;
45
+ readonly nestedScore: number;
46
+ readonly momentumScore: number;
47
+ readonly cooldownPenalty: number;
48
+ }[];
49
+ readonly checksum: string;
50
+ }
51
+ export interface DeterministicBotDocumentTraceCoverage {
52
+ readonly declaredModelKinds: readonly string[];
53
+ readonly executedModelKinds: readonly string[];
54
+ readonly hfsmTransitionTicks: number;
55
+ readonly behaviorActionTicks: number;
56
+ readonly behaviorServiceTicks: number;
57
+ readonly utilitySelectionTicks: number;
58
+ readonly utilityCurveTicks: number;
59
+ readonly utilityNestedTicks: number;
60
+ readonly utilityMomentumTicks: number;
61
+ readonly utilityCooldownTicks: number;
62
+ readonly assetRefCount: number;
63
+ readonly declaredHfsmStates: readonly string[];
64
+ readonly visitedHfsmStates: readonly string[];
65
+ readonly unvisitedHfsmStates: readonly string[];
66
+ readonly declaredHfsmTransitions: readonly string[];
67
+ readonly coveredHfsmTransitions: readonly string[];
68
+ readonly uncoveredHfsmTransitions: readonly string[];
69
+ readonly declaredBehaviorNodes: readonly string[];
70
+ readonly visitedBehaviorNodes: readonly string[];
71
+ readonly unvisitedBehaviorNodes: readonly string[];
72
+ readonly declaredUtilityConsiderations: readonly string[];
73
+ readonly scoredUtilityConsiderations: readonly string[];
74
+ readonly unscoredUtilityConsiderations: readonly string[];
75
+ readonly declaredModelCoverageComplete: boolean;
76
+ readonly fullModelCoverageComplete: boolean;
77
+ readonly advancedCoverageComplete: boolean;
78
+ readonly advancedCoverageFailures: readonly string[];
79
+ readonly failures: readonly string[];
80
+ }
81
+ export interface DeterministicBotDocumentTraceCounterDelta {
82
+ readonly transitions: number;
83
+ readonly utilitySelections: number;
84
+ readonly behaviorRunningTicks: number;
85
+ readonly behaviorResumeTicks: number;
86
+ readonly behaviorDecoratorTicks: number;
87
+ readonly behaviorServiceTicks: number;
88
+ readonly utilityCurveSelections: number;
89
+ readonly utilityNestedSelections: number;
90
+ readonly utilityMomentumSelections: number;
91
+ readonly utilityCooldownSuppressions: number;
92
+ }
93
+ export declare function compileDeterministicBotDocument(document: DeterministicBotDocument, validAssetRefs?: readonly string[]): CompiledDeterministicBotDocument;
94
+ export declare function traceDeterministicBotDocument(document: DeterministicBotDocument, slots: readonly number[], frames: number, validAssetRefs?: readonly string[]): readonly DeterministicBotDocumentTrace[];
95
+ export declare function summarizeDeterministicBotDocumentTraceCoverage(document: DeterministicBotDocument, traces: readonly DeterministicBotDocumentTrace[]): DeterministicBotDocumentTraceCoverage;
96
+ export declare function runDeterministicBotDocumentFixture(players: number, frames: number, document?: DeterministicBotDocument): {
97
+ readonly checksum: string;
98
+ readonly compiledAssetHash: string;
99
+ readonly blackboardHash: string;
100
+ readonly frames: number;
101
+ readonly transitions: number;
102
+ readonly ticks: number;
103
+ readonly utilitySelections: number;
104
+ readonly behaviorRunningTicks: number;
105
+ readonly behaviorResumeTicks: number;
106
+ readonly behaviorDecoratorTicks: number;
107
+ readonly behaviorServiceTicks: number;
108
+ readonly utilityCurveSelections: number;
109
+ readonly utilityNestedSelections: number;
110
+ readonly utilityMomentumSelections: number;
111
+ readonly utilityCooldownSuppressions: number;
112
+ readonly botReplacementSlots: number;
113
+ readonly debugTraceCount: number;
114
+ };
115
+ export declare function summarizeDeterministicBotDocumentTraceCounterDelta(trace: Pick<DeterministicBotDocumentTrace, 'hfsmState' | 'utilityAction' | 'behaviorStatus' | 'resumedBehaviorNodes' | 'visitedBehaviorNodes' | 'behaviorServiceActions' | 'utilityScores'>): DeterministicBotDocumentTraceCounterDelta;
@@ -0,0 +1,549 @@
1
+ import { canonicalStringify, defaultChecksum } from './canonical.js';
2
+ import { cookDeterministicBotAsset, createDeterministicBotBlackboard, selectDeterministicUtility, tickDeterministicBehaviorTreeWithMemory, tickDeterministicHfsm, } from './bot-primitives.js';
3
+ export function compileDeterministicBotDocument(document, validAssetRefs = []) {
4
+ validateBotDocument(document, validAssetRefs);
5
+ const normalized = {
6
+ id: document.id,
7
+ version: document.version,
8
+ blackboard: createDeterministicBotBlackboard(document.blackboard ?? []),
9
+ cooked: cookDeterministicBotAsset({
10
+ id: document.id,
11
+ hfsm: document.hfsm,
12
+ behaviorTree: document.behaviorTree,
13
+ utility: document.utility,
14
+ }),
15
+ assetRefs: [...(document.assetRefs ?? [])].sort(compareStrings),
16
+ };
17
+ const bytes = canonicalStringify(normalized);
18
+ return {
19
+ id: document.id,
20
+ hash: defaultChecksum(bytes),
21
+ bytes,
22
+ blackboard: normalized.blackboard,
23
+ debugMetadata: {
24
+ stateCount: document.hfsm?.states.length ?? 0,
25
+ behaviorNodeCount: document.behaviorTree === undefined ? 0 : countBehaviorNodes(document.behaviorTree),
26
+ utilityCount: document.utility?.length ?? 0,
27
+ assetRefs: normalized.assetRefs,
28
+ },
29
+ };
30
+ }
31
+ export function traceDeterministicBotDocument(document, slots, frames, validAssetRefs = document.assetRefs ?? []) {
32
+ const compiled = compileDeterministicBotDocument(document, validAssetRefs);
33
+ const traces = [];
34
+ const stateBySlot = new Map([...slots].sort(compareNumbers).map((slot) => [slot, createBotDocumentRuntimeState(compiled.blackboard, document.hfsm)]));
35
+ for (let frame = 0; frame < frames; frame += 1) {
36
+ for (const slot of [...slots].sort((left, right) => left - right)) {
37
+ const runtimeState = stateBySlot.get(slot);
38
+ let blackboard = advanceBotDocumentBlackboard(runtimeState.blackboard, frame);
39
+ let hfsm = runtimeState.hfsm;
40
+ const hfsmTransitionKey = document.hfsm === undefined || hfsm === undefined
41
+ ? undefined
42
+ : selectBotDocumentHfsmTransitionKey(document.hfsm, hfsm, blackboard);
43
+ if (document.hfsm !== undefined && hfsm !== undefined) {
44
+ const advanced = tickDeterministicHfsm(document.hfsm, hfsm, blackboard);
45
+ hfsm = {
46
+ ...advanced,
47
+ actionLog: advanced.actionLog.slice(-4),
48
+ };
49
+ }
50
+ const behavior = document.behaviorTree === undefined
51
+ ? emptyBotDocumentBehaviorTick(runtimeState.behaviorMemory)
52
+ : tickDeterministicBehaviorTreeWithMemory(document.behaviorTree, blackboard, runtimeState.behaviorMemory);
53
+ const utility = document.utility === undefined ? undefined : selectDeterministicUtility(document.utility, blackboard);
54
+ traces.push({
55
+ frame,
56
+ slot,
57
+ hfsmState: hfsm?.state,
58
+ hfsmTransitionKey,
59
+ behaviorStatus: behavior.status,
60
+ behaviorActions: behavior.actions,
61
+ behaviorServiceActions: behavior.serviceActions,
62
+ behaviorServiceNodeIds: behavior.serviceNodeIds,
63
+ visitedBehaviorNodes: behavior.visitedNodeIds,
64
+ resumedBehaviorNodes: behavior.resumedNodeIds,
65
+ behaviorRunningNodes: behavior.memory.runningNodeIds,
66
+ utilityAction: utility?.action,
67
+ utilityConsiderationId: utility?.considerationId,
68
+ utilityScoredConsiderationIds: utility?.scores.map((score) => score.id).sort(compareStrings) ?? [],
69
+ utilityScores: utility?.scores ?? [],
70
+ checksum: defaultChecksum({ frame, slot, hfsm, behavior, utility }),
71
+ });
72
+ stateBySlot.set(slot, { blackboard, hfsm, behaviorMemory: behavior.memory });
73
+ }
74
+ }
75
+ return traces;
76
+ }
77
+ export function summarizeDeterministicBotDocumentTraceCoverage(document, traces) {
78
+ const declaredModelKinds = [
79
+ ...(document.hfsm === undefined ? [] : ['hfsm']),
80
+ ...(document.behaviorTree === undefined ? [] : ['behavior-tree']),
81
+ ...(document.utility === undefined ? [] : ['utility']),
82
+ ];
83
+ const hfsmTransitionTicks = document.hfsm === undefined
84
+ ? 0
85
+ : traces.filter((trace) => trace.hfsmState !== undefined && trace.hfsmState !== document.hfsm?.initialState).length;
86
+ const behaviorActionTicks = traces.filter((trace) => trace.behaviorActions.length > 0).length;
87
+ const behaviorServiceTicks = traces.filter((trace) => trace.behaviorServiceActions.length > 0).length;
88
+ const utilitySelectionTicks = traces.filter((trace) => trace.utilityAction !== undefined).length;
89
+ const utilityCurveTicks = traces.filter((trace) => trace.utilityScores.some((score) => score.baseScore !== 0)).length;
90
+ const utilityNestedTicks = traces.filter((trace) => trace.utilityScores.some((score) => score.nestedScore !== 0)).length;
91
+ const utilityMomentumTicks = traces.filter((trace) => trace.utilityScores.some((score) => score.momentumScore !== 0)).length;
92
+ const utilityCooldownTicks = traces.filter((trace) => trace.utilityScores.some((score) => score.cooldownPenalty > 0)).length;
93
+ const declaredHfsmStates = [...(document.hfsm?.states.map((state) => state.id) ?? [])].sort(compareStrings);
94
+ const visitedHfsmStates = [...new Set([
95
+ ...(document.hfsm === undefined ? [] : [document.hfsm.initialState]),
96
+ ...traces.map((trace) => trace.hfsmState).filter(isDefinedString),
97
+ ])].sort(compareStrings);
98
+ const unvisitedHfsmStates = declaredHfsmStates.filter((state) => !visitedHfsmStates.includes(state));
99
+ const declaredHfsmTransitions = [...(document.hfsm?.transitions.map(botDocumentHfsmTransitionKey) ?? [])].sort(compareStrings);
100
+ const coveredHfsmTransitions = [...new Set(traces.map((trace) => trace.hfsmTransitionKey).filter(isDefinedString))].sort(compareStrings);
101
+ const uncoveredHfsmTransitions = declaredHfsmTransitions.filter((transition) => !coveredHfsmTransitions.includes(transition));
102
+ const declaredBehaviorNodes = document.behaviorTree === undefined ? [] : [...collectBehaviorNodeIds(document.behaviorTree)].sort(compareStrings);
103
+ const visitedBehaviorNodes = [...new Set(traces.flatMap((trace) => trace.visitedBehaviorNodes))].sort(compareStrings);
104
+ const unvisitedBehaviorNodes = declaredBehaviorNodes.filter((node) => !visitedBehaviorNodes.includes(node));
105
+ const declaredUtilityConsiderations = [...(document.utility?.map((consideration) => consideration.id) ?? [])].sort(compareStrings);
106
+ const scoredUtilityConsiderations = [...new Set(traces.flatMap((trace) => trace.utilityScoredConsiderationIds))].sort(compareStrings);
107
+ const unscoredUtilityConsiderations = declaredUtilityConsiderations.filter((consideration) => !scoredUtilityConsiderations.includes(consideration));
108
+ const declaresBehaviorServices = document.behaviorTree !== undefined && collectBehaviorServices(document.behaviorTree).length > 0;
109
+ const declaresUtilityCurves = (document.utility ?? []).some((consideration) => consideration.curve !== undefined && consideration.curve !== 'linear');
110
+ const declaresUtilityNested = (document.utility ?? []).some((consideration) => (consideration.nested?.length ?? 0) > 0);
111
+ const declaresUtilityMomentum = (document.utility ?? []).some((consideration) => consideration.momentumKey !== undefined);
112
+ const declaresUtilityCooldown = (document.utility ?? []).some((consideration) => consideration.cooldownKey !== undefined);
113
+ const assetRefCount = document.assetRefs?.length ?? 0;
114
+ const advancedCoverageFailures = [
115
+ ...(declaresBehaviorServices && behaviorServiceTicks === 0 ? ['behavior services declared but none executed'] : []),
116
+ ...(declaresUtilityNested && utilityNestedTicks === 0 ? ['nested utility considerations declared but none affected scoring'] : []),
117
+ ...(declaresUtilityMomentum && utilityMomentumTicks === 0 ? ['utility momentum declared but none affected scoring'] : []),
118
+ ...(declaresUtilityCooldown && utilityCooldownTicks === 0 ? ['utility cooldowns declared but none suppressed scoring'] : []),
119
+ ];
120
+ const executedModelKinds = [
121
+ ...(hfsmTransitionTicks > 0 ? ['hfsm'] : []),
122
+ ...(behaviorActionTicks > 0 ? ['behavior-tree'] : []),
123
+ ...(utilitySelectionTicks > 0 ? ['utility'] : []),
124
+ ];
125
+ const failures = [
126
+ ...(declaredModelKinds.length === 0 ? ['bot document declares no executable model'] : []),
127
+ ...(document.hfsm !== undefined && hfsmTransitionTicks === 0 ? ['HFSM declared but no transition executed'] : []),
128
+ ...(document.behaviorTree !== undefined && behaviorActionTicks === 0 ? ['behavior tree declared but no action executed'] : []),
129
+ ...(declaresBehaviorServices && behaviorServiceTicks === 0 ? ['behavior services declared but none executed'] : []),
130
+ ...(document.utility !== undefined && utilitySelectionTicks === 0 ? ['utility model declared but no selection executed'] : []),
131
+ ...(declaresUtilityCurves && utilityCurveTicks === 0 ? ['utility curves declared but none affected scoring'] : []),
132
+ ...(declaresUtilityNested && utilityNestedTicks === 0 ? ['nested utility considerations declared but none affected scoring'] : []),
133
+ ...(declaresUtilityMomentum && utilityMomentumTicks === 0 ? ['utility momentum declared but none affected scoring'] : []),
134
+ ...(declaresUtilityCooldown && utilityCooldownTicks === 0 ? ['utility cooldowns declared but none suppressed scoring'] : []),
135
+ ...unvisitedHfsmStates.map((state) => `unvisited HFSM state ${state}`),
136
+ ...uncoveredHfsmTransitions.map((transition) => `uncovered HFSM transition ${transition}`),
137
+ ...unvisitedBehaviorNodes.map((node) => `unvisited behavior node ${node}`),
138
+ ...unscoredUtilityConsiderations.map((consideration) => `unscored utility consideration ${consideration}`),
139
+ ...(assetRefCount === 0 ? ['bot document has no asset-backed action refs'] : []),
140
+ ];
141
+ const hfsmCoverageComplete = document.hfsm === undefined
142
+ || (unvisitedHfsmStates.length === 0 && uncoveredHfsmTransitions.length === 0);
143
+ const behaviorCoverageComplete = document.behaviorTree === undefined || unvisitedBehaviorNodes.length === 0;
144
+ const utilityCoverageComplete = document.utility === undefined || unscoredUtilityConsiderations.length === 0;
145
+ const advancedBehaviorCoverageComplete = !declaresBehaviorServices || behaviorServiceTicks > 0;
146
+ const advancedUtilityCoverageComplete = (!declaresUtilityCurves || utilityCurveTicks > 0)
147
+ && (!declaresUtilityNested || utilityNestedTicks > 0)
148
+ && (!declaresUtilityMomentum || utilityMomentumTicks > 0)
149
+ && (!declaresUtilityCooldown || utilityCooldownTicks > 0);
150
+ return {
151
+ declaredModelKinds,
152
+ executedModelKinds,
153
+ hfsmTransitionTicks,
154
+ behaviorActionTicks,
155
+ behaviorServiceTicks,
156
+ utilitySelectionTicks,
157
+ utilityCurveTicks,
158
+ utilityNestedTicks,
159
+ utilityMomentumTicks,
160
+ utilityCooldownTicks,
161
+ assetRefCount,
162
+ declaredHfsmStates,
163
+ visitedHfsmStates,
164
+ unvisitedHfsmStates,
165
+ declaredHfsmTransitions,
166
+ coveredHfsmTransitions,
167
+ uncoveredHfsmTransitions,
168
+ declaredBehaviorNodes,
169
+ visitedBehaviorNodes,
170
+ unvisitedBehaviorNodes,
171
+ declaredUtilityConsiderations,
172
+ scoredUtilityConsiderations,
173
+ unscoredUtilityConsiderations,
174
+ declaredModelCoverageComplete: declaredModelKinds.length > 0
175
+ && declaredModelKinds.every((kind) => executedModelKinds.includes(kind)),
176
+ fullModelCoverageComplete: hfsmTransitionTicks > 0
177
+ && behaviorActionTicks > 0
178
+ && utilitySelectionTicks > 0
179
+ && hfsmCoverageComplete
180
+ && behaviorCoverageComplete
181
+ && utilityCoverageComplete
182
+ && advancedBehaviorCoverageComplete
183
+ && advancedUtilityCoverageComplete
184
+ && assetRefCount > 0,
185
+ advancedCoverageComplete: advancedCoverageFailures.length === 0,
186
+ advancedCoverageFailures,
187
+ failures,
188
+ };
189
+ }
190
+ export function runDeterministicBotDocumentFixture(players, frames, document = defaultBotDocument()) {
191
+ const compiled = compileDeterministicBotDocument(document, document.assetRefs ?? []);
192
+ const botSlots = Array.from({ length: players }, (_, slot) => slot).filter((slot) => slot % 4 !== 0);
193
+ const stateBySlot = new Map(botSlots.map((slot) => [slot, createBotDocumentRuntimeState(compiled.blackboard, document.hfsm)]));
194
+ let checksum = defaultChecksum({ id: document.id, frames, botSlots });
195
+ let transitions = 0;
196
+ let ticks = 0;
197
+ let utilitySelections = 0;
198
+ let behaviorRunningTicks = 0;
199
+ let behaviorResumeTicks = 0;
200
+ let behaviorDecoratorTicks = 0;
201
+ let behaviorServiceTicks = 0;
202
+ let utilityCurveSelections = 0;
203
+ let utilityNestedSelections = 0;
204
+ let utilityMomentumSelections = 0;
205
+ let utilityCooldownSuppressions = 0;
206
+ for (let frame = 0; frame < frames; frame += 1) {
207
+ for (const slot of botSlots) {
208
+ const runtimeState = stateBySlot.get(slot);
209
+ let blackboard = advanceBotDocumentBlackboard(runtimeState.blackboard, frame);
210
+ let hfsm = runtimeState.hfsm;
211
+ if (document.hfsm !== undefined && hfsm !== undefined) {
212
+ const advanced = tickDeterministicHfsm(document.hfsm, hfsm, blackboard);
213
+ hfsm = {
214
+ ...advanced,
215
+ actionLog: advanced.actionLog.slice(-4),
216
+ };
217
+ }
218
+ const behavior = document.behaviorTree === undefined
219
+ ? emptyBotDocumentBehaviorTick(runtimeState.behaviorMemory)
220
+ : tickDeterministicBehaviorTreeWithMemory(document.behaviorTree, blackboard, runtimeState.behaviorMemory);
221
+ const utility = document.utility === undefined ? undefined : selectDeterministicUtility(document.utility, blackboard);
222
+ const trace = {
223
+ frame,
224
+ slot,
225
+ hfsmState: hfsm?.state,
226
+ behaviorStatus: behavior.status,
227
+ behaviorActions: behavior.actions,
228
+ behaviorServiceActions: behavior.serviceActions,
229
+ behaviorServiceNodeIds: behavior.serviceNodeIds,
230
+ visitedBehaviorNodes: behavior.visitedNodeIds,
231
+ resumedBehaviorNodes: behavior.resumedNodeIds,
232
+ behaviorRunningNodes: behavior.memory.runningNodeIds,
233
+ utilityAction: utility?.action,
234
+ utilityConsiderationId: utility?.considerationId,
235
+ utilityScoredConsiderationIds: utility?.scores.map((score) => score.id).sort(compareStrings) ?? [],
236
+ utilityScores: utility?.scores ?? [],
237
+ };
238
+ checksum = defaultChecksum({ checksum, trace });
239
+ const counterDelta = summarizeDeterministicBotDocumentTraceCounterDelta(trace);
240
+ transitions += counterDelta.transitions;
241
+ ticks += 1;
242
+ utilitySelections += counterDelta.utilitySelections;
243
+ behaviorRunningTicks += counterDelta.behaviorRunningTicks;
244
+ behaviorResumeTicks += counterDelta.behaviorResumeTicks;
245
+ behaviorDecoratorTicks += counterDelta.behaviorDecoratorTicks;
246
+ behaviorServiceTicks += counterDelta.behaviorServiceTicks;
247
+ utilityCurveSelections += counterDelta.utilityCurveSelections;
248
+ utilityNestedSelections += counterDelta.utilityNestedSelections;
249
+ utilityMomentumSelections += counterDelta.utilityMomentumSelections;
250
+ utilityCooldownSuppressions += counterDelta.utilityCooldownSuppressions;
251
+ stateBySlot.set(slot, { blackboard, hfsm, behaviorMemory: behavior.memory });
252
+ }
253
+ }
254
+ return {
255
+ checksum,
256
+ compiledAssetHash: compiled.hash,
257
+ blackboardHash: compiled.blackboard.hash,
258
+ frames,
259
+ transitions,
260
+ ticks,
261
+ utilitySelections,
262
+ behaviorRunningTicks,
263
+ behaviorResumeTicks,
264
+ behaviorDecoratorTicks,
265
+ behaviorServiceTicks,
266
+ utilityCurveSelections,
267
+ utilityNestedSelections,
268
+ utilityMomentumSelections,
269
+ utilityCooldownSuppressions,
270
+ botReplacementSlots: botSlots.length,
271
+ debugTraceCount: ticks,
272
+ };
273
+ }
274
+ export function summarizeDeterministicBotDocumentTraceCounterDelta(trace) {
275
+ return {
276
+ transitions: trace.hfsmState === 'attack' ? 1 : 0,
277
+ utilitySelections: trace.utilityAction === undefined ? 0 : 1,
278
+ behaviorRunningTicks: trace.behaviorStatus === 'running' ? 1 : 0,
279
+ behaviorResumeTicks: trace.resumedBehaviorNodes.length > 0 ? 1 : 0,
280
+ behaviorDecoratorTicks: trace.visitedBehaviorNodes.includes('maybe-patrol') ? 1 : 0,
281
+ behaviorServiceTicks: trace.behaviorServiceActions.length > 0 ? 1 : 0,
282
+ utilityCurveSelections: trace.utilityScores.some((score) => score.baseScore !== 0) ? 1 : 0,
283
+ utilityNestedSelections: trace.utilityScores.some((score) => score.nestedScore !== 0) ? 1 : 0,
284
+ utilityMomentumSelections: trace.utilityScores.some((score) => score.momentumScore !== 0) ? 1 : 0,
285
+ utilityCooldownSuppressions: trace.utilityScores.some((score) => score.cooldownPenalty > 0) ? 1 : 0,
286
+ };
287
+ }
288
+ function createBotDocumentRuntimeState(blackboard, hfsmDefinition) {
289
+ return {
290
+ blackboard,
291
+ hfsm: hfsmDefinition === undefined
292
+ ? undefined
293
+ : { state: hfsmDefinition.initialState, transitionsTaken: 0, actionLog: [] },
294
+ behaviorMemory: { runningNodeIds: [], completedRunningNodeIds: [] },
295
+ };
296
+ }
297
+ function emptyBotDocumentBehaviorTick(memory) {
298
+ return {
299
+ status: 'success',
300
+ actions: [],
301
+ serviceActions: [],
302
+ serviceNodeIds: [],
303
+ visitedNodeIds: [],
304
+ memory,
305
+ resumedNodeIds: [],
306
+ };
307
+ }
308
+ function advanceBotDocumentBlackboard(blackboard, frame) {
309
+ return createDeterministicBotBlackboard(blackboard.entries.map((entry) => {
310
+ if (entry.key === 'frame') {
311
+ return { key: entry.key, value: frame };
312
+ }
313
+ if (entry.key === 'attackCooldown') {
314
+ return { key: entry.key, value: frame % 5 === 0 ? 2 : 0 };
315
+ }
316
+ if (entry.key === 'lastActionAttack') {
317
+ return { key: entry.key, value: frame % 3 === 0 };
318
+ }
319
+ if (typeof entry.value !== 'number') {
320
+ return entry;
321
+ }
322
+ return { key: entry.key, value: entry.value + (frame % 2) };
323
+ }));
324
+ }
325
+ function selectBotDocumentHfsmTransitionKey(definition, agent, blackboard) {
326
+ const states = new Map(definition.states.map((state) => [state.id, state]));
327
+ const current = states.get(agent.state);
328
+ /* istanbul ignore if -- validated/ticked HFSM agents cannot hold undeclared states. */
329
+ if (current === undefined) {
330
+ throw new Error(`Unknown HFSM state ${agent.state}`);
331
+ }
332
+ const transition = definition.transitions
333
+ .filter((candidate) => candidate.from === agent.state || candidate.from === current.parent)
334
+ .sort(compareBotDocumentHfsmTransitions)
335
+ .find((candidate) => botDocumentBlackboardValue(blackboard, candidate.whenKey) === candidate.equals);
336
+ return transition === undefined ? undefined : botDocumentHfsmTransitionKey(transition);
337
+ }
338
+ function botDocumentBlackboardValue(blackboard, key) {
339
+ return blackboard.entries.find((entry) => entry.key === key)?.value;
340
+ }
341
+ function botDocumentHfsmTransitionKey(transition) {
342
+ return `${transition.from}->${transition.to}|when:${transition.whenKey}=${typeof transition.equals}:${String(transition.equals)}`;
343
+ }
344
+ function compareBotDocumentHfsmTransitions(left, right) {
345
+ return compareStrings(`${left.from}:${left.whenKey}:${left.to}`, `${right.from}:${right.whenKey}:${right.to}`);
346
+ }
347
+ function validateBotDocument(document, validAssetRefs) {
348
+ if (document.version !== 1) {
349
+ throw new Error(`Unsupported bot document version ${document.version}`);
350
+ }
351
+ const knownRefs = new Set(validAssetRefs);
352
+ const refs = [...(document.assetRefs ?? [])].sort(compareStrings);
353
+ for (const ref of refs) {
354
+ if (!knownRefs.has(ref)) {
355
+ throw new Error(`Unknown bot asset ref ${ref}`);
356
+ }
357
+ }
358
+ const blackboardKeys = new Set(createDeterministicBotBlackboard(document.blackboard ?? []).entries.map((entry) => entry.key));
359
+ if (document.hfsm !== undefined) {
360
+ const stateIds = uniqueIds(document.hfsm.states.map((state) => state.id), 'HFSM state');
361
+ if (!stateIds.has(document.hfsm.initialState)) {
362
+ throw new Error(`Invalid HFSM initial state ${document.hfsm.initialState}`);
363
+ }
364
+ for (const state of document.hfsm.states) {
365
+ if (state.parent !== undefined && !stateIds.has(state.parent)) {
366
+ throw new Error(`Invalid HFSM parent ${state.parent} for state ${state.id}`);
367
+ }
368
+ assertAcyclicHfsmParent(document.hfsm.states, state.id);
369
+ }
370
+ uniqueIds(document.hfsm.transitions.map((transition) => `${transition.from}->${transition.to}:${transition.whenKey}:${String(transition.equals)}`), 'HFSM transition');
371
+ for (const transition of document.hfsm.transitions) {
372
+ if (!stateIds.has(transition.from) || !stateIds.has(transition.to)) {
373
+ throw new Error(`Invalid HFSM transition ${transition.from}->${transition.to}`);
374
+ }
375
+ assertKnownBlackboardKey(blackboardKeys, transition.whenKey, `HFSM transition ${transition.from}->${transition.to}`);
376
+ }
377
+ }
378
+ if (document.behaviorTree !== undefined) {
379
+ uniqueIds(collectBehaviorNodeIds(document.behaviorTree), 'behavior node');
380
+ uniqueIds(collectBehaviorServices(document.behaviorTree).map((service) => service.id), 'behavior service');
381
+ validateBehaviorTreeBlackboardRefs(document.behaviorTree, blackboardKeys);
382
+ validateBehaviorTreeServices(document.behaviorTree);
383
+ }
384
+ if (document.utility !== undefined) {
385
+ if (document.utility.length === 0) {
386
+ throw new Error('Utility document requires at least one consideration');
387
+ }
388
+ uniqueIds(document.utility.map((consideration) => consideration.id), 'utility consideration');
389
+ for (const consideration of document.utility) {
390
+ assertKnownBlackboardKey(blackboardKeys, consideration.inputKey, `utility consideration ${consideration.id}`);
391
+ if (consideration.momentumKey !== undefined) {
392
+ assertKnownBlackboardKey(blackboardKeys, consideration.momentumKey, `utility momentum ${consideration.id}`);
393
+ }
394
+ if (consideration.cooldownKey !== undefined) {
395
+ assertKnownBlackboardKey(blackboardKeys, consideration.cooldownKey, `utility cooldown ${consideration.id}`);
396
+ }
397
+ uniqueIds((consideration.nested ?? []).map((nested) => nested.id), `nested utility consideration ${consideration.id}`);
398
+ for (const nested of consideration.nested ?? []) {
399
+ assertKnownBlackboardKey(blackboardKeys, nested.inputKey, `nested utility consideration ${consideration.id}.${nested.id}`);
400
+ }
401
+ }
402
+ }
403
+ uniqueIds(refs, 'bot asset ref');
404
+ }
405
+ function assertAcyclicHfsmParent(states, stateId) {
406
+ const byId = new Map(states.map((state) => [state.id, state]));
407
+ const seen = new Set();
408
+ let current = byId.get(stateId);
409
+ while (current?.parent !== undefined) {
410
+ if (seen.has(current.parent)) {
411
+ throw new Error(`Cyclic HFSM parent hierarchy at ${stateId}`);
412
+ }
413
+ seen.add(current.parent);
414
+ current = byId.get(current.parent);
415
+ }
416
+ }
417
+ function validateBehaviorTreeBlackboardRefs(node, blackboardKeys) {
418
+ if (node.type === 'condition') {
419
+ assertKnownBlackboardKey(blackboardKeys, node.key, `behavior condition ${node.id}`);
420
+ return;
421
+ }
422
+ if (node.type === 'decorator') {
423
+ validateBehaviorTreeBlackboardRefs(node.child, blackboardKeys);
424
+ return;
425
+ }
426
+ if (node.type === 'sequence' || node.type === 'selector') {
427
+ for (const child of node.children) {
428
+ validateBehaviorTreeBlackboardRefs(child, blackboardKeys);
429
+ }
430
+ }
431
+ }
432
+ function validateBehaviorTreeServices(node) {
433
+ for (const service of node.services ?? []) {
434
+ if (!Number.isInteger(service.intervalFrames) || service.intervalFrames <= 0) {
435
+ throw new Error(`Behavior service ${service.id} interval must be positive`);
436
+ }
437
+ }
438
+ if (node.type === 'decorator') {
439
+ validateBehaviorTreeServices(node.child);
440
+ return;
441
+ }
442
+ if (node.type === 'sequence' || node.type === 'selector') {
443
+ for (const child of node.children) {
444
+ validateBehaviorTreeServices(child);
445
+ }
446
+ }
447
+ }
448
+ function assertKnownBlackboardKey(blackboardKeys, key, owner) {
449
+ if (!blackboardKeys.has(key)) {
450
+ throw new Error(`Unknown bot blackboard key ${key} in ${owner}`);
451
+ }
452
+ }
453
+ function defaultBotDocument() {
454
+ return {
455
+ id: 'bot.document.default',
456
+ version: 1,
457
+ blackboard: [
458
+ { key: 'enemyNear', value: true },
459
+ { key: 'health', value: 8 },
460
+ { key: 'ammo', value: 4 },
461
+ { key: 'lastActionAttack', value: true },
462
+ { key: 'attackCooldown', value: 0 },
463
+ { key: 'frame', value: 0 },
464
+ ],
465
+ assetRefs: ['ability.dash'],
466
+ hfsm: {
467
+ initialState: 'idle',
468
+ states: [{ id: 'idle', action: 'scan' }, { id: 'attack', action: 'fire' }],
469
+ transitions: [{ from: 'idle', to: 'attack', whenKey: 'enemyNear', equals: true }],
470
+ },
471
+ behaviorTree: {
472
+ id: 'root',
473
+ type: 'sequence',
474
+ services: [{ id: 'root-target-service', action: 'refresh-target', intervalFrames: 2 }],
475
+ children: [
476
+ { id: 'enemy', type: 'condition', key: 'enemyNear', equals: true },
477
+ {
478
+ id: 'maybe-patrol',
479
+ type: 'decorator',
480
+ mode: 'force-success',
481
+ child: { id: 'calm', type: 'condition', key: 'health', equals: 99 },
482
+ },
483
+ { id: 'shoot', type: 'action', action: 'shoot', status: 'running' },
484
+ ],
485
+ },
486
+ utility: [
487
+ { id: 'retreat', inputKey: 'health', action: 'retreat', min: 0, max: 10, weight: 1, rank: 1, curve: 'inverse' },
488
+ {
489
+ id: 'pressure',
490
+ inputKey: 'health',
491
+ action: 'pressure',
492
+ min: 0,
493
+ max: 10,
494
+ weight: 2,
495
+ curve: 'quadratic',
496
+ momentumKey: 'lastActionAttack',
497
+ momentum: 500,
498
+ cooldownKey: 'attackCooldown',
499
+ cooldownPenalty: 500,
500
+ nested: [{ id: 'ammo-ready', inputKey: 'ammo', min: 0, max: 5, weight: 1, curve: 'step' }],
501
+ },
502
+ ],
503
+ };
504
+ }
505
+ function countBehaviorNodes(node) {
506
+ if (node.type === 'action' || node.type === 'condition') {
507
+ return 1;
508
+ }
509
+ if (node.type === 'decorator') {
510
+ return 1 + countBehaviorNodes(node.child);
511
+ }
512
+ return 1 + node.children.reduce((sum, child) => sum + countBehaviorNodes(child), 0);
513
+ }
514
+ function collectBehaviorNodeIds(node) {
515
+ if (node.type === 'action' || node.type === 'condition') {
516
+ return [node.id];
517
+ }
518
+ if (node.type === 'decorator') {
519
+ return [node.id, ...collectBehaviorNodeIds(node.child)];
520
+ }
521
+ return [node.id, ...node.children.flatMap((child) => collectBehaviorNodeIds(child))];
522
+ }
523
+ function collectBehaviorServices(node) {
524
+ const current = [...(node.services ?? [])];
525
+ if (node.type === 'action' || node.type === 'condition') {
526
+ return current;
527
+ }
528
+ if (node.type === 'decorator') {
529
+ return [...current, ...collectBehaviorServices(node.child)];
530
+ }
531
+ return [...current, ...node.children.flatMap((child) => collectBehaviorServices(child))];
532
+ }
533
+ function uniqueIds(ids, label) {
534
+ const sorted = [...ids].sort(compareStrings);
535
+ const duplicate = sorted.find((id, index) => index > 0 && id === sorted[index - 1]);
536
+ if (duplicate !== undefined) {
537
+ throw new Error(`Duplicate ${label} id ${duplicate}`);
538
+ }
539
+ return new Set(sorted);
540
+ }
541
+ function compareStrings(left, right) {
542
+ return left < right ? -1 : left > right ? 1 : 0;
543
+ }
544
+ function compareNumbers(left, right) {
545
+ return left - right;
546
+ }
547
+ function isDefinedString(value) {
548
+ return value !== undefined;
549
+ }