@its-not-rocket-science/ananke 0.1.69 → 0.5.1

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 (165) hide show
  1. package/CHANGELOG.md +254 -0
  2. package/README.md +150 -432
  3. package/STABLE_API.md +113 -490
  4. package/conformance/README.md +46 -44
  5. package/conformance/lockstep-sequence.json +395 -395
  6. package/conformance/phase-order.json +21 -0
  7. package/conformance/replay-parity.json +65 -65
  8. package/conformance/state-hash-regression.json +39 -0
  9. package/conformance/state-hash.json +23 -22
  10. package/dist/src/conformance.d.ts +8 -2
  11. package/dist/src/content/index.d.ts +3 -0
  12. package/dist/src/content/index.js +2 -0
  13. package/dist/src/content/injector.d.ts +19 -0
  14. package/dist/src/content/injector.js +61 -0
  15. package/dist/src/content/loader.d.ts +3 -0
  16. package/dist/src/content/loader.js +170 -0
  17. package/dist/src/content/types.d.ts +56 -0
  18. package/dist/src/content/types.js +1 -0
  19. package/dist/src/content/validator.d.ts +6 -0
  20. package/dist/src/content/validator.js +14 -0
  21. package/dist/src/content-pack.d.ts +3 -2
  22. package/dist/src/content-pack.js +205 -10
  23. package/dist/src/data-governance.d.ts +73 -0
  24. package/dist/src/data-governance.js +188 -0
  25. package/dist/src/debug.d.ts +6 -0
  26. package/dist/src/debug.js +9 -0
  27. package/dist/src/determinism.d.ts +12 -0
  28. package/dist/src/determinism.js +93 -0
  29. package/dist/src/dialogue.d.ts +41 -0
  30. package/dist/src/dialogue.js +113 -2
  31. package/dist/src/economy.d.ts +97 -3
  32. package/dist/src/economy.js +191 -37
  33. package/dist/src/history/autosave.d.ts +19 -0
  34. package/dist/src/history/autosave.js +98 -0
  35. package/dist/src/history/timetravel.d.ts +28 -0
  36. package/dist/src/history/timetravel.js +61 -0
  37. package/dist/src/index.d.ts +19 -31
  38. package/dist/src/index.js +20 -38
  39. package/dist/src/model3d.js +63 -2
  40. package/dist/src/narrative/combat-logger.d.ts +25 -0
  41. package/dist/src/narrative/combat-logger.js +97 -0
  42. package/dist/src/narrative/plausibility.d.ts +30 -0
  43. package/dist/src/narrative/plausibility.js +70 -0
  44. package/dist/src/narrative-layer.d.ts +2 -0
  45. package/dist/src/narrative-layer.js +2 -0
  46. package/dist/src/navigation/causal-chain.d.ts +26 -0
  47. package/dist/src/navigation/causal-chain.js +74 -0
  48. package/dist/src/netcode.d.ts +1 -1
  49. package/dist/src/netcode.js +1 -1
  50. package/dist/src/performance/adaptive-tick.d.ts +23 -0
  51. package/dist/src/performance/adaptive-tick.js +50 -0
  52. package/dist/src/plugins/loader.d.ts +23 -0
  53. package/dist/src/plugins/loader.js +86 -0
  54. package/dist/src/plugins/registry.d.ts +11 -0
  55. package/dist/src/plugins/registry.js +28 -0
  56. package/dist/src/plugins/types.d.ts +31 -0
  57. package/dist/src/plugins/types.js +7 -0
  58. package/dist/src/replay.js +5 -2
  59. package/dist/src/scenario.js +2 -1
  60. package/dist/src/serialization/binary.d.ts +5 -0
  61. package/dist/src/serialization/binary.js +193 -0
  62. package/dist/src/session.d.ts +115 -0
  63. package/dist/src/session.js +228 -0
  64. package/dist/src/sim/ai/decide.js +1 -1
  65. package/dist/src/sim/ai/personality.js +2 -2
  66. package/dist/src/sim/bodyplan.d.ts +30 -0
  67. package/dist/src/sim/context.d.ts +2 -0
  68. package/dist/src/sim/kernel.js +80 -1294
  69. package/dist/src/sim/normalization.d.ts +12 -0
  70. package/dist/src/sim/normalization.js +92 -0
  71. package/dist/src/sim/resolvers/attack-resolver.d.ts +30 -0
  72. package/dist/src/sim/resolvers/attack-resolver.js +310 -0
  73. package/dist/src/sim/resolvers/capability-resolver.d.ts +15 -0
  74. package/dist/src/sim/resolvers/capability-resolver.js +270 -0
  75. package/dist/src/sim/resolvers/grapple-resolver.d.ts +28 -0
  76. package/dist/src/sim/resolvers/grapple-resolver.js +70 -0
  77. package/dist/src/sim/resolvers/shoot-resolver.d.ts +27 -0
  78. package/dist/src/sim/resolvers/shoot-resolver.js +212 -0
  79. package/dist/src/sim/resolvers/treat-resolver.d.ts +16 -0
  80. package/dist/src/sim/resolvers/treat-resolver.js +77 -0
  81. package/dist/src/sim/step/apply/intents.d.ts +6 -0
  82. package/dist/src/sim/step/apply/intents.js +83 -0
  83. package/dist/src/sim/step/kernel-pipeline.d.ts +9 -0
  84. package/dist/src/sim/step/kernel-pipeline.js +15 -0
  85. package/dist/src/sim/step/phases/capability-phase.d.ts +8 -0
  86. package/dist/src/sim/step/phases/capability-phase.js +65 -0
  87. package/dist/src/sim/step/phases/cooldowns-phase.d.ts +3 -0
  88. package/dist/src/sim/step/phases/cooldowns-phase.js +37 -0
  89. package/dist/src/sim/step/phases/prepare-phase.d.ts +10 -0
  90. package/dist/src/sim/step/phases/prepare-phase.js +33 -0
  91. package/dist/src/sim/step/pipeline.d.ts +2 -0
  92. package/dist/src/sim/step/pipeline.js +15 -0
  93. package/dist/src/sim/step/resolvers/impact-resolver.d.ts +9 -0
  94. package/dist/src/sim/step/resolvers/impact-resolver.js +55 -0
  95. package/dist/src/sim/step/world-phases.d.ts +7 -0
  96. package/dist/src/sim/step/world-phases.js +15 -0
  97. package/dist/src/sim/step/world-step-context.d.ts +23 -0
  98. package/dist/src/sim/step/world-step-context.js +1 -0
  99. package/dist/src/sim/systemic-toxicology.js +1 -1
  100. package/dist/src/sim/team.js +2 -2
  101. package/dist/src/sim/testing.js +4 -3
  102. package/dist/src/sim/toxicology.d.ts +1 -1
  103. package/dist/src/sim/toxicology.js +1 -1
  104. package/dist/src/sim/world.d.ts +17 -24
  105. package/dist/src/snapshot.js +3 -6
  106. package/dist/src/tier2.d.ts +19 -0
  107. package/dist/src/tier2.js +21 -0
  108. package/dist/src/tier3.d.ts +10 -0
  109. package/dist/src/tier3.js +12 -0
  110. package/dist/src/version.d.ts +6 -0
  111. package/dist/src/version.js +8 -0
  112. package/dist/src/wasm/bridge.d.ts +14 -0
  113. package/dist/src/wasm/bridge.js +84 -0
  114. package/dist/src/wasm-kernel.d.ts +2 -0
  115. package/dist/src/wasm-kernel.js +1 -0
  116. package/dist/src/world-evolution-backend/engine.d.ts +4 -0
  117. package/dist/src/world-evolution-backend/engine.js +347 -0
  118. package/dist/src/world-evolution-backend/host-schema.d.ts +145 -0
  119. package/dist/src/world-evolution-backend/host-schema.js +375 -0
  120. package/dist/src/world-evolution-backend/index.d.ts +1 -0
  121. package/dist/src/world-evolution-backend/index.js +1 -0
  122. package/dist/src/world-evolution-backend/open-world-host-adapter.d.ts +96 -0
  123. package/dist/src/world-evolution-backend/open-world-host-adapter.js +208 -0
  124. package/dist/src/world-evolution-backend/profiles.d.ts +4 -0
  125. package/dist/src/world-evolution-backend/profiles.js +136 -0
  126. package/dist/src/world-evolution-backend/public.d.ts +11 -0
  127. package/dist/src/world-evolution-backend/public.js +7 -0
  128. package/dist/src/world-evolution-backend/reproducibility.d.ts +8 -0
  129. package/dist/src/world-evolution-backend/reproducibility.js +35 -0
  130. package/dist/src/world-evolution-backend/timeline.d.ts +6 -0
  131. package/dist/src/world-evolution-backend/timeline.js +337 -0
  132. package/dist/src/world-evolution-backend/types.d.ts +138 -0
  133. package/dist/src/world-evolution-backend/types.js +1 -0
  134. package/dist/src/world-evolution-host-backend.d.ts +51 -0
  135. package/dist/src/world-evolution-host-backend.js +97 -0
  136. package/dist/src/world-evolution.d.ts +201 -0
  137. package/dist/src/world-evolution.js +543 -0
  138. package/dist/src/world-factory.js +2 -1
  139. package/dist/tools/pack-cli.js +55 -2
  140. package/docs/bridge-contract.md +140 -332
  141. package/docs/completion-evidence-dashboard.md +63 -0
  142. package/docs/host-contract.md +86 -310
  143. package/docs/host-world-evolution-schema.md +155 -0
  144. package/docs/integration-primer.md +29 -22
  145. package/docs/migration-monolith-to-modular.md +102 -120
  146. package/docs/package-architecture.md +116 -323
  147. package/docs/plugins/README.md +42 -0
  148. package/docs/plugins/hooks.md +22 -0
  149. package/docs/plugins/publishing.md +29 -0
  150. package/docs/plugins/security-model.md +273 -0
  151. package/docs/project-overview.md +30 -30
  152. package/docs/release-dashboard.md +47 -0
  153. package/docs/session-api.md +127 -0
  154. package/docs/stable-api-manifest.json +52 -0
  155. package/docs/trust-dashboard.md +37 -0
  156. package/docs/versioning.md +256 -245
  157. package/docs/wire-protocol.md +140 -209
  158. package/docs/world-evolution-backend.md +200 -0
  159. package/docs/world-evolution-engine.md +150 -0
  160. package/docs/world-evolution-host-backend.md +70 -0
  161. package/docs/world-evolution-orchestration.md +248 -0
  162. package/package.json +125 -6
  163. package/schema/content-pack.schema.json +85 -0
  164. package/schema/plugin.schema.json +57 -0
  165. package/schema/world-evolution-input.schema.json +196 -0
