@aura3d/engine 1.0.3 → 1.0.5

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