@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,12 @@
1
+ import type { Entity } from "./entity.js";
2
+ import type { WorldState } from "./world.js";
3
+ /**
4
+ * Normalize one entity into the current kernel-ready shape.
5
+ * Mutates the entity in-place for performance and deterministic map identity.
6
+ */
7
+ export declare function normalizeEntityInPlace(e: Entity): void;
8
+ /**
9
+ * Normalize world and all entities to current kernel-ready shape.
10
+ * Mutates and returns the same world object.
11
+ */
12
+ export declare function normalizeWorldInPlace(world: WorldState): WorldState;
@@ -0,0 +1,92 @@
1
+ import { q, SCALE } from "../units.js";
2
+ import { defaultAction } from "./action.js";
3
+ import { defaultIntent } from "./intent.js";
4
+ import { buildLimbStates } from "./limb.js";
5
+ import { DEFAULT_PERCEPTION } from "./sensory.js";
6
+ /**
7
+ * Normalize one entity into the current kernel-ready shape.
8
+ * Mutates the entity in-place for performance and deterministic map identity.
9
+ */
10
+ export function normalizeEntityInPlace(e) {
11
+ if (!(e).intent)
12
+ (e).intent = defaultIntent();
13
+ if (!(e).action)
14
+ (e).action = defaultAction();
15
+ if (!(e).grapple) {
16
+ (e).grapple = { holdingTargetId: 0, heldByIds: [], gripQ: q(0), position: "standing" };
17
+ }
18
+ else if ((e).grapple.position === undefined) {
19
+ (e).grapple.position = "standing";
20
+ }
21
+ if ((e).action.grappleCooldownTicks === undefined)
22
+ (e).action.grappleCooldownTicks = 0;
23
+ if ((e).condition?.pinned === undefined)
24
+ (e).condition.pinned = false;
25
+ if ((e).action.weaponBindPartnerId === undefined)
26
+ (e).action.weaponBindPartnerId = 0;
27
+ if ((e).action.weaponBindTicks === undefined)
28
+ (e).action.weaponBindTicks = 0;
29
+ if ((e).action.shootCooldownTicks === undefined)
30
+ (e).action.shootCooldownTicks = 0;
31
+ if ((e).condition.suppressedTicks === undefined)
32
+ (e).condition.suppressedTicks = 0;
33
+ if ((e).action.swingMomentumQ === undefined)
34
+ (e).action.swingMomentumQ = 0;
35
+ if ((e).action.aimTicks === undefined)
36
+ (e).action.aimTicks = 0;
37
+ if ((e).action.aimTargetId === undefined)
38
+ (e).action.aimTargetId = 0;
39
+ if (!(e.attributes).perception)
40
+ (e.attributes).perception = DEFAULT_PERCEPTION;
41
+ if (!e.ai)
42
+ e.ai = { focusTargetId: 0, retargetCooldownTicks: 0, decisionCooldownTicks: 0 };
43
+ else if ((e.ai).decisionCooldownTicks === undefined)
44
+ (e.ai).decisionCooldownTicks = 0;
45
+ if ((e.condition).fearQ === undefined)
46
+ (e.condition).fearQ = q(0);
47
+ if ((e.condition).suppressionFearMul === undefined)
48
+ (e.condition).suppressionFearMul = SCALE.Q;
49
+ if ((e.condition).recentAllyDeaths === undefined)
50
+ (e.condition).recentAllyDeaths = 0;
51
+ if ((e.condition).lastAllyDeathTick === undefined)
52
+ (e.condition).lastAllyDeathTick = -1;
53
+ if ((e.condition).surrendered === undefined)
54
+ (e.condition).surrendered = false;
55
+ if ((e.condition).rallyCooldownTicks === undefined)
56
+ (e.condition).rallyCooldownTicks = 0;
57
+ if ((e.condition).blindTicks === undefined)
58
+ (e.condition).blindTicks = 0;
59
+ if ((e.injury).hemolymphLoss === undefined)
60
+ (e.injury).hemolymphLoss = q(0);
61
+ for (const reg of Object.values(e.injury.byRegion)) {
62
+ if ((reg).fractured === undefined)
63
+ (reg).fractured = false;
64
+ if ((reg).infectedTick === undefined)
65
+ (reg).infectedTick = -1;
66
+ if ((reg).bleedDuration_ticks === undefined)
67
+ (reg).bleedDuration_ticks = 0;
68
+ if ((reg).permanentDamage === undefined)
69
+ (reg).permanentDamage = q(0);
70
+ }
71
+ if (!e.armourState) {
72
+ const ablativeItems = e.loadout.items.filter((it) => it.kind === "armour" && !!it.ablative);
73
+ if (ablativeItems.length > 0) {
74
+ e.armourState = new Map(ablativeItems.map(it => [it.id, { resistRemaining_J: it.resist_J }]));
75
+ }
76
+ }
77
+ if (!e.limbStates && e.bodyPlan) {
78
+ const limbs = buildLimbStates(e.bodyPlan);
79
+ if (limbs.length > 0)
80
+ e.limbStates = limbs;
81
+ }
82
+ }
83
+ /**
84
+ * Normalize world and all entities to current kernel-ready shape.
85
+ * Mutates and returns the same world object.
86
+ */
87
+ export function normalizeWorldInPlace(world) {
88
+ world.entities.sort((a, b) => a.id - b.id);
89
+ for (const e of world.entities)
90
+ normalizeEntityInPlace(e);
91
+ return world;
92
+ }
@@ -0,0 +1,30 @@
1
+ import type { WorldState } from "../world.js";
2
+ import { type Entity } from "../entity.js";
3
+ import type { AttackCommand } 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
+ import type { SpatialIndex } from "../spatial.js";
12
+ import type { WorldIndex } from "../indexing.js";
13
+ export type HitSegmentId = string;
14
+ type ResolveAttackOptions = {
15
+ world: WorldState;
16
+ attacker: Entity;
17
+ cmd: AttackCommand;
18
+ tuning: SimulationTuning;
19
+ index: WorldIndex;
20
+ impacts: ImpactEvent[];
21
+ spatial: SpatialIndex;
22
+ trace: TraceSink;
23
+ ctx: KernelContext;
24
+ resolveTargetHitSegment: (target: Entity, roll01: Q, sideBit: 0 | 1, fallbackArea?: HitArea) => HitSegmentId;
25
+ regionCoverageQ: (coverageByRegion: Record<string, Q>, segmentId: HitSegmentId) => Q;
26
+ shieldBlocksSegment: (shield: Shield | undefined, target: Entity, segmentId: HitSegmentId, area: HitArea | undefined) => boolean;
27
+ armourCoversHit: (world: WorldState, coverage: Q, aId: number, bId: number) => boolean;
28
+ };
29
+ export declare function resolveAttack(options: ResolveAttackOptions): void;
30
+ export {};
@@ -0,0 +1,310 @@
1
+ import { SCALE, q, clampQ, qMul, mulDiv, to } from "../../units.js";
2
+ import { DamageChannel } from "../../channels.js";
3
+ import { deriveArmourProfile, findWeapon, findShield, findExoskeleton, findSensor } from "../../equipment.js";
4
+ import { deriveFunctionalState } from "../impairment.js";
5
+ import { resolveHit } from "../combat.js";
6
+ import { normaliseDirCheapQ, dotDirQ } from "../vec3.js";
7
+ import { eventSeed } from "../seeds.js";
8
+ import { elevationAtPosition } from "../terrain.js";
9
+ import { CORE_TEMP_NORMAL_Q, deriveTempModifiers } from "../thermoregulation.js";
10
+ import { getSkill } from "../skills.js";
11
+ import { TICK_HZ } from "../tick.js";
12
+ import { parryLeverageQ } from "../combat.js";
13
+ import { reachDomPenaltyQ, twoHandedAttackBonusQ, missRecoveryTicks, bindChanceQ, bindDurationTicks, } from "../weapon_dynamics.js";
14
+ import { TraceKinds } from "../kinds.js";
15
+ import { DEFAULT_SENSORY_ENV, canDetect } from "../sensory.js";
16
+ import { FEAR_SURPRISE, painBlocksAction } from "../morale.js";
17
+ import { isMeleeLaneOccludedByFriendly } from "../occlusion.js";
18
+ const SWING_MOMENTUM_MAX = q(0.12);
19
+ export function resolveAttack(options) {
20
+ const { world, attacker, cmd, tuning, index, impacts, spatial, trace, ctx, resolveTargetHitSegment, regionCoverageQ, shieldBlocksSegment, armourCoversHit } = options;
21
+ if (attacker.action.attackCooldownTicks > 0)
22
+ return;
23
+ if (attacker.action.weaponBindPartnerId !== 0)
24
+ return;
25
+ const target = index.byId.get(cmd.targetId);
26
+ if (!target || target.injury.dead)
27
+ return;
28
+ const funcA = deriveFunctionalState(attacker, tuning);
29
+ const funcB = deriveFunctionalState(target, tuning);
30
+ if (!funcA.canAct)
31
+ return;
32
+ if (tuning.realism !== "arcade") {
33
+ const painSeed = eventSeed(world.seed, world.tick, attacker.id, target.id, 0xA77A2);
34
+ if (painBlocksAction(painSeed, attacker.injury.shock, attacker.attributes.resilience.distressTolerance))
35
+ return;
36
+ }
37
+ const wpn = findWeapon(attacker.loadout, cmd.weaponId);
38
+ if (!wpn)
39
+ return;
40
+ const reach_m = wpn.reach_m ?? Math.trunc(attacker.attributes.morphology.stature_m * 0.45);
41
+ const dx = target.position_m.x - attacker.position_m.x;
42
+ const dy = target.position_m.y - attacker.position_m.y;
43
+ const dz = target.position_m.z - attacker.position_m.z;
44
+ const cellSizeA = ctx.cellSize_m ?? Math.trunc(4 * SCALE.m);
45
+ const elevA = elevationAtPosition(ctx.elevationGrid, cellSizeA, attacker.position_m.x, attacker.position_m.y);
46
+ const elevT = elevationAtPosition(ctx.elevationGrid, cellSizeA, target.position_m.x, target.position_m.y);
47
+ const dzWithElev = dz + (elevT - elevA);
48
+ const dist2 = BigInt(dx) * BigInt(dx) + BigInt(dy) * BigInt(dy) + BigInt(dzWithElev) * BigInt(dzWithElev);
49
+ const reach2 = BigInt(reach_m) * BigInt(reach_m);
50
+ if (dist2 > reach2)
51
+ return;
52
+ if (tuning.realism !== "arcade") {
53
+ const blocked = isMeleeLaneOccludedByFriendly(world, attacker, target, index, spatial, { laneRadius_m: Math.trunc(0.35 * SCALE.m) });
54
+ if (blocked)
55
+ return;
56
+ }
57
+ const dirToTarget = normaliseDirCheapQ({ x: dx, y: dy, z: dz });
58
+ const readyTime_s = wpn.readyTime_s ?? to.s(0.6);
59
+ const attackerMeleeSkill = getSkill(attacker.skills, "meleeCombat");
60
+ const effectiveReadyTime = Math.max(Math.trunc(readyTime_s / 3), readyTime_s + attackerMeleeSkill.hitTimingOffset_s);
61
+ attacker.action.attackCooldownTicks = Math.max(1, Math.trunc((effectiveReadyTime * TICK_HZ) / SCALE.s));
62
+ const clampedIntensity = clampQ(cmd.intensity ?? q(1.0), q(0.1), q(1.0));
63
+ attacker.energy.reserveEnergy_J = Math.max(0, attacker.energy.reserveEnergy_J - strikeCost_J(attacker, clampedIntensity));
64
+ const attackSkillBase = clampQ(qMul(attacker.attributes.control.controlQuality, attacker.attributes.control.fineControl), q(0.05), q(0.99));
65
+ let attackSkill = clampQ(qMul(attackSkillBase, qMul(funcA.coordinationMul, funcA.manipulationMul)), q(0.01), q(0.99));
66
+ const defenceSkillBase = clampQ(qMul(target.attributes.control.controlQuality, target.attributes.control.stability), q(0.05), q(0.99));
67
+ let defenceSkill = clampQ(qMul(defenceSkillBase, qMul(funcB.coordinationMul, funcB.mobilityMul)), q(0.01), q(0.99));
68
+ const geomDot = dotDirQ(attacker.action.facingDirQ, dirToTarget);
69
+ const grappling = attacker.grapple.holdingTargetId === target.id;
70
+ if (tuning.realism !== "arcade" && !target.condition.prone && !grappling) {
71
+ const tgtWpn = findWeapon(target.loadout);
72
+ if (tgtWpn) {
73
+ const tgtReach_m = tgtWpn.reach_m ?? Math.trunc(target.attributes.morphology.stature_m * 0.45);
74
+ const penalty = reachDomPenaltyQ(reach_m, tgtReach_m);
75
+ attackSkill = clampQ(qMul(attackSkill, penalty), q(0.01), q(0.99));
76
+ }
77
+ }
78
+ if (tuning.realism !== "arcade") {
79
+ const elevDiff = elevA - elevT;
80
+ if (elevDiff > to.m(0.5)) {
81
+ const bonus = clampQ(mulDiv(elevDiff - to.m(0.5), q(0.05), to.m(1)), q(0), q(0.10));
82
+ attackSkill = clampQ(qMul(attackSkill, (SCALE.Q + bonus)), q(0.01), q(0.99));
83
+ }
84
+ }
85
+ const defMeleeSkill = getSkill(target.skills, "meleeDefence");
86
+ defenceSkill = clampQ(qMul(defenceSkill, defMeleeSkill.energyTransferMul), q(0.01), q(0.99));
87
+ const defenceModeEffective = target.action.weaponBindPartnerId !== 0
88
+ ? "none"
89
+ : target.intent.defence.mode;
90
+ let defenceIntensityEffective = target.action.weaponBindPartnerId !== 0
91
+ ? q(0)
92
+ : target.intent.defence.intensity;
93
+ if (defenceModeEffective === "block") {
94
+ const tgtShield = findShield(target.loadout);
95
+ if (tgtShield) {
96
+ const shieldSkill = getSkill(target.skills, "shieldCraft");
97
+ defenceSkill = clampQ(qMul(defenceSkill, shieldSkill.energyTransferMul), q(0.01), q(0.99));
98
+ }
99
+ }
100
+ if (tuning.realism !== "arcade") {
101
+ const sEnv = world.runtimeState?.sensoryEnv ?? DEFAULT_SENSORY_ENV;
102
+ const tgtSensor = findSensor(target.loadout);
103
+ const tgtSensorBoost = tgtSensor
104
+ ? { visionRangeMul: tgtSensor.visionRangeMul, hearingRangeMul: tgtSensor.hearingRangeMul }
105
+ : undefined;
106
+ const detectionQ = canDetect(target, attacker, sEnv, tgtSensorBoost);
107
+ if (detectionQ <= 0) {
108
+ defenceIntensityEffective = q(0);
109
+ target.condition.fearQ = clampQ((target.condition.fearQ ?? 0) + FEAR_SURPRISE, 0, SCALE.Q);
110
+ }
111
+ else if (detectionQ < q(0.8)) {
112
+ defenceIntensityEffective = qMul(defenceIntensityEffective, detectionQ);
113
+ }
114
+ }
115
+ if (tuning.realism !== "arcade" && defenceModeEffective === "parry" && !grappling) {
116
+ const defWpnReach = findWeapon(target.loadout);
117
+ if (defWpnReach) {
118
+ const defReach = defWpnReach.reach_m ?? Math.trunc(target.attributes.morphology.stature_m * 0.45);
119
+ defenceSkill = clampQ(qMul(defenceSkill, reachDomPenaltyQ(defReach, reach_m)), q(0.01), q(0.99));
120
+ }
121
+ }
122
+ const meleeBypassQ = (wpn).shieldBypassQ ?? 0;
123
+ const defenceIntensityForHit = (meleeBypassQ > 0 && defenceModeEffective === "block")
124
+ ? qMul(defenceIntensityEffective, SCALE.Q - meleeBypassQ)
125
+ : defenceIntensityEffective;
126
+ const seed = eventSeed(world.seed, world.tick, attacker.id, target.id, 0xA11AC);
127
+ const res = resolveHit(seed, attackSkill, defenceSkill, geomDot, defenceModeEffective, defenceIntensityForHit);
128
+ trace.onEvent({
129
+ kind: TraceKinds.AttackAttempt,
130
+ tick: world.tick,
131
+ attackerId: attacker.id,
132
+ targetId: target.id,
133
+ hit: res.hit,
134
+ blocked: res.blocked,
135
+ parried: res.parried,
136
+ hitQuality: res.hitQuality,
137
+ area: res.area,
138
+ });
139
+ if (!res.hit) {
140
+ attacker.action.attackCooldownTicks += Math.trunc(mulDiv(missRecoveryTicks(wpn), clampedIntensity, SCALE.Q));
141
+ attacker.action.swingMomentumQ = q(0);
142
+ return;
143
+ }
144
+ const hitSeed = eventSeed(world.seed, world.tick, attacker.id, target.id, 0x51DE);
145
+ const sideBit = (hitSeed & 1);
146
+ const region = resolveTargetHitSegment(target, ((hitSeed >>> 8) % SCALE.Q), sideBit, res.area);
147
+ const defenderBlocking = (target.intent.defence.mode === "block");
148
+ const shield = findShield(target.loadout);
149
+ const shieldBlocked = res.hit &&
150
+ res.blocked &&
151
+ defenderBlocking &&
152
+ !!shield &&
153
+ shieldBlocksSegment(shield, target, region, res.area);
154
+ const baseIntensity = clampQ(cmd.intensity ?? q(1.0), q(0.1), q(1.0));
155
+ const handling = wpn.handlingMul ?? q(1.0);
156
+ const handlingPenalty = clampQ(q(1.0) - qMul(q(0.18), (handling - SCALE.Q)), q(0.70), q(1.0));
157
+ const intensity = clampQ(qMul(baseIntensity, qMul(funcA.manipulationMul, handlingPenalty)), q(0.1), q(1.0));
158
+ const coreTempQ = (attacker.condition).coreTemp_Q ?? CORE_TEMP_NORMAL_Q;
159
+ const tempMods = deriveTempModifiers(coreTempQ);
160
+ const P = Math.trunc(qMul(attacker.attributes.performance.peakPower_W, tempMods.powerMul));
161
+ const base = clampI32(Math.trunc((P * SCALE.mps) / 200), Math.trunc(2 * SCALE.mps), Math.trunc(12 * SCALE.mps));
162
+ const wMul = wpn.strikeSpeedMul ?? q(1.0);
163
+ const cMul = attacker.attributes.control.controlQuality;
164
+ const qualMul = q(0.70) + qMul(res.hitQuality, q(0.30));
165
+ const vStrike = mulDiv(mulDiv(mulDiv(mulDiv(base, wMul, SCALE.Q), cMul, SCALE.Q), intensity, SCALE.Q), qualMul, SCALE.Q);
166
+ const vStrikeVec = scaleDirToSpeed(dirToTarget, vStrike);
167
+ const APPROACH_CAP = Math.trunc(2.0 * SCALE.mps);
168
+ const bodyRelX = clampI32(attacker.velocity_mps.x - target.velocity_mps.x, -APPROACH_CAP, APPROACH_CAP);
169
+ const bodyRelY = clampI32(attacker.velocity_mps.y - target.velocity_mps.y, -APPROACH_CAP, APPROACH_CAP);
170
+ const bodyRelZ = clampI32(attacker.velocity_mps.z - target.velocity_mps.z, -APPROACH_CAP, APPROACH_CAP);
171
+ const rel = {
172
+ x: bodyRelX + vStrikeVec.x,
173
+ y: bodyRelY + vStrikeVec.y,
174
+ z: bodyRelZ + vStrikeVec.z,
175
+ };
176
+ const hasOffHand = attacker.loadout.items.some(it => it.kind === "shield") ||
177
+ attacker.loadout.items.filter(it => it.kind === "weapon").length > 1;
178
+ const twoHandBonus = twoHandedAttackBonusQ(wpn, funcA.leftArmDisabled, funcA.rightArmDisabled, hasOffHand);
179
+ const baseEnergy_J = mulDiv(mulDiv((impactEnergy_J(attacker, wpn, rel)), funcA.manipulationMul, SCALE.Q), twoHandBonus, SCALE.Q);
180
+ const attackerExo = findExoskeleton(attacker.loadout);
181
+ const exoForceMul = attackerExo ? attackerExo.forceMultiplier : SCALE.Q;
182
+ let energy_J = mulDiv(mulDiv(baseEnergy_J, attackerMeleeSkill.energyTransferMul, SCALE.Q), exoForceMul, SCALE.Q);
183
+ const momentumBonus_J = Math.trunc(qMul(energy_J, qMul(attacker.action.swingMomentumQ, SWING_MOMENTUM_MAX)));
184
+ energy_J += momentumBonus_J;
185
+ let mitigated = energy_J;
186
+ if (res.blocked || res.parried) {
187
+ target.energy.reserveEnergy_J = Math.max(0, target.energy.reserveEnergy_J - defenceCost_J(target));
188
+ const leverage = parryLeverageQ(wpn, attacker);
189
+ const handed = (wpn.handedness ?? "oneHand") === "twoHand" ? q(1.10) : q(1.0);
190
+ const defenceMul = qMul(leverage, handed);
191
+ if (res.blocked) {
192
+ const m = clampQ(q(0.40) - qMul(q(0.12), (defenceMul - SCALE.Q)), q(0.25), q(0.60));
193
+ mitigated = mulDiv(mitigated, m, SCALE.Q);
194
+ }
195
+ if (res.parried) {
196
+ const m = clampQ(q(0.25) - qMul(q(0.15), (defenceMul - SCALE.Q)), q(0.10), q(0.45));
197
+ mitigated = mulDiv(mitigated, m, SCALE.Q);
198
+ if (tuning.realism !== "arcade"
199
+ && attacker.action.weaponBindPartnerId === 0
200
+ && target.action.weaponBindPartnerId === 0) {
201
+ const defWpn = findWeapon(target.loadout);
202
+ if (defWpn) {
203
+ const bindSeed = eventSeed(world.seed, world.tick, attacker.id, target.id, 0xB1DE);
204
+ const bindRoll = (bindSeed % SCALE.Q);
205
+ const bChanceBase = bindChanceQ(wpn, defWpn);
206
+ const avgFatigue = ((attacker.energy.fatigue + target.energy.fatigue) >>> 1);
207
+ const fatigueMod = (SCALE.Q + qMul(avgFatigue, q(0.20)));
208
+ const bChance = clampQ(qMul(bChanceBase, fatigueMod), q(0), q(0.45));
209
+ if (bindRoll < bChance) {
210
+ const dur = bindDurationTicks(wpn, defWpn);
211
+ attacker.action.weaponBindPartnerId = target.id;
212
+ attacker.action.weaponBindTicks = dur;
213
+ target.action.weaponBindPartnerId = attacker.id;
214
+ target.action.weaponBindTicks = dur;
215
+ trace.onEvent({
216
+ kind: TraceKinds.WeaponBind,
217
+ tick: world.tick,
218
+ attackerId: attacker.id,
219
+ targetId: target.id,
220
+ durationTicks: dur,
221
+ });
222
+ }
223
+ }
224
+ }
225
+ }
226
+ if (res.shieldBlocked) {
227
+ const m = clampQ(q(0.35) - qMul(q(0.10), (defenceMul - SCALE.Q)), q(0.20), q(0.55));
228
+ mitigated = mulDiv(mitigated, m, SCALE.Q);
229
+ }
230
+ }
231
+ const armour = deriveArmourProfile(target.loadout, target.armourState);
232
+ const isEnergyWeapon = !!(wpn).energyType;
233
+ const CHANNEL_MASK = isEnergyWeapon ? (1 << DamageChannel.Energy) : (1 << DamageChannel.Kinetic);
234
+ const armourHit = armourCoversHit(world, regionCoverageQ(armour.coverageByRegion, region), attacker.id, target.id);
235
+ const protectedByArmour = armourHit && ((armour.protects & CHANNEL_MASK) !== 0);
236
+ let finalEnergy = mitigated;
237
+ if (protectedByArmour) {
238
+ if (isEnergyWeapon && armour.reflectivity > q(0)) {
239
+ finalEnergy = mulDiv(finalEnergy, SCALE.Q - armour.reflectivity, SCALE.Q);
240
+ }
241
+ else if (!isEnergyWeapon) {
242
+ finalEnergy = applyKineticArmourPenetration(mitigated, armour.resist_J, armour.protectedDamageMul);
243
+ }
244
+ if (target.armourState) {
245
+ const armourItems = target.loadout.items.filter(it => it.kind === "armour");
246
+ for (const it of armourItems) {
247
+ if ((it).ablative && target.armourState.has(it.id)) {
248
+ const st = target.armourState.get(it.id);
249
+ st.resistRemaining_J = Math.max(0, st.resistRemaining_J - mitigated);
250
+ }
251
+ }
252
+ }
253
+ }
254
+ const kbBodyMass = mulDiv(attacker.attributes.morphology.mass_kg, wpn.strikeEffectiveMassFrac ?? q(0.10), SCALE.Q);
255
+ const kbMassEff = wpn.mass_kg + kbBodyMass;
256
+ impacts.push({
257
+ kind: "impact",
258
+ attackerId: attacker.id,
259
+ targetId: target.id,
260
+ region,
261
+ energy_J: finalEnergy,
262
+ protectedByArmour,
263
+ weaponId: wpn.id,
264
+ wpn,
265
+ blocked: res.blocked,
266
+ parried: res.parried,
267
+ hitQuality: clampQ(res.hitQuality, q(0.05), q(1.0)),
268
+ shieldBlocked,
269
+ massEff_kg: kbMassEff,
270
+ });
271
+ if (res.blocked || res.parried) {
272
+ attacker.action.swingMomentumQ = q(0);
273
+ }
274
+ else {
275
+ attacker.action.swingMomentumQ = clampQ(qMul(clampedIntensity, q(0.80)), q(0), SCALE.Q);
276
+ }
277
+ }
278
+ function strikeCost_J(attacker, intensity) {
279
+ const base = Math.max(20, mulDiv(attacker.attributes.performance.peakPower_W, 4, 100));
280
+ return Math.max(5, mulDiv(base, intensity, SCALE.Q));
281
+ }
282
+ function defenceCost_J(defender) {
283
+ return Math.max(5, mulDiv(defender.attributes.performance.peakPower_W, 25, 1000));
284
+ }
285
+ function applyKineticArmourPenetration(energy_J, resist_J, postMul) {
286
+ const remaining = Math.max(0, energy_J - Math.max(0, resist_J));
287
+ return mulDiv(remaining, postMul, SCALE.Q);
288
+ }
289
+ function impactEnergy_J(attacker, wpn, relVel_mps) {
290
+ const frac = wpn.strikeEffectiveMassFrac ?? q(0.10);
291
+ const bodyEffMass = mulDiv(attacker.attributes.morphology.mass_kg, frac, SCALE.Q);
292
+ const mEff = wpn.mass_kg + bodyEffMass;
293
+ const vx = BigInt(relVel_mps.x);
294
+ const vy = BigInt(relVel_mps.y);
295
+ const vz = BigInt(relVel_mps.z);
296
+ const v2 = vx * vx + vy * vy + vz * vz;
297
+ const denom = 2n * BigInt(SCALE.kg) * BigInt(SCALE.mps) * BigInt(SCALE.mps);
298
+ const num = BigInt(mEff) * v2;
299
+ return Math.max(0, Number(num / denom));
300
+ }
301
+ function scaleDirToSpeed(dirQ, speed_mps) {
302
+ return {
303
+ x: mulDiv(speed_mps, dirQ.x, SCALE.Q),
304
+ y: mulDiv(speed_mps, dirQ.y, SCALE.Q),
305
+ z: mulDiv(speed_mps, dirQ.z, SCALE.Q),
306
+ };
307
+ }
308
+ function clampI32(x, lo, hi) {
309
+ return Math.max(lo, Math.min(hi, x));
310
+ }
@@ -0,0 +1,15 @@
1
+ import type { WorldState } from "../world.js";
2
+ import { type Entity } from "../entity.js";
3
+ import type { ActivateCommand } from "../commands.js";
4
+ import type { CapabilityEffect, EffectPayload } from "../capability.js";
5
+ import type { KernelContext } from "../context.js";
6
+ import { type Weapon } from "../../equipment.js";
7
+ import type { TraceSink } from "../trace.js";
8
+ type CapabilityResolverHelpers = {
9
+ resolveCapabilityHitSegment: (world: WorldState, tick: number, actor: Entity, target: Entity, salt: number) => string;
10
+ applyImpactToInjury: (target: Entity, wpn: Weapon, energy_J: number, region: string, armoured: boolean, trace: TraceSink, tick: number, tempCavityMul_Q?: number) => void;
11
+ };
12
+ export declare function applyPayload(world: WorldState, actor: Entity, target: Entity, payload: EffectPayload, trace: TraceSink, tick: number, effectId: string, helpers: CapabilityResolverHelpers): void;
13
+ export declare function applyCapabilityEffect(world: WorldState, actor: Entity, targetId: number | undefined, effect: CapabilityEffect, trace: TraceSink, tick: number, helpers: CapabilityResolverHelpers): void;
14
+ export declare function resolveActivation(world: WorldState, actor: Entity, cmd: ActivateCommand, ctx: KernelContext, trace: TraceSink, tick: number, onInstantResolve?: (effect: CapabilityEffect) => void): void;
15
+ export {};