@aura3d/engine 1.0.3 → 1.0.6

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 (210) hide show
  1. package/README.md +338 -14
  2. package/dist/animation/AnimationClipEvents.d.ts +57 -0
  3. package/dist/animation/AnimationClipEvents.d.ts.map +1 -0
  4. package/dist/animation/AnimationClipEvents.js +171 -0
  5. package/dist/animation/AnimationClipEvents.js.map +1 -0
  6. package/dist/animation/AnimationClipRegistry.d.ts +76 -0
  7. package/dist/animation/AnimationClipRegistry.d.ts.map +1 -0
  8. package/dist/animation/AnimationClipRegistry.js +130 -0
  9. package/dist/animation/AnimationClipRegistry.js.map +1 -0
  10. package/dist/animation/AnimationController.d.ts +168 -0
  11. package/dist/animation/AnimationController.d.ts.map +1 -0
  12. package/dist/animation/AnimationController.js +619 -0
  13. package/dist/animation/AnimationController.js.map +1 -0
  14. package/dist/animation/AnimationStateGraph.d.ts +2 -0
  15. package/dist/animation/AnimationStateGraph.d.ts.map +1 -0
  16. package/dist/animation/AnimationStateGraph.js +2 -0
  17. package/dist/animation/AnimationStateGraph.js.map +1 -0
  18. package/dist/animation/AnimationStateMachine.d.ts +16 -0
  19. package/dist/animation/AnimationStateMachine.d.ts.map +1 -1
  20. package/dist/animation/AnimationStateMachine.js +69 -7
  21. package/dist/animation/AnimationStateMachine.js.map +1 -1
  22. package/dist/animation/HumanoidRetargeting.d.ts +76 -0
  23. package/dist/animation/HumanoidRetargeting.d.ts.map +1 -0
  24. package/dist/animation/HumanoidRetargeting.js +331 -0
  25. package/dist/animation/HumanoidRetargeting.js.map +1 -0
  26. package/dist/animation/browser-index.d.ts +18 -0
  27. package/dist/animation/browser-index.d.ts.map +1 -1
  28. package/dist/animation/browser-index.js +13 -0
  29. package/dist/animation/browser-index.js.map +1 -1
  30. package/dist/animation/index.d.ts +17 -1
  31. package/dist/animation/index.d.ts.map +1 -1
  32. package/dist/animation/index.js +12 -1
  33. package/dist/animation/index.js.map +1 -1
  34. package/dist/animation/threejs-compatibility/AnimationDiagnostics.d.ts.map +1 -1
  35. package/dist/animation/threejs-compatibility/AnimationDiagnostics.js +3 -5
  36. package/dist/animation/threejs-compatibility/AnimationDiagnostics.js.map +1 -1
  37. package/dist/assets/GLTFAnimationRuntime.js +1 -1
  38. package/dist/assets/GLTFLoader.js +1 -1
  39. package/dist/aura3d-cli/cli.js +225 -8
  40. package/dist/aura3d-cli/cli.js.map +1 -1
  41. package/dist/aura3d-cli/index.d.ts +283 -3
  42. package/dist/aura3d-cli/index.d.ts.map +1 -1
  43. package/dist/aura3d-cli/index.js +1028 -4
  44. package/dist/aura3d-cli/index.js.map +1 -1
  45. package/dist/aura3d-cli/pull-bridge.d.ts +108 -0
  46. package/dist/aura3d-cli/pull-bridge.d.ts.map +1 -0
  47. package/dist/aura3d-cli/pull-bridge.js +333 -0
  48. package/dist/aura3d-cli/pull-bridge.js.map +1 -0
  49. package/dist/create-aura3d/index.d.ts +1 -1
  50. package/dist/create-aura3d/index.d.ts.map +1 -1
  51. package/dist/create-aura3d/index.js +9 -2
  52. package/dist/create-aura3d/index.js.map +1 -1
  53. package/dist/editor-runtime/ProjectSerializer.d.ts +74 -1
  54. package/dist/editor-runtime/ProjectSerializer.d.ts.map +1 -1
  55. package/dist/editor-runtime/ProjectSerializer.js +123 -6
  56. package/dist/editor-runtime/ProjectSerializer.js.map +1 -1
  57. package/dist/editor-runtime/TimelineModel.d.ts +18 -0
  58. package/dist/editor-runtime/TimelineModel.d.ts.map +1 -1
  59. package/dist/editor-runtime/TimelineModel.js +67 -3
  60. package/dist/editor-runtime/TimelineModel.js.map +1 -1
  61. package/dist/editor-runtime/TimelineRuntimeBridge.d.ts +98 -0
  62. package/dist/editor-runtime/TimelineRuntimeBridge.d.ts.map +1 -0
  63. package/dist/editor-runtime/TimelineRuntimeBridge.js +186 -0
  64. package/dist/editor-runtime/TimelineRuntimeBridge.js.map +1 -0
  65. package/dist/editor-runtime/index.d.ts +3 -1
  66. package/dist/editor-runtime/index.d.ts.map +1 -1
  67. package/dist/editor-runtime/index.js +1 -0
  68. package/dist/editor-runtime/index.js.map +1 -1
  69. package/dist/engine/agent-api/AnimationController.d.ts +607 -0
  70. package/dist/engine/agent-api/AnimationController.d.ts.map +1 -0
  71. package/dist/engine/agent-api/AnimationController.js +2192 -0
  72. package/dist/engine/agent-api/AnimationController.js.map +1 -0
  73. package/dist/engine/agent-api/AssetEvidence.d.ts +88 -0
  74. package/dist/engine/agent-api/AssetEvidence.d.ts.map +1 -0
  75. package/dist/engine/agent-api/AssetEvidence.js +157 -0
  76. package/dist/engine/agent-api/AssetEvidence.js.map +1 -0
  77. package/dist/engine/agent-api/AuraAppHandle.d.ts +55 -0
  78. package/dist/engine/agent-api/AuraAppHandle.d.ts.map +1 -0
  79. package/dist/engine/agent-api/AuraAppHandle.js +15 -0
  80. package/dist/engine/agent-api/AuraAppHandle.js.map +1 -0
  81. package/dist/engine/agent-api/AuraVoiceBridge.d.ts +96 -0
  82. package/dist/engine/agent-api/AuraVoiceBridge.d.ts.map +1 -0
  83. package/dist/engine/agent-api/AuraVoiceBridge.js +370 -0
  84. package/dist/engine/agent-api/AuraVoiceBridge.js.map +1 -0
  85. package/dist/engine/agent-api/CartoonDirector.d.ts +95 -0
  86. package/dist/engine/agent-api/CartoonDirector.d.ts.map +1 -0
  87. package/dist/engine/agent-api/CartoonDirector.js +342 -0
  88. package/dist/engine/agent-api/CartoonDirector.js.map +1 -0
  89. package/dist/engine/agent-api/CartoonPerformance.d.ts +149 -0
  90. package/dist/engine/agent-api/CartoonPerformance.d.ts.map +1 -0
  91. package/dist/engine/agent-api/CartoonPerformance.js +317 -0
  92. package/dist/engine/agent-api/CartoonPerformance.js.map +1 -0
  93. package/dist/engine/agent-api/CartoonRenderQueue.d.ts +132 -0
  94. package/dist/engine/agent-api/CartoonRenderQueue.d.ts.map +1 -0
  95. package/dist/engine/agent-api/CartoonRenderQueue.js +385 -0
  96. package/dist/engine/agent-api/CartoonRenderQueue.js.map +1 -0
  97. package/dist/engine/agent-api/CharacterAssembly.d.ts +126 -0
  98. package/dist/engine/agent-api/CharacterAssembly.d.ts.map +1 -0
  99. package/dist/engine/agent-api/CharacterAssembly.js +280 -0
  100. package/dist/engine/agent-api/CharacterAssembly.js.map +1 -0
  101. package/dist/engine/agent-api/DialoguePerformance.d.ts +150 -0
  102. package/dist/engine/agent-api/DialoguePerformance.d.ts.map +1 -0
  103. package/dist/engine/agent-api/DialoguePerformance.js +335 -0
  104. package/dist/engine/agent-api/DialoguePerformance.js.map +1 -0
  105. package/dist/engine/agent-api/FrameLoop.d.ts +70 -0
  106. package/dist/engine/agent-api/FrameLoop.d.ts.map +1 -0
  107. package/dist/engine/agent-api/FrameLoop.js +165 -0
  108. package/dist/engine/agent-api/FrameLoop.js.map +1 -0
  109. package/dist/engine/agent-api/GameAppRuntime.d.ts +62 -0
  110. package/dist/engine/agent-api/GameAppRuntime.d.ts.map +1 -0
  111. package/dist/engine/agent-api/GameAppRuntime.js +189 -0
  112. package/dist/engine/agent-api/GameAppRuntime.js.map +1 -0
  113. package/dist/engine/agent-api/GameAssetValidation.d.ts +279 -0
  114. package/dist/engine/agent-api/GameAssetValidation.d.ts.map +1 -0
  115. package/dist/engine/agent-api/GameAssetValidation.js +719 -0
  116. package/dist/engine/agent-api/GameAssetValidation.js.map +1 -0
  117. package/dist/engine/agent-api/GameEvidence.d.ts +148 -0
  118. package/dist/engine/agent-api/GameEvidence.d.ts.map +1 -0
  119. package/dist/engine/agent-api/GameEvidence.js +269 -0
  120. package/dist/engine/agent-api/GameEvidence.js.map +1 -0
  121. package/dist/engine/agent-api/GameRuntime.d.ts +931 -0
  122. package/dist/engine/agent-api/GameRuntime.d.ts.map +1 -0
  123. package/dist/engine/agent-api/GameRuntime.js +2229 -0
  124. package/dist/engine/agent-api/GameRuntime.js.map +1 -0
  125. package/dist/engine/agent-api/GameSceneBridge.d.ts +54 -0
  126. package/dist/engine/agent-api/GameSceneBridge.d.ts.map +1 -0
  127. package/dist/engine/agent-api/GameSceneBridge.js +110 -0
  128. package/dist/engine/agent-api/GameSceneBridge.js.map +1 -0
  129. package/dist/engine/agent-api/PromptAnimationContract.d.ts +278 -0
  130. package/dist/engine/agent-api/PromptAnimationContract.d.ts.map +1 -0
  131. package/dist/engine/agent-api/PromptAnimationContract.js +238 -0
  132. package/dist/engine/agent-api/PromptAnimationContract.js.map +1 -0
  133. package/dist/engine/agent-api/PromptAnimationEvidence.d.ts +183 -0
  134. package/dist/engine/agent-api/PromptAnimationEvidence.d.ts.map +1 -0
  135. package/dist/engine/agent-api/PromptAnimationEvidence.js +454 -0
  136. package/dist/engine/agent-api/PromptAnimationEvidence.js.map +1 -0
  137. package/dist/engine/agent-api/RuntimeNodeHandle.d.ts +100 -0
  138. package/dist/engine/agent-api/RuntimeNodeHandle.d.ts.map +1 -0
  139. package/dist/engine/agent-api/RuntimeNodeHandle.js +36 -0
  140. package/dist/engine/agent-api/RuntimeNodeHandle.js.map +1 -0
  141. package/dist/engine/agent-api/ShotTimeline.d.ts +179 -0
  142. package/dist/engine/agent-api/ShotTimeline.d.ts.map +1 -0
  143. package/dist/engine/agent-api/ShotTimeline.js +264 -0
  144. package/dist/engine/agent-api/ShotTimeline.js.map +1 -0
  145. package/dist/engine/agent-api/VisemeController.d.ts +89 -0
  146. package/dist/engine/agent-api/VisemeController.d.ts.map +1 -0
  147. package/dist/engine/agent-api/VisemeController.js +207 -0
  148. package/dist/engine/agent-api/VisemeController.js.map +1 -0
  149. package/dist/engine/agent-api/game-kits/fighting.d.ts +123 -0
  150. package/dist/engine/agent-api/game-kits/fighting.d.ts.map +1 -0
  151. package/dist/engine/agent-api/game-kits/fighting.js +483 -0
  152. package/dist/engine/agent-api/game-kits/fighting.js.map +1 -0
  153. package/dist/engine/agent-api/game-kits/index.d.ts +15 -0
  154. package/dist/engine/agent-api/game-kits/index.d.ts.map +1 -0
  155. package/dist/engine/agent-api/game-kits/index.js +6 -0
  156. package/dist/engine/agent-api/game-kits/index.js.map +1 -0
  157. package/dist/engine/agent-api/humanoid-walk-runtime.d.ts +1 -0
  158. package/dist/engine/agent-api/humanoid-walk-runtime.d.ts.map +1 -1
  159. package/dist/engine/agent-api/index.d.ts +495 -1
  160. package/dist/engine/agent-api/index.d.ts.map +1 -1
  161. package/dist/engine/agent-api/index.js +752 -6
  162. package/dist/engine/agent-api/index.js.map +1 -1
  163. package/dist/engine/agent-api/product-viewer-runtime.d.ts.map +1 -1
  164. package/dist/index.d.ts +1 -0
  165. package/dist/index.js +1 -0
  166. package/dist/physics/CollisionVolumes.d.ts +57 -0
  167. package/dist/physics/CollisionVolumes.d.ts.map +1 -0
  168. package/dist/physics/CollisionVolumes.js +159 -0
  169. package/dist/physics/CollisionVolumes.js.map +1 -0
  170. package/dist/physics/HitboxWorld.d.ts +250 -0
  171. package/dist/physics/HitboxWorld.d.ts.map +1 -0
  172. package/dist/physics/HitboxWorld.js +771 -0
  173. package/dist/physics/HitboxWorld.js.map +1 -0
  174. package/dist/physics/KinematicBody.d.ts +157 -0
  175. package/dist/physics/KinematicBody.d.ts.map +1 -0
  176. package/dist/physics/KinematicBody.js +405 -0
  177. package/dist/physics/KinematicBody.js.map +1 -0
  178. package/dist/physics/KinematicWorld.d.ts +58 -0
  179. package/dist/physics/KinematicWorld.d.ts.map +1 -0
  180. package/dist/physics/KinematicWorld.js +246 -0
  181. package/dist/physics/KinematicWorld.js.map +1 -0
  182. package/dist/physics/index.d.ts +4 -0
  183. package/dist/physics/index.d.ts.map +1 -1
  184. package/dist/physics/index.js +4 -0
  185. package/dist/physics/index.js.map +1 -1
  186. package/dist/rendering/ForwardPass.js +2 -2
  187. package/dist/rendering/ShaderLibrary.js +2 -2
  188. package/dist/rendering/SkinnedLitMaterial.js +3 -3
  189. package/dist/rendering/SkinnedUnlitMaterial.js +3 -3
  190. package/dist/scene/Renderable.js +2 -2
  191. package/dist/scripting/VisualGraph.d.ts +2 -1
  192. package/dist/scripting/VisualGraph.d.ts.map +1 -1
  193. package/dist/scripting/VisualGraph.js +118 -1
  194. package/dist/scripting/VisualGraph.js.map +1 -1
  195. package/dist/scripting/VisualGraphContext.d.ts +123 -0
  196. package/dist/scripting/VisualGraphContext.d.ts.map +1 -0
  197. package/dist/scripting/VisualGraphContext.js +2 -0
  198. package/dist/scripting/VisualGraphContext.js.map +1 -0
  199. package/dist/scripting/VisualGraphExecutor.d.ts +6 -1
  200. package/dist/scripting/VisualGraphExecutor.d.ts.map +1 -1
  201. package/dist/scripting/VisualGraphExecutor.js +364 -7
  202. package/dist/scripting/VisualGraphExecutor.js.map +1 -1
  203. package/dist/scripting/VisualNodeCatalog.d.ts +1 -1
  204. package/dist/scripting/VisualNodeCatalog.d.ts.map +1 -1
  205. package/dist/scripting/VisualNodeCatalog.js +61 -1
  206. package/dist/scripting/VisualNodeCatalog.js.map +1 -1
  207. package/dist/scripting/index.d.ts +1 -0
  208. package/dist/scripting/index.d.ts.map +1 -1
  209. package/dist/scripting/index.js.map +1 -1
  210. package/package.json +203 -118
