@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,201 @@
|
|
|
1
|
+
// src/sim/hazard.ts — Phase 60: Environmental Hazard Zones
|
|
2
|
+
//
|
|
3
|
+
// Persistent 2-D circular hazard zones that inflict per-second effects on entities
|
|
4
|
+
// within their radius. Five hazard types cover the main environmental threats:
|
|
5
|
+
//
|
|
6
|
+
// fire — fatigue drain, thermal heating, surface damage
|
|
7
|
+
// radiation — cumulative dose accumulation (see Phase 53 radiation_dose toxin)
|
|
8
|
+
// toxic_gas — fatigue drain + disease exposure (marsh_fever by default)
|
|
9
|
+
// acid — surface damage, minor fatigue
|
|
10
|
+
// extreme_cold — thermal cooling, fatigue from shivering
|
|
11
|
+
//
|
|
12
|
+
// Exposure model: linear falloff from full intensity at the hazard centre to zero
|
|
13
|
+
// at the edge. Effects are per-second rates; the host multiplies by dt.
|
|
14
|
+
//
|
|
15
|
+
// exposureQ = max(0, (radius − dist) × intensity / radius) [0..intensity_Q]
|
|
16
|
+
//
|
|
17
|
+
// Temperature offsets (thermalDelta_Q) use the same Q-per-degree encoding as
|
|
18
|
+
// Phase 29/51 (WEATHER_Q_PER_DEG_C ≈ 185; so 1 000 Q ≈ +5.4 °C, −2 000 Q ≈ −10.8 °C).
|
|
19
|
+
//
|
|
20
|
+
// Public API:
|
|
21
|
+
// computeDistToHazard(x_Sm, y_Sm, hazard) → SCALE.m
|
|
22
|
+
// isInsideHazard(x_Sm, y_Sm, hazard) → boolean
|
|
23
|
+
// computeHazardExposure(dist_Sm, hazard) → Q [0..intensity_Q]
|
|
24
|
+
// deriveHazardEffect(hazard, exposureQ) → HazardEffect (per-second rates)
|
|
25
|
+
// stepHazardZone(hazard, elapsedSeconds) → mutates hazard.durationSeconds
|
|
26
|
+
// isHazardExpired(hazard) → boolean
|
|
27
|
+
import { q, clampQ, to, SCALE } from "../units.js";
|
|
28
|
+
/** All hazard type identifiers (useful for validation and iteration). */
|
|
29
|
+
export const ALL_HAZARD_TYPES = [
|
|
30
|
+
"fire", "radiation", "toxic_gas", "acid", "extreme_cold",
|
|
31
|
+
];
|
|
32
|
+
const BASE_EFFECTS = {
|
|
33
|
+
fire: {
|
|
34
|
+
fatigueInc_Q: q(0.0333), // 3.33 % fatigue/s at full intensity → fully fatigued in 30 s
|
|
35
|
+
thermalDelta_Q: 1_000, // ≈ +5.4 °C ambient bias
|
|
36
|
+
radiationDose_Q: q(0),
|
|
37
|
+
surfaceDamageInc_Q: q(0.005), // 0.5 % surface damage/s
|
|
38
|
+
},
|
|
39
|
+
radiation: {
|
|
40
|
+
fatigueInc_Q: q(0),
|
|
41
|
+
thermalDelta_Q: 0,
|
|
42
|
+
radiationDose_Q: q(0.010), // 1 % cumulative dose/s at full exposure
|
|
43
|
+
surfaceDamageInc_Q: q(0),
|
|
44
|
+
},
|
|
45
|
+
toxic_gas: {
|
|
46
|
+
fatigueInc_Q: q(0.010), // 1 % fatigue/s
|
|
47
|
+
thermalDelta_Q: 0,
|
|
48
|
+
radiationDose_Q: q(0),
|
|
49
|
+
surfaceDamageInc_Q: q(0),
|
|
50
|
+
diseaseExposureId: "marsh_fever",
|
|
51
|
+
},
|
|
52
|
+
acid: {
|
|
53
|
+
fatigueInc_Q: q(0.005), // 0.5 % fatigue/s (chemical burns)
|
|
54
|
+
thermalDelta_Q: 0,
|
|
55
|
+
radiationDose_Q: q(0),
|
|
56
|
+
surfaceDamageInc_Q: q(0.015), // 1.5 % surface damage/s
|
|
57
|
+
},
|
|
58
|
+
extreme_cold: {
|
|
59
|
+
fatigueInc_Q: q(0.008), // 0.8 % fatigue/s (shivering)
|
|
60
|
+
thermalDelta_Q: -2_000, // ≈ −10.8 °C ambient bias
|
|
61
|
+
radiationDose_Q: q(0),
|
|
62
|
+
surfaceDamageInc_Q: q(0),
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
const ZERO_EFFECT = {
|
|
66
|
+
fatigueInc_Q: q(0),
|
|
67
|
+
thermalDelta_Q: 0,
|
|
68
|
+
radiationDose_Q: q(0),
|
|
69
|
+
surfaceDamageInc_Q: q(0),
|
|
70
|
+
};
|
|
71
|
+
// ── Sample hazard zones ───────────────────────────────────────────────────────
|
|
72
|
+
/** A modest campfire — 3 m radius, 1-hour duration. */
|
|
73
|
+
export const CAMPFIRE = {
|
|
74
|
+
id: "campfire",
|
|
75
|
+
type: "fire",
|
|
76
|
+
x_Sm: 0,
|
|
77
|
+
y_Sm: 0,
|
|
78
|
+
radius_Sm: to.m(3),
|
|
79
|
+
intensity_Q: q(0.60),
|
|
80
|
+
durationSeconds: 3_600,
|
|
81
|
+
};
|
|
82
|
+
/** A contaminated crater — 50 m radius, permanent. */
|
|
83
|
+
export const RADIATION_ZONE = {
|
|
84
|
+
id: "radiation_zone",
|
|
85
|
+
type: "radiation",
|
|
86
|
+
x_Sm: 0,
|
|
87
|
+
y_Sm: 0,
|
|
88
|
+
radius_Sm: to.m(50),
|
|
89
|
+
intensity_Q: q(0.40),
|
|
90
|
+
durationSeconds: -1, // permanent
|
|
91
|
+
};
|
|
92
|
+
/** A drifting toxic-gas cloud — 20 m radius, 30-minute duration. */
|
|
93
|
+
export const MUSTARD_GAS = {
|
|
94
|
+
id: "mustard_gas",
|
|
95
|
+
type: "toxic_gas",
|
|
96
|
+
x_Sm: 0,
|
|
97
|
+
y_Sm: 0,
|
|
98
|
+
radius_Sm: to.m(20),
|
|
99
|
+
intensity_Q: q(0.80),
|
|
100
|
+
durationSeconds: 1_800,
|
|
101
|
+
};
|
|
102
|
+
/** A corrosive acid pool — 2 m radius, 2-hour duration. */
|
|
103
|
+
export const ACID_POOL = {
|
|
104
|
+
id: "acid_pool",
|
|
105
|
+
type: "acid",
|
|
106
|
+
x_Sm: 0,
|
|
107
|
+
y_Sm: 0,
|
|
108
|
+
radius_Sm: to.m(2),
|
|
109
|
+
intensity_Q: q(0.90),
|
|
110
|
+
durationSeconds: 7_200,
|
|
111
|
+
};
|
|
112
|
+
/** A severe cold zone — 100 m radius, 6-hour duration. */
|
|
113
|
+
export const BLIZZARD_ZONE = {
|
|
114
|
+
id: "blizzard_zone",
|
|
115
|
+
type: "extreme_cold",
|
|
116
|
+
x_Sm: 0,
|
|
117
|
+
y_Sm: 0,
|
|
118
|
+
radius_Sm: to.m(100),
|
|
119
|
+
intensity_Q: q(0.70),
|
|
120
|
+
durationSeconds: 21_600,
|
|
121
|
+
};
|
|
122
|
+
/** All sample hazard zones. */
|
|
123
|
+
export const ALL_SAMPLE_HAZARDS = [
|
|
124
|
+
CAMPFIRE, RADIATION_ZONE, MUSTARD_GAS, ACID_POOL, BLIZZARD_ZONE,
|
|
125
|
+
];
|
|
126
|
+
// ── Core computation ──────────────────────────────────────────────────────────
|
|
127
|
+
/**
|
|
128
|
+
* Euclidean distance from a world position to the hazard centre [SCALE.m].
|
|
129
|
+
*
|
|
130
|
+
* Uses float sqrt for a one-time calculation; result is truncated to integer.
|
|
131
|
+
*/
|
|
132
|
+
export function computeDistToHazard(x_Sm, y_Sm, hazard) {
|
|
133
|
+
const dx = x_Sm - hazard.x_Sm;
|
|
134
|
+
const dy = y_Sm - hazard.y_Sm;
|
|
135
|
+
return Math.trunc(Math.sqrt(dx * dx + dy * dy));
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* True if the given position is within or on the hazard boundary.
|
|
139
|
+
*
|
|
140
|
+
* Uses integer squared-distance comparison to avoid float precision issues.
|
|
141
|
+
*/
|
|
142
|
+
export function isInsideHazard(x_Sm, y_Sm, hazard) {
|
|
143
|
+
const dx = x_Sm - hazard.x_Sm;
|
|
144
|
+
const dy = y_Sm - hazard.y_Sm;
|
|
145
|
+
return dx * dx + dy * dy <= hazard.radius_Sm * hazard.radius_Sm;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Compute the exposure intensity at a given distance from the hazard centre.
|
|
149
|
+
*
|
|
150
|
+
* Linear falloff: `exposure = (radius − dist) × intensity / radius`
|
|
151
|
+
* Returns `q(0)` when `dist >= radius`.
|
|
152
|
+
*
|
|
153
|
+
* @param dist_Sm Distance from hazard centre [SCALE.m].
|
|
154
|
+
*/
|
|
155
|
+
export function computeHazardExposure(dist_Sm, hazard) {
|
|
156
|
+
if (dist_Sm >= hazard.radius_Sm || hazard.intensity_Q <= 0)
|
|
157
|
+
return q(0);
|
|
158
|
+
return clampQ(Math.round((hazard.radius_Sm - dist_Sm) * hazard.intensity_Q / hazard.radius_Sm), q(0), SCALE.Q);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Derive per-second hazard effect rates from an exposure level.
|
|
162
|
+
*
|
|
163
|
+
* `exposureQ` is the output of `computeHazardExposure` — already in [0, intensity_Q].
|
|
164
|
+
* Each base rate is scaled linearly: `rate = base × exposureQ / SCALE.Q`.
|
|
165
|
+
*
|
|
166
|
+
* `thermalDelta_Q` uses the same scaling so the thermal offset fades toward the
|
|
167
|
+
* hazard boundary.
|
|
168
|
+
*
|
|
169
|
+
* Returns a zero-effect record when `exposureQ <= 0`.
|
|
170
|
+
*/
|
|
171
|
+
export function deriveHazardEffect(hazard, exposureQ) {
|
|
172
|
+
if (exposureQ <= 0)
|
|
173
|
+
return { ...ZERO_EFFECT };
|
|
174
|
+
const b = BASE_EFFECTS[hazard.type];
|
|
175
|
+
return {
|
|
176
|
+
fatigueInc_Q: clampQ(Math.round(b.fatigueInc_Q * exposureQ / SCALE.Q), 0, SCALE.Q),
|
|
177
|
+
thermalDelta_Q: Math.round(b.thermalDelta_Q * exposureQ / SCALE.Q),
|
|
178
|
+
radiationDose_Q: clampQ(Math.round(b.radiationDose_Q * exposureQ / SCALE.Q), 0, SCALE.Q),
|
|
179
|
+
surfaceDamageInc_Q: clampQ(Math.round(b.surfaceDamageInc_Q * exposureQ / SCALE.Q), 0, SCALE.Q),
|
|
180
|
+
...(b.diseaseExposureId ? { diseaseExposureId: b.diseaseExposureId } : {}),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
// ── Lifecycle ─────────────────────────────────────────────────────────────────
|
|
184
|
+
/**
|
|
185
|
+
* Advance a hazard zone's lifetime by `elapsedSeconds`.
|
|
186
|
+
*
|
|
187
|
+
* Permanent hazards (`durationSeconds === -1`) are untouched.
|
|
188
|
+
* Mutates: `hazard.durationSeconds`.
|
|
189
|
+
*/
|
|
190
|
+
export function stepHazardZone(hazard, elapsedSeconds) {
|
|
191
|
+
if (hazard.durationSeconds < 0)
|
|
192
|
+
return;
|
|
193
|
+
hazard.durationSeconds = Math.max(0, hazard.durationSeconds - elapsedSeconds);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* True when the hazard has run out of duration and should be removed from the world.
|
|
197
|
+
* Always false for permanent hazards.
|
|
198
|
+
*/
|
|
199
|
+
export function isHazardExpired(hazard) {
|
|
200
|
+
return hazard.durationSeconds >= 0 && hazard.durationSeconds === 0;
|
|
201
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Q } from "../units.js";
|
|
2
|
+
import { BodyRegion, BoneRegion, MajorOrgan } from "./body.js";
|
|
3
|
+
import { BodySegment } from "./bodyplan.js";
|
|
4
|
+
/** Minimum projectile velocity for temporary cavity effect (600 m/s in SCALE.mps). */
|
|
5
|
+
export declare const HYDROSTATIC_THRESHOLD_mps: number;
|
|
6
|
+
/** Minimum projectile velocity for cavitation bleed bonus (900 m/s in SCALE.mps). */
|
|
7
|
+
export declare const CAVITATION_THRESHOLD_mps: number;
|
|
8
|
+
/**
|
|
9
|
+
* Tissue compliance by region: lower = less elastic = more temporary cavity damage.
|
|
10
|
+
* Look up by region id; fall back to DEFAULT_COMPLIANCE for unknown regions.
|
|
11
|
+
*
|
|
12
|
+
* Reference values (lower bound = bone q(0.05), upper = elastic muscle q(0.60)):
|
|
13
|
+
* bone/skull — q(0.05): extremely brittle; cavitation causes shattering
|
|
14
|
+
* brain/liver/spleen — q(0.10): very inelastic fluid organs
|
|
15
|
+
* lung — q(0.30): intermediate; partially elastic air-filled tissue
|
|
16
|
+
* muscle/torso — q(0.60): moderately elastic
|
|
17
|
+
*/
|
|
18
|
+
export declare const TISSUE_COMPLIANCE: Record<BodyRegion | BodySegment["id"] | MajorOrgan | BoneRegion, Q>;
|
|
19
|
+
export type TISSUE_COMPLIANCE_KEY = keyof typeof TISSUE_COMPLIANCE;
|
|
20
|
+
/** Compliance used when a region is not in the table (muscle-level). */
|
|
21
|
+
export declare const DEFAULT_COMPLIANCE: Q;
|
|
22
|
+
/**
|
|
23
|
+
* Compute the temporary-cavity multiplier on internal-fraction damage.
|
|
24
|
+
*
|
|
25
|
+
* Returns a Q-scaled integer multiplier: `q(1.0)` = no extra damage, `q(3.0)` = maximum.
|
|
26
|
+
* Only activates when `v_impact > HYDROSTATIC_THRESHOLD_mps`.
|
|
27
|
+
*
|
|
28
|
+
* Formula (fixed-point):
|
|
29
|
+
* v_ratio_Q = (v / 600)² × SCALE.Q
|
|
30
|
+
* tissue_factor_Q = v_ratio_Q × (SCALE.Q − compliance_Q) / SCALE.Q
|
|
31
|
+
* result = clamp(q(1.0) + tissue_factor_Q, q(1.0), q(3.0))
|
|
32
|
+
*
|
|
33
|
+
* Calibration:
|
|
34
|
+
* 9mm (370 m/s) → q(1.0) (below gate) ✓
|
|
35
|
+
* 5.56mm (960 m/s) → q(3.0) for liver; q(2.0) for muscle ✓
|
|
36
|
+
* subsonic .45 ACP (270 m/s) → q(1.0) ✓
|
|
37
|
+
*
|
|
38
|
+
* @param v_impact Projectile velocity at impact point (SCALE.mps units).
|
|
39
|
+
* @param region Hit region id.
|
|
40
|
+
*/
|
|
41
|
+
export declare function computeTemporaryCavityMul(v_impact: number, region: TISSUE_COMPLIANCE_KEY): Q;
|
|
42
|
+
/**
|
|
43
|
+
* Apply cavitation-induced haemorrhage boost to a region's bleedingRate.
|
|
44
|
+
*
|
|
45
|
+
* Only active when:
|
|
46
|
+
* - `v_impact > CAVITATION_THRESHOLD_mps` (900 m/s)
|
|
47
|
+
* - `region` is fluid-saturated tissue (lung, liver, spleen, torso, legs)
|
|
48
|
+
*
|
|
49
|
+
* Formula:
|
|
50
|
+
* cavMul = q(1.0) + (v − 900) / 300 [in Q-space]
|
|
51
|
+
* newBleed = clamp(currentBleed × cavMul, 0, q(1.0))
|
|
52
|
+
*
|
|
53
|
+
* @param v_impact Projectile velocity at impact (SCALE.mps units).
|
|
54
|
+
* @param currentBleed Existing bleedingRate after primary wound application (Q integer).
|
|
55
|
+
* @param region Hit region id (bone and non-fluid regions return unchanged bleed).
|
|
56
|
+
* @returns Updated bleedingRate (Q integer, clamped to q(1.0) max).
|
|
57
|
+
*/
|
|
58
|
+
export declare function computeCavitationBleed(v_impact: number, currentBleed: number, region: string): number;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// src/sim/hydrostatic.ts — Phase 27: Hydrostatic Shock & Cavitation
|
|
2
|
+
//
|
|
3
|
+
// High-velocity projectiles (> 600 m/s) produce a temporary radial stretch wave
|
|
4
|
+
// that amplifies internal damage in inelastic tissue. Above 900 m/s, momentary
|
|
5
|
+
// vacuum cavitation further increases haemorrhage in fluid-saturated tissue.
|
|
6
|
+
//
|
|
7
|
+
// This module is pure computation — no entity mutation, no RNG.
|
|
8
|
+
import { SCALE, q, clampQ, mulDiv } from "../units.js";
|
|
9
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
10
|
+
/** Minimum projectile velocity for temporary cavity effect (600 m/s in SCALE.mps). */
|
|
11
|
+
export const HYDROSTATIC_THRESHOLD_mps = Math.trunc(600 * SCALE.mps);
|
|
12
|
+
/** Minimum projectile velocity for cavitation bleed bonus (900 m/s in SCALE.mps). */
|
|
13
|
+
export const CAVITATION_THRESHOLD_mps = Math.trunc(900 * SCALE.mps);
|
|
14
|
+
// ── Tissue compliance ─────────────────────────────────────────────────────────
|
|
15
|
+
/**
|
|
16
|
+
* Tissue compliance by region: lower = less elastic = more temporary cavity damage.
|
|
17
|
+
* Look up by region id; fall back to DEFAULT_COMPLIANCE for unknown regions.
|
|
18
|
+
*
|
|
19
|
+
* Reference values (lower bound = bone q(0.05), upper = elastic muscle q(0.60)):
|
|
20
|
+
* bone/skull — q(0.05): extremely brittle; cavitation causes shattering
|
|
21
|
+
* brain/liver/spleen — q(0.10): very inelastic fluid organs
|
|
22
|
+
* lung — q(0.30): intermediate; partially elastic air-filled tissue
|
|
23
|
+
* muscle/torso — q(0.60): moderately elastic
|
|
24
|
+
*/
|
|
25
|
+
export const TISSUE_COMPLIANCE = {
|
|
26
|
+
// Organ segments (advanced body plans)
|
|
27
|
+
brain: q(0.10),
|
|
28
|
+
liver: q(0.10),
|
|
29
|
+
spleen: q(0.10),
|
|
30
|
+
lung: q(0.30),
|
|
31
|
+
// Humanoid string-literal regions
|
|
32
|
+
head: q(0.10), // brain-dominated
|
|
33
|
+
torso: q(0.40), // mixed thoracic/abdominal
|
|
34
|
+
leftArm: q(0.60),
|
|
35
|
+
rightArm: q(0.60),
|
|
36
|
+
leftLeg: q(0.60),
|
|
37
|
+
rightLeg: q(0.60),
|
|
38
|
+
// Bone segment ids
|
|
39
|
+
bone: q(0.05),
|
|
40
|
+
skull: q(0.05),
|
|
41
|
+
femur: q(0.05),
|
|
42
|
+
tibia: q(0.05),
|
|
43
|
+
};
|
|
44
|
+
/** Compliance used when a region is not in the table (muscle-level). */
|
|
45
|
+
export const DEFAULT_COMPLIANCE = q(0.60);
|
|
46
|
+
/**
|
|
47
|
+
* Region ids susceptible to cavitation bubble formation.
|
|
48
|
+
* Excludes bone (non-fluid) and brain (inelastic but not vascular enough for macroscopic bubbles).
|
|
49
|
+
*/
|
|
50
|
+
const CAVITATION_TISSUE = new Set([
|
|
51
|
+
"lung", "liver", "spleen",
|
|
52
|
+
"torso", "leftLeg", "rightLeg",
|
|
53
|
+
]);
|
|
54
|
+
// ── computeTemporaryCavityMul ─────────────────────────────────────────────────
|
|
55
|
+
/**
|
|
56
|
+
* Compute the temporary-cavity multiplier on internal-fraction damage.
|
|
57
|
+
*
|
|
58
|
+
* Returns a Q-scaled integer multiplier: `q(1.0)` = no extra damage, `q(3.0)` = maximum.
|
|
59
|
+
* Only activates when `v_impact > HYDROSTATIC_THRESHOLD_mps`.
|
|
60
|
+
*
|
|
61
|
+
* Formula (fixed-point):
|
|
62
|
+
* v_ratio_Q = (v / 600)² × SCALE.Q
|
|
63
|
+
* tissue_factor_Q = v_ratio_Q × (SCALE.Q − compliance_Q) / SCALE.Q
|
|
64
|
+
* result = clamp(q(1.0) + tissue_factor_Q, q(1.0), q(3.0))
|
|
65
|
+
*
|
|
66
|
+
* Calibration:
|
|
67
|
+
* 9mm (370 m/s) → q(1.0) (below gate) ✓
|
|
68
|
+
* 5.56mm (960 m/s) → q(3.0) for liver; q(2.0) for muscle ✓
|
|
69
|
+
* subsonic .45 ACP (270 m/s) → q(1.0) ✓
|
|
70
|
+
*
|
|
71
|
+
* @param v_impact Projectile velocity at impact point (SCALE.mps units).
|
|
72
|
+
* @param region Hit region id.
|
|
73
|
+
*/
|
|
74
|
+
export function computeTemporaryCavityMul(v_impact, region) {
|
|
75
|
+
if (v_impact <= HYDROSTATIC_THRESHOLD_mps)
|
|
76
|
+
return q(1.0);
|
|
77
|
+
const compliance_Q = TISSUE_COMPLIANCE[region] ?? DEFAULT_COMPLIANCE;
|
|
78
|
+
// (v / threshold)² in Q-units — BigInt prevents integer overflow
|
|
79
|
+
// Max practical v: ~2000 m/s = 2×10^7; v² = 4×10^14 → safe in BigInt
|
|
80
|
+
const vB = BigInt(v_impact);
|
|
81
|
+
const tB = BigInt(HYDROSTATIC_THRESHOLD_mps);
|
|
82
|
+
const v_ratio_Q = Number((vB * vB * BigInt(SCALE.Q)) / (tB * tB));
|
|
83
|
+
// tissue_factor_Q = v_ratio_Q × (1 − compliance) — both in Q-space
|
|
84
|
+
const tissue_factor_Q = mulDiv(v_ratio_Q, SCALE.Q - compliance_Q, SCALE.Q);
|
|
85
|
+
// result = q(1.0) + tissue_factor_Q, clamped to [q(1.0), q(3.0)]
|
|
86
|
+
return clampQ(SCALE.Q + tissue_factor_Q, SCALE.Q, 3 * SCALE.Q);
|
|
87
|
+
}
|
|
88
|
+
// ── computeCavitationBleed ────────────────────────────────────────────────────
|
|
89
|
+
/**
|
|
90
|
+
* Apply cavitation-induced haemorrhage boost to a region's bleedingRate.
|
|
91
|
+
*
|
|
92
|
+
* Only active when:
|
|
93
|
+
* - `v_impact > CAVITATION_THRESHOLD_mps` (900 m/s)
|
|
94
|
+
* - `region` is fluid-saturated tissue (lung, liver, spleen, torso, legs)
|
|
95
|
+
*
|
|
96
|
+
* Formula:
|
|
97
|
+
* cavMul = q(1.0) + (v − 900) / 300 [in Q-space]
|
|
98
|
+
* newBleed = clamp(currentBleed × cavMul, 0, q(1.0))
|
|
99
|
+
*
|
|
100
|
+
* @param v_impact Projectile velocity at impact (SCALE.mps units).
|
|
101
|
+
* @param currentBleed Existing bleedingRate after primary wound application (Q integer).
|
|
102
|
+
* @param region Hit region id (bone and non-fluid regions return unchanged bleed).
|
|
103
|
+
* @returns Updated bleedingRate (Q integer, clamped to q(1.0) max).
|
|
104
|
+
*/
|
|
105
|
+
export function computeCavitationBleed(v_impact, currentBleed, region) {
|
|
106
|
+
if (v_impact <= CAVITATION_THRESHOLD_mps)
|
|
107
|
+
return currentBleed;
|
|
108
|
+
if (!CAVITATION_TISSUE.has(region))
|
|
109
|
+
return currentBleed;
|
|
110
|
+
if (currentBleed <= 0)
|
|
111
|
+
return currentBleed;
|
|
112
|
+
const CAVITATION_RANGE_mps = Math.trunc(300 * SCALE.mps);
|
|
113
|
+
const v_excess = v_impact - CAVITATION_THRESHOLD_mps;
|
|
114
|
+
const bleedBonus_Q = mulDiv(v_excess, SCALE.Q, CAVITATION_RANGE_mps);
|
|
115
|
+
const cavMul_Q = SCALE.Q + bleedBonus_Q; // q(1.0) + proportional bonus
|
|
116
|
+
return Math.min(SCALE.Q, mulDiv(currentBleed, cavMul_Q, SCALE.Q));
|
|
117
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Q } from "../units.js";
|
|
2
|
+
import { type Entity } from "./entity.js";
|
|
3
|
+
import type { SimulationTuning } from "./tuning.js";
|
|
4
|
+
export interface FunctionalState {
|
|
5
|
+
mobilityMul: Q;
|
|
6
|
+
manipulationMul: Q;
|
|
7
|
+
coordinationMul: Q;
|
|
8
|
+
staminaMul: Q;
|
|
9
|
+
leftArmDisabled: boolean;
|
|
10
|
+
rightArmDisabled: boolean;
|
|
11
|
+
leftLegDisabled: boolean;
|
|
12
|
+
rightLegDisabled: boolean;
|
|
13
|
+
canStand: boolean;
|
|
14
|
+
canAct: boolean;
|
|
15
|
+
disabledFunctions: ReadonlySet<string>;
|
|
16
|
+
}
|
|
17
|
+
export declare function deriveFunctionalState(e: Entity, tuning?: SimulationTuning): FunctionalState;
|
|
18
|
+
export declare function hasDisabledFunction(func: FunctionalState, functionId: string): boolean;
|
|
19
|
+
export declare function hasAllDisabledFunctions(func: FunctionalState, ...functionIds: readonly string[]): boolean;
|
|
20
|
+
export declare function hasAnyDisabledFunction(func: FunctionalState, ...functionIds: readonly string[]): boolean;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { q, clampQ, SCALE, qMul, mulDiv } from "../units.js";
|
|
2
|
+
import { ensureAnatomyRuntime } from "./entity.js";
|
|
3
|
+
import { TUNING } from "./tuning.js";
|
|
4
|
+
const mean2 = (a, b) => Math.trunc((a + b) / 2);
|
|
5
|
+
function applyImpairmentsClamped(base, minOut, maxOut, ...terms) {
|
|
6
|
+
let out = base;
|
|
7
|
+
for (const [value, weight] of terms)
|
|
8
|
+
out = (out - qMul(value, weight));
|
|
9
|
+
return clampQ(out, minOut, maxOut);
|
|
10
|
+
}
|
|
11
|
+
export function deriveFunctionalState(e, tuning = TUNING.tactical) {
|
|
12
|
+
const inj = e.injury;
|
|
13
|
+
const disabledFunctions = computeDisabledFunctions(e, tuning);
|
|
14
|
+
// Derive legacy booleans from the single source of truth.
|
|
15
|
+
const leftArmDisabled = disabledFunctions.has("leftManipulation");
|
|
16
|
+
const rightArmDisabled = disabledFunctions.has("rightManipulation");
|
|
17
|
+
const leftLegDisabled = disabledFunctions.has("leftLocomotion");
|
|
18
|
+
const rightLegDisabled = disabledFunctions.has("rightLocomotion");
|
|
19
|
+
let legStr;
|
|
20
|
+
let legInt;
|
|
21
|
+
let armStr;
|
|
22
|
+
let armInt;
|
|
23
|
+
let headInt;
|
|
24
|
+
let headStr;
|
|
25
|
+
let fractureLegQ = q(0);
|
|
26
|
+
let fractureArmQ = q(0);
|
|
27
|
+
const { helpers } = ensureAnatomyRuntime(e);
|
|
28
|
+
if (helpers?.functionalDamage) {
|
|
29
|
+
const summary = helpers.functionalDamage.summarize(inj);
|
|
30
|
+
legStr = summary.mobility.structural;
|
|
31
|
+
legInt = summary.mobility.internal;
|
|
32
|
+
armStr = summary.manipulation.structural;
|
|
33
|
+
armInt = summary.manipulation.internal;
|
|
34
|
+
headInt = summary.coordination.internal;
|
|
35
|
+
headStr = summary.coordination.structural;
|
|
36
|
+
fractureLegQ = summary.mobility.fracture;
|
|
37
|
+
fractureArmQ = summary.manipulation.fracture;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// Backward compat: humanoid hardcoded regions
|
|
41
|
+
const leftArmStr = inj.byRegion.leftArm?.structuralDamage ?? q(0);
|
|
42
|
+
const rightArmStr = inj.byRegion.rightArm?.structuralDamage ?? q(0);
|
|
43
|
+
const leftLegStr = inj.byRegion.leftLeg?.structuralDamage ?? q(0);
|
|
44
|
+
const rightLegStr = inj.byRegion.rightLeg?.structuralDamage ?? q(0);
|
|
45
|
+
const leftLegIntVal = inj.byRegion.leftLeg?.internalDamage ?? q(0);
|
|
46
|
+
const rightLegIntVal = inj.byRegion.rightLeg?.internalDamage ?? q(0);
|
|
47
|
+
const leftArmIntVal = inj.byRegion.leftArm?.internalDamage ?? q(0);
|
|
48
|
+
const rightArmIntVal = inj.byRegion.rightArm?.internalDamage ?? q(0);
|
|
49
|
+
legStr = mean2(leftLegStr, rightLegStr);
|
|
50
|
+
legInt = mean2(leftLegIntVal, rightLegIntVal);
|
|
51
|
+
armStr = mean2(leftArmStr, rightArmStr);
|
|
52
|
+
armInt = mean2(leftArmIntVal, rightArmIntVal);
|
|
53
|
+
headInt = inj.byRegion.head?.internalDamage ?? q(0);
|
|
54
|
+
headStr = inj.byRegion.head?.structuralDamage ?? q(0);
|
|
55
|
+
const leftLegFrac = inj.byRegion.leftLeg?.fractured ?? false;
|
|
56
|
+
const rightLegFrac = inj.byRegion.rightLeg?.fractured ?? false;
|
|
57
|
+
const leftArmFrac = inj.byRegion.leftArm?.fractured ?? false;
|
|
58
|
+
const rightArmFrac = inj.byRegion.rightArm?.fractured ?? false;
|
|
59
|
+
fractureLegQ = Math.trunc(((leftLegFrac ? 1 : 0) + (rightLegFrac ? 1 : 0)) * SCALE.Q / 2);
|
|
60
|
+
fractureArmQ = Math.trunc(((leftArmFrac ? 1 : 0) + (rightArmFrac ? 1 : 0)) * SCALE.Q / 2);
|
|
61
|
+
}
|
|
62
|
+
const shock = inj.shock;
|
|
63
|
+
const fatigue = e.energy.fatigue;
|
|
64
|
+
const stun = e.condition.stunned;
|
|
65
|
+
const concLoss = (SCALE.Q - inj.consciousness);
|
|
66
|
+
const fluidLoss = inj.fluidLoss;
|
|
67
|
+
const pinnedQ = (e.condition?.pinned ?? false) ? SCALE.Q : 0;
|
|
68
|
+
const heldQ = (e.grapple?.heldByIds?.length ?? 0) > 0 ? SCALE.Q : 0;
|
|
69
|
+
const suppressedQ = (e.condition.suppressedTicks ?? 0) > 0 ? SCALE.Q : 0;
|
|
70
|
+
const fearQ = e.condition.fearQ ?? q(0);
|
|
71
|
+
const EXHAUSTION_THRESHOLD = q(0.15);
|
|
72
|
+
const baselineReserve = Math.max(1, e.attributes.performance.reserveEnergy_J);
|
|
73
|
+
const currentReserve = Math.max(0, e.energy.reserveEnergy_J);
|
|
74
|
+
const reserveRatioQ = clampQ(mulDiv(currentReserve, SCALE.Q, baselineReserve), q(0), q(1.0));
|
|
75
|
+
const exhaustionQ = reserveRatioQ >= EXHAUSTION_THRESHOLD
|
|
76
|
+
? q(0)
|
|
77
|
+
: clampQ(mulDiv((EXHAUSTION_THRESHOLD - reserveRatioQ), SCALE.Q, EXHAUSTION_THRESHOLD), q(0), q(1.0));
|
|
78
|
+
const mobilityMul = applyImpairmentsClamped(q(1.0), q(0.05), q(1.0), [legStr, q(0.60)], [legInt, q(0.25)], [shock, q(0.15)], [fatigue, q(0.25)], [stun, q(0.35)], [concLoss, q(0.10)], [pinnedQ, q(0.80)], [heldQ, q(0.30)], [exhaustionQ, q(0.30)], [fractureLegQ, q(0.30)]);
|
|
79
|
+
const manipulationMul = applyImpairmentsClamped(q(1.0), q(0.05), q(1.0), [armStr, q(0.55)], [armInt, q(0.20)], [shock, q(0.10)], [fatigue, q(0.20)], [stun, q(0.25)], [concLoss, q(0.20)], [pinnedQ, q(0.60)], [heldQ, q(0.20)], [exhaustionQ, q(0.25)], [fearQ, q(0.15)], [fractureArmQ, q(0.25)]);
|
|
80
|
+
const coordinationMul = applyImpairmentsClamped(q(1.0), q(0.05), q(1.0), [headInt, q(0.45)], [headStr, q(0.15)], [shock, q(0.20)], [fatigue, q(0.20)], [stun, q(0.40)], [concLoss, q(0.35)], [exhaustionQ, q(0.20)], [suppressedQ, q(0.10)], [fearQ, q(0.15)]);
|
|
81
|
+
const staminaMul = applyImpairmentsClamped(q(1.0), q(0.00), q(1.0), [fatigue, q(0.65)], [shock, q(0.15)], [fluidLoss, q(0.35)], [exhaustionQ, q(0.50)]);
|
|
82
|
+
const canAct = inj.consciousness >= tuning.unconsciousThreshold && !inj.dead;
|
|
83
|
+
const legsOut = disabledFunctions.has("leftLocomotion") && disabledFunctions.has("rightLocomotion");
|
|
84
|
+
const canStand = canAct && !legsOut && mobilityMul >= tuning.standFailThreshold;
|
|
85
|
+
return {
|
|
86
|
+
mobilityMul,
|
|
87
|
+
manipulationMul,
|
|
88
|
+
coordinationMul,
|
|
89
|
+
staminaMul,
|
|
90
|
+
leftArmDisabled,
|
|
91
|
+
rightArmDisabled,
|
|
92
|
+
leftLegDisabled,
|
|
93
|
+
rightLegDisabled,
|
|
94
|
+
canStand,
|
|
95
|
+
canAct,
|
|
96
|
+
disabledFunctions,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export function hasDisabledFunction(func, functionId) {
|
|
100
|
+
return func.disabledFunctions.has(functionId);
|
|
101
|
+
}
|
|
102
|
+
export function hasAllDisabledFunctions(func, ...functionIds) {
|
|
103
|
+
return functionIds.every((id) => func.disabledFunctions.has(id));
|
|
104
|
+
}
|
|
105
|
+
export function hasAnyDisabledFunction(func, ...functionIds) {
|
|
106
|
+
return functionIds.some((id) => func.disabledFunctions.has(id));
|
|
107
|
+
}
|
|
108
|
+
function computeDisabledFunctions(e, tuning) {
|
|
109
|
+
const out = new Set();
|
|
110
|
+
const { helpers } = ensureAnatomyRuntime(e);
|
|
111
|
+
if (helpers?.functionalDamage) {
|
|
112
|
+
// General aggregate functions from anatomy model (no positional IDs here)
|
|
113
|
+
const fd = helpers.functionalDamage;
|
|
114
|
+
const candidates = [
|
|
115
|
+
"mobility", "locomotion", "manipulation", "coordination", "cognition",
|
|
116
|
+
"cns", "respiration", "circulation", "sensor", "vision", "balance",
|
|
117
|
+
"stancePosture",
|
|
118
|
+
];
|
|
119
|
+
for (const id of candidates) {
|
|
120
|
+
if (fd.isFunctionDisabled(e.injury, id))
|
|
121
|
+
out.add(id);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Positional left/right flags: always derived from body plan structure or hardcoded
|
|
125
|
+
// humanoid region names. These are not indexed as anatomy-model function IDs.
|
|
126
|
+
addPositionalFlags(out, e, tuning);
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Populates "leftManipulation", "rightManipulation", "leftLocomotion", "rightLocomotion"
|
|
131
|
+
* in `out` by inspecting injury thresholds on the primary body-plan segments.
|
|
132
|
+
*
|
|
133
|
+
* When a body plan is present the primary segment ordering is used ([0] = left, [1] = right).
|
|
134
|
+
* When absent the legacy humanoid region names ("leftArm", "rightArm", …) are used.
|
|
135
|
+
*/
|
|
136
|
+
function addPositionalFlags(out, e, tuning) {
|
|
137
|
+
const byRegion = e.injury.byRegion;
|
|
138
|
+
const plan = e.bodyPlan;
|
|
139
|
+
if (plan) {
|
|
140
|
+
const primaryManip = plan.segments.filter(s => s.manipulationRole === "primary");
|
|
141
|
+
const primaryLoco = plan.segments.filter(s => s.locomotionRole === "primary");
|
|
142
|
+
if ((byRegion[primaryManip[0]?.id ?? ""]?.structuralDamage ?? q(0)) >= tuning.armDisableThreshold)
|
|
143
|
+
out.add("leftManipulation");
|
|
144
|
+
if ((byRegion[primaryManip[1]?.id ?? ""]?.structuralDamage ?? q(0)) >= tuning.armDisableThreshold)
|
|
145
|
+
out.add("rightManipulation");
|
|
146
|
+
if ((byRegion[primaryLoco[0]?.id ?? ""]?.structuralDamage ?? q(0)) >= tuning.legDisableThreshold)
|
|
147
|
+
out.add("leftLocomotion");
|
|
148
|
+
if ((byRegion[primaryLoco[1]?.id ?? ""]?.structuralDamage ?? q(0)) >= tuning.legDisableThreshold)
|
|
149
|
+
out.add("rightLocomotion");
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Humanoid fallback
|
|
153
|
+
if ((byRegion.leftArm?.structuralDamage ?? q(0)) >= tuning.armDisableThreshold)
|
|
154
|
+
out.add("leftManipulation");
|
|
155
|
+
if ((byRegion.rightArm?.structuralDamage ?? q(0)) >= tuning.armDisableThreshold)
|
|
156
|
+
out.add("rightManipulation");
|
|
157
|
+
if ((byRegion.leftLeg?.structuralDamage ?? q(0)) >= tuning.legDisableThreshold)
|
|
158
|
+
out.add("leftLocomotion");
|
|
159
|
+
if ((byRegion.rightLeg?.structuralDamage ?? q(0)) >= tuning.legDisableThreshold)
|
|
160
|
+
out.add("rightLocomotion");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Q } from "../units.js";
|
|
2
|
+
/**
|
|
3
|
+
* Structural damage fraction at which a fracture is recorded.
|
|
4
|
+
* Once set, `fractured` persists until surgically cleared.
|
|
5
|
+
*/
|
|
6
|
+
export declare const FRACTURE_THRESHOLD: Q;
|
|
7
|
+
export interface RegionInjury {
|
|
8
|
+
surfaceDamage: Q;
|
|
9
|
+
internalDamage: Q;
|
|
10
|
+
structuralDamage: Q;
|
|
11
|
+
bleedingRate: Q;
|
|
12
|
+
/** Structural damage has crossed FRACTURE_THRESHOLD; cleared only by surgery. */
|
|
13
|
+
fractured: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Tick at which infection began; -1 = no infection.
|
|
16
|
+
* Set when a region bleeds for INFECTION_ONSET_TICKS consecutively with
|
|
17
|
+
* sufficient internal damage.
|
|
18
|
+
*/
|
|
19
|
+
infectedTick: number;
|
|
20
|
+
/** Consecutive ticks the region has been actively bleeding (used for infection timer). */
|
|
21
|
+
bleedDuration_ticks: number;
|
|
22
|
+
/**
|
|
23
|
+
* Irreversible damage floor. Set when structuralDamage ≥ PERMANENT_THRESHOLD.
|
|
24
|
+
* Treatment cannot reduce structuralDamage below this value.
|
|
25
|
+
*/
|
|
26
|
+
permanentDamage: Q;
|
|
27
|
+
}
|
|
28
|
+
export interface InjuryState {
|
|
29
|
+
/** Keyed by segment id (BodyRegion strings for humanoid, or custom ids for other body plans). */
|
|
30
|
+
byRegion: Record<string, RegionInjury>;
|
|
31
|
+
fluidLoss: Q;
|
|
32
|
+
shock: Q;
|
|
33
|
+
consciousness: Q;
|
|
34
|
+
dead: boolean;
|
|
35
|
+
/** Phase 8B forward compat: hemolymph loss for open-fluid exoskeleton segments (0..1). */
|
|
36
|
+
hemolymphLoss: Q;
|
|
37
|
+
}
|
|
38
|
+
export declare const defaultRegionInjury: () => RegionInjury;
|
|
39
|
+
/**
|
|
40
|
+
* Create a default InjuryState for the given segment ids.
|
|
41
|
+
* When omitted, defaults to the standard humanoid six regions.
|
|
42
|
+
*/
|
|
43
|
+
export declare const defaultInjury: (segmentIds?: readonly string[]) => InjuryState;
|
|
44
|
+
export type DamageType = 'surfaceDamage' | 'internalDamage' | 'structuralDamage' | 'bleedingRate';
|
|
45
|
+
export declare function totalSurfaceDamage(i: InjuryState): Q;
|
|
46
|
+
export declare function totalInternalDamage(i: InjuryState): Q;
|
|
47
|
+
export declare function totalStructuralDamage(i: InjuryState): Q;
|
|
48
|
+
export declare function totalBleedingRate(i: InjuryState): Q;
|
|
49
|
+
/**
|
|
50
|
+
* Compute KO risk from CNS-critical region damage.
|
|
51
|
+
* For humanoid and any plan that uses "head"/"torso" segment ids.
|
|
52
|
+
* For other body plans, falls back gracefully to q(0) for absent segments.
|
|
53
|
+
*/
|
|
54
|
+
export declare function regionKOFactor(i: InjuryState): Q;
|