@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,109 @@
|
|
|
1
|
+
import { type Q } from "../units.js";
|
|
2
|
+
import type { Entity } from "./entity.js";
|
|
3
|
+
export type ToxinCategory = "alcohol" | "sedative" | "alkaloid" | "heavy_metal" | "radiation";
|
|
4
|
+
export interface IngestedToxinProfile {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
category: ToxinCategory;
|
|
8
|
+
/** Seconds from ingestion to first symptomatic effect. */
|
|
9
|
+
onsetDelay_s: number;
|
|
10
|
+
/**
|
|
11
|
+
* Metabolic half-life [seconds].
|
|
12
|
+
* Per-second decay fraction ≈ ln(2) / halfLife_s.
|
|
13
|
+
* Effective decayQ = max(1, round(6931 / halfLife_s)) [Q units/s from SCALE.Q].
|
|
14
|
+
*/
|
|
15
|
+
halfLife_s: number;
|
|
16
|
+
/** Per-second internal damage to torso (Q) while symptomatic. */
|
|
17
|
+
damageRate_Q?: Q;
|
|
18
|
+
/**
|
|
19
|
+
* Motor impairment at full concentration.
|
|
20
|
+
* Applied as per-second fatigue increase proportional to (SCALE.Q − motorMul_Q) × concentration.
|
|
21
|
+
* q(0.60) → 40% impairment; q(1.0) → no impairment.
|
|
22
|
+
*/
|
|
23
|
+
motorMul_Q?: Q;
|
|
24
|
+
/**
|
|
25
|
+
* Cognitive impairment at full concentration.
|
|
26
|
+
* Applied as per-second consciousness erosion.
|
|
27
|
+
* q(0.50) → severe impairment; q(1.0) → no impairment.
|
|
28
|
+
*/
|
|
29
|
+
cognitiveMul_Q?: Q;
|
|
30
|
+
/**
|
|
31
|
+
* Signed per-second fear delta [Q units] while symptomatic.
|
|
32
|
+
* Positive → fear increase (alkaloid panic); negative → fear decrease (alcohol disinhibition).
|
|
33
|
+
*/
|
|
34
|
+
fearMod_perS?: number;
|
|
35
|
+
/** True = this toxin accumulates irreversibly in tissue (heavy metals, radiation). */
|
|
36
|
+
cumulative?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Per-second irreversible dose accumulation while symptomatic [Q/s].
|
|
39
|
+
* Adds to CumulativeExposureRecord.totalDose_Q at rate: conc_Q × irreversibleRate_Q / SCALE.Q / s.
|
|
40
|
+
*/
|
|
41
|
+
irreversibleRate_Q?: Q;
|
|
42
|
+
/** True = sustained use triggers a withdrawal state when concentration clears. */
|
|
43
|
+
addictive?: boolean;
|
|
44
|
+
/** Duration of withdrawal period [seconds] (default: halfLife_s × 2). */
|
|
45
|
+
withdrawalDuration_s?: number;
|
|
46
|
+
}
|
|
47
|
+
export interface ActiveIngestedToxin {
|
|
48
|
+
profile: IngestedToxinProfile;
|
|
49
|
+
/** Total seconds elapsed since ingestion. */
|
|
50
|
+
elapsedSeconds: number;
|
|
51
|
+
/**
|
|
52
|
+
* Current systemic concentration (Q 0..SCALE.Q).
|
|
53
|
+
* Initialized to SCALE.Q on ingestion; decays exponentially via halfLife_s.
|
|
54
|
+
*/
|
|
55
|
+
concentration_Q: Q;
|
|
56
|
+
/**
|
|
57
|
+
* Seconds spent in the symptomatic phase (elapsed ≥ onsetDelay_s).
|
|
58
|
+
* Drives withdrawal eligibility for addictive toxins.
|
|
59
|
+
*/
|
|
60
|
+
sustainedSeconds: number;
|
|
61
|
+
}
|
|
62
|
+
/** Irreversible lifetime dose accumulation for heavy metals and radiation. */
|
|
63
|
+
export interface CumulativeExposureRecord {
|
|
64
|
+
toxinId: string;
|
|
65
|
+
/** Accumulated irreversible dose (Q 0..SCALE.Q). Increases with each symptomatic exposure. */
|
|
66
|
+
totalDose_Q: Q;
|
|
67
|
+
}
|
|
68
|
+
/** Active withdrawal state — temporary penalty period after an addictive toxin clears. */
|
|
69
|
+
export interface WithdrawalState {
|
|
70
|
+
toxinId: string;
|
|
71
|
+
elapsedSeconds: number;
|
|
72
|
+
duration_s: number;
|
|
73
|
+
/** Q 0..SCALE.Q — scales all withdrawal symptom magnitudes. Higher = worse symptoms. */
|
|
74
|
+
severity_Q: Q;
|
|
75
|
+
}
|
|
76
|
+
export declare const INGESTED_TOXIN_PROFILES: readonly IngestedToxinProfile[];
|
|
77
|
+
/** Look up an IngestedToxinProfile by id. Returns undefined if unknown. */
|
|
78
|
+
export declare function getIngestedToxinProfile(id: string): IngestedToxinProfile | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* Add an ingested toxin to an entity.
|
|
81
|
+
*
|
|
82
|
+
* Initializes concentration at SCALE.Q (full dose). Multiple ingestions of the same
|
|
83
|
+
* toxin create separate entries — this models repeated ingestion (two cups of alcohol, etc.).
|
|
84
|
+
*
|
|
85
|
+
* @returns true if the toxin id is known; false otherwise.
|
|
86
|
+
*/
|
|
87
|
+
export declare function ingestToxin(entity: Entity, toxinId: string): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Advance all active ingested toxins on an entity by `delta_s` seconds.
|
|
90
|
+
*
|
|
91
|
+
* Mutates:
|
|
92
|
+
* entity.activeIngestedToxins (concentration decayed; expired entries removed)
|
|
93
|
+
* entity.cumulativeExposure (irreversible dose records updated for cumulative toxins)
|
|
94
|
+
* entity.withdrawal (new withdrawal states added; existing states advanced and expired)
|
|
95
|
+
* entity.energy.fatigue (motor-impairing toxins and withdrawal)
|
|
96
|
+
* entity.injury.consciousness (cognitive-impairing toxins)
|
|
97
|
+
* entity.injury (torso internalDamage or shock for toxins with damageRate_Q)
|
|
98
|
+
* entity.condition.fearQ (fearMod_perS and withdrawal)
|
|
99
|
+
*/
|
|
100
|
+
export declare function stepIngestedToxicology(entity: Entity, delta_s: number): void;
|
|
101
|
+
/**
|
|
102
|
+
* Cumulative toxicity score (Q 0..SCALE.Q) from all irreversible dose records.
|
|
103
|
+
*
|
|
104
|
+
* Sums totalDose_Q across all CumulativeExposureRecord entries and clamps to SCALE.Q.
|
|
105
|
+
* Returns q(0) if no records exist.
|
|
106
|
+
*
|
|
107
|
+
* Usage: AI layer reads this to modulate attribute checks for chronically poisoned entities.
|
|
108
|
+
*/
|
|
109
|
+
export declare function deriveCumulativeToxicity(entity: Entity): Q;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// src/sim/systemic-toxicology.ts — Phase 53: Systemic Toxicology (Ingested / Cumulative)
|
|
2
|
+
//
|
|
3
|
+
// Extends Phase 32C (injection venom) and Phase 10 (pharmacokinetics) with:
|
|
4
|
+
// - Ingested toxins with long onset times (minutes) and metabolic half-lives (hours)
|
|
5
|
+
// - Cumulative exposure: heavy metals, radiation — irreversible dose accumulation
|
|
6
|
+
// - Withdrawal states after sustained addictive toxin use
|
|
7
|
+
//
|
|
8
|
+
// Follows the 1 Hz accumulator pattern; called from the kernel's __nutritionAccum gate.
|
|
9
|
+
//
|
|
10
|
+
// Data flow:
|
|
11
|
+
// ingestToxin(entity, id) → entity.activeIngestedToxins[]
|
|
12
|
+
// stepIngestedToxicology(entity, delta_s) → mutates condition/injury/energy; manages withdrawal
|
|
13
|
+
// deriveCumulativeToxicity(entity) → Q summary for AI / combat layer queries
|
|
14
|
+
import { q, clampQ, SCALE } from "../units.js";
|
|
15
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
16
|
+
/** round(ln(2) × SCALE.Q) — numerator for per-second decay fraction. */
|
|
17
|
+
const LN2_Q = 6931;
|
|
18
|
+
/** Minimum sustained seconds before withdrawal becomes possible. */
|
|
19
|
+
const WITHDRAWAL_MIN_SUSTAINED_s = 120;
|
|
20
|
+
/**
|
|
21
|
+
* Fatigue increase per unit of motor impairment × concentration per second.
|
|
22
|
+
* Formula: fatigueInc = (SCALE.Q − motorMul_Q) × conc_Q / SCALE.Q × MOTOR_IMPAIRMENT_RATE / SCALE.Q
|
|
23
|
+
* At full impairment (motorMul = q(0)) and full conc (q(1.0)): adds MOTOR_IMPAIRMENT_RATE Q/s.
|
|
24
|
+
*/
|
|
25
|
+
const MOTOR_IMPAIRMENT_RATE = 50; // q(0.005)/s at maximum motor impairment + full concentration
|
|
26
|
+
/** Consciousness erosion per unit of cognitive impairment × concentration per second. */
|
|
27
|
+
const COGNITIVE_IMPAIRMENT_RATE = 30; // q(0.003)/s at maximum impairment + full concentration
|
|
28
|
+
/** Fatigue added per Q of withdrawal severity per second. */
|
|
29
|
+
const WITHDRAWAL_FATIGUE_RATE = 8; // q(0.0008)/s at severity q(1.0)
|
|
30
|
+
/** Fear added per Q of withdrawal severity per second. */
|
|
31
|
+
const WITHDRAWAL_FEAR_RATE = 5; // q(0.0005)/s at severity q(1.0)
|
|
32
|
+
// ── Toxin catalogue ───────────────────────────────────────────────────────────
|
|
33
|
+
export const INGESTED_TOXIN_PROFILES = [
|
|
34
|
+
{
|
|
35
|
+
id: "alcohol",
|
|
36
|
+
name: "Alcohol (ethanol)",
|
|
37
|
+
category: "alcohol",
|
|
38
|
+
onsetDelay_s: 900, // 15 min to first motor effect
|
|
39
|
+
halfLife_s: 3_600, // 1 h hepatic clearance (decayQ ≈ 2/s)
|
|
40
|
+
motorMul_Q: q(0.60), // 40% motor impairment at peak
|
|
41
|
+
fearMod_perS: -3, // slight disinhibition: −3 Q/s (≈ −q(0.0003)/s)
|
|
42
|
+
addictive: true,
|
|
43
|
+
withdrawalDuration_s: 7_200, // 2 h withdrawal
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "sedative_plant",
|
|
47
|
+
name: "Sedative alkaloid (valerian, poppy)",
|
|
48
|
+
category: "sedative",
|
|
49
|
+
onsetDelay_s: 1_800, // 30 min
|
|
50
|
+
halfLife_s: 7_200, // 2 h clearance (decayQ ≈ 1/s)
|
|
51
|
+
cognitiveMul_Q: q(0.50), // severe consciousness erosion
|
|
52
|
+
addictive: true,
|
|
53
|
+
withdrawalDuration_s: 14_400, // 4 h withdrawal
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "alkaloid_poison",
|
|
57
|
+
name: "Plant alkaloid toxin (nightshade, hemlock)",
|
|
58
|
+
category: "alkaloid",
|
|
59
|
+
onsetDelay_s: 1_200, // 20 min
|
|
60
|
+
halfLife_s: 1_800, // 30 min — clears relatively quickly
|
|
61
|
+
damageRate_Q: q(0.004), // internal damage per second
|
|
62
|
+
fearMod_perS: 8, // panic response: +8 Q/s (≈ +q(0.0008)/s)
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "heavy_lead",
|
|
66
|
+
name: "Lead (plumbum) — chronic poisoning",
|
|
67
|
+
category: "heavy_metal",
|
|
68
|
+
onsetDelay_s: 3_600, // 1 h onset (slow GI absorption)
|
|
69
|
+
halfLife_s: 86_400, // nominal 24 h (fixed-point effective: decayQ = max(1, round(0.08)) = 1)
|
|
70
|
+
cognitiveMul_Q: q(0.70), // neurological impairment
|
|
71
|
+
damageRate_Q: q(0.001), // slow systemic damage
|
|
72
|
+
cumulative: true,
|
|
73
|
+
irreversibleRate_Q: q(0.005), // 50 Q/s accumulation at full concentration
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "radiation_dose",
|
|
77
|
+
name: "Ionising radiation dose (acute)",
|
|
78
|
+
category: "radiation",
|
|
79
|
+
onsetDelay_s: 7_200, // 2 h (ARS latent period before symptoms manifest)
|
|
80
|
+
halfLife_s: 604_800, // nominal 7 days (effective decayQ = 1)
|
|
81
|
+
damageRate_Q: q(0.002),
|
|
82
|
+
motorMul_Q: q(0.80), // bone marrow suppression → fatigue
|
|
83
|
+
cumulative: true,
|
|
84
|
+
irreversibleRate_Q: q(0.001), // 10 Q/s — DNA damage accumulates slower than lead
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
const TOXIN_BY_ID = new Map(INGESTED_TOXIN_PROFILES.map(t => [t.id, t]));
|
|
88
|
+
/** Look up an IngestedToxinProfile by id. Returns undefined if unknown. */
|
|
89
|
+
export function getIngestedToxinProfile(id) {
|
|
90
|
+
return TOXIN_BY_ID.get(id);
|
|
91
|
+
}
|
|
92
|
+
// ── Public API ────────────────────────────────────────────────────────────────
|
|
93
|
+
/**
|
|
94
|
+
* Add an ingested toxin to an entity.
|
|
95
|
+
*
|
|
96
|
+
* Initializes concentration at SCALE.Q (full dose). Multiple ingestions of the same
|
|
97
|
+
* toxin create separate entries — this models repeated ingestion (two cups of alcohol, etc.).
|
|
98
|
+
*
|
|
99
|
+
* @returns true if the toxin id is known; false otherwise.
|
|
100
|
+
*/
|
|
101
|
+
export function ingestToxin(entity, toxinId) {
|
|
102
|
+
const profile = TOXIN_BY_ID.get(toxinId);
|
|
103
|
+
if (!profile)
|
|
104
|
+
return false;
|
|
105
|
+
if (!entity.activeIngestedToxins)
|
|
106
|
+
entity.activeIngestedToxins = [];
|
|
107
|
+
entity.activeIngestedToxins.push({
|
|
108
|
+
profile,
|
|
109
|
+
elapsedSeconds: 0,
|
|
110
|
+
concentration_Q: SCALE.Q,
|
|
111
|
+
sustainedSeconds: 0,
|
|
112
|
+
});
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Advance all active ingested toxins on an entity by `delta_s` seconds.
|
|
117
|
+
*
|
|
118
|
+
* Mutates:
|
|
119
|
+
* entity.activeIngestedToxins (concentration decayed; expired entries removed)
|
|
120
|
+
* entity.cumulativeExposure (irreversible dose records updated for cumulative toxins)
|
|
121
|
+
* entity.withdrawal (new withdrawal states added; existing states advanced and expired)
|
|
122
|
+
* entity.energy.fatigue (motor-impairing toxins and withdrawal)
|
|
123
|
+
* entity.injury.consciousness (cognitive-impairing toxins)
|
|
124
|
+
* entity.injury (torso internalDamage or shock for toxins with damageRate_Q)
|
|
125
|
+
* entity.condition.fearQ (fearMod_perS and withdrawal)
|
|
126
|
+
*/
|
|
127
|
+
export function stepIngestedToxicology(entity, delta_s) {
|
|
128
|
+
const delta_int = Math.round(delta_s); // integer seconds for accumulation arithmetic
|
|
129
|
+
_stepActiveToxins(entity, delta_s, delta_int);
|
|
130
|
+
_stepWithdrawal(entity, delta_int);
|
|
131
|
+
}
|
|
132
|
+
function _stepActiveToxins(entity, delta_s, delta_int) {
|
|
133
|
+
const toxins = entity.activeIngestedToxins;
|
|
134
|
+
if (!toxins || toxins.length === 0)
|
|
135
|
+
return;
|
|
136
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
137
|
+
const cond = entity.condition;
|
|
138
|
+
const torsoRegion = entity.injury.byRegion?.["torso"];
|
|
139
|
+
const toRemove = [];
|
|
140
|
+
for (let i = 0; i < toxins.length; i++) {
|
|
141
|
+
const at = toxins[i];
|
|
142
|
+
at.elapsedSeconds += delta_s;
|
|
143
|
+
// Decay concentration: decayQ = max(1, round(ln(2) × SCALE.Q / halfLife_s))
|
|
144
|
+
// Minimum decrement of 1 per delta_int seconds ensures toxin always eventually clears.
|
|
145
|
+
// (Without this, integer truncation freezes decay for very small concentrations.)
|
|
146
|
+
const decayQ = Math.max(1, Math.round(LN2_Q / at.profile.halfLife_s));
|
|
147
|
+
const rawDecrement = Math.trunc(at.concentration_Q * decayQ / SCALE.Q);
|
|
148
|
+
const decrement = at.concentration_Q > 0 ? Math.max(delta_int, rawDecrement * delta_int) : 0;
|
|
149
|
+
at.concentration_Q = clampQ((at.concentration_Q - decrement), q(0), SCALE.Q);
|
|
150
|
+
if (at.elapsedSeconds < at.profile.onsetDelay_s)
|
|
151
|
+
continue; // pre-onset: no effect yet
|
|
152
|
+
// Symptomatic — apply effects
|
|
153
|
+
at.sustainedSeconds += delta_s;
|
|
154
|
+
const conc = at.concentration_Q;
|
|
155
|
+
const p = at.profile;
|
|
156
|
+
// Internal damage (alkaloids, heavy metals, radiation)
|
|
157
|
+
if (p.damageRate_Q) {
|
|
158
|
+
const dmgInc = Math.trunc(p.damageRate_Q * delta_int);
|
|
159
|
+
if (torsoRegion !== undefined) {
|
|
160
|
+
torsoRegion.internalDamage = clampQ((torsoRegion.internalDamage + dmgInc), q(0), q(1.0));
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
entity.injury.shock = clampQ((entity.injury.shock + dmgInc), q(0), SCALE.Q);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Motor impairment → fatigue accumulation
|
|
167
|
+
if (p.motorMul_Q !== undefined) {
|
|
168
|
+
const impairFrac = Math.trunc((SCALE.Q - p.motorMul_Q) * conc / SCALE.Q);
|
|
169
|
+
const fatigueInc = Math.trunc(impairFrac * MOTOR_IMPAIRMENT_RATE / SCALE.Q) * delta_int;
|
|
170
|
+
entity.energy.fatigue = clampQ((entity.energy.fatigue + fatigueInc), q(0), SCALE.Q);
|
|
171
|
+
}
|
|
172
|
+
// Cognitive impairment → consciousness erosion
|
|
173
|
+
if (p.cognitiveMul_Q !== undefined) {
|
|
174
|
+
const impairFrac = Math.trunc((SCALE.Q - p.cognitiveMul_Q) * conc / SCALE.Q);
|
|
175
|
+
const consciousLoss = Math.trunc(impairFrac * COGNITIVE_IMPAIRMENT_RATE / SCALE.Q) * delta_int;
|
|
176
|
+
entity.injury.consciousness = clampQ((entity.injury.consciousness - consciousLoss), q(0), SCALE.Q);
|
|
177
|
+
}
|
|
178
|
+
// Fear modifier (signed: negative for disinhibition, positive for panic)
|
|
179
|
+
if (p.fearMod_perS !== undefined && p.fearMod_perS !== 0) {
|
|
180
|
+
const fearDelta = Math.trunc(p.fearMod_perS * delta_int);
|
|
181
|
+
cond.fearQ = clampQ((cond.fearQ + fearDelta), q(0), SCALE.Q);
|
|
182
|
+
}
|
|
183
|
+
// Cumulative irreversible dose accumulation
|
|
184
|
+
if (p.cumulative && p.irreversibleRate_Q) {
|
|
185
|
+
const irrInc = Math.trunc(conc * p.irreversibleRate_Q / SCALE.Q) * delta_int;
|
|
186
|
+
if (irrInc > 0) {
|
|
187
|
+
_accumulateDose(entity, p.id, irrInc);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Schedule for removal if concentration is negligible
|
|
191
|
+
if (at.concentration_Q < 1) {
|
|
192
|
+
// Trigger withdrawal if applicable
|
|
193
|
+
if (p.addictive && at.sustainedSeconds >= WITHDRAWAL_MIN_SUSTAINED_s) {
|
|
194
|
+
_triggerWithdrawal(entity, p, at.sustainedSeconds);
|
|
195
|
+
}
|
|
196
|
+
toRemove.push(i);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Remove expired entries in reverse order to preserve indices
|
|
200
|
+
for (let k = toRemove.length - 1; k >= 0; k--) {
|
|
201
|
+
toxins.splice(toRemove[k], 1);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function _accumulateDose(entity, toxinId, amount) {
|
|
205
|
+
if (!entity.cumulativeExposure)
|
|
206
|
+
entity.cumulativeExposure = [];
|
|
207
|
+
let rec = entity.cumulativeExposure.find(r => r.toxinId === toxinId);
|
|
208
|
+
if (!rec) {
|
|
209
|
+
rec = { toxinId, totalDose_Q: q(0) };
|
|
210
|
+
entity.cumulativeExposure.push(rec);
|
|
211
|
+
}
|
|
212
|
+
rec.totalDose_Q = clampQ((rec.totalDose_Q + amount), q(0), SCALE.Q);
|
|
213
|
+
}
|
|
214
|
+
function _triggerWithdrawal(entity, profile, sustainedSeconds) {
|
|
215
|
+
const duration_s = profile.withdrawalDuration_s ?? profile.halfLife_s * 2;
|
|
216
|
+
// Severity: capped to SCALE.Q; scales with how long entity was symptomatic
|
|
217
|
+
const severity_Q = clampQ(Math.trunc(sustainedSeconds * SCALE.Q / duration_s), q(0.10), // minimum severity when withdrawal triggers
|
|
218
|
+
SCALE.Q);
|
|
219
|
+
if (!entity.withdrawal)
|
|
220
|
+
entity.withdrawal = [];
|
|
221
|
+
entity.withdrawal.push({
|
|
222
|
+
toxinId: profile.id,
|
|
223
|
+
elapsedSeconds: 0,
|
|
224
|
+
duration_s,
|
|
225
|
+
severity_Q,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function _stepWithdrawal(entity, delta_int) {
|
|
229
|
+
const states = entity.withdrawal;
|
|
230
|
+
if (!states || states.length === 0)
|
|
231
|
+
return;
|
|
232
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
233
|
+
const cond = entity.condition;
|
|
234
|
+
for (const ws of states) {
|
|
235
|
+
ws.elapsedSeconds += delta_int;
|
|
236
|
+
// Apply withdrawal symptoms scaled by severity
|
|
237
|
+
const fatiguePenalty = Math.trunc(ws.severity_Q * WITHDRAWAL_FATIGUE_RATE / SCALE.Q) * delta_int;
|
|
238
|
+
entity.energy.fatigue = clampQ((entity.energy.fatigue + fatiguePenalty), q(0), SCALE.Q);
|
|
239
|
+
const fearPenalty = Math.trunc(ws.severity_Q * WITHDRAWAL_FEAR_RATE / SCALE.Q) * delta_int;
|
|
240
|
+
cond.fearQ = clampQ((cond.fearQ + fearPenalty), q(0), SCALE.Q);
|
|
241
|
+
}
|
|
242
|
+
// Remove expired withdrawal states
|
|
243
|
+
entity.withdrawal = states.filter(ws => ws.elapsedSeconds < ws.duration_s);
|
|
244
|
+
}
|
|
245
|
+
// ── Query functions ───────────────────────────────────────────────────────────
|
|
246
|
+
/**
|
|
247
|
+
* Cumulative toxicity score (Q 0..SCALE.Q) from all irreversible dose records.
|
|
248
|
+
*
|
|
249
|
+
* Sums totalDose_Q across all CumulativeExposureRecord entries and clamps to SCALE.Q.
|
|
250
|
+
* Returns q(0) if no records exist.
|
|
251
|
+
*
|
|
252
|
+
* Usage: AI layer reads this to modulate attribute checks for chronically poisoned entities.
|
|
253
|
+
*/
|
|
254
|
+
export function deriveCumulativeToxicity(entity) {
|
|
255
|
+
if (!entity.cumulativeExposure || entity.cumulativeExposure.length === 0) {
|
|
256
|
+
return q(0);
|
|
257
|
+
}
|
|
258
|
+
let total = 0;
|
|
259
|
+
for (const rec of entity.cumulativeExposure) {
|
|
260
|
+
total += rec.totalDose_Q;
|
|
261
|
+
}
|
|
262
|
+
return clampQ(total, q(0), SCALE.Q);
|
|
263
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Entity } from "./entity.js";
|
|
2
|
+
import type { WorldState } from "./world.js";
|
|
3
|
+
export declare function isEnemy(a: Entity, b: Entity): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Phase 48: Determine if two entities are hostile, considering team, party, and faction relationships.
|
|
6
|
+
* Returns true if they should treat each other as enemies.
|
|
7
|
+
* Self-defence override: injured entities (shock > 0 or fluidLoss > 0) ignore friendly faction/party thresholds.
|
|
8
|
+
*/
|
|
9
|
+
export declare function areEntitiesHostile(a: Entity, b: Entity, world: WorldState): boolean;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { effectiveStanding, STANDING_HOSTILE_THRESHOLD, STANDING_FRIENDLY_THRESHOLD } from "../faction.js";
|
|
2
|
+
import { areEntitiesHostileByParty, areEntitiesFriendlyByParty } from "../party.js";
|
|
3
|
+
export function isEnemy(a, b) {
|
|
4
|
+
return a.teamId !== b.teamId;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Phase 48: Determine if two entities are hostile, considering team, party, and faction relationships.
|
|
8
|
+
* Returns true if they should treat each other as enemies.
|
|
9
|
+
* Self-defence override: injured entities (shock > 0 or fluidLoss > 0) ignore friendly faction/party thresholds.
|
|
10
|
+
*/
|
|
11
|
+
export function areEntitiesHostile(a, b, world) {
|
|
12
|
+
// same team → never hostile
|
|
13
|
+
if (a.teamId === b.teamId)
|
|
14
|
+
return false;
|
|
15
|
+
const isInjured = a.injury.shock > 0 || a.injury.fluidLoss > 0;
|
|
16
|
+
// party check
|
|
17
|
+
const partyRegistry = world.__partyRegistry;
|
|
18
|
+
if (partyRegistry) {
|
|
19
|
+
// if parties are friendly, not hostile (unless injured)
|
|
20
|
+
if (!isInjured && areEntitiesFriendlyByParty(partyRegistry, a, b))
|
|
21
|
+
return false;
|
|
22
|
+
// if parties are hostile, hostile
|
|
23
|
+
if (areEntitiesHostileByParty(partyRegistry, a, b))
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
// faction check
|
|
27
|
+
const factionRegistry = world.__factionRegistry;
|
|
28
|
+
if (factionRegistry && a.faction && b.faction) {
|
|
29
|
+
const standing = effectiveStanding(factionRegistry, a, b);
|
|
30
|
+
if (!isInjured && standing >= STANDING_FRIENDLY_THRESHOLD)
|
|
31
|
+
return false;
|
|
32
|
+
if (standing < STANDING_HOSTILE_THRESHOLD)
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
// default: different team → hostile
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 11 — Technology Spectrum
|
|
3
|
+
*
|
|
4
|
+
* TechContext gates which items and capabilities are available in a given scenario.
|
|
5
|
+
* Items declare their requirements via `requiredCapabilities`; validateLoadout()
|
|
6
|
+
* checks them against the active TechContext.
|
|
7
|
+
*
|
|
8
|
+
* Design: era provides a sensible default capability set, but any capability
|
|
9
|
+
* can be added or removed for bespoke scenarios (time-travel, magitech, etc.).
|
|
10
|
+
*/
|
|
11
|
+
/** Numeric era codes; higher = more advanced. */
|
|
12
|
+
export declare const TechEra: {
|
|
13
|
+
readonly Prehistoric: 0;
|
|
14
|
+
readonly Ancient: 1;
|
|
15
|
+
readonly Medieval: 2;
|
|
16
|
+
readonly EarlyModern: 3;
|
|
17
|
+
readonly Industrial: 4;
|
|
18
|
+
readonly Modern: 5;
|
|
19
|
+
readonly NearFuture: 6;
|
|
20
|
+
readonly FarFuture: 7;
|
|
21
|
+
readonly DeepSpace: 8;
|
|
22
|
+
};
|
|
23
|
+
export type TechEra = typeof TechEra[keyof typeof TechEra];
|
|
24
|
+
/**
|
|
25
|
+
* Discrete capabilities that items may require.
|
|
26
|
+
* A scenario can have any combination regardless of era.
|
|
27
|
+
*/
|
|
28
|
+
export type TechCapability = "MetallicArmour" | "FirearmsPropellant" | "ExplosiveMunitions" | "BallisticArmour" | "PoweredExoskeleton" | "EnergyWeapons" | "ReactivePlating" | "NanomedicalRepair" | "ArcaneMagic" | "DivineMagic" | "Psionics" | "Nanotech";
|
|
29
|
+
export interface TechContext {
|
|
30
|
+
era: TechEra;
|
|
31
|
+
available: Set<TechCapability>;
|
|
32
|
+
}
|
|
33
|
+
/** Build a TechContext from a named era with its default capability set. */
|
|
34
|
+
export declare function defaultTechContext(era: TechEra): TechContext;
|
|
35
|
+
/** Return true if the given capability is available in this context. */
|
|
36
|
+
export declare function isCapabilityAvailable(ctx: TechContext, cap: TechCapability): boolean;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 11 — Technology Spectrum
|
|
3
|
+
*
|
|
4
|
+
* TechContext gates which items and capabilities are available in a given scenario.
|
|
5
|
+
* Items declare their requirements via `requiredCapabilities`; validateLoadout()
|
|
6
|
+
* checks them against the active TechContext.
|
|
7
|
+
*
|
|
8
|
+
* Design: era provides a sensible default capability set, but any capability
|
|
9
|
+
* can be added or removed for bespoke scenarios (time-travel, magitech, etc.).
|
|
10
|
+
*/
|
|
11
|
+
/** Numeric era codes; higher = more advanced. */
|
|
12
|
+
export const TechEra = {
|
|
13
|
+
Prehistoric: 0,
|
|
14
|
+
Ancient: 1,
|
|
15
|
+
Medieval: 2,
|
|
16
|
+
EarlyModern: 3,
|
|
17
|
+
Industrial: 4,
|
|
18
|
+
Modern: 5,
|
|
19
|
+
NearFuture: 6,
|
|
20
|
+
FarFuture: 7,
|
|
21
|
+
DeepSpace: 8,
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Default capability sets for each era.
|
|
25
|
+
* Capabilities are cumulative: each era includes all capabilities of previous eras.
|
|
26
|
+
*/
|
|
27
|
+
const ERA_DEFAULTS = [
|
|
28
|
+
/* Prehistoric */ [],
|
|
29
|
+
/* Ancient */ ["MetallicArmour"],
|
|
30
|
+
/* Medieval */ ["MetallicArmour"],
|
|
31
|
+
/* EarlyModern */ ["MetallicArmour", "FirearmsPropellant"],
|
|
32
|
+
/* Industrial */ ["MetallicArmour", "FirearmsPropellant", "ExplosiveMunitions"],
|
|
33
|
+
/* Modern */ ["MetallicArmour", "FirearmsPropellant", "ExplosiveMunitions", "BallisticArmour"],
|
|
34
|
+
/* NearFuture */ ["MetallicArmour", "FirearmsPropellant", "ExplosiveMunitions", "BallisticArmour", "PoweredExoskeleton"],
|
|
35
|
+
/* FarFuture */ ["MetallicArmour", "FirearmsPropellant", "ExplosiveMunitions", "BallisticArmour", "PoweredExoskeleton", "EnergyWeapons", "ReactivePlating"],
|
|
36
|
+
/* DeepSpace */ ["MetallicArmour", "FirearmsPropellant", "ExplosiveMunitions", "BallisticArmour", "PoweredExoskeleton", "EnergyWeapons", "ReactivePlating", "NanomedicalRepair"],
|
|
37
|
+
];
|
|
38
|
+
/** Build a TechContext from a named era with its default capability set. */
|
|
39
|
+
export function defaultTechContext(era) {
|
|
40
|
+
const caps = ERA_DEFAULTS[era] ?? [];
|
|
41
|
+
return { era, available: new Set(caps) };
|
|
42
|
+
}
|
|
43
|
+
/** Return true if the given capability is available in this context. */
|
|
44
|
+
export function isCapabilityAvailable(ctx, cap) {
|
|
45
|
+
return ctx.available.has(cap);
|
|
46
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { type Q, type I32 } from "../units.js";
|
|
2
|
+
/**
|
|
3
|
+
* Physical surface classification. Determines traction available to entities.
|
|
4
|
+
*/
|
|
5
|
+
export type SurfaceType = "normal" | "mud" | "ice" | "slope_up" | "slope_down";
|
|
6
|
+
/**
|
|
7
|
+
* Traction coefficient (Q) per surface type.
|
|
8
|
+
*
|
|
9
|
+
* Normal ground ≈ 0.80 (existing KernelContext default).
|
|
10
|
+
* Mud roughly halves usable force; ice allows only ~0.25×.
|
|
11
|
+
*/
|
|
12
|
+
export declare const SURFACE_TRACTION: Record<SurfaceType, Q>;
|
|
13
|
+
/**
|
|
14
|
+
* Terrain speed multiplier (Q) per surface type.
|
|
15
|
+
*
|
|
16
|
+
* Applied directly to maxSprintSpeed to capture effects that traction alone
|
|
17
|
+
* does not model (viscous drag in mud, slip uncertainty on ice, grade resistance).
|
|
18
|
+
*
|
|
19
|
+
* Normal = q(1.0) (no penalty).
|
|
20
|
+
*/
|
|
21
|
+
export declare const SURFACE_SPEED_MUL: Record<SurfaceType, Q>;
|
|
22
|
+
/**
|
|
23
|
+
* Sparse terrain grid. Keys are cell-index strings "cx,cy".
|
|
24
|
+
* Cells absent from the map use whatever default the caller provides (usually "normal").
|
|
25
|
+
*/
|
|
26
|
+
export type TerrainGrid = Map<string, SurfaceType>;
|
|
27
|
+
/**
|
|
28
|
+
* Encode integer cell coordinates as a lookup key.
|
|
29
|
+
*/
|
|
30
|
+
export declare function terrainKey(cellX: number, cellY: number): string;
|
|
31
|
+
/**
|
|
32
|
+
* Decode a terrain key back into cell coordinates.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseTerrainKey(key: string): {
|
|
35
|
+
cellX: number;
|
|
36
|
+
cellY: number;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Look up the traction coefficient at a given world position.
|
|
40
|
+
*
|
|
41
|
+
* @param grid Terrain grid (may be undefined or empty)
|
|
42
|
+
* @param cellSize_m Cell size in SCALE.m units (e.g. 4*SCALE.m for 4 m cells)
|
|
43
|
+
* @param pos_x Entity x position in SCALE.m units
|
|
44
|
+
* @param pos_y Entity y position in SCALE.m units
|
|
45
|
+
* @param defaultTraction Fallback traction when no grid cell is found
|
|
46
|
+
* @returns Traction coefficient Q
|
|
47
|
+
*/
|
|
48
|
+
export declare function tractionAtPosition(grid: TerrainGrid | undefined, cellSize_m: I32, pos_x: I32, pos_y: I32, defaultTraction: Q): Q;
|
|
49
|
+
/**
|
|
50
|
+
* Look up the speed multiplier at a given world position.
|
|
51
|
+
*
|
|
52
|
+
* Returns q(1.0) when the position has no terrain entry (no penalty).
|
|
53
|
+
*/
|
|
54
|
+
export declare function speedMulAtPosition(grid: TerrainGrid | undefined, cellSize_m: I32, pos_x: I32, pos_y: I32): Q;
|
|
55
|
+
/**
|
|
56
|
+
* Convenience: build a TerrainGrid from a flat record of "cx,cy" → SurfaceType.
|
|
57
|
+
*/
|
|
58
|
+
export declare function buildTerrainGrid(cells: Record<string, SurfaceType>): TerrainGrid;
|
|
59
|
+
/**
|
|
60
|
+
* Sparse obstacle / cover grid. Q value = cover fraction (0 = no cover, q(1.0) = impassable).
|
|
61
|
+
* Partial cover (e.g. q(0.50)) halves the effective target body-width used in hit determination,
|
|
62
|
+
* making the target harder to shoot. Full hard cover (q(1.0)) blocks movement and direct fire.
|
|
63
|
+
*/
|
|
64
|
+
export type ObstacleGrid = Map<string, Q>;
|
|
65
|
+
/**
|
|
66
|
+
* Look up cover fraction at a world position.
|
|
67
|
+
* Returns 0 (no cover) if the grid is absent or the cell has no entry.
|
|
68
|
+
*/
|
|
69
|
+
export declare function coverFractionAtPosition(grid: ObstacleGrid | undefined, cellSize_m: I32, pos_x: I32, pos_y: I32): Q;
|
|
70
|
+
/**
|
|
71
|
+
* Convenience: build an ObstacleGrid from a record of "cx,cy" → cover fraction Q.
|
|
72
|
+
* Use q(1.0) for impassable walls; fractional values for partial cover.
|
|
73
|
+
*/
|
|
74
|
+
export declare function buildObstacleGrid(cells: Record<string, Q>): ObstacleGrid;
|
|
75
|
+
/**
|
|
76
|
+
* Sparse elevation grid. Values are heights in SCALE.m units above the ground plane
|
|
77
|
+
* (e.g. `to.m(2)` = 2 m high ground). Height differentials are added to the vertical
|
|
78
|
+
* separation when computing 3D reach (melee) and projectile range (ranged).
|
|
79
|
+
*/
|
|
80
|
+
export type ElevationGrid = Map<string, I32>;
|
|
81
|
+
/**
|
|
82
|
+
* Look up elevation (height in SCALE.m units) at a world position.
|
|
83
|
+
* Returns 0 (ground level) if the grid is absent or the cell has no entry.
|
|
84
|
+
*/
|
|
85
|
+
export declare function elevationAtPosition(grid: ElevationGrid | undefined, cellSize_m: I32, pos_x: I32, pos_y: I32): I32;
|
|
86
|
+
/**
|
|
87
|
+
* Convenience: build an ElevationGrid from a record of "cx,cy" → height (SCALE.m units).
|
|
88
|
+
*/
|
|
89
|
+
export declare function buildElevationGrid(cells: Record<string, I32>): ElevationGrid;
|
|
90
|
+
/**
|
|
91
|
+
* Slope descriptor per cell.
|
|
92
|
+
* `grade` is a Q fraction where q(0) = flat and q(1.0) = maximum steepness.
|
|
93
|
+
*/
|
|
94
|
+
export interface SlopeInfo {
|
|
95
|
+
type: "uphill" | "downhill";
|
|
96
|
+
grade: Q;
|
|
97
|
+
}
|
|
98
|
+
/** Sparse slope grid. Keys are "cx,cy" cell-index strings. */
|
|
99
|
+
export type SlopeGrid = Map<string, SlopeInfo>;
|
|
100
|
+
/**
|
|
101
|
+
* Look up slope info at a world position.
|
|
102
|
+
* Returns undefined if the grid is absent or the cell has no entry.
|
|
103
|
+
*/
|
|
104
|
+
export declare function slopeAtPosition(grid: SlopeGrid | undefined, cellSize_m: I32, pos_x: I32, pos_y: I32): SlopeInfo | undefined;
|
|
105
|
+
/** Convenience: build a SlopeGrid from a record of "cx,cy" → SlopeInfo. */
|
|
106
|
+
export declare function buildSlopeGrid(cells: Record<string, SlopeInfo>): SlopeGrid;
|
|
107
|
+
/**
|
|
108
|
+
* Dynamic hazard cell.
|
|
109
|
+
* `intensity` ∈ [0, q(1.0)] scales per-tick damage.
|
|
110
|
+
* `duration_ticks`: 0 = permanent; >0 is decremented each tick and the cell is
|
|
111
|
+
* removed when it reaches 0.
|
|
112
|
+
*/
|
|
113
|
+
export interface HazardCell {
|
|
114
|
+
type: "fire" | "radiation" | "poison_gas";
|
|
115
|
+
intensity: Q;
|
|
116
|
+
duration_ticks: number;
|
|
117
|
+
}
|
|
118
|
+
/** Sparse hazard grid. Keys are "cx,cy" cell-index strings. */
|
|
119
|
+
export type HazardGrid = Map<string, HazardCell>;
|
|
120
|
+
/** Convenience: build a HazardGrid from a record of "cx,cy" → HazardCell. */
|
|
121
|
+
export declare function buildHazardGrid(cells: Record<string, HazardCell>): HazardGrid;
|