@@ -0,0 +1,270 @@
1
+ import { SCALE, q, clampQ, qMul } from "../../units.js";
2
+ import { DamageChannel } from "../../channels.js";
3
+ import { isCapabilityAvailable } from "../tech.js";
4
+ import { eventSeed } from "../seeds.js";
5
+ import { entityInCone } from "../cone.js";
6
+ import { vAdd } from "../vec3.js";
7
+ import { TraceKinds } from "../kinds.js";
8
+ const CAPABILITY_CHANNEL_WEAPONS = {
9
+ [DamageChannel.Kinetic]: { id: "cap_kinetic", kind: "weapon", name: "Kinetic Force", mass_kg: 0, bulk: q(0), damage: { penetrationBias: q(0.30), surfaceFrac: q(0.30), internalFrac: q(0.30), structuralFrac: q(0.40), bleedFactor: q(0.30) } },
10
+ [DamageChannel.Thermal]: { id: "cap_thermal", kind: "weapon", name: "Thermal", mass_kg: 0, bulk: q(0), damage: { penetrationBias: q(0), surfaceFrac: q(0.40), internalFrac: q(0.50), structuralFrac: q(0.10), bleedFactor: q(0.10) } },
11
+ [DamageChannel.Electrical]: { id: "cap_elec", kind: "weapon", name: "Electrical", mass_kg: 0, bulk: q(0), damage: { penetrationBias: q(0.20), surfaceFrac: q(0.20), internalFrac: q(0.60), structuralFrac: q(0.20), bleedFactor: q(0.05) } },
12
+ [DamageChannel.Chemical]: { id: "cap_chem", kind: "weapon", name: "Chemical", mass_kg: 0, bulk: q(0), damage: { penetrationBias: q(0), surfaceFrac: q(0.45), internalFrac: q(0.45), structuralFrac: q(0.10), bleedFactor: q(0.20) } },
13
+ [DamageChannel.Radiation]: { id: "cap_rad", kind: "weapon", name: "Radiation", mass_kg: 0, bulk: q(0), damage: { penetrationBias: q(0), surfaceFrac: q(0.05), internalFrac: q(0.90), structuralFrac: q(0.05), bleedFactor: q(0.05) } },
14
+ };
15
+ const CAPABILITY_WEAPON_DEFAULT = {
16
+ id: "cap_generic", kind: "weapon", name: "Capability", mass_kg: 0, bulk: q(0),
17
+ damage: { penetrationBias: q(0.10), surfaceFrac: q(0.30), internalFrac: q(0.40), structuralFrac: q(0.30), bleedFactor: q(0.20) },
18
+ };
19
+ export function applyPayload(world, actor, target, payload, trace, tick, effectId, helpers) {
20
+ switch (payload.kind) {
21
+ case "impact": {
22
+ const hitRegion = helpers.resolveCapabilityHitSegment(world, tick, actor, target, 0xCAB1);
23
+ if (!target.injury.byRegion[hitRegion])
24
+ break;
25
+ let effectiveEnergy = payload.spec.energy_J;
26
+ if ((target.condition.shieldReserve_J ?? 0) > 0 &&
27
+ target.condition.shieldExpiry_tick !== undefined &&
28
+ tick <= target.condition.shieldExpiry_tick) {
29
+ const absorbed = Math.min(target.condition.shieldReserve_J, effectiveEnergy);
30
+ target.condition.shieldReserve_J -= absorbed;
31
+ effectiveEnergy -= absorbed;
32
+ }
33
+ if (effectiveEnergy > 0) {
34
+ const wpn = CAPABILITY_CHANNEL_WEAPONS[payload.spec.channel] ?? CAPABILITY_WEAPON_DEFAULT;
35
+ helpers.applyImpactToInjury(target, wpn, effectiveEnergy, hitRegion, false, trace, tick);
36
+ }
37
+ break;
38
+ }
39
+ case "treatment": {
40
+ const BASE_CAP_HEAL = q(0.0050);
41
+ const healRate = qMul(BASE_CAP_HEAL, payload.rateMul);
42
+ for (const reg of Object.values(target.injury.byRegion)) {
43
+ if (reg.bleedingRate > 0) {
44
+ reg.bleedingRate = clampQ((reg.bleedingRate - healRate), 0, SCALE.Q);
45
+ }
46
+ }
47
+ target.injury.shock = clampQ((target.injury.shock - qMul(q(0.01), payload.rateMul)), 0, SCALE.Q);
48
+ break;
49
+ }
50
+ case "armourLayer": {
51
+ target.condition.shieldReserve_J = (target.condition.shieldReserve_J ?? 0) + payload.resist_J;
52
+ const newExpiry = tick + payload.duration_ticks;
53
+ target.condition.shieldExpiry_tick = Math.max(target.condition.shieldExpiry_tick ?? 0, newExpiry);
54
+ break;
55
+ }
56
+ case "velocity": {
57
+ target.velocity_mps = vAdd(target.velocity_mps, payload.delta_mps);
58
+ break;
59
+ }
60
+ case "substance": {
61
+ if (!target.substances)
62
+ target.substances = [];
63
+ target.substances.push({ ...payload.substance });
64
+ break;
65
+ }
66
+ case "structuralRepair": {
67
+ const seg = target.injury.byRegion[payload.region];
68
+ if (seg) {
69
+ const floor = seg.permanentDamage ?? 0;
70
+ const repaired = Math.max(floor, seg.structuralDamage - payload.amount);
71
+ seg.structuralDamage = clampQ(repaired, 0, SCALE.Q);
72
+ }
73
+ break;
74
+ }
75
+ case "fieldEffect": {
76
+ if (!world.activeFieldEffects)
77
+ world.activeFieldEffects = [];
78
+ const fe = {
79
+ ...payload.spec,
80
+ id: `${actor.id}_${effectId}_${tick}`,
81
+ origin: { x: actor.position_m.x, y: actor.position_m.y, z: actor.position_m.z },
82
+ placedByEntityId: actor.id,
83
+ };
84
+ world.activeFieldEffects.push(fe);
85
+ break;
86
+ }
87
+ case "weaponImpact": {
88
+ const wpn = {
89
+ id: "cap_weaponimpact",
90
+ kind: "weapon",
91
+ name: "WeaponImpact",
92
+ mass_kg: 0,
93
+ bulk: q(0),
94
+ damage: payload.profile,
95
+ };
96
+ const hitRegion = helpers.resolveCapabilityHitSegment(world, tick, actor, target, 0xCAB2);
97
+ if (!target.injury.byRegion[hitRegion])
98
+ break;
99
+ let effectiveEnergy = payload.energy_J;
100
+ if ((target.condition.shieldReserve_J ?? 0) > 0 &&
101
+ target.condition.shieldExpiry_tick !== undefined &&
102
+ tick <= target.condition.shieldExpiry_tick) {
103
+ const absorbed = Math.min(target.condition.shieldReserve_J, effectiveEnergy);
104
+ target.condition.shieldReserve_J -= absorbed;
105
+ effectiveEnergy -= absorbed;
106
+ }
107
+ if (effectiveEnergy > 0) {
108
+ helpers.applyImpactToInjury(target, wpn, effectiveEnergy, hitRegion, false, trace, tick);
109
+ }
110
+ break;
111
+ }
112
+ }
113
+ }
114
+ export function applyCapabilityEffect(world, actor, targetId, effect, trace, tick, helpers) {
115
+ const payloads = Array.isArray(effect.payload)
116
+ ? effect.payload
117
+ : [effect.payload];
118
+ let targets;
119
+ if (effect.coneHalfAngle_rad !== undefined) {
120
+ const range_m = effect.range_m ?? 0;
121
+ let dir;
122
+ if (effect.coneDir === "fixed" && effect.coneDirFixed) {
123
+ dir = effect.coneDirFixed;
124
+ }
125
+ else {
126
+ const fx = actor.action.facingDirQ.x;
127
+ const fy = actor.action.facingDirQ.y;
128
+ const mag = Math.sqrt(fx * fx + fy * fy);
129
+ const sc = mag > 0 ? SCALE.m / mag : 1;
130
+ dir = { dx: Math.round(fx * sc), dy: Math.round(fy * sc) };
131
+ }
132
+ const cone = {
133
+ origin: { x: actor.position_m.x, y: actor.position_m.y },
134
+ dir,
135
+ halfAngle_rad: effect.coneHalfAngle_rad,
136
+ range_m,
137
+ };
138
+ targets = world.entities.filter(e => !e.injury.dead && entityInCone(e, cone));
139
+ }
140
+ else if (effect.aoeRadius_m !== undefined) {
141
+ const origin = targetId !== undefined
142
+ ? (world.entities.find(e => e.id === targetId)?.position_m ?? actor.position_m)
143
+ : actor.position_m;
144
+ const radSq = effect.aoeRadius_m * effect.aoeRadius_m;
145
+ targets = world.entities.filter(e => {
146
+ if (e.injury.dead)
147
+ return false;
148
+ const dx = e.position_m.x - origin.x;
149
+ const dy = e.position_m.y - origin.y;
150
+ return dx * dx + dy * dy <= radSq;
151
+ });
152
+ }
153
+ else if (targetId !== undefined) {
154
+ const t = world.entities.find(e => e.id === targetId);
155
+ targets = t && !t.injury.dead ? [t] : [];
156
+ }
157
+ else {
158
+ targets = [actor];
159
+ }
160
+ for (const target of targets) {
161
+ if (target.id !== actor.id) {
162
+ const mr = target.attributes.resilience.magicResist ?? 0;
163
+ if (mr > 0) {
164
+ const resistSeed = eventSeed(world.seed, tick, actor.id, target.id, 0x5E515);
165
+ if ((resistSeed % SCALE.Q) < mr)
166
+ continue;
167
+ }
168
+ }
169
+ for (const p of payloads) {
170
+ applyPayload(world, actor, target, p, trace, tick, effect.id, helpers);
171
+ }
172
+ }
173
+ }
174
+ export function resolveActivation(world, actor, cmd, ctx, trace, tick, onInstantResolve) {
175
+ if (!actor.capabilitySources)
176
+ return;
177
+ const source = actor.capabilitySources.find(s => s.id === cmd.sourceId);
178
+ if (!source)
179
+ return;
180
+ const effect = source.effects.find(ef => ef.id === cmd.effectId);
181
+ if (!effect)
182
+ return;
183
+ const cooldownKey = `${source.id}:${effect.id}`;
184
+ if ((actor.action.capabilityCooldowns?.get(cooldownKey) ?? 0) > 0)
185
+ return;
186
+ if (effect.requiredCapability !== undefined && ctx.techCtx !== undefined) {
187
+ if (!isCapabilityAvailable(ctx.techCtx, effect.requiredCapability))
188
+ return;
189
+ }
190
+ const ax = actor.position_m.x;
191
+ const ay = actor.position_m.y;
192
+ const suppressed = (world.activeFieldEffects ?? []).some(fe => {
193
+ const dx = ax - fe.origin.x;
194
+ const dy = ay - fe.origin.y;
195
+ const distSq = dx * dx + dy * dy;
196
+ const radSq = fe.radius_m * fe.radius_m;
197
+ return distSq <= radSq && source.tags.some(t => fe.suppressesTags.includes(t));
198
+ });
199
+ if (suppressed) {
200
+ trace.onEvent({ kind: TraceKinds.CapabilitySuppressed, tick, entityId: actor.id, sourceId: cmd.sourceId, effectId: cmd.effectId });
201
+ return;
202
+ }
203
+ if (effect.range_m !== undefined && cmd.targetId !== undefined) {
204
+ const tgt = world.entities.find(e => e.id === cmd.targetId);
205
+ if (tgt) {
206
+ const dx = tgt.position_m.x - ax;
207
+ const dy = tgt.position_m.y - ay;
208
+ if (dx * dx + dy * dy > effect.range_m * effect.range_m)
209
+ return;
210
+ }
211
+ }
212
+ if (effect.castTime_ticks < 0) {
213
+ actor.activeConcentration = {
214
+ sourceId: source.id,
215
+ effectId: effect.id,
216
+ ...(cmd.targetId !== undefined ? { targetId: cmd.targetId } : {}),
217
+ };
218
+ trace.onEvent({ kind: TraceKinds.CapabilityActivated, tick, entityId: actor.id, sourceId: source.id, effectId: effect.id });
219
+ return;
220
+ }
221
+ const isBoundless = source.regenModel.type === "boundless";
222
+ let sourceToDraw = source;
223
+ if (!isBoundless && source.reserve_J < effect.cost_J) {
224
+ if (source.linkedFallbackId) {
225
+ const fallback = actor.capabilitySources.find(s => s.id === source.linkedFallbackId);
226
+ if (fallback && (fallback.regenModel.type === "boundless" || fallback.reserve_J >= effect.cost_J)) {
227
+ sourceToDraw = fallback;
228
+ }
229
+ else {
230
+ return;
231
+ }
232
+ }
233
+ else {
234
+ return;
235
+ }
236
+ }
237
+ const drawIsBoundless = sourceToDraw.regenModel.type === "boundless";
238
+ if (effect.castTime_ticks > 0) {
239
+ if (!actor.pendingActivation) {
240
+ if (!drawIsBoundless)
241
+ sourceToDraw.reserve_J -= effect.cost_J;
242
+ actor.pendingActivation = cmd.targetId !== undefined
243
+ ? { sourceId: cmd.sourceId, effectId: cmd.effectId, targetId: cmd.targetId, resolveAtTick: tick + effect.castTime_ticks }
244
+ : { sourceId: cmd.sourceId, effectId: cmd.effectId, resolveAtTick: tick + effect.castTime_ticks };
245
+ if (effect.cooldown_ticks && effect.cooldown_ticks > 0) {
246
+ if (!actor.action.capabilityCooldowns)
247
+ actor.action.capabilityCooldowns = new Map();
248
+ actor.action.capabilityCooldowns.set(cooldownKey, effect.cooldown_ticks);
249
+ }
250
+ }
251
+ return;
252
+ }
253
+ if (!drawIsBoundless)
254
+ sourceToDraw.reserve_J -= effect.cost_J;
255
+ onInstantResolve?.(effect);
256
+ trace.onEvent({ kind: TraceKinds.CapabilityActivated, tick, entityId: actor.id, sourceId: cmd.sourceId, effectId: cmd.effectId });
257
+ if (effect.cooldown_ticks && effect.cooldown_ticks > 0) {
258
+ if (!actor.action.capabilityCooldowns)
259
+ actor.action.capabilityCooldowns = new Map();
260
+ actor.action.capabilityCooldowns.set(cooldownKey, effect.cooldown_ticks);
261
+ }
262
+ if (effect.sustainedTicks && effect.sustainedTicks > 1) {
263
+ actor.action.sustainedEmission = {
264
+ sourceId: cmd.sourceId,
265
+ effectId: cmd.effectId,
266
+ ...(cmd.targetId !== undefined ? { targetId: cmd.targetId } : {}),
267
+ remainingTicks: effect.sustainedTicks - 1,
268
+ };
269
+ }
270
+ }
@@ -0,0 +1,28 @@
1
+ import type { WorldState } from "../world.js";
2
+ import { type Entity } from "../entity.js";
3
+ import type { BreakGrappleCommand, GrappleCommand } from "../commands.js";
4
+ import { type SimulationTuning } from "../tuning.js";
5
+ import { type Q } from "../../units.js";
6
+ import type { WorldIndex } from "../indexing.js";
7
+ import { type ImpactEvent } from "../events.js";
8
+ import type { TraceSink } from "../trace.js";
9
+ type ResolveGrappleCommandOptions = {
10
+ world: WorldState;
11
+ entity: Entity;
12
+ command: GrappleCommand;
13
+ tuning: SimulationTuning;
14
+ index: WorldIndex;
15
+ impacts: ImpactEvent[];
16
+ trace: TraceSink;
17
+ };
18
+ export declare function resolveGrappleCommand(options: ResolveGrappleCommandOptions): void;
19
+ type ResolveBreakBindOptions = {
20
+ world: WorldState;
21
+ entity: Entity;
22
+ intensity: Q;
23
+ index: WorldIndex;
24
+ trace: TraceSink;
25
+ };
26
+ export declare function resolveBreakBind(options: ResolveBreakBindOptions): void;
27
+ export declare function resolveBreakGrappleCommand(world: WorldState, entity: Entity, command: BreakGrappleCommand, tuning: SimulationTuning, index: WorldIndex, trace: TraceSink): void;
28
+ export {};
@@ -0,0 +1,70 @@
1
+ import { SCALE, clampQ, q, qMul } from "../../units.js";
2
+ import { findWeapon } from "../../equipment.js";
3
+ import { eventSeed } from "../seeds.js";
4
+ import { breakBindContestQ } from "../weapon_dynamics.js";
5
+ import { resolveGrappleAttempt, resolveGrappleThrow, resolveGrappleChoke, resolveGrappleJointLock, resolveBreakGrapple, } from "../grapple.js";
6
+ import { TraceKinds } from "../kinds.js";
7
+ export function resolveGrappleCommand(options) {
8
+ const { world, entity, command, tuning, index, impacts, trace } = options;
9
+ const target = index.byId.get(command.targetId);
10
+ if (!target || target.injury.dead)
11
+ return;
12
+ const mode = command.mode ?? "grapple";
13
+ if (mode === "grapple") {
14
+ if (entity.grapple.holdingTargetId === 0 || entity.grapple.holdingTargetId !== command.targetId) {
15
+ resolveGrappleAttempt(world, entity, target, command.intensity, tuning, impacts, trace);
16
+ }
17
+ else {
18
+ trace.onEvent({
19
+ kind: TraceKinds.Grapple,
20
+ tick: world.tick, attackerId: entity.id, targetId: target.id,
21
+ phase: "tick", strengthQ: entity.grapple.gripQ,
22
+ });
23
+ }
24
+ }
25
+ else if (mode === "throw") {
26
+ resolveGrappleThrow(world, entity, target, command.intensity, tuning, impacts, trace);
27
+ }
28
+ else if (mode === "choke") {
29
+ resolveGrappleChoke(entity, target, command.intensity, tuning);
30
+ }
31
+ else if (mode === "jointLock") {
32
+ resolveGrappleJointLock(world, entity, target, command.intensity, tuning, impacts);
33
+ }
34
+ }
35
+ export function resolveBreakBind(options) {
36
+ const { world, entity, intensity, index, trace } = options;
37
+ if (entity.action.weaponBindPartnerId === 0)
38
+ return;
39
+ const partner = index.byId.get(entity.action.weaponBindPartnerId);
40
+ if (!partner || partner.injury.dead) {
41
+ entity.action.weaponBindPartnerId = 0;
42
+ entity.action.weaponBindTicks = 0;
43
+ return;
44
+ }
45
+ const breakerWpn = findWeapon(entity.loadout);
46
+ const holderWpn = findWeapon(partner.loadout);
47
+ const breakerArm = breakerWpn?.momentArm_m ?? Math.trunc(0.55 * SCALE.m);
48
+ const holderArm = holderWpn?.momentArm_m ?? Math.trunc(0.55 * SCALE.m);
49
+ const baseWinQ = breakBindContestQ(entity.attributes.performance.peakForce_N, partner.attributes.performance.peakForce_N, breakerArm, holderArm);
50
+ const winQ = clampQ(qMul(baseWinQ, intensity), q(0.05), q(0.95));
51
+ const breakSeed = eventSeed(world.seed, world.tick, entity.id, partner.id, 0xBB1D);
52
+ const breakRoll = (breakSeed % SCALE.Q);
53
+ if (breakRoll < winQ) {
54
+ partner.condition.stunned = clampQ(partner.condition.stunned + q(0.05), 0, SCALE.Q);
55
+ entity.action.weaponBindPartnerId = 0;
56
+ entity.action.weaponBindTicks = 0;
57
+ partner.action.weaponBindPartnerId = 0;
58
+ partner.action.weaponBindTicks = 0;
59
+ trace.onEvent({
60
+ kind: TraceKinds.WeaponBindBreak,
61
+ tick: world.tick,
62
+ entityId: entity.id,
63
+ partnerId: partner.id,
64
+ reason: "forced",
65
+ });
66
+ }
67
+ }
68
+ export function resolveBreakGrappleCommand(world, entity, command, tuning, index, trace) {
69
+ resolveBreakGrapple(world, entity, command.intensity, tuning, index, trace);
70
+ }
@@ -0,0 +1,27 @@
1
+ import type { WorldState } from "../world.js";
2
+ import { type Entity } from "../entity.js";
3
+ import type { ShootCommand } from "../commands.js";
4
+ import type { KernelContext } from "../context.js";
5
+ import { type Q } from "../../units.js";
6
+ import { type Shield } from "../../equipment.js";
7
+ import { type SimulationTuning } from "../tuning.js";
8
+ import { type HitArea } from "../combat.js";
9
+ import { type ImpactEvent } from "../events.js";
10
+ import type { TraceSink } from "../trace.js";
11
+ export type HitSegmentId = string;
12
+ type ResolveShootOptions = {
13
+ world: WorldState;
14
+ shooter: Entity;
15
+ cmd: ShootCommand;
16
+ tuning: SimulationTuning;
17
+ impacts: ImpactEvent[];
18
+ trace: TraceSink;
19
+ ctx: KernelContext;
20
+ target: Entity | undefined;
21
+ resolveTargetHitSegment: (target: Entity, roll01: Q, sideBit: 0 | 1, fallbackArea?: HitArea) => HitSegmentId;
22
+ shieldBlocksSegment: (shield: Shield | undefined, target: Entity, segmentId: HitSegmentId, area: HitArea | undefined) => boolean;
23
+ regionCoverageQ: (coverageByRegion: Record<string, Q>, segmentId: HitSegmentId) => Q;
24
+ armourCoversHit: (world: WorldState, coverage: Q, aId: number, bId: number) => boolean;
25
+ };
26
+ export declare function resolveShoot(options: ResolveShootOptions): void;
27
+ export {};
@@ -0,0 +1,212 @@
1
+ import { SCALE, q, clampQ, qMul, mulDiv } from "../../units.js";
2
+ import { DamageChannel } from "../../channels.js";
3
+ import { deriveArmourProfile, findRangedWeapon, findShield } from "../../equipment.js";
4
+ import { deriveFunctionalState } from "../impairment.js";
5
+ import { chooseArea } from "../combat.js";
6
+ import { eventSeed } from "../seeds.js";
7
+ import { coverFractionAtPosition, elevationAtPosition } from "../terrain.js";
8
+ import { computeWindAimError } from "../weather.js";
9
+ import { energyAtRange_J, adjustedDispersionQ, groupingRadius_m, thrownLaunchEnergy_J, recycleTicks, shootCost_J, } from "../ranged.js";
10
+ import { getSkill } from "../skills.js";
11
+ import { TICK_HZ } from "../tick.js";
12
+ import { TraceKinds } from "../kinds.js";
13
+ const AIM_MAX_TICKS = 20;
14
+ const AIM_MIN_MUL = q(0.50);
15
+ const AIM_STILL_THRESHOLD = 5_000;
16
+ export function resolveShoot(options) {
17
+ const { world, shooter, cmd, tuning, impacts, trace, ctx, target, resolveTargetHitSegment, shieldBlocksSegment, regionCoverageQ, armourCoversHit } = options;
18
+ void tuning;
19
+ {
20
+ const svx = shooter.velocity_mps.x;
21
+ const svy = shooter.velocity_mps.y;
22
+ const shooterVelMag = Math.trunc(Math.sqrt(svx * svx + svy * svy));
23
+ if (cmd.targetId !== shooter.action.aimTargetId || shooterVelMag > AIM_STILL_THRESHOLD) {
24
+ shooter.action.aimTicks = 0;
25
+ shooter.action.aimTargetId = cmd.targetId;
26
+ }
27
+ else if (shooter.action.shootCooldownTicks > 0 && shooterVelMag <= AIM_STILL_THRESHOLD) {
28
+ shooter.action.aimTicks = Math.min(shooter.action.aimTicks + 1, AIM_MAX_TICKS);
29
+ }
30
+ }
31
+ if (shooter.action.shootCooldownTicks > 0)
32
+ return;
33
+ const wpn = findRangedWeapon(shooter.loadout, cmd.weaponId);
34
+ if (!wpn)
35
+ return;
36
+ if (!target || target.injury.dead)
37
+ return;
38
+ const ammo = cmd.ammoId ? wpn.ammo?.find(a => a.id === cmd.ammoId) : undefined;
39
+ const projMass_kg = ammo?.projectileMass_kg ?? wpn.projectileMass_kg;
40
+ const dragCoeff_perM = ammo?.dragCoeff_perM ?? wpn.dragCoeff_perM;
41
+ const ammoDamage = ammo?.damage ?? wpn.damage;
42
+ const launchMul = ammo?.launchEnergyMul ?? SCALE.Q;
43
+ const funcA = deriveFunctionalState(shooter, tuning);
44
+ if (!funcA.canAct)
45
+ return;
46
+ const dx = BigInt(target.position_m.x - shooter.position_m.x);
47
+ const dy = BigInt(target.position_m.y - shooter.position_m.y);
48
+ const cellSizeRS = ctx.cellSize_m ?? Math.trunc(4 * SCALE.m);
49
+ const elevSh = elevationAtPosition(ctx.elevationGrid, cellSizeRS, shooter.position_m.x, shooter.position_m.y);
50
+ const elevTg = elevationAtPosition(ctx.elevationGrid, cellSizeRS, target.position_m.x, target.position_m.y);
51
+ const dz = BigInt(target.position_m.z - shooter.position_m.z + (elevTg - elevSh));
52
+ const dist_m = Number(isqrtBig(dx * dx + dy * dy + dz * dz));
53
+ const intensity = clampQ(cmd.intensity ?? q(1.0), q(0.1), q(1.0));
54
+ const launchEnergy = wpn.category === "thrown"
55
+ ? mulDiv(thrownLaunchEnergy_J(shooter.attributes.performance.peakPower_W), getSkill(shooter.skills, "throwingWeapons").energyTransferMul, SCALE.Q)
56
+ : Math.trunc(qMul(wpn.launchEnergy_J, launchMul));
57
+ const energy_J = energyAtRange_J(launchEnergy, dragCoeff_perM, dist_m);
58
+ const v_impact_mps = projMass_kg > 0
59
+ ? Math.trunc(Math.sqrt(2 * energy_J * SCALE.kg / projMass_kg) * SCALE.mps)
60
+ : 0;
61
+ const ctrl = shooter.attributes.control;
62
+ const adjDisp = adjustedDispersionQ(wpn.dispersionQ, ctrl.controlQuality, ctrl.fineControl, shooter.energy.fatigue, intensity);
63
+ const rangedSkill = getSkill(shooter.skills, "rangedCombat");
64
+ const skillAdjDisp = qMul(adjDisp, rangedSkill.dispersionMul);
65
+ let gRadius_m = groupingRadius_m(skillAdjDisp, dist_m);
66
+ const aimReduction = mulDiv(SCALE.Q - AIM_MIN_MUL, Math.min(shooter.action.aimTicks, AIM_MAX_TICKS), AIM_MAX_TICKS);
67
+ const aimMul = (SCALE.Q - aimReduction);
68
+ gRadius_m = Math.trunc(qMul(gRadius_m, aimMul));
69
+ const tvx = target.velocity_mps.x;
70
+ const tvy = target.velocity_mps.y;
71
+ const targetVelMag = Math.trunc(Math.sqrt(tvx * tvx + tvy * tvy));
72
+ const leadError_m = mulDiv(targetVelMag, 2_000, SCALE.mps);
73
+ gRadius_m += leadError_m;
74
+ if (ctx.weather?.wind && v_impact_mps > 0 && dist_m > 0) {
75
+ gRadius_m += computeWindAimError(ctx.weather.wind, Number(dx), Number(dy), dist_m, v_impact_mps);
76
+ }
77
+ const rawHalfWidth_m = mulDiv(shooter.attributes.morphology.stature_m, 2000, SCALE.Q);
78
+ const cover = ctx.obstacleGrid
79
+ ? coverFractionAtPosition(ctx.obstacleGrid, cellSizeRS, target.position_m.x, target.position_m.y)
80
+ : 0;
81
+ const bodyHalfWidth_m = cover > 0
82
+ ? mulDiv(rawHalfWidth_m, Math.max(0, SCALE.Q - cover), SCALE.Q)
83
+ : rawHalfWidth_m;
84
+ const dispSeed = eventSeed(world.seed, world.tick, shooter.id, target.id, 0xD15A);
85
+ const errorMag_m = gRadius_m > 0
86
+ ? mulDiv(dispSeed % SCALE.Q, gRadius_m, SCALE.Q)
87
+ : 0;
88
+ const hit = errorMag_m <= bodyHalfWidth_m;
89
+ const suppressed = !hit && errorMag_m <= bodyHalfWidth_m * 3;
90
+ shooter.energy.reserveEnergy_J = Math.max(0, shooter.energy.reserveEnergy_J - shootCost_J(wpn, intensity, shooter.attributes.performance.peakPower_W));
91
+ shooter.action.aimTicks = 0;
92
+ if (wpn.magCapacity !== undefined) {
93
+ if (shooter.action.roundsInMag === undefined) {
94
+ shooter.action.roundsInMag = wpn.magCapacity;
95
+ }
96
+ shooter.action.roundsInMag -= 1;
97
+ if (shooter.action.roundsInMag <= 0) {
98
+ shooter.action.roundsInMag = wpn.magCapacity;
99
+ shooter.action.shootCooldownTicks = recycleTicks(wpn, TICK_HZ);
100
+ }
101
+ else {
102
+ shooter.action.shootCooldownTicks =
103
+ wpn.shotInterval_s !== undefined
104
+ ? Math.ceil((wpn.shotInterval_s * TICK_HZ) / SCALE.s)
105
+ : recycleTicks(wpn, TICK_HZ);
106
+ }
107
+ }
108
+ else {
109
+ shooter.action.shootCooldownTicks = recycleTicks(wpn, TICK_HZ);
110
+ }
111
+ if (suppressed) {
112
+ target.condition.suppressedTicks = Math.max(target.condition.suppressedTicks, 4);
113
+ target.condition.suppressionFearMul = wpn.suppressionFearMul ?? SCALE.Q;
114
+ }
115
+ let hitRegion;
116
+ if (hit && energy_J > 0) {
117
+ const sideSeed = eventSeed(world.seed, world.tick, shooter.id, target.id, 0xD15B);
118
+ const areaSeed = eventSeed(world.seed, world.tick, shooter.id, target.id, 0xD15C);
119
+ const hitArea = target.bodyPlan
120
+ ? undefined
121
+ : chooseArea((areaSeed % SCALE.Q));
122
+ const sideBit = (sideSeed & 1);
123
+ hitRegion = resolveTargetHitSegment(target, ((areaSeed >>> 8) % SCALE.Q), sideBit, hitArea);
124
+ const shield = findShield(target.loadout);
125
+ const shieldSeed = eventSeed(world.seed, world.tick, shooter.id, target.id, 0xD15D);
126
+ const projBypassQ = ("shieldBypassQ" in wpn) ? wpn.shieldBypassQ : 0;
127
+ const effectiveCoverageQ = projBypassQ > 0
128
+ ? Math.max(0, qMul((shield)?.coverageQ ?? 0, SCALE.Q - projBypassQ))
129
+ : ((shield)?.coverageQ ?? 0);
130
+ const shieldHit = shield !== undefined &&
131
+ ((shieldSeed % SCALE.Q) < effectiveCoverageQ) &&
132
+ shieldBlocksSegment(shield, target, hitRegion, hitArea);
133
+ const armour = deriveArmourProfile(target.loadout, target.armourState);
134
+ const isEnergyProjectile = !!(wpn).energyType;
135
+ const PROJ_CHANNEL_MASK = isEnergyProjectile ? (1 << DamageChannel.Energy) : (1 << DamageChannel.Kinetic);
136
+ const armourHit = armourCoversHit(world, regionCoverageQ(armour.coverageByRegion, hitRegion), shooter.id, target.id);
137
+ const protectedByArmour = armourHit && ((armour.protects & PROJ_CHANNEL_MASK) !== 0);
138
+ let finalEnergy = energy_J;
139
+ if (shield && shieldHit) {
140
+ const shieldResidual = Math.max(0, energy_J - (shield).blockResist_J);
141
+ finalEnergy = mulDiv(shieldResidual, (shield).deflectQ ?? q(0.30), SCALE.Q);
142
+ }
143
+ if (protectedByArmour) {
144
+ if (isEnergyProjectile && armour.reflectivity > q(0)) {
145
+ finalEnergy = mulDiv(finalEnergy, SCALE.Q - armour.reflectivity, SCALE.Q);
146
+ }
147
+ else if (!isEnergyProjectile) {
148
+ finalEnergy = applyKineticArmourPenetration(finalEnergy, armour.resist_J, armour.protectedDamageMul);
149
+ }
150
+ if (target.armourState) {
151
+ const armourItems = target.loadout.items.filter(it => it.kind === "armour");
152
+ for (const it of armourItems) {
153
+ if ((it).ablative && target.armourState.has(it.id)) {
154
+ const st = target.armourState.get(it.id);
155
+ st.resistRemaining_J = Math.max(0, st.resistRemaining_J - energy_J);
156
+ }
157
+ }
158
+ }
159
+ }
160
+ const wpnProxy = {
161
+ id: wpn.id,
162
+ kind: "weapon",
163
+ name: wpn.name,
164
+ mass_kg: projMass_kg,
165
+ bulk: q(0),
166
+ damage: ammoDamage,
167
+ };
168
+ impacts.push({
169
+ kind: "impact",
170
+ attackerId: shooter.id,
171
+ targetId: target.id,
172
+ region: hitRegion,
173
+ energy_J: finalEnergy,
174
+ protectedByArmour,
175
+ weaponId: wpn.id,
176
+ wpn: wpnProxy,
177
+ blocked: shieldHit ?? false,
178
+ parried: false,
179
+ hitQuality: q(0.75),
180
+ shieldBlocked: shieldHit ?? false,
181
+ massEff_kg: projMass_kg,
182
+ v_impact_mps,
183
+ });
184
+ }
185
+ trace.onEvent({
186
+ kind: TraceKinds.ProjectileHit,
187
+ tick: world.tick,
188
+ shooterId: shooter.id,
189
+ targetId: target.id,
190
+ weaponId: wpn.id,
191
+ hit,
192
+ ...(hitRegion !== undefined ? { region: hitRegion } : {}),
193
+ distance_m: dist_m,
194
+ energyAtImpact_J: energy_J,
195
+ suppressed,
196
+ });
197
+ }
198
+ function applyKineticArmourPenetration(energy_J, resist_J, postMul) {
199
+ const remaining = Math.max(0, energy_J - Math.max(0, resist_J));
200
+ return mulDiv(remaining, postMul, SCALE.Q);
201
+ }
202
+ function isqrtBig(n) {
203
+ if (n <= 0n)
204
+ return 0n;
205
+ let r = n;
206
+ let r1 = (r + 1n) >> 1n;
207
+ while (r1 < r) {
208
+ r = r1;
209
+ r1 = (r + n / r) >> 1n;
210
+ }
211
+ return r;
212
+ }
@@ -0,0 +1,16 @@
1
+ import type { WorldState } from "../world.js";
2
+ import { type Entity } from "../entity.js";
3
+ import type { TreatCommand } from "../commands.js";
4
+ import type { KernelContext } from "../context.js";
5
+ import type { WorldIndex } from "../indexing.js";
6
+ import type { TraceSink } from "../trace.js";
7
+ type ResolveTreatOptions = {
8
+ world: WorldState;
9
+ treater: Entity;
10
+ cmd: TreatCommand;
11
+ index: WorldIndex;
12
+ trace: TraceSink;
13
+ ctx: KernelContext;
14
+ };
15
+ export declare function resolveTreat(options: ResolveTreatOptions): void;
16
+ export {};