@@ -0,0 +1,771 @@
1
+ import { aabbPenetration, guardbox, hitbox, hurtbox, overlapsVolume, pushbox, resolveCollisionVolume, withVolumeOwner } from "./CollisionVolumes.js";
2
+ import { cloneVec3, validateFiniteVec3 } from "./Shape.js";
3
+ export class HitboxWorld {
4
+ detectPushboxOverlaps;
5
+ lockOnKnockout;
6
+ combatantsById = new Map();
7
+ hitboxesById = new Map();
8
+ nextHitboxId = 1;
9
+ frame = 0;
10
+ roundLocked = false;
11
+ lastEvents = [];
12
+ pendingEvents = [];
13
+ constructor(descriptor = {}) {
14
+ this.detectPushboxOverlaps = descriptor.detectPushboxOverlaps ?? true;
15
+ this.lockOnKnockout = descriptor.lockOnKnockout ?? true;
16
+ }
17
+ registerCombatant(descriptor) {
18
+ if (this.combatantsById.has(descriptor.id)) {
19
+ throw new Error(`Combatant ${String(descriptor.id)} already exists.`);
20
+ }
21
+ const combatant = createCombatantRecord(descriptor);
22
+ this.combatantsById.set(combatant.id, combatant);
23
+ return snapshotCombatant(combatant);
24
+ }
25
+ upsertCombatant(descriptor) {
26
+ const existing = this.combatantsById.get(descriptor.id);
27
+ if (!existing) {
28
+ return this.registerCombatant(descriptor);
29
+ }
30
+ applyCombatantDescriptor(existing, descriptor);
31
+ return snapshotCombatant(existing);
32
+ }
33
+ removeCombatant(id) {
34
+ this.combatantsById.delete(id);
35
+ for (const hitboxRecord of this.hitboxesById.values()) {
36
+ if (hitboxRecord.ownerId === id) {
37
+ hitboxRecord.expired = true;
38
+ }
39
+ }
40
+ }
41
+ getCombatant(id) {
42
+ const combatant = this.combatantsById.get(id);
43
+ return combatant ? snapshotCombatant(combatant) : undefined;
44
+ }
45
+ setPose(id, pose) {
46
+ const combatant = this.requireCombatant(id);
47
+ if (pose.position) {
48
+ validateFiniteVec3(pose.position, "combatant pose position");
49
+ combatant.position = cloneVec3(pose.position);
50
+ }
51
+ if (pose.facing) {
52
+ combatant.facing = pose.facing < 0 ? -1 : 1;
53
+ }
54
+ }
55
+ setGuarding(id, blocking) {
56
+ const combatant = this.requireCombatant(id);
57
+ combatant.blocking = !combatant.knockedOut && blocking;
58
+ }
59
+ setHurtboxes(id, volumes) {
60
+ this.requireCombatant(id).hurtboxes = volumes.map((volume) => requireVolumeKind(volume, "hurtbox"));
61
+ }
62
+ setGuardBoxes(id, volumes) {
63
+ this.requireCombatant(id).guardBoxes = volumes.map((volume) => requireVolumeKind(volume, "guardbox"));
64
+ }
65
+ setPushbox(id, volume) {
66
+ this.requireCombatant(id).pushbox = volume === null ? null : requireVolumeKind(volume, "pushbox");
67
+ }
68
+ spawnHitbox(descriptor) {
69
+ const owner = this.requireCombatant(descriptor.ownerId);
70
+ const id = descriptor.id ?? `${String(descriptor.ownerId)}:${descriptor.moveId}:${this.nextHitboxId}`;
71
+ this.nextHitboxId += 1;
72
+ const activeFrames = normalizeActiveFrames(descriptor.activeFrames ?? { start: 0, end: 2 });
73
+ if (this.hitboxesById.has(id)) {
74
+ throw new Error(`Hitbox ${id} already exists.`);
75
+ }
76
+ const record = {
77
+ id,
78
+ ownerId: descriptor.ownerId,
79
+ team: descriptor.team ?? owner.team,
80
+ moveId: descriptor.moveId,
81
+ boxes: descriptor.boxes.map((volume) => requireVolumeKind(volume, "hitbox")),
82
+ activeFrames,
83
+ recoveryFrames: nonNegativeInteger(descriptor.recoveryFrames ?? 0, "hitbox recoveryFrames"),
84
+ damage: nonNegativeFinite(descriptor.damage ?? 10, "hitbox damage"),
85
+ guardDamage: nonNegativeFinite(descriptor.guardDamage ?? Math.ceil((descriptor.damage ?? 10) * 0.35), "hitbox guardDamage"),
86
+ hitstunFrames: nonNegativeInteger(descriptor.hitstunFrames ?? 14, "hitbox hitstunFrames"),
87
+ blockstunFrames: nonNegativeInteger(descriptor.blockstunFrames ?? 8, "hitbox blockstunFrames"),
88
+ hitStopFrames: nonNegativeInteger(descriptor.hitStopFrames ?? 6, "hitbox hitStopFrames"),
89
+ selfHitStopFrames: nonNegativeInteger(descriptor.selfHitStopFrames ?? descriptor.hitStopFrames ?? 6, "hitbox selfHitStopFrames"),
90
+ knockback: cloneVec3(descriptor.knockback ?? [2.4, 0.8, 0]),
91
+ blockKnockback: cloneVec3(descriptor.blockKnockback ?? [1.1, 0, 0]),
92
+ priority: finite(descriptor.priority ?? 0, "hitbox priority"),
93
+ canHitOwner: descriptor.canHitOwner ?? false,
94
+ canHitSameTeam: descriptor.canHitSameTeam ?? false,
95
+ hitOnce: descriptor.hitOnce ?? true,
96
+ maxHits: positiveInteger(descriptor.maxHits ?? Number.MAX_SAFE_INTEGER, "hitbox maxHits"),
97
+ metadata: descriptor.metadata,
98
+ ageFrames: 0,
99
+ hitCount: 0,
100
+ expired: false,
101
+ hitTargets: new Set(),
102
+ recoveryStarted: false
103
+ };
104
+ validateFiniteVec3(record.knockback, "hitbox knockback");
105
+ validateFiniteVec3(record.blockKnockback, "hitbox blockKnockback");
106
+ if (this.roundLocked || owner.knockedOut) {
107
+ return snapshotHitbox({
108
+ ...record,
109
+ expired: true
110
+ });
111
+ }
112
+ this.hitboxesById.set(record.id, record);
113
+ this.pendingEvents.push({
114
+ type: "hitbox-spawned",
115
+ frame: this.frame,
116
+ hitboxId: record.id,
117
+ ownerId: record.ownerId,
118
+ moveId: record.moveId,
119
+ activeFrames: record.activeFrames
120
+ });
121
+ return snapshotHitbox(record);
122
+ }
123
+ clearHitboxes(ownerId) {
124
+ for (const [id, record] of this.hitboxesById) {
125
+ if (ownerId === undefined || record.ownerId === ownerId) {
126
+ this.hitboxesById.delete(id);
127
+ }
128
+ }
129
+ }
130
+ reset(combatants) {
131
+ this.hitboxesById.clear();
132
+ this.pendingEvents.length = 0;
133
+ this.lastEvents = [];
134
+ this.roundLocked = false;
135
+ if (combatants) {
136
+ this.combatantsById.clear();
137
+ for (const combatant of combatants) {
138
+ this.registerCombatant(combatant);
139
+ }
140
+ }
141
+ else {
142
+ for (const combatant of this.combatantsById.values()) {
143
+ resetCombatantRecord(combatant);
144
+ }
145
+ }
146
+ this.lastEvents = [
147
+ {
148
+ type: "round-reset",
149
+ frame: this.frame,
150
+ combatants: this.combatants().map((combatant) => combatant.id)
151
+ }
152
+ ];
153
+ return this.snapshot();
154
+ }
155
+ step(frames = 1) {
156
+ const frameCount = positiveInteger(frames, "HitboxWorld.step frames");
157
+ const events = [];
158
+ events.push(...this.drainPendingEvents());
159
+ for (let index = 0; index < frameCount; index += 1) {
160
+ events.push(...this.stepOneFrame());
161
+ }
162
+ this.lastEvents = events;
163
+ return events;
164
+ }
165
+ drainEvents() {
166
+ const events = [...this.pendingEvents, ...this.lastEvents];
167
+ this.pendingEvents.length = 0;
168
+ this.lastEvents = [];
169
+ return events;
170
+ }
171
+ snapshot() {
172
+ return {
173
+ frame: this.frame,
174
+ roundLocked: this.roundLocked,
175
+ combatants: this.combatants(),
176
+ hitboxes: this.hitboxes(),
177
+ events: this.lastEvents.map(cloneCombatEvent)
178
+ };
179
+ }
180
+ combatants() {
181
+ return Array.from(this.combatantsById.values()).sort(compareCombatants).map(snapshotCombatant);
182
+ }
183
+ hitboxes() {
184
+ return Array.from(this.hitboxesById.values()).sort(compareHitboxes).map(snapshotHitbox);
185
+ }
186
+ stepOneFrame() {
187
+ const events = [];
188
+ if (this.detectPushboxOverlaps) {
189
+ events.push(...this.collectPushboxEvents());
190
+ }
191
+ if (this.roundLocked) {
192
+ this.frame += 1;
193
+ return events;
194
+ }
195
+ for (const record of this.sortedHitboxRecords()) {
196
+ if (record.expired || record.hitCount >= record.maxHits) {
197
+ record.expired = true;
198
+ continue;
199
+ }
200
+ const owner = this.combatantsById.get(record.ownerId);
201
+ if (!owner) {
202
+ record.expired = true;
203
+ continue;
204
+ }
205
+ if (owner.knockedOut) {
206
+ record.expired = true;
207
+ continue;
208
+ }
209
+ if (isHitboxActive(record)) {
210
+ events.push(...this.resolveHitbox(record, owner));
211
+ if (this.roundLocked) {
212
+ break;
213
+ }
214
+ }
215
+ if (!record.recoveryStarted && record.ageFrames > record.activeFrames.end && record.recoveryFrames > 0) {
216
+ record.recoveryStarted = true;
217
+ owner.recoveryFrames = Math.max(owner.recoveryFrames, record.recoveryFrames);
218
+ events.push({
219
+ type: "recovery-start",
220
+ frame: this.frame,
221
+ combatantId: owner.id,
222
+ frames: record.recoveryFrames
223
+ });
224
+ }
225
+ }
226
+ if (this.roundLocked) {
227
+ this.expireAllHitboxes(events);
228
+ events.push(...this.tickTimers());
229
+ this.frame += 1;
230
+ return events;
231
+ }
232
+ events.push(...this.tickTimers());
233
+ for (const record of this.sortedHitboxRecords()) {
234
+ const owner = this.combatantsById.get(record.ownerId);
235
+ if (owner && owner.hitStopFrames <= 0) {
236
+ record.ageFrames += 1;
237
+ }
238
+ if (record.ageFrames > record.activeFrames.end + record.recoveryFrames || record.hitCount >= record.maxHits || record.expired) {
239
+ this.expireHitbox(record, events);
240
+ }
241
+ }
242
+ this.frame += 1;
243
+ return events;
244
+ }
245
+ resolveHitbox(record, owner) {
246
+ const events = [];
247
+ const hitVolumes = record.boxes.map((volume) => resolveCollisionVolume(withVolumeOwner(volume, owner.id), owner.position, owner.facing));
248
+ for (const defender of this.sortedCombatantRecords()) {
249
+ if (!canHitCombatant(record, owner, defender)) {
250
+ continue;
251
+ }
252
+ if (defender.invulnerableFrames > 0) {
253
+ continue;
254
+ }
255
+ if (record.hitOnce && record.hitTargets.has(defender.id)) {
256
+ continue;
257
+ }
258
+ const blockEvent = defender.blocking ? this.tryResolveBlock(record, owner, defender, hitVolumes) : null;
259
+ if (blockEvent) {
260
+ record.hitTargets.add(defender.id);
261
+ record.hitCount += 1;
262
+ events.push(blockEvent);
263
+ events.push(...this.applyBlockTimers(record, owner, defender));
264
+ continue;
265
+ }
266
+ const hitEvent = this.tryResolveHit(record, owner, defender, hitVolumes);
267
+ if (hitEvent) {
268
+ record.hitTargets.add(defender.id);
269
+ record.hitCount += 1;
270
+ events.push(hitEvent);
271
+ events.push(...this.applyHitTimers(record, owner, defender));
272
+ if (defender.health <= 0 && !defender.knockedOut) {
273
+ events.push(...this.knockOutCombatant(defender, owner, record));
274
+ if (this.roundLocked) {
275
+ break;
276
+ }
277
+ }
278
+ }
279
+ }
280
+ return events;
281
+ }
282
+ tryResolveBlock(record, owner, defender, hitVolumes) {
283
+ const guardVolumes = (defender.guardBoxes.length > 0 ? defender.guardBoxes : defender.hurtboxes).map((volume) => resolveCollisionVolume(withVolumeOwner(volume, defender.id), defender.position, defender.facing));
284
+ const pair = firstOverlappingPair(hitVolumes, guardVolumes);
285
+ if (!pair) {
286
+ return null;
287
+ }
288
+ defender.guard = Math.max(0, defender.guard - record.guardDamage);
289
+ return {
290
+ type: "blocked",
291
+ frame: this.frame,
292
+ attackerId: owner.id,
293
+ defenderId: defender.id,
294
+ hitboxId: record.id,
295
+ moveId: record.moveId,
296
+ hitboxVolumeId: pair.attack.id,
297
+ guardboxId: pair.defense.id,
298
+ guardDamage: record.guardDamage,
299
+ defenderGuard: defender.guard,
300
+ blockstunFrames: record.blockstunFrames,
301
+ hitStopFrames: record.hitStopFrames,
302
+ knockback: orientVec3(record.blockKnockback, owner.facing),
303
+ point: contactPoint(pair.attack, pair.defense),
304
+ normal: combatNormal(owner, defender)
305
+ };
306
+ }
307
+ tryResolveHit(record, owner, defender, hitVolumes) {
308
+ const hurtVolumes = defender.hurtboxes.map((volume) => resolveCollisionVolume(withVolumeOwner(volume, defender.id), defender.position, defender.facing));
309
+ const pair = firstOverlappingPair(hitVolumes, hurtVolumes);
310
+ if (!pair) {
311
+ return null;
312
+ }
313
+ defender.health = Math.max(0, defender.health - record.damage);
314
+ return {
315
+ type: "hit",
316
+ frame: this.frame,
317
+ attackerId: owner.id,
318
+ defenderId: defender.id,
319
+ hitboxId: record.id,
320
+ moveId: record.moveId,
321
+ hitboxVolumeId: pair.attack.id,
322
+ hurtboxId: pair.defense.id,
323
+ damage: record.damage,
324
+ defenderHealth: defender.health,
325
+ hitstunFrames: record.hitstunFrames,
326
+ hitStopFrames: record.hitStopFrames,
327
+ knockback: orientVec3(record.knockback, owner.facing),
328
+ point: contactPoint(pair.attack, pair.defense),
329
+ normal: combatNormal(owner, defender)
330
+ };
331
+ }
332
+ knockOutCombatant(defender, owner, record) {
333
+ defender.knockedOut = true;
334
+ defender.blocking = false;
335
+ defender.invulnerableFrames = 0;
336
+ defender.hitStopFrames = 0;
337
+ defender.hitstunFrames = 0;
338
+ defender.blockstunFrames = 0;
339
+ defender.recoveryFrames = 0;
340
+ if (this.lockOnKnockout) {
341
+ this.roundLocked = true;
342
+ }
343
+ return [
344
+ {
345
+ type: "knockout",
346
+ frame: this.frame,
347
+ combatantId: defender.id,
348
+ attackerId: owner.id,
349
+ moveId: record.moveId
350
+ }
351
+ ];
352
+ }
353
+ applyHitTimers(record, owner, defender) {
354
+ const events = [];
355
+ events.push(...setTimer(defender, "hitstunFrames", record.hitstunFrames, "hitstun-start", this.frame));
356
+ events.push(...setTimer(defender, "hitStopFrames", record.hitStopFrames, "hitstop-start", this.frame));
357
+ events.push(...setTimer(owner, "hitStopFrames", record.selfHitStopFrames, "hitstop-start", this.frame));
358
+ return events;
359
+ }
360
+ applyBlockTimers(record, owner, defender) {
361
+ const events = [];
362
+ events.push(...setTimer(defender, "blockstunFrames", record.blockstunFrames, "blockstun-start", this.frame));
363
+ events.push(...setTimer(defender, "hitStopFrames", record.hitStopFrames, "hitstop-start", this.frame));
364
+ events.push(...setTimer(owner, "hitStopFrames", record.selfHitStopFrames, "hitstop-start", this.frame));
365
+ return events;
366
+ }
367
+ tickTimers() {
368
+ const events = [];
369
+ for (const combatant of this.sortedCombatantRecords()) {
370
+ events.push(...tickTimer(combatant, "hitStopFrames", "hitstop-end", this.frame));
371
+ events.push(...tickTimer(combatant, "hitstunFrames", "hitstun-end", this.frame));
372
+ events.push(...tickTimer(combatant, "blockstunFrames", "blockstun-end", this.frame));
373
+ events.push(...tickTimer(combatant, "recoveryFrames", "recovery-end", this.frame));
374
+ if (combatant.invulnerableFrames > 0) {
375
+ combatant.invulnerableFrames -= 1;
376
+ }
377
+ }
378
+ return events;
379
+ }
380
+ collectPushboxEvents() {
381
+ const events = [];
382
+ const combatants = this.sortedCombatantRecords().filter((combatant) => combatant.pushbox !== null && !combatant.knockedOut);
383
+ for (let aIndex = 0; aIndex < combatants.length; aIndex += 1) {
384
+ for (let bIndex = aIndex + 1; bIndex < combatants.length; bIndex += 1) {
385
+ const combatantA = combatants[aIndex];
386
+ const combatantB = combatants[bIndex];
387
+ const pushA = resolveCollisionVolume(withVolumeOwner(combatantA.pushbox, combatantA.id), combatantA.position, combatantA.facing);
388
+ const pushB = resolveCollisionVolume(withVolumeOwner(combatantB.pushbox, combatantB.id), combatantB.position, combatantB.facing);
389
+ if (!overlapsVolume(pushA, pushB)) {
390
+ continue;
391
+ }
392
+ const penetration = aabbPenetration(pushA.bounds, pushB.bounds);
393
+ if (!penetration) {
394
+ continue;
395
+ }
396
+ events.push({
397
+ type: "pushbox-overlap",
398
+ frame: this.frame,
399
+ combatantA: combatantA.id,
400
+ combatantB: combatantB.id,
401
+ normal: penetration.normal,
402
+ penetration: penetration.depth
403
+ });
404
+ }
405
+ }
406
+ return events;
407
+ }
408
+ expireHitbox(record, events) {
409
+ if (!this.hitboxesById.has(record.id)) {
410
+ return;
411
+ }
412
+ if (record.hitCount === 0) {
413
+ events.push({
414
+ type: "whiff",
415
+ frame: this.frame,
416
+ hitboxId: record.id,
417
+ ownerId: record.ownerId,
418
+ moveId: record.moveId
419
+ });
420
+ }
421
+ events.push({
422
+ type: "hitbox-expired",
423
+ frame: this.frame,
424
+ hitboxId: record.id,
425
+ ownerId: record.ownerId,
426
+ moveId: record.moveId,
427
+ hitCount: record.hitCount
428
+ });
429
+ this.hitboxesById.delete(record.id);
430
+ }
431
+ expireAllHitboxes(events) {
432
+ for (const record of this.sortedHitboxRecords()) {
433
+ this.expireHitbox(record, events);
434
+ }
435
+ }
436
+ requireCombatant(id) {
437
+ const combatant = this.combatantsById.get(id);
438
+ if (!combatant) {
439
+ throw new Error(`Combatant ${String(id)} does not exist.`);
440
+ }
441
+ return combatant;
442
+ }
443
+ sortedCombatantRecords() {
444
+ return Array.from(this.combatantsById.values()).sort(compareCombatants);
445
+ }
446
+ sortedHitboxRecords() {
447
+ return Array.from(this.hitboxesById.values()).sort(compareHitboxes);
448
+ }
449
+ drainPendingEvents() {
450
+ const events = [...this.pendingEvents];
451
+ this.pendingEvents.length = 0;
452
+ return events;
453
+ }
454
+ }
455
+ function createCombatantRecord(descriptor) {
456
+ const maxHealth = positiveFinite(descriptor.maxHealth ?? descriptor.health ?? 100, "combatant maxHealth");
457
+ const maxGuard = positiveFinite(descriptor.maxGuard ?? descriptor.guard ?? 100, "combatant maxGuard");
458
+ const position = cloneVec3(descriptor.position ?? [0, 0.9, 0]);
459
+ validateFiniteVec3(position, "combatant position");
460
+ const health = clampNonNegative(descriptor.health ?? maxHealth, maxHealth);
461
+ const guard = clampNonNegative(descriptor.guard ?? maxGuard, maxGuard);
462
+ const facing = descriptor.facing && descriptor.facing < 0 ? -1 : 1;
463
+ const blocking = descriptor.blocking ?? false;
464
+ const invulnerableFrames = nonNegativeInteger(descriptor.invulnerableFrames ?? 0, "combatant invulnerableFrames");
465
+ const meter = nonNegativeFinite(descriptor.meter ?? 0, "combatant meter");
466
+ return {
467
+ id: descriptor.id,
468
+ team: descriptor.team,
469
+ position,
470
+ facing,
471
+ hurtboxes: (descriptor.hurtboxes ?? [hurtbox({ id: "body", halfExtents: [0.36, 0.86, 0.28] })]).map((volume) => requireVolumeKind(volume, "hurtbox")),
472
+ guardBoxes: (descriptor.guardBoxes ?? [guardbox({ id: "guard", offset: [0.18, 0, 0], halfExtents: [0.28, 0.82, 0.3] })]).map((volume) => requireVolumeKind(volume, "guardbox")),
473
+ pushbox: descriptor.pushbox === null ? null : requireVolumeKind(descriptor.pushbox ?? pushbox({ id: "push", halfExtents: [0.32, 0.84, 0.26] }), "pushbox"),
474
+ health,
475
+ maxHealth,
476
+ guard,
477
+ maxGuard,
478
+ meter,
479
+ blocking,
480
+ invulnerableFrames,
481
+ hitStopFrames: 0,
482
+ hitstunFrames: 0,
483
+ blockstunFrames: 0,
484
+ recoveryFrames: 0,
485
+ knockedOut: descriptor.knockedOut ?? health <= 0,
486
+ metadata: descriptor.metadata,
487
+ baseline: {
488
+ position: cloneVec3(position),
489
+ facing,
490
+ health,
491
+ maxHealth,
492
+ guard,
493
+ maxGuard,
494
+ meter,
495
+ blocking,
496
+ invulnerableFrames
497
+ }
498
+ };
499
+ }
500
+ function applyCombatantDescriptor(record, descriptor) {
501
+ if (descriptor.team !== undefined) {
502
+ record.team = descriptor.team;
503
+ }
504
+ if (descriptor.position) {
505
+ validateFiniteVec3(descriptor.position, "combatant position");
506
+ record.position = cloneVec3(descriptor.position);
507
+ }
508
+ if (descriptor.facing) {
509
+ record.facing = descriptor.facing < 0 ? -1 : 1;
510
+ }
511
+ if (descriptor.hurtboxes) {
512
+ record.hurtboxes = descriptor.hurtboxes.map((volume) => requireVolumeKind(volume, "hurtbox"));
513
+ }
514
+ if (descriptor.guardBoxes) {
515
+ record.guardBoxes = descriptor.guardBoxes.map((volume) => requireVolumeKind(volume, "guardbox"));
516
+ }
517
+ if (descriptor.pushbox !== undefined) {
518
+ record.pushbox = descriptor.pushbox === null ? null : requireVolumeKind(descriptor.pushbox, "pushbox");
519
+ }
520
+ if (descriptor.maxHealth !== undefined) {
521
+ record.maxHealth = positiveFinite(descriptor.maxHealth, "combatant maxHealth");
522
+ }
523
+ if (descriptor.health !== undefined) {
524
+ record.health = clampNonNegative(descriptor.health, record.maxHealth);
525
+ record.knockedOut = descriptor.knockedOut ?? record.health <= 0;
526
+ }
527
+ if (descriptor.maxGuard !== undefined) {
528
+ record.maxGuard = positiveFinite(descriptor.maxGuard, "combatant maxGuard");
529
+ }
530
+ if (descriptor.guard !== undefined) {
531
+ record.guard = clampNonNegative(descriptor.guard, record.maxGuard);
532
+ }
533
+ if (descriptor.meter !== undefined) {
534
+ record.meter = nonNegativeFinite(descriptor.meter, "combatant meter");
535
+ }
536
+ if (descriptor.blocking !== undefined) {
537
+ record.blocking = descriptor.blocking;
538
+ }
539
+ if (descriptor.invulnerableFrames !== undefined) {
540
+ record.invulnerableFrames = nonNegativeInteger(descriptor.invulnerableFrames, "combatant invulnerableFrames");
541
+ }
542
+ if (descriptor.knockedOut !== undefined) {
543
+ record.knockedOut = descriptor.knockedOut;
544
+ if (record.knockedOut) {
545
+ record.health = 0;
546
+ record.blocking = false;
547
+ }
548
+ }
549
+ if (descriptor.metadata !== undefined) {
550
+ record.metadata = descriptor.metadata;
551
+ }
552
+ }
553
+ function canHitCombatant(record, owner, defender) {
554
+ if (owner.knockedOut || defender.knockedOut) {
555
+ return false;
556
+ }
557
+ if (owner.id === defender.id && !record.canHitOwner) {
558
+ return false;
559
+ }
560
+ if (!record.canHitSameTeam && record.team !== undefined && defender.team !== undefined && record.team === defender.team) {
561
+ return false;
562
+ }
563
+ return true;
564
+ }
565
+ function resetCombatantRecord(record) {
566
+ record.position = cloneVec3(record.baseline.position);
567
+ record.facing = record.baseline.facing;
568
+ record.health = record.baseline.health;
569
+ record.maxHealth = record.baseline.maxHealth;
570
+ record.guard = record.baseline.guard;
571
+ record.maxGuard = record.baseline.maxGuard;
572
+ record.meter = record.baseline.meter;
573
+ record.blocking = record.baseline.blocking;
574
+ record.invulnerableFrames = record.baseline.invulnerableFrames;
575
+ record.hitStopFrames = 0;
576
+ record.hitstunFrames = 0;
577
+ record.blockstunFrames = 0;
578
+ record.recoveryFrames = 0;
579
+ record.knockedOut = record.health <= 0;
580
+ }
581
+ function firstOverlappingPair(attackVolumes, defenseVolumes) {
582
+ const attacks = [...attackVolumes].sort(compareResolvedVolumes);
583
+ const defenses = [...defenseVolumes].sort(compareResolvedVolumes);
584
+ for (const attack of attacks) {
585
+ for (const defense of defenses) {
586
+ if (overlapsVolume(attack, defense)) {
587
+ return { attack, defense };
588
+ }
589
+ }
590
+ }
591
+ return null;
592
+ }
593
+ function setTimer(combatant, key, frames, eventType, frame) {
594
+ if (frames <= 0 || combatant[key] >= frames) {
595
+ return [];
596
+ }
597
+ combatant[key] = frames;
598
+ return [
599
+ {
600
+ type: eventType,
601
+ frame,
602
+ combatantId: combatant.id,
603
+ frames
604
+ }
605
+ ];
606
+ }
607
+ function tickTimer(combatant, key, eventType, frame) {
608
+ if (combatant[key] <= 0) {
609
+ return [];
610
+ }
611
+ combatant[key] -= 1;
612
+ if (combatant[key] > 0) {
613
+ return [];
614
+ }
615
+ return [
616
+ {
617
+ type: eventType,
618
+ frame,
619
+ combatantId: combatant.id
620
+ }
621
+ ];
622
+ }
623
+ function isHitboxActive(record) {
624
+ return record.ageFrames >= record.activeFrames.start && record.ageFrames <= record.activeFrames.end;
625
+ }
626
+ function normalizeActiveFrames(value) {
627
+ const start = nonNegativeInteger(value.start, "hitbox activeFrames.start");
628
+ const end = nonNegativeInteger(value.end, "hitbox activeFrames.end");
629
+ if (end < start) {
630
+ throw new Error("hitbox activeFrames.end must be greater than or equal to activeFrames.start.");
631
+ }
632
+ return { start, end };
633
+ }
634
+ function requireVolumeKind(volume, kind) {
635
+ if (volume.kind !== kind) {
636
+ throw new Error(`Expected ${kind} volume but received ${volume.kind}.`);
637
+ }
638
+ return volume;
639
+ }
640
+ function contactPoint(attack, defense) {
641
+ return [
642
+ (Math.max(attack.bounds.min[0], defense.bounds.min[0]) + Math.min(attack.bounds.max[0], defense.bounds.max[0])) * 0.5,
643
+ (Math.max(attack.bounds.min[1], defense.bounds.min[1]) + Math.min(attack.bounds.max[1], defense.bounds.max[1])) * 0.5,
644
+ (Math.max(attack.bounds.min[2], defense.bounds.min[2]) + Math.min(attack.bounds.max[2], defense.bounds.max[2])) * 0.5
645
+ ];
646
+ }
647
+ function combatNormal(owner, defender) {
648
+ if (owner.position[0] === defender.position[0]) {
649
+ return [owner.facing, 0, 0];
650
+ }
651
+ return [owner.position[0] < defender.position[0] ? 1 : -1, 0, 0];
652
+ }
653
+ function orientVec3(value, facing) {
654
+ return [value[0] * facing, value[1], value[2]];
655
+ }
656
+ function snapshotCombatant(record) {
657
+ return {
658
+ id: record.id,
659
+ ...(record.team === undefined ? {} : { team: record.team }),
660
+ position: cloneVec3(record.position),
661
+ facing: record.facing,
662
+ health: record.health,
663
+ maxHealth: record.maxHealth,
664
+ guard: record.guard,
665
+ maxGuard: record.maxGuard,
666
+ meter: record.meter,
667
+ blocking: record.blocking,
668
+ invulnerableFrames: record.invulnerableFrames,
669
+ hitStopFrames: record.hitStopFrames,
670
+ hitstunFrames: record.hitstunFrames,
671
+ blockstunFrames: record.blockstunFrames,
672
+ recoveryFrames: record.recoveryFrames,
673
+ knockedOut: record.knockedOut
674
+ };
675
+ }
676
+ function snapshotHitbox(record) {
677
+ return {
678
+ id: record.id,
679
+ ownerId: record.ownerId,
680
+ moveId: record.moveId,
681
+ ageFrames: record.ageFrames,
682
+ activeFrames: record.activeFrames,
683
+ recoveryFrames: record.recoveryFrames,
684
+ hitCount: record.hitCount,
685
+ expired: record.expired
686
+ };
687
+ }
688
+ function cloneCombatEvent(event) {
689
+ switch (event.type) {
690
+ case "hit":
691
+ return {
692
+ ...event,
693
+ knockback: cloneVec3(event.knockback),
694
+ point: cloneVec3(event.point),
695
+ normal: cloneVec3(event.normal)
696
+ };
697
+ case "blocked":
698
+ return {
699
+ ...event,
700
+ knockback: cloneVec3(event.knockback),
701
+ point: cloneVec3(event.point),
702
+ normal: cloneVec3(event.normal)
703
+ };
704
+ case "pushbox-overlap":
705
+ return {
706
+ ...event,
707
+ normal: cloneVec3(event.normal)
708
+ };
709
+ default:
710
+ return { ...event };
711
+ }
712
+ }
713
+ function compareCombatants(a, b) {
714
+ return compareIds(a.id, b.id);
715
+ }
716
+ function compareHitboxes(a, b) {
717
+ if (a.priority !== b.priority) {
718
+ return b.priority - a.priority;
719
+ }
720
+ return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
721
+ }
722
+ function compareResolvedVolumes(a, b) {
723
+ const left = `${a.kind}:${a.id}`;
724
+ const right = `${b.kind}:${b.id}`;
725
+ if (left === right) {
726
+ return 0;
727
+ }
728
+ return left < right ? -1 : 1;
729
+ }
730
+ function compareIds(a, b) {
731
+ const left = String(a);
732
+ const right = String(b);
733
+ if (left === right) {
734
+ return 0;
735
+ }
736
+ return left < right ? -1 : 1;
737
+ }
738
+ function finite(value, name) {
739
+ if (!Number.isFinite(value)) {
740
+ throw new Error(`${name} must be finite.`);
741
+ }
742
+ return value;
743
+ }
744
+ function nonNegativeFinite(value, name) {
745
+ if (!Number.isFinite(value) || value < 0) {
746
+ throw new Error(`${name} must be finite and non-negative.`);
747
+ }
748
+ return value;
749
+ }
750
+ function positiveFinite(value, name) {
751
+ if (!Number.isFinite(value) || value <= 0) {
752
+ throw new Error(`${name} must be finite and positive.`);
753
+ }
754
+ return value;
755
+ }
756
+ function nonNegativeInteger(value, name) {
757
+ if (!Number.isInteger(value) || value < 0) {
758
+ throw new Error(`${name} must be a non-negative integer.`);
759
+ }
760
+ return value;
761
+ }
762
+ function positiveInteger(value, name) {
763
+ if (!Number.isInteger(value) || value <= 0) {
764
+ throw new Error(`${name} must be a positive integer.`);
765
+ }
766
+ return value;
767
+ }
768
+ function clampNonNegative(value, max) {
769
+ return Math.max(0, Math.min(max, nonNegativeFinite(value, "combatant value")));
770
+ }
771
+ //# sourceMappingURL=HitboxWorld.js.map