@its-not-rocket-science/ananke 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +135 -0
- package/LICENSE +21 -0
- package/README.md +2199 -0
- package/STABLE_API.md +266 -0
- package/dist/src/anatomy/anatomy-compiler.d.ts +14 -0
- package/dist/src/anatomy/anatomy-compiler.js +277 -0
- package/dist/src/anatomy/anatomy-contracts.d.ts +94 -0
- package/dist/src/anatomy/anatomy-contracts.js +1 -0
- package/dist/src/anatomy/anatomy-helpers.d.ts +82 -0
- package/dist/src/anatomy/anatomy-helpers.js +233 -0
- package/dist/src/anatomy/anatomy-schema.d.ts +28 -0
- package/dist/src/anatomy/anatomy-schema.js +388 -0
- package/dist/src/anatomy/index.d.ts +4 -0
- package/dist/src/anatomy/index.js +4 -0
- package/dist/src/archetypes.d.ts +87 -0
- package/dist/src/archetypes.js +285 -0
- package/dist/src/arena.d.ts +173 -0
- package/dist/src/arena.js +695 -0
- package/dist/src/bridge/bridge-engine.d.ts +46 -0
- package/dist/src/bridge/bridge-engine.js +252 -0
- package/dist/src/bridge/index.d.ts +4 -0
- package/dist/src/bridge/index.js +5 -0
- package/dist/src/bridge/interpolation.d.ts +64 -0
- package/dist/src/bridge/interpolation.js +130 -0
- package/dist/src/bridge/mapping.d.ts +33 -0
- package/dist/src/bridge/mapping.js +54 -0
- package/dist/src/bridge/types.d.ts +94 -0
- package/dist/src/bridge/types.js +2 -0
- package/dist/src/campaign.d.ts +141 -0
- package/dist/src/campaign.js +235 -0
- package/dist/src/channels.d.ts +15 -0
- package/dist/src/channels.js +20 -0
- package/dist/src/chronicle.d.ts +124 -0
- package/dist/src/chronicle.js +232 -0
- package/dist/src/collective-activities.d.ts +154 -0
- package/dist/src/collective-activities.js +247 -0
- package/dist/src/competence/acoustic.d.ts +101 -0
- package/dist/src/competence/acoustic.js +242 -0
- package/dist/src/competence/catalogue.d.ts +30 -0
- package/dist/src/competence/catalogue.js +241 -0
- package/dist/src/competence/crafting.d.ts +35 -0
- package/dist/src/competence/crafting.js +88 -0
- package/dist/src/competence/engineering.d.ts +53 -0
- package/dist/src/competence/engineering.js +108 -0
- package/dist/src/competence/framework.d.ts +68 -0
- package/dist/src/competence/framework.js +694 -0
- package/dist/src/competence/index.d.ts +12 -0
- package/dist/src/competence/index.js +13 -0
- package/dist/src/competence/interspecies.d.ts +81 -0
- package/dist/src/competence/interspecies.js +108 -0
- package/dist/src/competence/language.d.ts +79 -0
- package/dist/src/competence/language.js +115 -0
- package/dist/src/competence/naturalist.d.ts +97 -0
- package/dist/src/competence/naturalist.js +187 -0
- package/dist/src/competence/navigation.d.ts +24 -0
- package/dist/src/competence/navigation.js +48 -0
- package/dist/src/competence/performance.d.ts +125 -0
- package/dist/src/competence/performance.js +210 -0
- package/dist/src/competence/teaching.d.ts +64 -0
- package/dist/src/competence/teaching.js +121 -0
- package/dist/src/competence/willpower.d.ts +74 -0
- package/dist/src/competence/willpower.js +114 -0
- package/dist/src/crafting/index.d.ts +55 -0
- package/dist/src/crafting/index.js +229 -0
- package/dist/src/crafting/manufacturing.d.ts +83 -0
- package/dist/src/crafting/manufacturing.js +165 -0
- package/dist/src/crafting/materials.d.ts +53 -0
- package/dist/src/crafting/materials.js +120 -0
- package/dist/src/crafting/recipes.d.ts +75 -0
- package/dist/src/crafting/recipes.js +233 -0
- package/dist/src/crafting/workshops.d.ts +61 -0
- package/dist/src/crafting/workshops.js +170 -0
- package/dist/src/debug.d.ts +86 -0
- package/dist/src/debug.js +76 -0
- package/dist/src/derive.d.ts +21 -0
- package/dist/src/derive.js +88 -0
- package/dist/src/describe.d.ts +29 -0
- package/dist/src/describe.js +276 -0
- package/dist/src/dialogue.d.ts +122 -0
- package/dist/src/dialogue.js +266 -0
- package/dist/src/dist.d.ts +20 -0
- package/dist/src/dist.js +39 -0
- package/dist/src/downtime.d.ts +89 -0
- package/dist/src/downtime.js +391 -0
- package/dist/src/economy.d.ts +116 -0
- package/dist/src/economy.js +182 -0
- package/dist/src/emotional-contagion.d.ts +142 -0
- package/dist/src/emotional-contagion.js +274 -0
- package/dist/src/equipment.d.ts +206 -0
- package/dist/src/equipment.js +598 -0
- package/dist/src/faction.d.ts +102 -0
- package/dist/src/faction.js +237 -0
- package/dist/src/generate.d.ts +35 -0
- package/dist/src/generate.js +166 -0
- package/dist/src/index.d.ts +42 -0
- package/dist/src/index.js +54 -0
- package/dist/src/inheritance.d.ts +69 -0
- package/dist/src/inheritance.js +136 -0
- package/dist/src/inventory.d.ts +194 -0
- package/dist/src/inventory.js +637 -0
- package/dist/src/item-durability.d.ts +69 -0
- package/dist/src/item-durability.js +308 -0
- package/dist/src/legend.d.ts +97 -0
- package/dist/src/legend.js +269 -0
- package/dist/src/lod.d.ts +9 -0
- package/dist/src/lod.js +84 -0
- package/dist/src/metrics.d.ts +51 -0
- package/dist/src/metrics.js +91 -0
- package/dist/src/model3d.d.ts +138 -0
- package/dist/src/model3d.js +214 -0
- package/dist/src/mythology.d.ts +101 -0
- package/dist/src/mythology.js +308 -0
- package/dist/src/narrative-render.d.ts +42 -0
- package/dist/src/narrative-render.js +194 -0
- package/dist/src/narrative-stress.d.ts +123 -0
- package/dist/src/narrative-stress.js +183 -0
- package/dist/src/narrative.d.ts +44 -0
- package/dist/src/narrative.js +257 -0
- package/dist/src/party.d.ts +70 -0
- package/dist/src/party.js +226 -0
- package/dist/src/polity.d.ts +262 -0
- package/dist/src/polity.js +398 -0
- package/dist/src/presets.d.ts +42 -0
- package/dist/src/presets.js +170 -0
- package/dist/src/progression.d.ts +170 -0
- package/dist/src/progression.js +256 -0
- package/dist/src/quest-generators.d.ts +76 -0
- package/dist/src/quest-generators.js +534 -0
- package/dist/src/quest.d.ts +239 -0
- package/dist/src/quest.js +520 -0
- package/dist/src/relationships-effects.d.ts +75 -0
- package/dist/src/relationships-effects.js +219 -0
- package/dist/src/relationships.d.ts +104 -0
- package/dist/src/relationships.js +347 -0
- package/dist/src/replay.d.ts +47 -0
- package/dist/src/replay.js +82 -0
- package/dist/src/rng.d.ts +9 -0
- package/dist/src/rng.js +37 -0
- package/dist/src/settlement-services.d.ts +67 -0
- package/dist/src/settlement-services.js +267 -0
- package/dist/src/settlement.d.ts +143 -0
- package/dist/src/settlement.js +419 -0
- package/dist/src/sim/action.d.ts +28 -0
- package/dist/src/sim/action.js +12 -0
- package/dist/src/sim/aging.d.ts +95 -0
- package/dist/src/sim/aging.js +243 -0
- package/dist/src/sim/ai/decide.d.ts +10 -0
- package/dist/src/sim/ai/decide.js +267 -0
- package/dist/src/sim/ai/perception.d.ts +12 -0
- package/dist/src/sim/ai/perception.js +54 -0
- package/dist/src/sim/ai/personality.d.ts +54 -0
- package/dist/src/sim/ai/personality.js +202 -0
- package/dist/src/sim/ai/presets.d.ts +2 -0
- package/dist/src/sim/ai/presets.js +28 -0
- package/dist/src/sim/ai/system.d.ts +6 -0
- package/dist/src/sim/ai/system.js +13 -0
- package/dist/src/sim/ai/targeting.d.ts +8 -0
- package/dist/src/sim/ai/targeting.js +42 -0
- package/dist/src/sim/ai/types.d.ts +14 -0
- package/dist/src/sim/ai/types.js +1 -0
- package/dist/src/sim/body.d.ts +9 -0
- package/dist/src/sim/body.js +32 -0
- package/dist/src/sim/bodyplan.d.ts +161 -0
- package/dist/src/sim/bodyplan.js +677 -0
- package/dist/src/sim/capability.d.ts +135 -0
- package/dist/src/sim/capability.js +8 -0
- package/dist/src/sim/combat.d.ts +21 -0
- package/dist/src/sim/combat.js +77 -0
- package/dist/src/sim/commandBuilders.d.ts +11 -0
- package/dist/src/sim/commandBuilders.js +39 -0
- package/dist/src/sim/commands.d.ts +71 -0
- package/dist/src/sim/commands.js +8 -0
- package/dist/src/sim/condition.d.ts +35 -0
- package/dist/src/sim/condition.js +21 -0
- package/dist/src/sim/cone.d.ts +40 -0
- package/dist/src/sim/cone.js +44 -0
- package/dist/src/sim/context.d.ts +68 -0
- package/dist/src/sim/context.js +1 -0
- package/dist/src/sim/density.d.ts +14 -0
- package/dist/src/sim/density.js +33 -0
- package/dist/src/sim/disease.d.ts +141 -0
- package/dist/src/sim/disease.js +353 -0
- package/dist/src/sim/entity.d.ts +251 -0
- package/dist/src/sim/entity.js +19 -0
- package/dist/src/sim/events.d.ts +25 -0
- package/dist/src/sim/events.js +5 -0
- package/dist/src/sim/explosion.d.ts +40 -0
- package/dist/src/sim/explosion.js +40 -0
- package/dist/src/sim/formation-unit.d.ts +138 -0
- package/dist/src/sim/formation-unit.js +197 -0
- package/dist/src/sim/formation.d.ts +12 -0
- package/dist/src/sim/formation.js +54 -0
- package/dist/src/sim/frontage.d.ts +30 -0
- package/dist/src/sim/frontage.js +84 -0
- package/dist/src/sim/grapple.d.ts +100 -0
- package/dist/src/sim/grapple.js +480 -0
- package/dist/src/sim/hazard.d.ts +104 -0
- package/dist/src/sim/hazard.js +201 -0
- package/dist/src/sim/hydrostatic.d.ts +58 -0
- package/dist/src/sim/hydrostatic.js +117 -0
- package/dist/src/sim/impairment.d.ts +20 -0
- package/dist/src/sim/impairment.js +162 -0
- package/dist/src/sim/indexing.d.ts +7 -0
- package/dist/src/sim/indexing.js +7 -0
- package/dist/src/sim/injury.d.ts +54 -0
- package/dist/src/sim/injury.js +66 -0
- package/dist/src/sim/intent.d.ts +26 -0
- package/dist/src/sim/intent.js +7 -0
- package/dist/src/sim/kernel.d.ts +45 -0
- package/dist/src/sim/kernel.js +1992 -0
- package/dist/src/sim/kinds.d.ts +64 -0
- package/dist/src/sim/kinds.js +56 -0
- package/dist/src/sim/knockback.d.ts +50 -0
- package/dist/src/sim/knockback.js +82 -0
- package/dist/src/sim/limb.d.ts +48 -0
- package/dist/src/sim/limb.js +78 -0
- package/dist/src/sim/medical.d.ts +32 -0
- package/dist/src/sim/medical.js +33 -0
- package/dist/src/sim/morale.d.ts +69 -0
- package/dist/src/sim/morale.js +92 -0
- package/dist/src/sim/mount.d.ts +150 -0
- package/dist/src/sim/mount.js +225 -0
- package/dist/src/sim/nutrition.d.ts +74 -0
- package/dist/src/sim/nutrition.js +168 -0
- package/dist/src/sim/occlusion.d.ts +8 -0
- package/dist/src/sim/occlusion.js +71 -0
- package/dist/src/sim/push.d.ts +11 -0
- package/dist/src/sim/push.js +79 -0
- package/dist/src/sim/ranged.d.ts +44 -0
- package/dist/src/sim/ranged.js +69 -0
- package/dist/src/sim/seeds.d.ts +3 -0
- package/dist/src/sim/seeds.js +16 -0
- package/dist/src/sim/sensory-extended.d.ts +103 -0
- package/dist/src/sim/sensory-extended.js +181 -0
- package/dist/src/sim/sensory.d.ts +38 -0
- package/dist/src/sim/sensory.js +109 -0
- package/dist/src/sim/skills.d.ts +70 -0
- package/dist/src/sim/skills.js +69 -0
- package/dist/src/sim/sleep.d.ts +107 -0
- package/dist/src/sim/sleep.js +215 -0
- package/dist/src/sim/spatial.d.ts +8 -0
- package/dist/src/sim/spatial.js +59 -0
- package/dist/src/sim/step/capability.d.ts +8 -0
- package/dist/src/sim/step/capability.js +77 -0
- package/dist/src/sim/step/concentration.d.ts +9 -0
- package/dist/src/sim/step/concentration.js +25 -0
- package/dist/src/sim/step/effects.d.ts +17 -0
- package/dist/src/sim/step/effects.js +96 -0
- package/dist/src/sim/step/energy.d.ts +3 -0
- package/dist/src/sim/step/energy.js +31 -0
- package/dist/src/sim/step/hazards.d.ts +4 -0
- package/dist/src/sim/step/hazards.js +19 -0
- package/dist/src/sim/step/injury.d.ts +10 -0
- package/dist/src/sim/step/injury.js +353 -0
- package/dist/src/sim/step/morale.d.ts +11 -0
- package/dist/src/sim/step/morale.js +130 -0
- package/dist/src/sim/step/movement.d.ts +5 -0
- package/dist/src/sim/step/movement.js +172 -0
- package/dist/src/sim/step/push.d.ts +11 -0
- package/dist/src/sim/step/push.js +79 -0
- package/dist/src/sim/step/substances.d.ts +3 -0
- package/dist/src/sim/step/substances.js +75 -0
- package/dist/src/sim/substance.d.ts +38 -0
- package/dist/src/sim/substance.js +57 -0
- package/dist/src/sim/systemic-toxicology.d.ts +109 -0
- package/dist/src/sim/systemic-toxicology.js +263 -0
- package/dist/src/sim/team.d.ts +9 -0
- package/dist/src/sim/team.js +37 -0
- package/dist/src/sim/tech.d.ts +36 -0
- package/dist/src/sim/tech.js +46 -0
- package/dist/src/sim/terrain.d.ts +121 -0
- package/dist/src/sim/terrain.js +141 -0
- package/dist/src/sim/testing.d.ts +13 -0
- package/dist/src/sim/testing.js +100 -0
- package/dist/src/sim/thermoregulation.d.ts +77 -0
- package/dist/src/sim/thermoregulation.js +161 -0
- package/dist/src/sim/tick.d.ts +3 -0
- package/dist/src/sim/tick.js +3 -0
- package/dist/src/sim/toxicology.d.ts +52 -0
- package/dist/src/sim/toxicology.js +104 -0
- package/dist/src/sim/trace.d.ts +141 -0
- package/dist/src/sim/trace.js +1 -0
- package/dist/src/sim/tuning.d.ts +16 -0
- package/dist/src/sim/tuning.js +42 -0
- package/dist/src/sim/vec3.d.ts +14 -0
- package/dist/src/sim/vec3.js +31 -0
- package/dist/src/sim/weapon_dynamics.d.ts +102 -0
- package/dist/src/sim/weapon_dynamics.js +142 -0
- package/dist/src/sim/weather.d.ts +95 -0
- package/dist/src/sim/weather.js +105 -0
- package/dist/src/sim/world.d.ts +52 -0
- package/dist/src/sim/world.js +1 -0
- package/dist/src/sim/wound-aging.d.ts +120 -0
- package/dist/src/sim/wound-aging.js +223 -0
- package/dist/src/species.d.ts +106 -0
- package/dist/src/species.js +664 -0
- package/dist/src/story-arcs.d.ts +17 -0
- package/dist/src/story-arcs.js +276 -0
- package/dist/src/tech-diffusion.d.ts +80 -0
- package/dist/src/tech-diffusion.js +185 -0
- package/dist/src/traits.d.ts +25 -0
- package/dist/src/traits.js +178 -0
- package/dist/src/types.d.ts +117 -0
- package/dist/src/types.js +1 -0
- package/dist/src/units.d.ts +41 -0
- package/dist/src/units.js +64 -0
- package/dist/src/weapons.d.ts +20 -0
- package/dist/src/weapons.js +824 -0
- package/dist/src/world-generation.d.ts +52 -0
- package/dist/src/world-generation.js +301 -0
- package/package.json +74 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { terrainKey } from "../terrain.js";
|
|
2
|
+
import { DT_S } from "../tick.js";
|
|
3
|
+
import { SCALE } from "../../units.js";
|
|
4
|
+
/**
|
|
5
|
+
* Per-entity per-tick regen of all capability sources.
|
|
6
|
+
* Called after stepMovement so velocity is current.
|
|
7
|
+
*/
|
|
8
|
+
export function stepCapabilitySources(e, world, ctx) {
|
|
9
|
+
if (!e.capabilitySources)
|
|
10
|
+
return;
|
|
11
|
+
const cellSize_m = ctx.cellSize_m ?? Math.trunc(4 * SCALE.m);
|
|
12
|
+
for (const source of e.capabilitySources) {
|
|
13
|
+
const model = source.regenModel;
|
|
14
|
+
if (model.type === "boundless")
|
|
15
|
+
continue;
|
|
16
|
+
let regenRate_W = 0;
|
|
17
|
+
switch (model.type) {
|
|
18
|
+
case "constant":
|
|
19
|
+
regenRate_W = model.regenRate_W;
|
|
20
|
+
break;
|
|
21
|
+
case "rest": {
|
|
22
|
+
const speedAbs = Math.max(Math.abs(e.velocity_mps.x), Math.abs(e.velocity_mps.y));
|
|
23
|
+
const isResting = speedAbs <= Math.trunc(0.05 * SCALE.mps) && e.action.attackCooldownTicks === 0;
|
|
24
|
+
if (isResting)
|
|
25
|
+
regenRate_W = model.regenRate_W;
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
case "ambient": {
|
|
29
|
+
const cx = Math.trunc(e.position_m.x / cellSize_m);
|
|
30
|
+
const cy = Math.trunc(e.position_m.y / cellSize_m);
|
|
31
|
+
const key = terrainKey(cx, cy);
|
|
32
|
+
const ambientVal = ctx.ambientGrid?.get(key) ?? 0;
|
|
33
|
+
if (ambientVal > 0) {
|
|
34
|
+
regenRate_W = Math.trunc(model.maxRate_W * ambientVal / SCALE.Q);
|
|
35
|
+
}
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
case "event": {
|
|
39
|
+
for (const trigger of model.triggers) {
|
|
40
|
+
if (trigger.on === "tick") {
|
|
41
|
+
if (trigger._nextTick === undefined)
|
|
42
|
+
trigger._nextTick = world.tick + trigger.every_n;
|
|
43
|
+
if (world.tick >= trigger._nextTick) {
|
|
44
|
+
source.reserve_J = Math.min(source.maxReserve_J, source.reserve_J + trigger.amount_J);
|
|
45
|
+
trigger._nextTick = world.tick + trigger.every_n;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// kill triggers dispatched by kernel death-detection loop; terrain triggers below
|
|
49
|
+
}
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (regenRate_W > 0) {
|
|
54
|
+
const regenThisTick = Math.trunc(regenRate_W * DT_S / SCALE.s);
|
|
55
|
+
source.reserve_J = Math.min(source.maxReserve_J, source.reserve_J + regenThisTick);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Phase 12B: terrain-entry triggers — fire once per cell-boundary crossing
|
|
59
|
+
if (ctx.terrainTagGrid) {
|
|
60
|
+
const cx = Math.trunc(e.position_m.x / cellSize_m);
|
|
61
|
+
const cy = Math.trunc(e.position_m.y / cellSize_m);
|
|
62
|
+
const currentKey = terrainKey(cx, cy);
|
|
63
|
+
if (currentKey !== e.action.lastCellKey) {
|
|
64
|
+
const tags = ctx.terrainTagGrid.get(currentKey) ?? [];
|
|
65
|
+
for (const source of e.capabilitySources) {
|
|
66
|
+
if (source.regenModel.type !== "event")
|
|
67
|
+
continue;
|
|
68
|
+
for (const trig of source.regenModel.triggers) {
|
|
69
|
+
if (trig.on === "terrain" && tags.includes(trig.tag)) {
|
|
70
|
+
source.reserve_J = Math.min(source.maxReserve_J, source.reserve_J + trig.amount_J);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
e.action.lastCellKey = currentKey;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { WorldState } from "../world.js";
|
|
2
|
+
import type { TraceSink } from "../trace.js";
|
|
3
|
+
import type { Entity } from "../entity.js";
|
|
4
|
+
/**
|
|
5
|
+
* Phase 12B: advance a concentration aura for one tick.
|
|
6
|
+
* Deducts cost_J from the source and applies the effect payload.
|
|
7
|
+
* Clears activeConcentration if reserve is exhausted or entity is shocked.
|
|
8
|
+
*/
|
|
9
|
+
export declare function stepConcentration(e: Entity, world: WorldState, trace: TraceSink, tick: number): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { applyCapabilityEffect } from "../kernel.js";
|
|
2
|
+
import { TraceKinds } from "../kinds.js";
|
|
3
|
+
import { q } from "../../units.js";
|
|
4
|
+
/**
|
|
5
|
+
* Phase 12B: advance a concentration aura for one tick.
|
|
6
|
+
* Deducts cost_J from the source and applies the effect payload.
|
|
7
|
+
* Clears activeConcentration if reserve is exhausted or entity is shocked.
|
|
8
|
+
*/
|
|
9
|
+
export function stepConcentration(e, world, trace, tick) {
|
|
10
|
+
const { sourceId, effectId, targetId } = e.activeConcentration;
|
|
11
|
+
const source = e.capabilitySources?.find(s => s.id === sourceId);
|
|
12
|
+
const effect = source?.effects.find(ef => ef.id === effectId);
|
|
13
|
+
const isBoundless = source?.regenModel.type === "boundless";
|
|
14
|
+
const interrupted = !source || !effect ||
|
|
15
|
+
(!isBoundless && source.reserve_J < effect.cost_J) ||
|
|
16
|
+
e.injury.shock >= q(0.30);
|
|
17
|
+
if (interrupted) {
|
|
18
|
+
delete e.activeConcentration;
|
|
19
|
+
trace.onEvent({ kind: TraceKinds.CastInterrupted, tick, entityId: e.id });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (!isBoundless)
|
|
23
|
+
source.reserve_J -= effect.cost_J;
|
|
24
|
+
applyCapabilityEffect(world, e, targetId, effect, trace, tick);
|
|
25
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Entity } from "../entity.js";
|
|
2
|
+
import type { WorldState } from "../world.js";
|
|
3
|
+
import type { TraceSink } from "../trace.js";
|
|
4
|
+
import { I32 } from "../../units.js";
|
|
5
|
+
import { HazardGrid } from "../terrain.js";
|
|
6
|
+
/**
|
|
7
|
+
* Phase 12B effect chains: apply chainPayload from each active FieldEffect to every
|
|
8
|
+
* living entity within its radius. Runs before expiry so the final tick still fires.
|
|
9
|
+
*/
|
|
10
|
+
export declare function stepChainEffects(world: WorldState, trace: TraceSink, tick: number): void;
|
|
11
|
+
/**
|
|
12
|
+
* Decrement duration on timed field effects; remove expired ones.
|
|
13
|
+
* Permanent effects (duration_ticks === -1) are never removed.
|
|
14
|
+
*/
|
|
15
|
+
export declare function stepFieldEffects(world: WorldState): void;
|
|
16
|
+
export declare function stepHazardEffects_legacy(entities: Entity[], grid: HazardGrid, cellSize_m: I32): void;
|
|
17
|
+
export declare function stepHazardEffects(entities: readonly Entity[], grid: HazardGrid, cellSize_m: I32): void;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { terrainKey } from "../terrain.js";
|
|
2
|
+
import { applyHazardDamage } from "./hazards.js";
|
|
3
|
+
import { applyPayload } from "../kernel.js";
|
|
4
|
+
/**
|
|
5
|
+
* Phase 12B effect chains: apply chainPayload from each active FieldEffect to every
|
|
6
|
+
* living entity within its radius. Runs before expiry so the final tick still fires.
|
|
7
|
+
*/
|
|
8
|
+
export function stepChainEffects(world, trace, tick) {
|
|
9
|
+
if (!world.activeFieldEffects?.length)
|
|
10
|
+
return;
|
|
11
|
+
for (const fe of world.activeFieldEffects) {
|
|
12
|
+
if (!fe.chainPayload)
|
|
13
|
+
continue;
|
|
14
|
+
const actor = world.entities.find(e => e.id === fe.placedByEntityId);
|
|
15
|
+
if (!actor)
|
|
16
|
+
continue;
|
|
17
|
+
const payloads = Array.isArray(fe.chainPayload)
|
|
18
|
+
? fe.chainPayload
|
|
19
|
+
: [fe.chainPayload];
|
|
20
|
+
const radSq = fe.radius_m * fe.radius_m;
|
|
21
|
+
for (const target of world.entities) {
|
|
22
|
+
if (target.injury.dead)
|
|
23
|
+
continue;
|
|
24
|
+
const dx = target.position_m.x - fe.origin.x;
|
|
25
|
+
const dy = target.position_m.y - fe.origin.y;
|
|
26
|
+
if (dx * dx + dy * dy > radSq)
|
|
27
|
+
continue;
|
|
28
|
+
for (const p of payloads) {
|
|
29
|
+
applyPayload(world, actor, target, p, trace, tick, fe.id);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Decrement duration on timed field effects; remove expired ones.
|
|
36
|
+
* Permanent effects (duration_ticks === -1) are never removed.
|
|
37
|
+
*/
|
|
38
|
+
export function stepFieldEffects(world) {
|
|
39
|
+
if (!world.activeFieldEffects?.length)
|
|
40
|
+
return;
|
|
41
|
+
world.activeFieldEffects = world.activeFieldEffects.filter(fe => {
|
|
42
|
+
if (fe.duration_ticks < 0)
|
|
43
|
+
return true; // permanent
|
|
44
|
+
fe.duration_ticks -= 1;
|
|
45
|
+
return fe.duration_ticks > 0;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export function stepHazardEffects_legacy(entities, grid, cellSize_m) {
|
|
49
|
+
const cs = Math.max(1, cellSize_m);
|
|
50
|
+
for (const e of entities) {
|
|
51
|
+
if (e.injury.dead)
|
|
52
|
+
continue;
|
|
53
|
+
const cx = Math.trunc(e.position_m.x / cs);
|
|
54
|
+
const cy = Math.trunc(e.position_m.y / cs);
|
|
55
|
+
const key = terrainKey(cx, cy);
|
|
56
|
+
const hazard = grid.get(key);
|
|
57
|
+
if (!hazard)
|
|
58
|
+
continue;
|
|
59
|
+
if (hazard.intensity > 0) {
|
|
60
|
+
applyHazardDamage(e, hazard);
|
|
61
|
+
}
|
|
62
|
+
if (hazard.duration_ticks > 0) {
|
|
63
|
+
hazard.duration_ticks -= 1;
|
|
64
|
+
if (hazard.duration_ticks === 0) {
|
|
65
|
+
grid.delete(key);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function hazardKeyForEntity(e, cellSize_m) {
|
|
71
|
+
const cx = Math.floor(e.position_m.x / cellSize_m);
|
|
72
|
+
const cy = Math.floor(e.position_m.y / cellSize_m);
|
|
73
|
+
return terrainKey(cx, cy);
|
|
74
|
+
}
|
|
75
|
+
export function stepHazardEffects(entities, grid, cellSize_m) {
|
|
76
|
+
// 1) Apply hazards to entities (no duration ticking here)
|
|
77
|
+
for (const e of entities) {
|
|
78
|
+
const key = hazardKeyForEntity(e, cellSize_m); // <-- replace with your actual key computation
|
|
79
|
+
const hazard = grid.get(key);
|
|
80
|
+
if (!hazard)
|
|
81
|
+
continue;
|
|
82
|
+
applyHazardDamage(e, hazard); // <-- replace with your actual application call(s)
|
|
83
|
+
}
|
|
84
|
+
// 2) Tick/expire hazards once per cell per tick
|
|
85
|
+
// Collect keys first to avoid mutating the map while iterating it.
|
|
86
|
+
const keysToDelete = [];
|
|
87
|
+
for (const [key, hazard] of grid) {
|
|
88
|
+
if (hazard.duration_ticks > 0) {
|
|
89
|
+
hazard.duration_ticks -= 1;
|
|
90
|
+
if (hazard.duration_ticks === 0)
|
|
91
|
+
keysToDelete.push(key);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const key of keysToDelete)
|
|
95
|
+
grid.delete(key);
|
|
96
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { DT_S } from "../tick.js";
|
|
2
|
+
import { q, qMul, clampQ, mulDiv, SCALE } from "../../units.js";
|
|
3
|
+
import { stepEnergyAndFatigue } from "../../derive.js";
|
|
4
|
+
import { getSkill } from "../skills.js";
|
|
5
|
+
import { findExoskeleton } from "../../equipment.js";
|
|
6
|
+
export function stepEnergy(e, ctx) {
|
|
7
|
+
const BASE_IDLE_W = 80;
|
|
8
|
+
const speedAbs = Math.max(Math.abs(e.velocity_mps.x), Math.abs(e.velocity_mps.y), Math.abs(e.velocity_mps.z));
|
|
9
|
+
const moving = speedAbs > Math.trunc(0.05 * SCALE.mps);
|
|
10
|
+
// Phase 11: powered exoskeleton adds continuous power draw to metabolic demand
|
|
11
|
+
const exoForEnergy = findExoskeleton(e.loadout);
|
|
12
|
+
// Phase 8B: flight increases stamina demand when entity is airborne
|
|
13
|
+
const flightSpecE = e.bodyPlan?.locomotion.flight;
|
|
14
|
+
const isFlying = flightSpecE !== undefined && e.attributes.morphology.mass_kg <= flightSpecE.liftCapacity_kg;
|
|
15
|
+
const flightDemandMul = (isFlying && moving) ? flightSpecE.flightStaminaCost : SCALE.Q;
|
|
16
|
+
const baseDemand = (moving ? 250 : BASE_IDLE_W) + (exoForEnergy ? exoForEnergy.powerDrain_W : 0);
|
|
17
|
+
const demand = mulDiv(baseDemand, flightDemandMul, SCALE.Q);
|
|
18
|
+
const fatigueBefore = e.energy.fatigue;
|
|
19
|
+
stepEnergyAndFatigue(e.attributes, e.energy, e.loadout, demand, DT_S, { tractionCoeff: ctx.tractionCoeff });
|
|
20
|
+
// Phase 7: athleticism.fatigueRateMul reduces fatigue accumulation each tick
|
|
21
|
+
const fatigueDelta = e.energy.fatigue - fatigueBefore;
|
|
22
|
+
if (fatigueDelta > 0) {
|
|
23
|
+
const athSkill = getSkill(e.skills, "athleticism");
|
|
24
|
+
if (athSkill.fatigueRateMul < SCALE.Q) {
|
|
25
|
+
e.energy.fatigue = clampQ((fatigueBefore + qMul(fatigueDelta, athSkill.fatigueRateMul)), 0, SCALE.Q);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (!moving && e.injury.shock < q(0.4)) {
|
|
29
|
+
e.energy.fatigue = clampQ(e.energy.fatigue - q(0.0020), 0, SCALE.Q);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { SCALE, q, clampQ, qMul } from "../../units.js";
|
|
2
|
+
/** Apply a single hazard cell's per-tick effect to an entity. */
|
|
3
|
+
export function applyHazardDamage(e, hazard) {
|
|
4
|
+
const torso = e.injury.byRegion["torso"];
|
|
5
|
+
if (!torso)
|
|
6
|
+
return;
|
|
7
|
+
const intensity = hazard.intensity;
|
|
8
|
+
if (hazard.type === "fire") {
|
|
9
|
+
torso.surfaceDamage = clampQ(torso.surfaceDamage + qMul(intensity, q(0.003)), 0, SCALE.Q);
|
|
10
|
+
e.injury.shock = clampQ(e.injury.shock + qMul(intensity, q(0.005)), 0, SCALE.Q);
|
|
11
|
+
}
|
|
12
|
+
else if (hazard.type === "radiation") {
|
|
13
|
+
torso.internalDamage = clampQ(torso.internalDamage + qMul(intensity, q(0.004)), 0, SCALE.Q);
|
|
14
|
+
}
|
|
15
|
+
else if (hazard.type === "poison_gas") {
|
|
16
|
+
torso.internalDamage = clampQ(torso.internalDamage + qMul(intensity, q(0.002)), 0, SCALE.Q);
|
|
17
|
+
e.injury.consciousness = clampQ(e.injury.consciousness - qMul(intensity, q(0.003)), 0, SCALE.Q);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Entity } from "../entity.js";
|
|
2
|
+
import type { WorldState } from "../world.js";
|
|
3
|
+
import { type Q } from "../../units.js";
|
|
4
|
+
export declare const SHOCK_FROM_FLUID: number;
|
|
5
|
+
export declare const SHOCK_FROM_INTERNAL: number;
|
|
6
|
+
export declare const CONSC_LOSS_FROM_SHOCK: number;
|
|
7
|
+
export declare const CONSC_LOSS_FROM_SUFF: number;
|
|
8
|
+
export declare const FATAL_FLUID_LOSS: Q;
|
|
9
|
+
export declare function stepConditionsToInjury(e: Entity, world: WorldState, ambientTemperature_Q?: Q): void;
|
|
10
|
+
export declare function stepInjuryProgression(e: Entity, tick: number): void;
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { q, clampQ, qMul, mulDiv, SCALE } from "../../units.js";
|
|
2
|
+
import { buildTraitProfile } from "../../traits.js";
|
|
3
|
+
import { deriveArmourProfile } from "../../equipment.js";
|
|
4
|
+
import { ALL_REGIONS, DEFAULT_REGION_WEIGHTS } from "../body.js";
|
|
5
|
+
import { getExposureWeight } from "../bodyplan.js";
|
|
6
|
+
import { DamageChannel } from "../../channels.js";
|
|
7
|
+
import { armourCoversHit } from "../kernel.js";
|
|
8
|
+
import { regionKOFactor, totalBleedingRate } from "../injury.js";
|
|
9
|
+
import { getSkill } from "../skills.js";
|
|
10
|
+
import { v3 } from "../vec3.js";
|
|
11
|
+
import { TICK_HZ, DT_S } from "../tick.js";
|
|
12
|
+
/* ------------------ Conditions -> injury (armour-aware) ------------------ */
|
|
13
|
+
export const SHOCK_FROM_FLUID = q(0.0040);
|
|
14
|
+
export const SHOCK_FROM_INTERNAL = q(0.0020);
|
|
15
|
+
export const CONSC_LOSS_FROM_SHOCK = q(0.0100);
|
|
16
|
+
export const CONSC_LOSS_FROM_SUFF = q(0.0200);
|
|
17
|
+
export const FATAL_FLUID_LOSS = q(0.80);
|
|
18
|
+
export function stepConditionsToInjury(e, world, ambientTemperature_Q) {
|
|
19
|
+
const traitProfile = buildTraitProfile(e.traits);
|
|
20
|
+
const armour = deriveArmourProfile(e.loadout);
|
|
21
|
+
// Phase 8: use body plan segments when available; fall back to humanoid defaults.
|
|
22
|
+
const planSegments = e.bodyPlan?.segments ?? null;
|
|
23
|
+
// Exposure weights: "what tends to be exposed" for systemic hazards.
|
|
24
|
+
const exposureWeights = (channel) => {
|
|
25
|
+
if (planSegments) {
|
|
26
|
+
// Data-driven: per-segment per-channel weights from body plan
|
|
27
|
+
const out = {};
|
|
28
|
+
for (const seg of planSegments)
|
|
29
|
+
out[seg.id] = getExposureWeight(seg, channel);
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
// Humanoid fallback
|
|
33
|
+
switch (channel) {
|
|
34
|
+
case DamageChannel.Thermal:
|
|
35
|
+
// Fire: limbs tend to be exposed and catch/keep burning; torso often partly shielded.
|
|
36
|
+
return {
|
|
37
|
+
head: q(0.18),
|
|
38
|
+
torso: q(0.28),
|
|
39
|
+
leftArm: q(0.14),
|
|
40
|
+
rightArm: q(0.14),
|
|
41
|
+
leftLeg: q(0.13),
|
|
42
|
+
rightLeg: q(0.13),
|
|
43
|
+
};
|
|
44
|
+
case DamageChannel.Chemical:
|
|
45
|
+
// Chemical/corrosive aerosols: more even, but torso still prominent.
|
|
46
|
+
// Note: condition.corrosiveExposure feeds this channel — Chemical and
|
|
47
|
+
// Corrosive are unified here. DamageChannel.Corrosive is reserved for
|
|
48
|
+
// future liquid-contact mechanics with a distinct distribution profile.
|
|
49
|
+
return {
|
|
50
|
+
head: q(0.16),
|
|
51
|
+
torso: q(0.36),
|
|
52
|
+
leftArm: q(0.12),
|
|
53
|
+
rightArm: q(0.12),
|
|
54
|
+
leftLeg: q(0.12),
|
|
55
|
+
rightLeg: q(0.12),
|
|
56
|
+
};
|
|
57
|
+
case DamageChannel.Radiation:
|
|
58
|
+
// Penetrating radiation: roughly proportional to mass (torso dominant).
|
|
59
|
+
return {
|
|
60
|
+
head: q(0.12),
|
|
61
|
+
torso: q(0.52),
|
|
62
|
+
leftArm: q(0.09),
|
|
63
|
+
rightArm: q(0.09),
|
|
64
|
+
leftLeg: q(0.09),
|
|
65
|
+
rightLeg: q(0.09),
|
|
66
|
+
};
|
|
67
|
+
case DamageChannel.Electrical:
|
|
68
|
+
// Conductive contact often through extremities.
|
|
69
|
+
return {
|
|
70
|
+
head: q(0.10),
|
|
71
|
+
torso: q(0.22),
|
|
72
|
+
leftArm: q(0.22),
|
|
73
|
+
rightArm: q(0.22),
|
|
74
|
+
leftLeg: q(0.12),
|
|
75
|
+
rightLeg: q(0.12),
|
|
76
|
+
};
|
|
77
|
+
default:
|
|
78
|
+
// Fallback: assume proportional to area.
|
|
79
|
+
return DEFAULT_REGION_WEIGHTS;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const applyDoseToRegion = (channel, region, dose) => {
|
|
83
|
+
if (dose <= 0)
|
|
84
|
+
return q(0);
|
|
85
|
+
if ((traitProfile.immuneMask & (1 << channel)) !== 0)
|
|
86
|
+
return q(0);
|
|
87
|
+
let out = dose;
|
|
88
|
+
if ((traitProfile.resistantMask & (1 << channel)) !== 0)
|
|
89
|
+
out = Math.trunc(out / 2);
|
|
90
|
+
const cov = (armour.coverageByRegion)[region] ?? q(0);
|
|
91
|
+
const armCovers = armourCoversHit(world, cov, e.id, (e.id ^ 0xBEEF) + (channel << 8) + regionSalt(region));
|
|
92
|
+
if (armCovers && ((armour.protects & (1 << channel)) !== 0)) {
|
|
93
|
+
const mul = armour.channelResistMul[channel] ?? q(1.0);
|
|
94
|
+
// A simple "resist factor" curve; for non-kinetic we treat resist_J as a generalised protective capacity.
|
|
95
|
+
const resistFactor = clampQ(q(1.0) - (mulDiv(Math.min(armour.resist_J, 800) * SCALE.Q, 1, 800)), q(0.20), q(1.0));
|
|
96
|
+
out = qMul(qMul(out, resistFactor), armour.protectedDamageMul);
|
|
97
|
+
out = qMul(out, mul);
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
};
|
|
101
|
+
const distribute = (channel, dose) => {
|
|
102
|
+
const w = exposureWeights(channel);
|
|
103
|
+
const out = {};
|
|
104
|
+
const regionList = planSegments ? planSegments.map(s => s.id) : ALL_REGIONS;
|
|
105
|
+
for (const r of regionList)
|
|
106
|
+
out[r] = qMul(dose, w[r] ?? q(0));
|
|
107
|
+
return out;
|
|
108
|
+
};
|
|
109
|
+
const fireBy = distribute(DamageChannel.Thermal, e.condition.onFire);
|
|
110
|
+
const corrBy = distribute(DamageChannel.Chemical, e.condition.corrosiveExposure);
|
|
111
|
+
const elecBy = distribute(DamageChannel.Electrical, e.condition.electricalOverload);
|
|
112
|
+
const radBy = distribute(DamageChannel.Radiation, e.condition.radiation);
|
|
113
|
+
// Suffocation is global rather than surface-localised.
|
|
114
|
+
const suff = (() => {
|
|
115
|
+
if ((traitProfile.immuneMask & (1 << DamageChannel.Suffocation)) !== 0)
|
|
116
|
+
return q(0);
|
|
117
|
+
let out = e.condition.suffocation;
|
|
118
|
+
if ((traitProfile.resistantMask & (1 << DamageChannel.Suffocation)) !== 0)
|
|
119
|
+
out = Math.trunc(out / 2);
|
|
120
|
+
// Simple: masks/helmets reduce suffocation slightly if they protect Suffocation.
|
|
121
|
+
const armCovers = armourCoversHit(world, (armour.coverageByRegion)["head"] ?? q(0), e.id, e.id ^ 0x5AFF);
|
|
122
|
+
if (armCovers && ((armour.protects & (1 << DamageChannel.Suffocation)) !== 0)) {
|
|
123
|
+
out = qMul(out, armour.protectedDamageMul);
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
})();
|
|
127
|
+
const FIRE_SURFACE_PER_TICK = q(0.0020);
|
|
128
|
+
const FIRE_SHOCK_PER_TICK = q(0.0010);
|
|
129
|
+
const CORR_SURFACE_PER_TICK = q(0.0015);
|
|
130
|
+
const CORR_INTERNAL_PER_TICK = q(0.0008);
|
|
131
|
+
const SUFF_SHOCK_PER_TICK = q(0.0015);
|
|
132
|
+
const ELEC_INTERNAL_PER_TICK = q(0.0010);
|
|
133
|
+
const ELEC_STUNNED_RISE = q(0.0200);
|
|
134
|
+
// Radiation: primary effect is internal cellular damage accumulating slowly.
|
|
135
|
+
// Rate calibrated so continuous exposure at q(1.0) reaches ~50% internal
|
|
136
|
+
// damage on the torso (highest-weight region) after ~250 ticks (12.5 s).
|
|
137
|
+
const RAD_INTERNAL_PER_TICK = q(0.0008);
|
|
138
|
+
const RAD_SHOCK_PER_TICK = q(0.0003);
|
|
139
|
+
const allRegionIds = planSegments ? planSegments.map(s => s.id) : ALL_REGIONS;
|
|
140
|
+
for (const r of allRegionIds) {
|
|
141
|
+
const fire = applyDoseToRegion(DamageChannel.Thermal, r, fireBy[r] ?? q(0));
|
|
142
|
+
const corr = applyDoseToRegion(DamageChannel.Chemical, r, corrBy[r] ?? q(0));
|
|
143
|
+
const elec = applyDoseToRegion(DamageChannel.Electrical, r, elecBy[r] ?? q(0));
|
|
144
|
+
const rad = applyDoseToRegion(DamageChannel.Radiation, r, radBy[r] ?? q(0));
|
|
145
|
+
const reg = e.injury.byRegion[r];
|
|
146
|
+
if (!reg)
|
|
147
|
+
continue;
|
|
148
|
+
if (fire > 0) {
|
|
149
|
+
reg.surfaceDamage = clampQ(reg.surfaceDamage + qMul(fire, FIRE_SURFACE_PER_TICK), 0, SCALE.Q);
|
|
150
|
+
e.injury.shock = clampQ(e.injury.shock + qMul(fire, FIRE_SHOCK_PER_TICK), 0, SCALE.Q);
|
|
151
|
+
}
|
|
152
|
+
if (corr > 0) {
|
|
153
|
+
reg.surfaceDamage = clampQ(reg.surfaceDamage + qMul(corr, CORR_SURFACE_PER_TICK), 0, SCALE.Q);
|
|
154
|
+
reg.internalDamage = clampQ(reg.internalDamage + qMul(corr, CORR_INTERNAL_PER_TICK), 0, SCALE.Q);
|
|
155
|
+
}
|
|
156
|
+
if (elec > 0) {
|
|
157
|
+
reg.internalDamage = clampQ(reg.internalDamage + qMul(elec, ELEC_INTERNAL_PER_TICK), 0, SCALE.Q);
|
|
158
|
+
e.condition.stunned = clampQ(e.condition.stunned + qMul(elec, ELEC_STUNNED_RISE), 0, SCALE.Q);
|
|
159
|
+
}
|
|
160
|
+
if (rad > 0) {
|
|
161
|
+
reg.internalDamage = clampQ(reg.internalDamage + qMul(rad, RAD_INTERNAL_PER_TICK), 0, SCALE.Q);
|
|
162
|
+
e.injury.shock = clampQ(e.injury.shock + qMul(rad, RAD_SHOCK_PER_TICK), 0, SCALE.Q);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (suff > 0) {
|
|
166
|
+
e.injury.shock = clampQ(e.injury.shock + qMul(suff, SUFF_SHOCK_PER_TICK), 0, SCALE.Q);
|
|
167
|
+
}
|
|
168
|
+
// Phase 10: ambient temperature stress
|
|
169
|
+
if (ambientTemperature_Q !== undefined) {
|
|
170
|
+
const COMFORT_HIGH = q(0.65);
|
|
171
|
+
const COMFORT_LOW = q(0.35);
|
|
172
|
+
if (ambientTemperature_Q > COMFORT_HIGH) {
|
|
173
|
+
// Heat stress: shock + mild surface damage; heatTolerance scales dose
|
|
174
|
+
const excess = clampQ((ambientTemperature_Q - COMFORT_HIGH), q(0), q(1.0));
|
|
175
|
+
const baseDose = qMul(excess, q(0.025));
|
|
176
|
+
const heatTol = Math.max(1, e.attributes.resilience.heatTolerance);
|
|
177
|
+
const dose = mulDiv(baseDose, SCALE.Q, heatTol);
|
|
178
|
+
e.injury.shock = clampQ((e.injury.shock + dose), 0, SCALE.Q);
|
|
179
|
+
const torsoReg = e.injury.byRegion["torso"] ?? Object.values(e.injury.byRegion)[0];
|
|
180
|
+
if (torsoReg) {
|
|
181
|
+
torsoReg.surfaceDamage = clampQ((torsoReg.surfaceDamage + qMul(dose, q(0.20))), 0, SCALE.Q);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else if (ambientTemperature_Q < COMFORT_LOW) {
|
|
185
|
+
// Cold stress: shock + fatigue; coldTolerance scales dose
|
|
186
|
+
const deficit = clampQ((COMFORT_LOW - ambientTemperature_Q), q(0), q(1.0));
|
|
187
|
+
const baseDose = qMul(deficit, q(0.020));
|
|
188
|
+
const coldTol = Math.max(1, e.attributes.resilience.coldTolerance);
|
|
189
|
+
const dose = mulDiv(baseDose, SCALE.Q, coldTol);
|
|
190
|
+
e.injury.shock = clampQ((e.injury.shock + dose), 0, SCALE.Q);
|
|
191
|
+
e.energy.fatigue = clampQ((e.energy.fatigue + qMul(dose, q(0.50))), 0, SCALE.Q);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
export function stepInjuryProgression(e, tick) {
|
|
196
|
+
if (e.injury.dead)
|
|
197
|
+
return;
|
|
198
|
+
// Phase 9: natural clotting — bleedingRate decays proportional to structural integrity.
|
|
199
|
+
// Heavily damaged tissue clots slowly; intact tissue clots quickly.
|
|
200
|
+
const CLOT_RATE_PER_TICK = q(0.0002);
|
|
201
|
+
const INFECTION_BLEED_THRESHOLD = q(0.05);
|
|
202
|
+
const INFECTION_INT_THRESHOLD = q(0.10);
|
|
203
|
+
const INFECTION_ONSET_TICKS = 100;
|
|
204
|
+
const INFECTION_DAMAGE_PER_TICK = q(0.0003);
|
|
205
|
+
const PERMANENT_THRESHOLD = q(0.90);
|
|
206
|
+
const PERMANENT_FLOOR_MUL = q(0.75);
|
|
207
|
+
for (const reg of Object.values(e.injury.byRegion)) {
|
|
208
|
+
// Clotting
|
|
209
|
+
if (reg.bleedingRate > 0) {
|
|
210
|
+
const structureIntegrity = clampQ((SCALE.Q - reg.structuralDamage), q(0), q(1.0));
|
|
211
|
+
const clotRate = qMul(structureIntegrity, CLOT_RATE_PER_TICK);
|
|
212
|
+
reg.bleedingRate = clampQ((reg.bleedingRate - clotRate), q(0), q(1.0));
|
|
213
|
+
}
|
|
214
|
+
// Infection timer — track consecutive ticks of active bleeding
|
|
215
|
+
if (reg.bleedingRate > INFECTION_BLEED_THRESHOLD) {
|
|
216
|
+
reg.bleedDuration_ticks++;
|
|
217
|
+
if (reg.bleedDuration_ticks >= INFECTION_ONSET_TICKS
|
|
218
|
+
&& reg.internalDamage > INFECTION_INT_THRESHOLD
|
|
219
|
+
&& reg.infectedTick < 0) {
|
|
220
|
+
reg.infectedTick = tick;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
reg.bleedDuration_ticks = Math.max(0, reg.bleedDuration_ticks - 1);
|
|
225
|
+
}
|
|
226
|
+
// Infection progression — infected regions accumulate internal damage
|
|
227
|
+
if (reg.infectedTick >= 0) {
|
|
228
|
+
reg.internalDamage = clampQ(reg.internalDamage + INFECTION_DAMAGE_PER_TICK, 0, SCALE.Q);
|
|
229
|
+
}
|
|
230
|
+
// Permanent damage floor update — set when structural damage is very high
|
|
231
|
+
if (reg.structuralDamage >= PERMANENT_THRESHOLD) {
|
|
232
|
+
const newFloor = qMul(reg.structuralDamage, PERMANENT_FLOOR_MUL);
|
|
233
|
+
if (newFloor > reg.permanentDamage)
|
|
234
|
+
reg.permanentDamage = newFloor;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Phase 8B: hemolymph accumulation — breached open-fluid segments leak each tick
|
|
238
|
+
if (e.bodyPlan) {
|
|
239
|
+
for (const seg of e.bodyPlan.segments) {
|
|
240
|
+
if (seg.fluidSystem !== "open" || seg.hemolymphLossRate === undefined)
|
|
241
|
+
continue;
|
|
242
|
+
const segState = e.injury.byRegion[seg.id];
|
|
243
|
+
if (!segState)
|
|
244
|
+
continue;
|
|
245
|
+
const breachAt = seg.breachThreshold ?? q(0.8);
|
|
246
|
+
if (segState.structuralDamage >= breachAt) {
|
|
247
|
+
const loss = qMul(seg.hemolymphLossRate, segState.structuralDamage);
|
|
248
|
+
e.injury.hemolymphLoss = clampQ((e.injury.hemolymphLoss ?? 0) + loss, 0, SCALE.Q);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Phase 8B: hemolymph fatal threshold — same as fluidLoss
|
|
253
|
+
const FATAL_HEMOLYMPH = q(0.80);
|
|
254
|
+
if ((e.injury.hemolymphLoss ?? 0) >= FATAL_HEMOLYMPH) {
|
|
255
|
+
e.injury.dead = true;
|
|
256
|
+
e.injury.consciousness = q(0);
|
|
257
|
+
e.velocity_mps = v3(0, 0, 0);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
// Phase 8B: molting tick countdown and structural repair on completion
|
|
261
|
+
if (e.molting?.active) {
|
|
262
|
+
e.molting.ticksRemaining = Math.max(0, e.molting.ticksRemaining - 1);
|
|
263
|
+
if (e.molting.ticksRemaining === 0) {
|
|
264
|
+
e.molting.active = false;
|
|
265
|
+
// Repair regeneratesViaMolting segments
|
|
266
|
+
if (e.bodyPlan) {
|
|
267
|
+
for (const seg of e.bodyPlan.segments) {
|
|
268
|
+
if (!seg.regeneratesViaMolting)
|
|
269
|
+
continue;
|
|
270
|
+
const segState = e.injury.byRegion[seg.id];
|
|
271
|
+
if (!segState)
|
|
272
|
+
continue;
|
|
273
|
+
segState.structuralDamage = clampQ((segState.structuralDamage - q(0.10)), 0, SCALE.Q);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// Phase 8B: hemolymph clotting — passive decay of hemolymph loss each tick
|
|
279
|
+
const HEMOLYMPH_CLOT_RATE = q(0.0001);
|
|
280
|
+
if ((e.injury.hemolymphLoss ?? 0) > 0) {
|
|
281
|
+
e.injury.hemolymphLoss = clampQ(((e.injury.hemolymphLoss ?? 0) - HEMOLYMPH_CLOT_RATE), 0, SCALE.Q);
|
|
282
|
+
}
|
|
283
|
+
// Phase 8B: auto-molt trigger — fires when average structural damage on
|
|
284
|
+
// regeneratesViaMolting segments reaches MOLT_TRIGGER_THRESHOLD and no molt
|
|
285
|
+
// is already active. Post-molt repair (−q(0.10)) typically drops average below
|
|
286
|
+
// threshold, preventing immediate re-trigger for minor damage; severely damaged
|
|
287
|
+
// entities will re-molt until damage falls below the threshold.
|
|
288
|
+
const MOLT_TRIGGER_THRESHOLD = q(0.40);
|
|
289
|
+
const MOLT_DURATION_TICKS = TICK_HZ * 60; // 60 seconds at TICK_HZ fps
|
|
290
|
+
if (e.bodyPlan && !e.molting?.active) {
|
|
291
|
+
const regenSegs = e.bodyPlan.segments.filter(s => s.regeneratesViaMolting);
|
|
292
|
+
if (regenSegs.length > 0) {
|
|
293
|
+
let totalDmg = 0;
|
|
294
|
+
for (const seg of regenSegs) {
|
|
295
|
+
totalDmg += e.injury.byRegion[seg.id]?.structuralDamage ?? 0;
|
|
296
|
+
}
|
|
297
|
+
const avgDmg = Math.trunc(totalDmg / regenSegs.length);
|
|
298
|
+
if (avgDmg >= MOLT_TRIGGER_THRESHOLD) {
|
|
299
|
+
e.molting = {
|
|
300
|
+
active: true,
|
|
301
|
+
ticksRemaining: MOLT_DURATION_TICKS,
|
|
302
|
+
softeningSegments: regenSegs.map(s => s.id),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// Phase 8B: wing passive regeneration — slow structural repair on wing segments
|
|
308
|
+
// when not actively molting (molting repair is handled above on completion).
|
|
309
|
+
const WING_REGEN_RATE = q(0.0001);
|
|
310
|
+
if (e.bodyPlan?.locomotion.flight && !e.molting?.active) {
|
|
311
|
+
for (const wid of e.bodyPlan.locomotion.flight.wingSegments) {
|
|
312
|
+
const ws = e.injury.byRegion[wid];
|
|
313
|
+
if (ws && ws.structuralDamage > 0) {
|
|
314
|
+
ws.structuralDamage = clampQ((ws.structuralDamage - WING_REGEN_RATE), 0, SCALE.Q);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const bleedRate = totalBleedingRate(e.injury);
|
|
319
|
+
const rawBleedThisTick = Math.trunc((bleedRate * DT_S) / SCALE.s);
|
|
320
|
+
// Phase 7: medical.treatmentRateMul reduces fluid loss (passive wound management)
|
|
321
|
+
const medSkill = getSkill(e.skills, "medical");
|
|
322
|
+
const bleedThisTick = medSkill.treatmentRateMul > SCALE.Q
|
|
323
|
+
? mulDiv(rawBleedThisTick, SCALE.Q, medSkill.treatmentRateMul)
|
|
324
|
+
: rawBleedThisTick;
|
|
325
|
+
e.injury.fluidLoss = clampQ(e.injury.fluidLoss + bleedThisTick, 0, SCALE.Q);
|
|
326
|
+
e.injury.shock = clampQ(e.injury.shock + qMul(e.injury.fluidLoss, SHOCK_FROM_FLUID) + qMul(e.injury.byRegion["torso"]?.internalDamage ?? q(0), SHOCK_FROM_INTERNAL), 0, SCALE.Q);
|
|
327
|
+
const loss = clampQ(qMul(e.injury.shock, CONSC_LOSS_FROM_SHOCK) + qMul(e.condition.suffocation, CONSC_LOSS_FROM_SUFF) + qMul(regionKOFactor(e.injury), q(0.0100)), 0, SCALE.Q);
|
|
328
|
+
e.injury.consciousness = clampQ(e.injury.consciousness - loss, 0, SCALE.Q);
|
|
329
|
+
// Phase 9: explicit fatal fluid loss threshold (complements the shock path)
|
|
330
|
+
if (e.injury.fluidLoss >= FATAL_FLUID_LOSS || e.injury.shock >= SCALE.Q || e.injury.consciousness === 0) {
|
|
331
|
+
e.injury.dead = true;
|
|
332
|
+
e.injury.consciousness = q(0);
|
|
333
|
+
e.velocity_mps = v3(0, 0, 0);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function regionSalt(region) {
|
|
337
|
+
// Well-known humanoid regions get stable salts; others use a hash of the id string.
|
|
338
|
+
switch (region) {
|
|
339
|
+
case "head": return 0x11;
|
|
340
|
+
case "torso": return 0x22;
|
|
341
|
+
case "leftArm": return 0x33;
|
|
342
|
+
case "rightArm": return 0x44;
|
|
343
|
+
case "leftLeg": return 0x55;
|
|
344
|
+
case "rightLeg": return 0x66;
|
|
345
|
+
default: {
|
|
346
|
+
// Deterministic hash of the segment id (FNV-1a-like)
|
|
347
|
+
let h = 0x77;
|
|
348
|
+
for (let i = 0; i < region.length; i++)
|
|
349
|
+
h = ((h ^ region.charCodeAt(i)) * 0x1f) & 0xFF;
|
|
350
|
+
return h || 0x77;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { WorldState } from "../world.js";
|
|
2
|
+
import type { Entity } from "../entity.js";
|
|
3
|
+
import type { WorldIndex } from "../indexing.js";
|
|
4
|
+
import type { SpatialIndex } from "../spatial.js";
|
|
5
|
+
import type { TraceSink } from "../trace.js";
|
|
6
|
+
import { KernelContext } from "../context.js";
|
|
7
|
+
/**
|
|
8
|
+
* Per-entity morale update — accumulates fear from all sources and applies decay.
|
|
9
|
+
* Emits a MoraleRoute trace event whenever the entity crosses the routing threshold.
|
|
10
|
+
*/
|
|
11
|
+
export declare function stepMoraleForEntity(world: WorldState, e: Entity, index: WorldIndex, spatial: SpatialIndex, aliveBeforeTick: Set<number>, teamRoutingFrac: Map<number, number>, trace: TraceSink, ctx: KernelContext): void;
|