@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,105 @@
|
|
|
1
|
+
// src/sim/weather.ts — Phase 51: Weather & Atmospheric Environment
|
|
2
|
+
//
|
|
3
|
+
// Pure computation module — no entity mutation. Defines weather state types
|
|
4
|
+
// and derives per-tick modifiers that the kernel applies to traction, sensory
|
|
5
|
+
// environment, ambient temperature, and ranged-combat aim error.
|
|
6
|
+
//
|
|
7
|
+
// Data flow:
|
|
8
|
+
// KernelContext.weather (WeatherState) → deriveWeatherModifiers → WeatherModifiers
|
|
9
|
+
// WeatherModifiers → kernel stepWorld → tractionCoeff, sensoryEnv, thermalAmbient_Q
|
|
10
|
+
// WeatherState.wind + resolveShoot → computeWindAimError → extra gRadius_m
|
|
11
|
+
import { SCALE, q, clampQ } from "../units.js";
|
|
12
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
13
|
+
/**
|
|
14
|
+
* Approximate Q units per degree Celsius in the Phase-29 thermal encoding.
|
|
15
|
+
* (SCALE.Q / 54) where 54 = 64°C − 10°C temperature range).
|
|
16
|
+
*/
|
|
17
|
+
export const WEATHER_Q_PER_DEG_C = 185;
|
|
18
|
+
/** Reference cone-weapon travel speed [SCALE.mps] for adjustConeRange. 20 m/s. */
|
|
19
|
+
const CONE_REF_SPEED_mps = 2_000;
|
|
20
|
+
// ── Precipitation lookup table ────────────────────────────────────────────────
|
|
21
|
+
/** Physical parameters indexed by PrecipitationType. */
|
|
22
|
+
const PRECIP_TABLE = {
|
|
23
|
+
none: { tractionMul: q(1.00), lightMul: q(1.00), precipVisionMul: q(1.00), thermalDegC: 0 },
|
|
24
|
+
rain: { tractionMul: q(0.85), lightMul: q(0.90), precipVisionMul: q(0.85), thermalDegC: 1 },
|
|
25
|
+
heavy_rain: { tractionMul: q(0.70), lightMul: q(0.70), precipVisionMul: q(0.60), thermalDegC: 2 },
|
|
26
|
+
snow: { tractionMul: q(0.60), lightMul: q(0.75), precipVisionMul: q(0.75), thermalDegC: 5 },
|
|
27
|
+
blizzard: { tractionMul: q(0.40), lightMul: q(0.30), precipVisionMul: q(0.30), thermalDegC: 12 },
|
|
28
|
+
hail: { tractionMul: q(0.75), lightMul: q(0.85), precipVisionMul: q(0.85), thermalDegC: 1 },
|
|
29
|
+
};
|
|
30
|
+
// ── deriveWeatherModifiers ─────────────────────────────────────────────────────
|
|
31
|
+
/**
|
|
32
|
+
* Compute all derived modifiers for a WeatherState.
|
|
33
|
+
* Pure function — no side effects.
|
|
34
|
+
*/
|
|
35
|
+
export function deriveWeatherModifiers(weather) {
|
|
36
|
+
const pt = PRECIP_TABLE[weather.precipitation ?? "none"];
|
|
37
|
+
// Fog multiplier: q(0) fog = q(1.0) light; q(1.0) fog = q(0.10) minimum light.
|
|
38
|
+
const fogMul = weather.fogDensity_Q !== undefined
|
|
39
|
+
? clampQ((SCALE.Q - weather.fogDensity_Q), q(0.10), SCALE.Q)
|
|
40
|
+
: SCALE.Q;
|
|
41
|
+
// Combined light reduction from precipitation and fog (multiplicative).
|
|
42
|
+
const lightMul_Q = clampQ(Math.trunc(pt.lightMul * fogMul / SCALE.Q), q(0.10), SCALE.Q);
|
|
43
|
+
return {
|
|
44
|
+
tractionMul_Q: pt.tractionMul,
|
|
45
|
+
lightMul_Q,
|
|
46
|
+
precipVisionMul_Q: pt.precipVisionMul,
|
|
47
|
+
thermalOffset_Q: pt.thermalDegC ? -(pt.thermalDegC * WEATHER_Q_PER_DEG_C) : 0,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// ── computeWindAimError ───────────────────────────────────────────────────────
|
|
51
|
+
/**
|
|
52
|
+
* Additional aim grouping radius from wind drift [SCALE.m].
|
|
53
|
+
*
|
|
54
|
+
* Physics: drift = v_wind_perp × (range / v_proj)
|
|
55
|
+
*
|
|
56
|
+
* The perpendicular wind component is the 2D cross product of shot direction
|
|
57
|
+
* and wind direction. Algebraic simplification yields:
|
|
58
|
+
*
|
|
59
|
+
* drift_scaled_m = wind.speed_mps × |shot × wind| / (SCALE.m × v_proj_mps)
|
|
60
|
+
*
|
|
61
|
+
* where |shot × wind| is the cross-product magnitude in SCALE.m² units with
|
|
62
|
+
* shot direction of magnitude `dist_m` and wind direction of magnitude SCALE.m.
|
|
63
|
+
* The `dist_m` factors cancel, leaving a formula independent of scale choice.
|
|
64
|
+
*
|
|
65
|
+
* @param wind Wind field (direction + speed).
|
|
66
|
+
* @param shotDx_m Shot direction x [SCALE.m] (raw, not normalised).
|
|
67
|
+
* @param shotDy_m Shot direction y [SCALE.m] (raw, not normalised).
|
|
68
|
+
* @param dist_m Range to target [SCALE.m].
|
|
69
|
+
* @param v_proj_mps Projectile speed [SCALE.mps].
|
|
70
|
+
* @returns Additional grouping radius [SCALE.m] (≥ 0).
|
|
71
|
+
*/
|
|
72
|
+
export function computeWindAimError(wind, shotDx_m, shotDy_m, dist_m, v_proj_mps) {
|
|
73
|
+
if (wind.speed_mps <= 0 || v_proj_mps <= 0 || dist_m <= 0)
|
|
74
|
+
return 0;
|
|
75
|
+
// 2D cross product magnitude: |shot × wind| in SCALE.m² units.
|
|
76
|
+
const crossRaw = Math.abs(shotDx_m * wind.dy_m - shotDy_m * wind.dx_m);
|
|
77
|
+
// drift_scaled_m = wind.speed_mps × crossRaw / (SCALE.m × v_proj_mps)
|
|
78
|
+
return Math.trunc(wind.speed_mps * crossRaw / (SCALE.m * v_proj_mps));
|
|
79
|
+
}
|
|
80
|
+
// ── adjustConeRange ───────────────────────────────────────────────────────────
|
|
81
|
+
/**
|
|
82
|
+
* Adjust cone range for wind effects (breath weapons, gas clouds, flamethrowers).
|
|
83
|
+
*
|
|
84
|
+
* - Headwind (wind opposes cone direction) reduces range up to −20%.
|
|
85
|
+
* - Tailwind extends range up to +10%.
|
|
86
|
+
* - Crosswind (perpendicular) has no effect on range.
|
|
87
|
+
*
|
|
88
|
+
* @param wind Wind field.
|
|
89
|
+
* @param coneDx_m Cone facing direction x (SCALE.m unit vector).
|
|
90
|
+
* @param coneDy_m Cone facing direction y (SCALE.m unit vector).
|
|
91
|
+
* @param range_m Base cone range [SCALE.m].
|
|
92
|
+
* @returns Adjusted range [SCALE.m] (≥ 0).
|
|
93
|
+
*/
|
|
94
|
+
export function adjustConeRange(wind, coneDx_m, coneDy_m, range_m) {
|
|
95
|
+
if (wind.speed_mps <= 0 || range_m <= 0)
|
|
96
|
+
return range_m;
|
|
97
|
+
// Dot product of cone dir (SCALE.m unit vector) and wind dir (SCALE.m unit vector).
|
|
98
|
+
// Result is in SCALE.m²; divide by SCALE.m to get aligned component in SCALE.m.
|
|
99
|
+
const dot_m = Math.trunc((coneDx_m * wind.dx_m + coneDy_m * wind.dy_m) / SCALE.m);
|
|
100
|
+
// Effect = aligned_speed / CONE_REF_SPEED as a Q fraction, clamped to ±20% / +10%.
|
|
101
|
+
// At CONE_REF_SPEED full-headwind (dot = −SCALE.m): effect_Q = −q(0.20).
|
|
102
|
+
const effect_Q = clampQ(Math.trunc(wind.speed_mps * dot_m * 2_000 / (SCALE.m * CONE_REF_SPEED_mps)), -2_000, // max −20% headwind reduction
|
|
103
|
+
1_000);
|
|
104
|
+
return Math.max(0, range_m + Math.trunc(range_m * effect_Q / SCALE.Q));
|
|
105
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Entity } from "./entity.js";
|
|
2
|
+
import type { FieldEffect } from "./capability.js";
|
|
3
|
+
import { SensoryEnvironment } from "./sensory.js";
|
|
4
|
+
import { FactionRegistry } from "../faction.js";
|
|
5
|
+
import { PartyRegistry } from "../party.js";
|
|
6
|
+
import { RelationshipGraph } from "../relationships.js";
|
|
7
|
+
/**
|
|
8
|
+
* Top-level simulation container.
|
|
9
|
+
*
|
|
10
|
+
* Fields are annotated with stability tiers identical to `Entity`:
|
|
11
|
+
* - **`@core`** — required by `stepWorld` every tick.
|
|
12
|
+
* - **`@subsystem(name)`** — optional state consumed only by a specific sub-module.
|
|
13
|
+
*/
|
|
14
|
+
export interface WorldState {
|
|
15
|
+
/** @core Current tick count; incremented by `stepWorld`. */
|
|
16
|
+
tick: number;
|
|
17
|
+
/** @core Deterministic RNG seed — same seed + same commands → identical output. */
|
|
18
|
+
seed: number;
|
|
19
|
+
/** @core All live and dead entities. Do not splice manually; use `stepWorld`. */
|
|
20
|
+
entities: Entity[];
|
|
21
|
+
/**
|
|
22
|
+
* @subsystem(capability) Active suppression zones and field-effect modifiers
|
|
23
|
+
* (e.g., mana-null zones, buff auras). Consumed by `src/sim/capability.ts`.
|
|
24
|
+
*/
|
|
25
|
+
activeFieldEffects?: FieldEffect[];
|
|
26
|
+
/**
|
|
27
|
+
* @subsystem(sensory) Ambient lighting and visibility environment.
|
|
28
|
+
* Consumed by `src/sim/sensory.ts` when provided. Prefixed `__` to discourage
|
|
29
|
+
* direct access — use the sensory API instead.
|
|
30
|
+
*/
|
|
31
|
+
__sensoryEnv?: SensoryEnvironment;
|
|
32
|
+
/**
|
|
33
|
+
* @subsystem(faction) Global faction-standing registry.
|
|
34
|
+
* Consumed by `src/faction.ts` and AI targeting.
|
|
35
|
+
*/
|
|
36
|
+
__factionRegistry?: FactionRegistry;
|
|
37
|
+
/**
|
|
38
|
+
* @subsystem(party) Global party registry.
|
|
39
|
+
* Consumed by `src/party.ts` for morale and formation bonuses.
|
|
40
|
+
*/
|
|
41
|
+
__partyRegistry?: PartyRegistry;
|
|
42
|
+
/**
|
|
43
|
+
* @subsystem(relationships) Inter-entity relationship graph.
|
|
44
|
+
* Consumed by `src/relationships.ts`.
|
|
45
|
+
*/
|
|
46
|
+
__relationshipGraph?: RelationshipGraph;
|
|
47
|
+
/**
|
|
48
|
+
* @subsystem(nutrition) Cross-tick nutrition accumulator.
|
|
49
|
+
* Consumed by the nutrition sub-step in `src/sim/kernel.ts`.
|
|
50
|
+
*/
|
|
51
|
+
__nutritionAccum?: number;
|
|
52
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { type Q } from "../units.js";
|
|
2
|
+
import type { Entity } from "./entity.js";
|
|
3
|
+
/**
|
|
4
|
+
* PTSD-like trauma state accumulating from severe shock events.
|
|
5
|
+
* Stored on `entity.traumaState`.
|
|
6
|
+
* Reduces effective fear threshold via `deriveFearThresholdMul`.
|
|
7
|
+
*/
|
|
8
|
+
export interface TraumaState {
|
|
9
|
+
/** Cumulative severity (Q 0..SCALE.Q). Grows with shock events; decays over time. */
|
|
10
|
+
severity_Q: Q;
|
|
11
|
+
}
|
|
12
|
+
/** Outcome summary returned by `stepWoundAging`. */
|
|
13
|
+
export interface WoundAgingResult {
|
|
14
|
+
/** Region ids whose surface or internal damage improved (healed). */
|
|
15
|
+
healedRegions: string[];
|
|
16
|
+
/** Region ids whose damage worsened (infection progression). */
|
|
17
|
+
worsenedRegions: string[];
|
|
18
|
+
/** True if any region crossed the sepsis damage threshold this step. */
|
|
19
|
+
newSepsis: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** Seconds per day — base unit for all per-day rates. */
|
|
22
|
+
export declare const SECONDS_PER_DAY = 86400;
|
|
23
|
+
/**
|
|
24
|
+
* Surface-damage healing rate [Q/day].
|
|
25
|
+
* At q(0.01)/day, a fully surface-damaged region takes ~100 days to heal.
|
|
26
|
+
*/
|
|
27
|
+
export declare const SURFACE_HEAL_Q_PER_DAY: Q;
|
|
28
|
+
/**
|
|
29
|
+
* Internal-damage healing rate [Q/day].
|
|
30
|
+
* Half the surface rate — internal injuries heal more slowly.
|
|
31
|
+
*/
|
|
32
|
+
export declare const INTERNAL_HEAL_Q_PER_DAY: Q;
|
|
33
|
+
/**
|
|
34
|
+
* Infection worsening rate [Q/day] applied to internalDamage while infected.
|
|
35
|
+
* 3× the internal heal rate — untreated infection outpaces natural recovery.
|
|
36
|
+
*/
|
|
37
|
+
export declare const INFECTION_WORSEN_Q_PER_DAY: Q;
|
|
38
|
+
/**
|
|
39
|
+
* Internal-damage threshold (Q) above which an infected region is considered
|
|
40
|
+
* to pose a sepsis risk (systemically threatening).
|
|
41
|
+
*/
|
|
42
|
+
export declare const SEPSIS_THRESHOLD: Q;
|
|
43
|
+
/**
|
|
44
|
+
* Chronic fatigue per-day rate [Q/day] applied when total permanent damage
|
|
45
|
+
* across all regions exceeds CHRONIC_FATIGUE_REGION_THRESHOLD.
|
|
46
|
+
*/
|
|
47
|
+
export declare const CHRONIC_FATIGUE_Q_PER_DAY: Q;
|
|
48
|
+
/**
|
|
49
|
+
* Minimum total permanent damage (summed across all regions, relative to
|
|
50
|
+
* SCALE.Q × regionCount) to activate chronic fatigue.
|
|
51
|
+
* Approximately q(0.10) average per region.
|
|
52
|
+
*/
|
|
53
|
+
export declare const CHRONIC_FATIGUE_THRESHOLD: Q;
|
|
54
|
+
/**
|
|
55
|
+
* Permanent-damage threshold (per region) above which a fractured region
|
|
56
|
+
* causes phantom pain during rest.
|
|
57
|
+
*/
|
|
58
|
+
export declare const PHANTOM_PAIN_THRESHOLD: Q;
|
|
59
|
+
/**
|
|
60
|
+
* Phantom pain shock injection per day per qualifying fractured region [Q/day].
|
|
61
|
+
* Scaled by the region's (permanentDamage / SCALE.Q) ratio.
|
|
62
|
+
*/
|
|
63
|
+
export declare const PHANTOM_PAIN_Q_PER_DAY: Q;
|
|
64
|
+
/**
|
|
65
|
+
* Trauma decay per day [Q/day] — natural recovery rate of traumaState.severity_Q.
|
|
66
|
+
* Slow: severe trauma takes months to fully resolve.
|
|
67
|
+
*/
|
|
68
|
+
export declare const TRAUMA_DECAY_Q_PER_DAY: Q;
|
|
69
|
+
/**
|
|
70
|
+
* Minimum shock increment that registers as a traumatic event.
|
|
71
|
+
* Events below this threshold are too minor to compound PTSD-like symptoms.
|
|
72
|
+
*/
|
|
73
|
+
export declare const TRAUMA_TRIGGER_THRESHOLD: Q;
|
|
74
|
+
/**
|
|
75
|
+
* Advance long-term wound state by `elapsedSeconds`.
|
|
76
|
+
*
|
|
77
|
+
* Intended for downtime / long-rest simulation (hours to weeks of elapsed time).
|
|
78
|
+
* At sub-minute resolution this function does nothing observable.
|
|
79
|
+
*
|
|
80
|
+
* Mutates:
|
|
81
|
+
* entity.injury.byRegion (surface/internal damage healed or worsened)
|
|
82
|
+
* entity.injury.shock (phantom pain)
|
|
83
|
+
* entity.energy.fatigue (chronic fatigue drain)
|
|
84
|
+
* entity.traumaState (natural severity decay)
|
|
85
|
+
*
|
|
86
|
+
* @param elapsedSeconds Wall-clock seconds elapsed (use 86400 per game-day).
|
|
87
|
+
* @returns WoundAgingResult summary (healed, worsened, newSepsis).
|
|
88
|
+
*/
|
|
89
|
+
export declare function stepWoundAging(entity: Entity, elapsedSeconds: number): WoundAgingResult;
|
|
90
|
+
/**
|
|
91
|
+
* Record a traumatic shock event, accumulating PTSD-like severity.
|
|
92
|
+
*
|
|
93
|
+
* Only events at or above TRAUMA_TRIGGER_THRESHOLD (q(0.20)) register.
|
|
94
|
+
* A q(1.0) shock event contributes q(0.30) to `traumaState.severity_Q`.
|
|
95
|
+
*
|
|
96
|
+
* Mutates: entity.traumaState (created if absent).
|
|
97
|
+
*
|
|
98
|
+
* @param shockIncrement_Q The shock delta from the triggering event (Q).
|
|
99
|
+
*/
|
|
100
|
+
export declare function recordTraumaEvent(entity: Entity, shockIncrement_Q: Q): void;
|
|
101
|
+
/**
|
|
102
|
+
* Derive the effective fear-threshold multiplier from accumulated trauma.
|
|
103
|
+
*
|
|
104
|
+
* Returns Q in [TRAUMA_FEAR_MUL_FLOOR, SCALE.Q]:
|
|
105
|
+
* q(1.0) → no trauma — fear threshold unchanged.
|
|
106
|
+
* q(0.50) → maximum trauma — entity triggers fear at half normal threshold.
|
|
107
|
+
*
|
|
108
|
+
* Usage (combat / morale layer):
|
|
109
|
+
* `effectiveFearThreshold_Q = Math.round(baseFearThreshold_Q × mul / SCALE.Q)`
|
|
110
|
+
*/
|
|
111
|
+
export declare function deriveFearThresholdMul(entity: Entity): Q;
|
|
112
|
+
/**
|
|
113
|
+
* Compute aggregate sepsis risk (Q 0..SCALE.Q) from all infected regions.
|
|
114
|
+
*
|
|
115
|
+
* Risk increases with both the number of infected regions and their
|
|
116
|
+
* internal damage level. Returns q(0) if no infected regions.
|
|
117
|
+
*
|
|
118
|
+
* Usage: AI / medical layer reads this to prioritise treatment.
|
|
119
|
+
*/
|
|
120
|
+
export declare function deriveSepsisRisk(entity: Entity): Q;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// src/sim/wound-aging.ts — Phase 54: Wound Aging & Long-Term Sequelae
|
|
2
|
+
//
|
|
3
|
+
// Extends Phase 21 (injury resolution) and Phase 9 (infection) with time-based
|
|
4
|
+
// wound progression relevant to multi-day / multi-week campaign simulation.
|
|
5
|
+
//
|
|
6
|
+
// Four long-term processes:
|
|
7
|
+
// 1. Healing — uninfected regions slowly recover surface and internal damage
|
|
8
|
+
// (clamped to permanentDamage floor; structural never recovers).
|
|
9
|
+
// 2. Infection — infected regions worsen; severe infection → sepsis flag.
|
|
10
|
+
// 3. Chronic fatigue — sustained permanent damage creates a baseline fatigue drain.
|
|
11
|
+
// 4. Phantom pain — fractured regions with significant permanent damage inject
|
|
12
|
+
// periodic shock increments during rest/downtime.
|
|
13
|
+
//
|
|
14
|
+
// Plus two PTSD-like trauma utilities:
|
|
15
|
+
// recordTraumaEvent(entity, shockQ) — accumulate trauma severity
|
|
16
|
+
// deriveFearThresholdMul(entity) → Q — fear multiplier from trauma
|
|
17
|
+
// deriveSepsisRisk(entity) → Q — aggregate infection severity
|
|
18
|
+
//
|
|
19
|
+
// Call stepWoundAging during downtime (long rests, between scenes) with the number
|
|
20
|
+
// of real elapsed seconds. At 1 real second this does nothing useful; at 86 400 s
|
|
21
|
+
// (one day) the full daily rates apply.
|
|
22
|
+
import { q, clampQ, SCALE } from "../units.js";
|
|
23
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
24
|
+
/** Seconds per day — base unit for all per-day rates. */
|
|
25
|
+
export const SECONDS_PER_DAY = 86_400;
|
|
26
|
+
/**
|
|
27
|
+
* Surface-damage healing rate [Q/day].
|
|
28
|
+
* At q(0.01)/day, a fully surface-damaged region takes ~100 days to heal.
|
|
29
|
+
*/
|
|
30
|
+
export const SURFACE_HEAL_Q_PER_DAY = q(0.01); // 100 Q/day
|
|
31
|
+
/**
|
|
32
|
+
* Internal-damage healing rate [Q/day].
|
|
33
|
+
* Half the surface rate — internal injuries heal more slowly.
|
|
34
|
+
*/
|
|
35
|
+
export const INTERNAL_HEAL_Q_PER_DAY = q(0.005); // 50 Q/day
|
|
36
|
+
/**
|
|
37
|
+
* Infection worsening rate [Q/day] applied to internalDamage while infected.
|
|
38
|
+
* 3× the internal heal rate — untreated infection outpaces natural recovery.
|
|
39
|
+
*/
|
|
40
|
+
export const INFECTION_WORSEN_Q_PER_DAY = q(0.015); // 150 Q/day
|
|
41
|
+
/**
|
|
42
|
+
* Internal-damage threshold (Q) above which an infected region is considered
|
|
43
|
+
* to pose a sepsis risk (systemically threatening).
|
|
44
|
+
*/
|
|
45
|
+
export const SEPSIS_THRESHOLD = q(0.85);
|
|
46
|
+
/**
|
|
47
|
+
* Chronic fatigue per-day rate [Q/day] applied when total permanent damage
|
|
48
|
+
* across all regions exceeds CHRONIC_FATIGUE_REGION_THRESHOLD.
|
|
49
|
+
*/
|
|
50
|
+
export const CHRONIC_FATIGUE_Q_PER_DAY = q(0.02); // 200 Q/day
|
|
51
|
+
/**
|
|
52
|
+
* Minimum total permanent damage (summed across all regions, relative to
|
|
53
|
+
* SCALE.Q × regionCount) to activate chronic fatigue.
|
|
54
|
+
* Approximately q(0.10) average per region.
|
|
55
|
+
*/
|
|
56
|
+
export const CHRONIC_FATIGUE_THRESHOLD = q(0.10); // per-region average
|
|
57
|
+
/**
|
|
58
|
+
* Permanent-damage threshold (per region) above which a fractured region
|
|
59
|
+
* causes phantom pain during rest.
|
|
60
|
+
*/
|
|
61
|
+
export const PHANTOM_PAIN_THRESHOLD = q(0.30);
|
|
62
|
+
/**
|
|
63
|
+
* Phantom pain shock injection per day per qualifying fractured region [Q/day].
|
|
64
|
+
* Scaled by the region's (permanentDamage / SCALE.Q) ratio.
|
|
65
|
+
*/
|
|
66
|
+
export const PHANTOM_PAIN_Q_PER_DAY = q(0.02); // 200 Q/day at max permanent damage
|
|
67
|
+
/**
|
|
68
|
+
* Trauma decay per day [Q/day] — natural recovery rate of traumaState.severity_Q.
|
|
69
|
+
* Slow: severe trauma takes months to fully resolve.
|
|
70
|
+
*/
|
|
71
|
+
export const TRAUMA_DECAY_Q_PER_DAY = q(0.002); // 20 Q/day
|
|
72
|
+
/**
|
|
73
|
+
* Minimum shock increment that registers as a traumatic event.
|
|
74
|
+
* Events below this threshold are too minor to compound PTSD-like symptoms.
|
|
75
|
+
*/
|
|
76
|
+
export const TRAUMA_TRIGGER_THRESHOLD = q(0.20);
|
|
77
|
+
/**
|
|
78
|
+
* Fraction of a shock increment that accumulates as trauma severity.
|
|
79
|
+
* q(0.30) → a q(1.0) shock event contributes q(0.30) to trauma severity.
|
|
80
|
+
*/
|
|
81
|
+
const TRAUMA_ACCUMULATION_RATE = q(0.30);
|
|
82
|
+
/**
|
|
83
|
+
* Maximum fear-threshold reduction from trauma (q(0.50) → trauma halves
|
|
84
|
+
* the effective fear threshold, making entities fearful at lower stimuli).
|
|
85
|
+
*/
|
|
86
|
+
const TRAUMA_FEAR_MUL_FLOOR = q(0.50);
|
|
87
|
+
// ── Public API ────────────────────────────────────────────────────────────────
|
|
88
|
+
/**
|
|
89
|
+
* Advance long-term wound state by `elapsedSeconds`.
|
|
90
|
+
*
|
|
91
|
+
* Intended for downtime / long-rest simulation (hours to weeks of elapsed time).
|
|
92
|
+
* At sub-minute resolution this function does nothing observable.
|
|
93
|
+
*
|
|
94
|
+
* Mutates:
|
|
95
|
+
* entity.injury.byRegion (surface/internal damage healed or worsened)
|
|
96
|
+
* entity.injury.shock (phantom pain)
|
|
97
|
+
* entity.energy.fatigue (chronic fatigue drain)
|
|
98
|
+
* entity.traumaState (natural severity decay)
|
|
99
|
+
*
|
|
100
|
+
* @param elapsedSeconds Wall-clock seconds elapsed (use 86400 per game-day).
|
|
101
|
+
* @returns WoundAgingResult summary (healed, worsened, newSepsis).
|
|
102
|
+
*/
|
|
103
|
+
export function stepWoundAging(entity, elapsedSeconds) {
|
|
104
|
+
const days = elapsedSeconds / SECONDS_PER_DAY;
|
|
105
|
+
const result = {
|
|
106
|
+
healedRegions: [],
|
|
107
|
+
worsenedRegions: [],
|
|
108
|
+
newSepsis: false,
|
|
109
|
+
};
|
|
110
|
+
const regions = entity.injury.byRegion;
|
|
111
|
+
const regionEntries = Object.entries(regions);
|
|
112
|
+
let totalPermanent = 0;
|
|
113
|
+
let regionCount = 0;
|
|
114
|
+
for (const [regionId, reg] of regionEntries) {
|
|
115
|
+
regionCount++;
|
|
116
|
+
const prevSurface = reg.surfaceDamage;
|
|
117
|
+
const prevInternal = reg.internalDamage;
|
|
118
|
+
const perm = reg.permanentDamage;
|
|
119
|
+
totalPermanent += perm;
|
|
120
|
+
if (reg.infectedTick !== -1) {
|
|
121
|
+
// ── Infection worsening ────────────────────────────────────────────────
|
|
122
|
+
const worsenAmount = Math.round(INFECTION_WORSEN_Q_PER_DAY * days);
|
|
123
|
+
reg.internalDamage = clampQ((reg.internalDamage + worsenAmount), q(0), q(1.0));
|
|
124
|
+
if (reg.internalDamage > prevInternal) {
|
|
125
|
+
result.worsenedRegions.push(regionId);
|
|
126
|
+
}
|
|
127
|
+
// Sepsis: infected region has crossed the critical threshold
|
|
128
|
+
if (!result.newSepsis && reg.internalDamage >= SEPSIS_THRESHOLD && prevInternal < SEPSIS_THRESHOLD) {
|
|
129
|
+
result.newSepsis = true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// ── Healing ───────────────────────────────────────────────────────────
|
|
134
|
+
const surfaceHeal = Math.round(SURFACE_HEAL_Q_PER_DAY * days);
|
|
135
|
+
const internalHeal = Math.round(INTERNAL_HEAL_Q_PER_DAY * days);
|
|
136
|
+
reg.surfaceDamage = clampQ((reg.surfaceDamage - surfaceHeal), perm, q(1.0));
|
|
137
|
+
reg.internalDamage = clampQ((reg.internalDamage - internalHeal), perm, q(1.0));
|
|
138
|
+
if (reg.surfaceDamage < prevSurface || reg.internalDamage < prevInternal) {
|
|
139
|
+
result.healedRegions.push(regionId);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// ── Phantom pain ──────────────────────────────────────────────────────────
|
|
143
|
+
if (reg.fractured && reg.permanentDamage >= PHANTOM_PAIN_THRESHOLD) {
|
|
144
|
+
// Shock injection scaled by permanent damage fraction
|
|
145
|
+
const painQ = Math.round(PHANTOM_PAIN_Q_PER_DAY * days * reg.permanentDamage / SCALE.Q);
|
|
146
|
+
if (painQ > 0) {
|
|
147
|
+
entity.injury.shock = clampQ((entity.injury.shock + painQ), q(0), SCALE.Q);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// ── Chronic fatigue ────────────────────────────────────────────────────────
|
|
152
|
+
if (regionCount > 0) {
|
|
153
|
+
const avgPermanent = totalPermanent / regionCount;
|
|
154
|
+
if (avgPermanent >= CHRONIC_FATIGUE_THRESHOLD) {
|
|
155
|
+
const fatigueInc = Math.round(CHRONIC_FATIGUE_Q_PER_DAY * days * avgPermanent / SCALE.Q);
|
|
156
|
+
if (fatigueInc > 0) {
|
|
157
|
+
entity.energy.fatigue = clampQ((entity.energy.fatigue + fatigueInc), q(0), SCALE.Q);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// ── Trauma natural decay ───────────────────────────────────────────────────
|
|
162
|
+
if (entity.traumaState && entity.traumaState.severity_Q > 0) {
|
|
163
|
+
const decayAmount = Math.round(TRAUMA_DECAY_Q_PER_DAY * days);
|
|
164
|
+
entity.traumaState.severity_Q = clampQ((entity.traumaState.severity_Q - decayAmount), q(0), SCALE.Q);
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
// ── Trauma utilities ──────────────────────────────────────────────────────────
|
|
169
|
+
/**
|
|
170
|
+
* Record a traumatic shock event, accumulating PTSD-like severity.
|
|
171
|
+
*
|
|
172
|
+
* Only events at or above TRAUMA_TRIGGER_THRESHOLD (q(0.20)) register.
|
|
173
|
+
* A q(1.0) shock event contributes q(0.30) to `traumaState.severity_Q`.
|
|
174
|
+
*
|
|
175
|
+
* Mutates: entity.traumaState (created if absent).
|
|
176
|
+
*
|
|
177
|
+
* @param shockIncrement_Q The shock delta from the triggering event (Q).
|
|
178
|
+
*/
|
|
179
|
+
export function recordTraumaEvent(entity, shockIncrement_Q) {
|
|
180
|
+
if (shockIncrement_Q < TRAUMA_TRIGGER_THRESHOLD)
|
|
181
|
+
return;
|
|
182
|
+
if (!entity.traumaState) {
|
|
183
|
+
entity.traumaState = { severity_Q: q(0) };
|
|
184
|
+
}
|
|
185
|
+
const increment = Math.round(shockIncrement_Q * TRAUMA_ACCUMULATION_RATE / SCALE.Q);
|
|
186
|
+
entity.traumaState.severity_Q = clampQ((entity.traumaState.severity_Q + increment), q(0), SCALE.Q);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Derive the effective fear-threshold multiplier from accumulated trauma.
|
|
190
|
+
*
|
|
191
|
+
* Returns Q in [TRAUMA_FEAR_MUL_FLOOR, SCALE.Q]:
|
|
192
|
+
* q(1.0) → no trauma — fear threshold unchanged.
|
|
193
|
+
* q(0.50) → maximum trauma — entity triggers fear at half normal threshold.
|
|
194
|
+
*
|
|
195
|
+
* Usage (combat / morale layer):
|
|
196
|
+
* `effectiveFearThreshold_Q = Math.round(baseFearThreshold_Q × mul / SCALE.Q)`
|
|
197
|
+
*/
|
|
198
|
+
export function deriveFearThresholdMul(entity) {
|
|
199
|
+
if (!entity.traumaState || entity.traumaState.severity_Q <= 0) {
|
|
200
|
+
return SCALE.Q;
|
|
201
|
+
}
|
|
202
|
+
// severity q(1.0) → reduction q(0.50); linear interpolation
|
|
203
|
+
const reduction = Math.round(entity.traumaState.severity_Q * (SCALE.Q - TRAUMA_FEAR_MUL_FLOOR) / SCALE.Q);
|
|
204
|
+
return clampQ((SCALE.Q - reduction), TRAUMA_FEAR_MUL_FLOOR, SCALE.Q);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Compute aggregate sepsis risk (Q 0..SCALE.Q) from all infected regions.
|
|
208
|
+
*
|
|
209
|
+
* Risk increases with both the number of infected regions and their
|
|
210
|
+
* internal damage level. Returns q(0) if no infected regions.
|
|
211
|
+
*
|
|
212
|
+
* Usage: AI / medical layer reads this to prioritise treatment.
|
|
213
|
+
*/
|
|
214
|
+
export function deriveSepsisRisk(entity) {
|
|
215
|
+
let totalRisk = 0;
|
|
216
|
+
for (const reg of Object.values(entity.injury.byRegion)) {
|
|
217
|
+
if (reg.infectedTick !== -1) {
|
|
218
|
+
// Risk contribution: internalDamage fraction of the infected region
|
|
219
|
+
totalRisk += reg.internalDamage;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return clampQ(totalRisk, q(0), SCALE.Q);
|
|
223
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 31 — Species & Race System
|
|
3
|
+
*
|
|
4
|
+
* Data-driven species definitions that compose Archetype + BodyPlan + innate traits
|
|
5
|
+
* + capabilities + physiological overrides into a single declarative record.
|
|
6
|
+
*
|
|
7
|
+
* Covers:
|
|
8
|
+
* Fantasy humanoids — elf, dwarf, halfling, orc, ogre, goblin, troll (7)
|
|
9
|
+
* Sci-fi humanoids — Vulcan, Klingon, Romulan (3)
|
|
10
|
+
* Mythological — dragon, centaur, satyr (3)
|
|
11
|
+
* Fictional — Heechee (1)
|
|
12
|
+
*/
|
|
13
|
+
import type { Archetype } from "./archetypes.js";
|
|
14
|
+
import type { IndividualAttributes } from "./types.js";
|
|
15
|
+
import type { BodyPlan } from "./sim/bodyplan.js";
|
|
16
|
+
import type { TraitId } from "./traits.js";
|
|
17
|
+
import type { CapabilitySource } from "./sim/capability.js";
|
|
18
|
+
import type { Weapon } from "./equipment.js";
|
|
19
|
+
import type { SkillMap } from "./sim/skills.js";
|
|
20
|
+
import { type Q } from "./units.js";
|
|
21
|
+
/** Runtime metabolic overrides attached to Entity.physiology (Phase 31). */
|
|
22
|
+
export interface SpeciesPhysiology {
|
|
23
|
+
/** True = ectotherm; stepCoreTemp is skipped entirely in the kernel. */
|
|
24
|
+
coldBlooded?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Multiply the computed BMR before caloric drain.
|
|
27
|
+
* q(1.0) = normal; q(0.70) = slower starvation (e.g. meditative Vulcan);
|
|
28
|
+
* q(1.20) = high metabolism (e.g. aggressive Klingon).
|
|
29
|
+
*/
|
|
30
|
+
bmrMultiplier?: Q;
|
|
31
|
+
/**
|
|
32
|
+
* Natural fur / scales / blubber insulation added to the armour insulation sum.
|
|
33
|
+
* Same unit as Armour.insulation_m2KW [m²K/W].
|
|
34
|
+
* Troll hide ≈ 0.08, Dragon scales ≈ 0.05.
|
|
35
|
+
*/
|
|
36
|
+
naturalInsulation_m2KW?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Everything `generateSpeciesIndividual` returns — the caller uses this to
|
|
40
|
+
* assemble a full Entity (set attributes, physiology, bodyPlan, apply traits,
|
|
41
|
+
* attach capabilities, add natural weapons to loadout).
|
|
42
|
+
*/
|
|
43
|
+
export interface SpeciesEntitySpec {
|
|
44
|
+
attributes: IndividualAttributes;
|
|
45
|
+
physiology?: SpeciesPhysiology;
|
|
46
|
+
bodyPlan?: BodyPlan;
|
|
47
|
+
innateTraits: TraitId[];
|
|
48
|
+
innateCapabilities: CapabilitySource[];
|
|
49
|
+
naturalWeapons: Weapon[];
|
|
50
|
+
skillAptitudes?: SkillMap;
|
|
51
|
+
}
|
|
52
|
+
/** Declarative species record. */
|
|
53
|
+
export interface SpeciesDefinition {
|
|
54
|
+
id: string;
|
|
55
|
+
name: string;
|
|
56
|
+
description: string;
|
|
57
|
+
archetype: Archetype;
|
|
58
|
+
bodyPlan?: BodyPlan;
|
|
59
|
+
innateTraits?: TraitId[];
|
|
60
|
+
innateCapabilities?: CapabilitySource[];
|
|
61
|
+
naturalWeapons?: Weapon[];
|
|
62
|
+
physiology?: SpeciesPhysiology;
|
|
63
|
+
skillAptitudes?: SkillMap;
|
|
64
|
+
lore?: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Generate an individual from a species definition using a deterministic seed.
|
|
68
|
+
* Applies innate traits via `applyTraitsToAttributes` (which deep-copies attrs).
|
|
69
|
+
*/
|
|
70
|
+
export declare function generateSpeciesIndividual(species: SpeciesDefinition, seed: number): SpeciesEntitySpec;
|
|
71
|
+
/** Elf — graceful, keen-sensed, sylvan endurance. */
|
|
72
|
+
export declare const ELF_SPECIES: SpeciesDefinition;
|
|
73
|
+
/** Dwarf — stocky, dense-boned, underground-adapted. */
|
|
74
|
+
export declare const DWARF_SPECIES: SpeciesDefinition;
|
|
75
|
+
/** Halfling — small, nimble, with surprising resilience. */
|
|
76
|
+
export declare const HALFLING_SPECIES: SpeciesDefinition;
|
|
77
|
+
/** Orc — powerful, pain-ignorant, high metabolic rate. */
|
|
78
|
+
export declare const ORC_SPECIES: SpeciesDefinition;
|
|
79
|
+
/** Ogre — massive, brutish, very slow decision-making. */
|
|
80
|
+
export declare const OGRE_SPECIES: SpeciesDefinition;
|
|
81
|
+
/** Goblin — small, cowardly, extremely fast reactions. */
|
|
82
|
+
export declare const GOBLIN_SPECIES: SpeciesDefinition;
|
|
83
|
+
/** Troll — massive regenerator, devastatingly vulnerable to fire. */
|
|
84
|
+
export declare const TROLL_SPECIES: SpeciesDefinition;
|
|
85
|
+
/** Vulcan — disciplined, strong, meditative metabolism; very low individual variance. */
|
|
86
|
+
export declare const VULCAN_SPECIES: SpeciesDefinition;
|
|
87
|
+
/** Klingon — aggressive warrior with redundant organs and thick cranial ridges. */
|
|
88
|
+
export declare const KLINGON_SPECIES: SpeciesDefinition;
|
|
89
|
+
/** Romulan — disciplined but more emotionally variable than Vulcans. */
|
|
90
|
+
export declare const ROMULAN_SPECIES: SpeciesDefinition;
|
|
91
|
+
/** Dragon — immense fire-breathing reptilian with scales and flight capability. */
|
|
92
|
+
export declare const DRAGON_SPECIES: SpeciesDefinition;
|
|
93
|
+
/** Centaur — horse body with human torso; CENTAUR_PLAN anatomy. */
|
|
94
|
+
export declare const CENTAUR_SPECIES: SpeciesDefinition;
|
|
95
|
+
/** Satyr — goat-human hybrid with natural horn and extraordinary balance. */
|
|
96
|
+
export declare const SATYR_SPECIES: SpeciesDefinition;
|
|
97
|
+
/**
|
|
98
|
+
* Heechee — Fred Pohl's Gateway aliens.
|
|
99
|
+
* Thin, soft-bodied, technologically advanced; fragile but extraordinarily precise.
|
|
100
|
+
*/
|
|
101
|
+
export declare const HEECHEE_SPECIES: SpeciesDefinition;
|
|
102
|
+
export declare const FANTASY_HUMANOID_SPECIES: readonly SpeciesDefinition[];
|
|
103
|
+
export declare const SCIFI_HUMANOID_SPECIES: readonly SpeciesDefinition[];
|
|
104
|
+
export declare const MYTHOLOGICAL_SPECIES: readonly SpeciesDefinition[];
|
|
105
|
+
export declare const FICTIONAL_SPECIES: readonly SpeciesDefinition[];
|
|
106
|
+
export declare const ALL_SPECIES: readonly SpeciesDefinition[];
|