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