@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,276 @@
|
|
|
1
|
+
import { SCALE } from "./units.js";
|
|
2
|
+
// breaks = [t1_max, t2_max, t3_max, t4_max, t5_max]
|
|
3
|
+
// value < breaks[0] → tier 1, …, value >= breaks[4] → tier 6
|
|
4
|
+
function rateTier(value, breaks) {
|
|
5
|
+
if (value < breaks[0])
|
|
6
|
+
return 1;
|
|
7
|
+
if (value < breaks[1])
|
|
8
|
+
return 2;
|
|
9
|
+
if (value < breaks[2])
|
|
10
|
+
return 3;
|
|
11
|
+
if (value < breaks[3])
|
|
12
|
+
return 4;
|
|
13
|
+
if (value < breaks[4])
|
|
14
|
+
return 5;
|
|
15
|
+
return 6;
|
|
16
|
+
}
|
|
17
|
+
// For inverted attrs (lower = better)
|
|
18
|
+
// breaks = [t6_max, t5_max, t4_max, t3_max, t2_max] (ascending)
|
|
19
|
+
// value < breaks[0] → tier 6, …, value >= breaks[4] → tier 1
|
|
20
|
+
function rateInverted(value, breaks) {
|
|
21
|
+
if (value < breaks[0])
|
|
22
|
+
return 6;
|
|
23
|
+
if (value < breaks[1])
|
|
24
|
+
return 5;
|
|
25
|
+
if (value < breaks[2])
|
|
26
|
+
return 4;
|
|
27
|
+
if (value < breaks[3])
|
|
28
|
+
return 3;
|
|
29
|
+
if (value < breaks[4])
|
|
30
|
+
return 2;
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
// Breakpoints in fixed-point units
|
|
34
|
+
// peakForce_N: fp = N * SCALE.N (100) — tiers at 500/1100/2000/3500/5500 N
|
|
35
|
+
const FORCE_BREAKS = [50_000, 110_000, 200_000, 350_000, 550_000];
|
|
36
|
+
// peakPower_W: SCALE.W=1 — tiers at 400/800/1400/2000/3000 W
|
|
37
|
+
const POWER_BREAKS = [400, 800, 1_400, 2_000, 3_000];
|
|
38
|
+
// continuousPower_W — tiers at 80/150/260/380/600 W
|
|
39
|
+
const CONT_BREAKS = [80, 150, 260, 380, 600];
|
|
40
|
+
// reserveEnergy_J: SCALE.J=1 — tiers at 8/15/23/38/58 kJ
|
|
41
|
+
const ENERGY_BREAKS = [8_000, 15_000, 23_000, 38_000, 58_000];
|
|
42
|
+
// reactionTime_s (inverted): SCALE.s=10000 — ascending [t6_max…t2_max] in fp
|
|
43
|
+
// tiers (seconds): <0.12 | 0.12–0.17 | 0.17–0.22 | 0.22–0.30 | 0.30–0.45 | >0.45
|
|
44
|
+
const REACT_BREAKS = [1_200, 1_700, 2_200, 3_000, 4_500];
|
|
45
|
+
// Q attrs 0–1 (controlQuality, stability, fineControl): SCALE.Q=10000
|
|
46
|
+
// tiers: <0.35 | 0.35–0.58 | 0.58–0.78 | 0.78–0.87 | 0.87–0.93 | >0.93
|
|
47
|
+
const Q_BREAKS = [3_500, 5_800, 7_800, 8_700, 9_300];
|
|
48
|
+
// resilience Q attrs (distressTolerance, shockTolerance, concussionTolerance)
|
|
49
|
+
// tiers: <0.25 | 0.25–0.45 | 0.45–0.62 | 0.62–0.75 | 0.75–0.88 | >0.88
|
|
50
|
+
const RES_BREAKS = [2_500, 4_500, 6_200, 7_500, 8_800];
|
|
51
|
+
// decisionLatency_s (inverted): SCALE.s=10000 — ascending [t6_max…t2_max] in fp
|
|
52
|
+
// tiers (seconds): <0.08 | 0.08–0.30 | 0.30–0.46 | 0.46–0.56 | 0.56–0.80 | >0.80
|
|
53
|
+
const DECISION_BREAKS = [800, 3_000, 4_600, 5_600, 8_000];
|
|
54
|
+
// Labels indexed by (tier - 1)
|
|
55
|
+
const PERF_LABELS = ["feeble", "weak", "average", "strong", "excellent", "exceptional"];
|
|
56
|
+
const SPEED_LABELS = ["sluggish", "slow", "average", "quick", "fast", "instant"];
|
|
57
|
+
const CTRL_LABELS = ["erratic", "poor", "average", "precise", "refined", "masterful"];
|
|
58
|
+
const RES_LABELS = ["fragile", "low", "average", "resilient", "tough", "ironclad"];
|
|
59
|
+
const COG_LABELS = ["sluggish", "slow", "average", "sharp", "razor-sharp", "machine-like"];
|
|
60
|
+
const FORCE_COMPARISONS = [
|
|
61
|
+
"weaker than most children",
|
|
62
|
+
"sedentary adult — below average output",
|
|
63
|
+
"average adult — baseline human force",
|
|
64
|
+
"trained athlete or competitive fighter",
|
|
65
|
+
"elite level — professional fighter strength",
|
|
66
|
+
"superhuman or powered — beyond biological norms",
|
|
67
|
+
];
|
|
68
|
+
const POWER_COMPARISONS = [
|
|
69
|
+
"minimal power output",
|
|
70
|
+
"below average explosive output",
|
|
71
|
+
"moderate explosive output",
|
|
72
|
+
"strong explosive performance",
|
|
73
|
+
"elite explosive output",
|
|
74
|
+
"extreme power — mechanical or enhanced",
|
|
75
|
+
];
|
|
76
|
+
const CONT_COMPARISONS = [
|
|
77
|
+
"very limited aerobic capacity",
|
|
78
|
+
"below average aerobic output",
|
|
79
|
+
"sustainable aerobic output",
|
|
80
|
+
"strong sustained performance",
|
|
81
|
+
"elite endurance athlete level",
|
|
82
|
+
"extraordinary sustained output",
|
|
83
|
+
];
|
|
84
|
+
const ENERGY_COMPARISONS = [
|
|
85
|
+
"very low energy reserves",
|
|
86
|
+
"below average combat stamina",
|
|
87
|
+
"typical combat energy reserves",
|
|
88
|
+
"good combat energy reserves",
|
|
89
|
+
"exceptional energy reserves",
|
|
90
|
+
"extraordinary — far beyond normal capacity",
|
|
91
|
+
];
|
|
92
|
+
const REACT_COMPARISONS = [
|
|
93
|
+
"very slow reflexes",
|
|
94
|
+
"below average response time",
|
|
95
|
+
"average adult response time",
|
|
96
|
+
"trained athlete response time",
|
|
97
|
+
"elite combat reflexes",
|
|
98
|
+
"machine-speed response",
|
|
99
|
+
];
|
|
100
|
+
const CTRL_COMPARISONS = [
|
|
101
|
+
"very poor motor control",
|
|
102
|
+
"below average movement quality",
|
|
103
|
+
"competent general movement",
|
|
104
|
+
"skilled coordinated movement",
|
|
105
|
+
"highly refined motor control",
|
|
106
|
+
"near-perfect motor precision",
|
|
107
|
+
];
|
|
108
|
+
const STABILITY_COMPARISONS = [
|
|
109
|
+
"very poor balance",
|
|
110
|
+
"below average stability",
|
|
111
|
+
"normal postural stability",
|
|
112
|
+
"good balance and stability",
|
|
113
|
+
"excellent balance and body control",
|
|
114
|
+
"extraordinary stability",
|
|
115
|
+
];
|
|
116
|
+
const FINE_COMPARISONS = [
|
|
117
|
+
"very clumsy fine motor",
|
|
118
|
+
"below average manual dexterity",
|
|
119
|
+
"everyday manual dexterity",
|
|
120
|
+
"skilled fine motor control",
|
|
121
|
+
"exceptional precision",
|
|
122
|
+
"near-surgical precision",
|
|
123
|
+
];
|
|
124
|
+
const PAIN_COMPARISONS = [
|
|
125
|
+
"extremely pain-sensitive",
|
|
126
|
+
"below average pain threshold",
|
|
127
|
+
"typical distress threshold",
|
|
128
|
+
"trained pain tolerance",
|
|
129
|
+
"high pain threshold",
|
|
130
|
+
"extreme pain suppression",
|
|
131
|
+
];
|
|
132
|
+
const SHOCK_COMPARISONS = [
|
|
133
|
+
"very fragile to shock",
|
|
134
|
+
"below average shock resistance",
|
|
135
|
+
"normal shock resistance",
|
|
136
|
+
"trained shock absorption",
|
|
137
|
+
"high shock resistance",
|
|
138
|
+
"extreme shock tolerance",
|
|
139
|
+
];
|
|
140
|
+
const CONC_COMPARISONS = [
|
|
141
|
+
"very vulnerable to head trauma",
|
|
142
|
+
"below average concussion resistance",
|
|
143
|
+
"standard skull protection",
|
|
144
|
+
"above average head protection",
|
|
145
|
+
"high concussion resistance",
|
|
146
|
+
"exceptional — distributed or no central brain",
|
|
147
|
+
];
|
|
148
|
+
const DECISION_COMPARISONS = [
|
|
149
|
+
"extremely slow decision making",
|
|
150
|
+
"slow tactical processing",
|
|
151
|
+
"normal human deliberation time",
|
|
152
|
+
"quick tactical processing",
|
|
153
|
+
"fast tactical decisions",
|
|
154
|
+
"machine-speed decision making",
|
|
155
|
+
];
|
|
156
|
+
// Value formatters
|
|
157
|
+
function fmtN(fp) { return `${Math.round(fp / SCALE.N)} N`; }
|
|
158
|
+
function fmtW(fp) { return `${fp} W`; }
|
|
159
|
+
function fmtJ(fp) { return fp >= 1000 ? `${(fp / 1000).toFixed(0)} kJ` : `${fp} J`; }
|
|
160
|
+
function fmtMs(fp) { return `${Math.round((fp / SCALE.s) * 1000)} ms`; }
|
|
161
|
+
function fmtQ(fp) { return `${(fp / SCALE.Q).toFixed(2)}`; }
|
|
162
|
+
function fmtM(fp) { return `${(fp / SCALE.m).toFixed(2)} m`; }
|
|
163
|
+
function fmtKg(fp) { return `${(fp / SCALE.kg).toFixed(1)} kg`; }
|
|
164
|
+
function makeRating(tier, labels, comparisons, value) {
|
|
165
|
+
return {
|
|
166
|
+
tier,
|
|
167
|
+
label: labels[tier - 1] ?? "",
|
|
168
|
+
comparison: comparisons[tier - 1] ?? "",
|
|
169
|
+
value,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function describeStature(fp) {
|
|
173
|
+
let label;
|
|
174
|
+
if (fp < 14_000)
|
|
175
|
+
label = "very short";
|
|
176
|
+
else if (fp <= 16_000)
|
|
177
|
+
label = "short";
|
|
178
|
+
else if (fp < 18_000)
|
|
179
|
+
label = "average height";
|
|
180
|
+
else if (fp < 19_500)
|
|
181
|
+
label = "tall";
|
|
182
|
+
else
|
|
183
|
+
label = "very tall";
|
|
184
|
+
return `${fmtM(fp)} — ${label}`;
|
|
185
|
+
}
|
|
186
|
+
function describeMass(fp) {
|
|
187
|
+
let label;
|
|
188
|
+
if (fp < 50_000)
|
|
189
|
+
label = "slight build";
|
|
190
|
+
else if (fp < 65_000)
|
|
191
|
+
label = "lean build";
|
|
192
|
+
else if (fp < 90_000)
|
|
193
|
+
label = "average build";
|
|
194
|
+
else if (fp < 115_000)
|
|
195
|
+
label = "heavy build";
|
|
196
|
+
else
|
|
197
|
+
label = "very heavy build";
|
|
198
|
+
return `${fmtKg(fp)} — ${label}`;
|
|
199
|
+
}
|
|
200
|
+
export function describeCharacter(attrs) {
|
|
201
|
+
const { morphology, performance, control, resilience, perception } = attrs;
|
|
202
|
+
const strengthTier = rateTier(performance.peakForce_N, FORCE_BREAKS);
|
|
203
|
+
const powerTier = rateTier(performance.peakPower_W, POWER_BREAKS);
|
|
204
|
+
const endTier = rateTier(performance.continuousPower_W, CONT_BREAKS);
|
|
205
|
+
const stamTier = rateTier(performance.reserveEnergy_J, ENERGY_BREAKS);
|
|
206
|
+
const reactTier = rateInverted(control.reactionTime_s, REACT_BREAKS);
|
|
207
|
+
const coordTier = rateTier(control.controlQuality, Q_BREAKS);
|
|
208
|
+
const balTier = rateTier(control.stability, Q_BREAKS);
|
|
209
|
+
const precTier = rateTier(control.fineControl, Q_BREAKS);
|
|
210
|
+
const painTier = rateTier(resilience.distressTolerance, RES_BREAKS);
|
|
211
|
+
const toughTier = rateTier(resilience.shockTolerance, RES_BREAKS);
|
|
212
|
+
const concTier = rateTier(resilience.concussionTolerance, RES_BREAKS);
|
|
213
|
+
const decTier = rateInverted(perception?.decisionLatency_s ?? 0, DECISION_BREAKS);
|
|
214
|
+
return {
|
|
215
|
+
stature: describeStature(morphology.stature_m),
|
|
216
|
+
mass: describeMass(morphology.mass_kg),
|
|
217
|
+
strength: makeRating(strengthTier, PERF_LABELS, FORCE_COMPARISONS, fmtN(performance.peakForce_N)),
|
|
218
|
+
explosivePower: makeRating(powerTier, PERF_LABELS, POWER_COMPARISONS, fmtW(performance.peakPower_W)),
|
|
219
|
+
endurance: makeRating(endTier, PERF_LABELS, CONT_COMPARISONS, fmtW(performance.continuousPower_W)),
|
|
220
|
+
stamina: makeRating(stamTier, PERF_LABELS, ENERGY_COMPARISONS, fmtJ(performance.reserveEnergy_J)),
|
|
221
|
+
reactionTime: makeRating(reactTier, SPEED_LABELS, REACT_COMPARISONS, fmtMs(control.reactionTime_s)),
|
|
222
|
+
coordination: makeRating(coordTier, CTRL_LABELS, CTRL_COMPARISONS, fmtQ(control.controlQuality)),
|
|
223
|
+
balance: makeRating(balTier, CTRL_LABELS, STABILITY_COMPARISONS, fmtQ(control.stability)),
|
|
224
|
+
precision: makeRating(precTier, CTRL_LABELS, FINE_COMPARISONS, fmtQ(control.fineControl)),
|
|
225
|
+
painTolerance: makeRating(painTier, RES_LABELS, PAIN_COMPARISONS, fmtQ(resilience.distressTolerance)),
|
|
226
|
+
toughness: makeRating(toughTier, RES_LABELS, SHOCK_COMPARISONS, fmtQ(resilience.shockTolerance)),
|
|
227
|
+
concussionResistance: makeRating(concTier, RES_LABELS, CONC_COMPARISONS, fmtQ(resilience.concussionTolerance)),
|
|
228
|
+
visionRange: `${Math.round((perception?.visionRange_m ?? 0) / SCALE.m)} m, ${perception?.visionArcDeg ?? 0}\u00b0 arc`,
|
|
229
|
+
hearingRange: `${Math.round((perception?.hearingRange_m ?? 0) / SCALE.m)} m`,
|
|
230
|
+
decisionSpeed: makeRating(decTier, COG_LABELS, DECISION_COMPARISONS, fmtMs(perception?.decisionLatency_s ?? 0)),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function pad(s, width) {
|
|
234
|
+
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
235
|
+
}
|
|
236
|
+
export function formatCharacterSheet(desc) {
|
|
237
|
+
const row = (name, r) => ` ${pad(name + ":", 14)}${pad(r.value, 12)} [${r.label}] ${r.comparison}`;
|
|
238
|
+
return [
|
|
239
|
+
"Body",
|
|
240
|
+
` Stature: ${desc.stature}`,
|
|
241
|
+
` Mass: ${desc.mass}`,
|
|
242
|
+
"",
|
|
243
|
+
"Performance",
|
|
244
|
+
row("Strength", desc.strength),
|
|
245
|
+
row("Power", desc.explosivePower),
|
|
246
|
+
row("Endurance", desc.endurance),
|
|
247
|
+
row("Stamina", desc.stamina),
|
|
248
|
+
"",
|
|
249
|
+
"Control",
|
|
250
|
+
row("Reaction", desc.reactionTime),
|
|
251
|
+
row("Coordination", desc.coordination),
|
|
252
|
+
row("Balance", desc.balance),
|
|
253
|
+
row("Precision", desc.precision),
|
|
254
|
+
"",
|
|
255
|
+
"Resilience",
|
|
256
|
+
row("Pain", desc.painTolerance),
|
|
257
|
+
row("Toughness", desc.toughness),
|
|
258
|
+
row("Concussion", desc.concussionResistance),
|
|
259
|
+
"",
|
|
260
|
+
"Perception",
|
|
261
|
+
` Vision: ${desc.visionRange}`,
|
|
262
|
+
` Hearing: ${desc.hearingRange}`,
|
|
263
|
+
row("Decision", desc.decisionSpeed),
|
|
264
|
+
].join("\n");
|
|
265
|
+
}
|
|
266
|
+
export function formatOneLine(desc) {
|
|
267
|
+
const statureParts = desc.stature.split(" — ");
|
|
268
|
+
const statureM = statureParts[0] ?? desc.stature;
|
|
269
|
+
const statureLbl = statureParts[1] ?? "";
|
|
270
|
+
const capLbl = statureLbl.charAt(0).toUpperCase() + statureLbl.slice(1);
|
|
271
|
+
const massKg = desc.mass.split(" — ")[0] ?? desc.mass;
|
|
272
|
+
return (`${capLbl} (${statureM}), ${massKg}; ` +
|
|
273
|
+
`strength ${desc.strength.label} (${desc.strength.value}), ` +
|
|
274
|
+
`reaction ${desc.reactionTime.label} (${desc.reactionTime.value}), ` +
|
|
275
|
+
`resilience ${desc.painTolerance.label}.`);
|
|
276
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { Q } from "./units.js";
|
|
2
|
+
import type { Entity } from "./sim/entity.js";
|
|
3
|
+
import type { NarrativeConfig } from "./narrative.js";
|
|
4
|
+
/** A lightweight trade item for negotiation offers. Full item economy is Phase 25. */
|
|
5
|
+
export interface TradeItem {
|
|
6
|
+
id: string;
|
|
7
|
+
value: number;
|
|
8
|
+
}
|
|
9
|
+
/** A proposed exchange: initiator gives items and receives items in return. */
|
|
10
|
+
export interface TradeOffer {
|
|
11
|
+
giving: TradeItem[];
|
|
12
|
+
receiving: TradeItem[];
|
|
13
|
+
}
|
|
14
|
+
/** All possible social actions an entity can take. */
|
|
15
|
+
export type DialogueAction = {
|
|
16
|
+
kind: "intimidate";
|
|
17
|
+
intensity_Q: Q;
|
|
18
|
+
} | {
|
|
19
|
+
kind: "persuade";
|
|
20
|
+
argument?: string;
|
|
21
|
+
} | {
|
|
22
|
+
kind: "deceive";
|
|
23
|
+
plausibility_Q: Q;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "surrender";
|
|
26
|
+
terms?: string;
|
|
27
|
+
} | {
|
|
28
|
+
kind: "negotiate";
|
|
29
|
+
offer: TradeOffer;
|
|
30
|
+
} | {
|
|
31
|
+
kind: "signal";
|
|
32
|
+
targetSpecies: string;
|
|
33
|
+
intent: "calm" | "submit" | "ally" | "territory";
|
|
34
|
+
};
|
|
35
|
+
/** The resolution result of a dialogue action. */
|
|
36
|
+
export type DialogueOutcome = {
|
|
37
|
+
result: "success";
|
|
38
|
+
moraleDelta?: Q;
|
|
39
|
+
fearDelta?: Q;
|
|
40
|
+
setSurrendered?: boolean;
|
|
41
|
+
comprehension_Q?: Q;
|
|
42
|
+
} | {
|
|
43
|
+
result: "failure";
|
|
44
|
+
cooldown_s: number;
|
|
45
|
+
aggravated?: boolean;
|
|
46
|
+
} | {
|
|
47
|
+
result: "escalate";
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Context for a dialogue resolution.
|
|
51
|
+
*
|
|
52
|
+
* `sharedFaction` — Phase 24 placeholder; set to true when entities share a faction.
|
|
53
|
+
* `priorFailedAttempts` — cumulative failed persuasion attempts by this initiator against
|
|
54
|
+
* this target; each one imposes a PERSUADE_FAILURE_PENALTY.
|
|
55
|
+
*/
|
|
56
|
+
export interface DialogueContext {
|
|
57
|
+
initiator: Entity;
|
|
58
|
+
target: Entity;
|
|
59
|
+
worldSeed: number;
|
|
60
|
+
tick: number;
|
|
61
|
+
sharedFaction?: boolean;
|
|
62
|
+
priorFailedAttempts?: number;
|
|
63
|
+
}
|
|
64
|
+
/** Reduction to intimidation probability when target has the "leader" trait. */
|
|
65
|
+
export declare const LEADER_INTIMIDATE_REDUCTION: Q;
|
|
66
|
+
/** Base success probability for an unmodified persuade attempt. */
|
|
67
|
+
export declare const PERSUADE_BASE: Q;
|
|
68
|
+
/** Persuasion bonus when initiator and target share a faction (Phase 24). */
|
|
69
|
+
export declare const PERSUADE_FACTION_BONUS: Q;
|
|
70
|
+
/** Persuasion penalty per prior failed attempt against this target. */
|
|
71
|
+
export declare const PERSUADE_FAILURE_PENALTY: Q;
|
|
72
|
+
/** Fear increase applied to target when intimidation succeeds. */
|
|
73
|
+
export declare const INTIMIDATE_FEAR_DELTA: Q;
|
|
74
|
+
/** Minimum fearQ for target to consider accepting surrender. Below this = always refuses. */
|
|
75
|
+
export declare const SURRENDER_THRESHOLD: Q;
|
|
76
|
+
/**
|
|
77
|
+
* If intimidation *fails* and target's fearQ is below this value,
|
|
78
|
+
* the target interprets the attempt as an insult and escalates.
|
|
79
|
+
*/
|
|
80
|
+
export declare const ESCALATE_THRESHOLD: Q;
|
|
81
|
+
/**
|
|
82
|
+
* Compute the success probability for a dialogue action without rolling RNG.
|
|
83
|
+
*
|
|
84
|
+
* Exported so tests can assert on exact probability values without seeding concerns.
|
|
85
|
+
* For "escalate" scenarios the probability returned is the *intimidation* probability;
|
|
86
|
+
* escalation is a post-failure branch, not a separate probability.
|
|
87
|
+
*/
|
|
88
|
+
export declare function dialogueProbability(action: DialogueAction, ctx: DialogueContext): Q;
|
|
89
|
+
/**
|
|
90
|
+
* Resolve a dialogue action and return the outcome.
|
|
91
|
+
*
|
|
92
|
+
* - Intimidate, persuade, and deceive use seeded RNG.
|
|
93
|
+
* - Surrender is deterministic: succeeds if target.fearQ > SURRENDER_THRESHOLD.
|
|
94
|
+
* - Negotiate is deterministic: succeeds if trade utility is positive for the target.
|
|
95
|
+
*
|
|
96
|
+
* @returns A DialogueOutcome. Use `applyDialogueOutcome` to write deltas back to entities.
|
|
97
|
+
*/
|
|
98
|
+
export declare function resolveDialogue(action: DialogueAction, ctx: DialogueContext): DialogueOutcome;
|
|
99
|
+
/**
|
|
100
|
+
* Write a successful outcome's deltas back to the target entity.
|
|
101
|
+
*
|
|
102
|
+
* - `fearDelta` — added directly to `target.condition.fearQ` (positive = more fear).
|
|
103
|
+
* - `moraleDelta` — subtracted from `target.condition.fearQ` (positive morale = less fear).
|
|
104
|
+
* - `setSurrendered` — sets `target.condition.surrendered = true`.
|
|
105
|
+
*
|
|
106
|
+
* No-op if `outcome.result !== "success"`.
|
|
107
|
+
*/
|
|
108
|
+
export declare function applyDialogueOutcome(outcome: DialogueOutcome, target: Entity): void;
|
|
109
|
+
/**
|
|
110
|
+
* Produce a human-readable description of a dialogue action and its outcome.
|
|
111
|
+
*
|
|
112
|
+
* Verbosity levels:
|
|
113
|
+
* - `terse` — single word label + result token (shortest)
|
|
114
|
+
* - `normal` — one sentence with action and outcome context
|
|
115
|
+
* - `verbose` — entity names + extended description of what happened
|
|
116
|
+
*
|
|
117
|
+
* @param ids — optional initiator/target entity ids used to resolve names from `cfg.nameMap`
|
|
118
|
+
*/
|
|
119
|
+
export declare function narrateDialogue(action: DialogueAction, outcome: DialogueOutcome, cfg: NarrativeConfig, ids?: {
|
|
120
|
+
initiatorId?: number;
|
|
121
|
+
targetId?: number;
|
|
122
|
+
}): string;
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// src/dialogue.ts — Phase 23: Dialogue & Negotiation Layer
|
|
2
|
+
//
|
|
3
|
+
// Non-combat resolution grounded in the same physical and psychological
|
|
4
|
+
// attributes as the combat engine. No arbitrary Charisma stats —
|
|
5
|
+
// intimidation strength comes from peakForce_N, persuasion from cognition,
|
|
6
|
+
// deception is defeated by attentionDepth.
|
|
7
|
+
//
|
|
8
|
+
// No kernel import — pure data-resolution module.
|
|
9
|
+
import { SCALE, q, clampQ, qMul, mulDiv, to } from "./units.js";
|
|
10
|
+
import { eventSeed } from "./sim/seeds.js";
|
|
11
|
+
import { makeRng } from "./rng.js";
|
|
12
|
+
import { resolveSignal } from "./competence/interspecies.js";
|
|
13
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
14
|
+
/** Real-newton threshold where intimidation force factor reaches q(1.0). */
|
|
15
|
+
const INTIMIDATE_FORCE_SCALE = to.N(4000);
|
|
16
|
+
/** Reduction to intimidation probability when target has the "leader" trait. */
|
|
17
|
+
export const LEADER_INTIMIDATE_REDUCTION = q(0.15);
|
|
18
|
+
/** Base success probability for an unmodified persuade attempt. */
|
|
19
|
+
export const PERSUADE_BASE = q(0.40);
|
|
20
|
+
/** Persuasion bonus when initiator and target share a faction (Phase 24). */
|
|
21
|
+
export const PERSUADE_FACTION_BONUS = q(0.10);
|
|
22
|
+
/** Persuasion penalty per prior failed attempt against this target. */
|
|
23
|
+
export const PERSUADE_FAILURE_PENALTY = q(0.10);
|
|
24
|
+
/** Fear increase applied to target when intimidation succeeds. */
|
|
25
|
+
export const INTIMIDATE_FEAR_DELTA = q(0.15);
|
|
26
|
+
/** Minimum fearQ for target to consider accepting surrender. Below this = always refuses. */
|
|
27
|
+
export const SURRENDER_THRESHOLD = q(0.40);
|
|
28
|
+
/**
|
|
29
|
+
* If intimidation *fails* and target's fearQ is below this value,
|
|
30
|
+
* the target interprets the attempt as an insult and escalates.
|
|
31
|
+
*/
|
|
32
|
+
export const ESCALATE_THRESHOLD = q(0.20);
|
|
33
|
+
// ── RNG salts ─────────────────────────────────────────────────────────────────
|
|
34
|
+
const SALT_INTIMIDATE = 0xD1A100;
|
|
35
|
+
const SALT_PERSUADE = 0xD1A101;
|
|
36
|
+
const SALT_DECEIVE = 0xD1A102;
|
|
37
|
+
const SALT_SIGNAL = 0xD1A103; // Phase 36: cross-species signaling
|
|
38
|
+
// ── Internal helpers ──────────────────────────────────────────────────────────
|
|
39
|
+
/**
|
|
40
|
+
* Convert a fixed-point force (SCALE.N units) to a Q fraction in [0, 1].
|
|
41
|
+
* q(1.0) corresponds to INTIMIDATE_FORCE_SCALE (4000 N real).
|
|
42
|
+
*/
|
|
43
|
+
function forceToQ(peakForce_N) {
|
|
44
|
+
return clampQ(mulDiv(peakForce_N, SCALE.Q, INTIMIDATE_FORCE_SCALE), 0, SCALE.Q);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Convert an integer `attentionDepth` to a Q fraction in [0, 1].
|
|
48
|
+
* Human baseline (4) → q(0.40); robot / sharp mind (10) → q(1.0).
|
|
49
|
+
*/
|
|
50
|
+
function attentionDepthQ(depth) {
|
|
51
|
+
return clampQ(Math.trunc((depth * SCALE.Q) / 10), 0, SCALE.Q);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Learning rate bonus for persuasion derived from attentionDepth.
|
|
55
|
+
* +250 per point above the human baseline of 4, capped at q(0.20).
|
|
56
|
+
*/
|
|
57
|
+
function learningBonus(attentionDepth) {
|
|
58
|
+
return clampQ(Math.max(0, attentionDepth - 4) * 250, 0, q(0.20));
|
|
59
|
+
}
|
|
60
|
+
// ── Probability computation ───────────────────────────────────────────────────
|
|
61
|
+
/**
|
|
62
|
+
* Compute the success probability for a dialogue action without rolling RNG.
|
|
63
|
+
*
|
|
64
|
+
* Exported so tests can assert on exact probability values without seeding concerns.
|
|
65
|
+
* For "escalate" scenarios the probability returned is the *intimidation* probability;
|
|
66
|
+
* escalation is a post-failure branch, not a separate probability.
|
|
67
|
+
*/
|
|
68
|
+
export function dialogueProbability(action, ctx) {
|
|
69
|
+
const { initiator, target } = ctx;
|
|
70
|
+
switch (action.kind) {
|
|
71
|
+
case "intimidate": {
|
|
72
|
+
const forceFrac = forceToQ(initiator.attributes.performance.peakForce_N);
|
|
73
|
+
const leaderRed = target.traits.includes("leader") ? LEADER_INTIMIDATE_REDUCTION : 0;
|
|
74
|
+
return clampQ(forceFrac
|
|
75
|
+
+ target.condition.fearQ
|
|
76
|
+
- target.attributes.resilience.distressTolerance
|
|
77
|
+
- leaderRed, 0, SCALE.Q);
|
|
78
|
+
}
|
|
79
|
+
case "persuade": {
|
|
80
|
+
const failed = ctx.priorFailedAttempts ?? 0;
|
|
81
|
+
// Phase 33: linguistic intelligence sets per-entity persuasion base
|
|
82
|
+
// Formula: q(0.20) + linguistic × q(0.30); human (0.65) → q(0.395); elf (0.80) → q(0.44)
|
|
83
|
+
const linguisticQ = initiator.attributes.cognition?.linguistic;
|
|
84
|
+
const dynamicBase = linguisticQ !== undefined
|
|
85
|
+
? clampQ((q(0.20) + mulDiv(q(0.30), linguisticQ, SCALE.Q)), q(0.20), q(0.50))
|
|
86
|
+
: PERSUADE_BASE;
|
|
87
|
+
return clampQ(dynamicBase
|
|
88
|
+
+ learningBonus(target.attributes.perception?.attentionDepth ?? 0)
|
|
89
|
+
+ (ctx.sharedFaction ? PERSUADE_FACTION_BONUS : 0)
|
|
90
|
+
- (failed * PERSUADE_FAILURE_PENALTY), 0, SCALE.Q);
|
|
91
|
+
}
|
|
92
|
+
case "deceive": {
|
|
93
|
+
// Phase 37: deception detection uses both attentionDepth AND interpersonal
|
|
94
|
+
// P_success = plausibility × (1 - attentionDepth) × (1 - interpersonal × 0.50)
|
|
95
|
+
const attentionFactor = (SCALE.Q - attentionDepthQ(target.attributes.perception?.attentionDepth ?? 0));
|
|
96
|
+
// Interpersonal gives defensive bonus against deception
|
|
97
|
+
const interpersonal = (target.attributes.cognition?.interpersonal ?? q(0.50));
|
|
98
|
+
const interpersonalFactor = clampQ((SCALE.Q - mulDiv(interpersonal, q(0.50), SCALE.Q)), q(0.50), SCALE.Q);
|
|
99
|
+
return qMul(qMul(action.plausibility_Q, attentionFactor), interpersonalFactor);
|
|
100
|
+
}
|
|
101
|
+
case "surrender":
|
|
102
|
+
// Returns non-zero only when target's fear exceeds the acceptance threshold.
|
|
103
|
+
return clampQ(target.condition.fearQ - SURRENDER_THRESHOLD, 0, SCALE.Q);
|
|
104
|
+
case "negotiate": {
|
|
105
|
+
const given = action.offer.giving.reduce((s, i) => s + i.value, 0);
|
|
106
|
+
const received = action.offer.receiving.reduce((s, i) => s + i.value, 0);
|
|
107
|
+
return given > received ? SCALE.Q : 0;
|
|
108
|
+
}
|
|
109
|
+
case "signal":
|
|
110
|
+
// Phase 36: signal probability is computed in resolveSignal; here we return a placeholder
|
|
111
|
+
// The actual resolution uses the interspecies resolver
|
|
112
|
+
return q(0.50);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// ── Resolution ────────────────────────────────────────────────────────────────
|
|
116
|
+
/**
|
|
117
|
+
* Resolve a dialogue action and return the outcome.
|
|
118
|
+
*
|
|
119
|
+
* - Intimidate, persuade, and deceive use seeded RNG.
|
|
120
|
+
* - Surrender is deterministic: succeeds if target.fearQ > SURRENDER_THRESHOLD.
|
|
121
|
+
* - Negotiate is deterministic: succeeds if trade utility is positive for the target.
|
|
122
|
+
*
|
|
123
|
+
* @returns A DialogueOutcome. Use `applyDialogueOutcome` to write deltas back to entities.
|
|
124
|
+
*/
|
|
125
|
+
export function resolveDialogue(action, ctx) {
|
|
126
|
+
const P = dialogueProbability(action, ctx);
|
|
127
|
+
// Deterministic branches
|
|
128
|
+
if (action.kind === "surrender") {
|
|
129
|
+
if (P > 0)
|
|
130
|
+
return { result: "success", setSurrendered: true };
|
|
131
|
+
return { result: "failure", cooldown_s: 0 };
|
|
132
|
+
}
|
|
133
|
+
if (action.kind === "negotiate") {
|
|
134
|
+
return P > 0
|
|
135
|
+
? { result: "success" }
|
|
136
|
+
: { result: "failure", cooldown_s: 0 };
|
|
137
|
+
}
|
|
138
|
+
// Phase 36: Signal action uses interspecies resolver
|
|
139
|
+
if (action.kind === "signal") {
|
|
140
|
+
const seed = eventSeed(ctx.worldSeed, ctx.tick, ctx.initiator.id, ctx.target.id, SALT_SIGNAL);
|
|
141
|
+
const signalOutcome = resolveSignal(ctx.initiator, {
|
|
142
|
+
targetSpecies: action.targetSpecies,
|
|
143
|
+
intent: action.intent,
|
|
144
|
+
targetFearQ: ctx.target.condition.fearQ ?? q(0),
|
|
145
|
+
}, seed);
|
|
146
|
+
if (signalOutcome.success) {
|
|
147
|
+
return { result: "success", comprehension_Q: signalOutcome.comprehension_Q };
|
|
148
|
+
}
|
|
149
|
+
return { result: "failure", cooldown_s: 45, aggravated: signalOutcome.aggravated };
|
|
150
|
+
}
|
|
151
|
+
// RNG-based branches (intimidate, persuade, deceive)
|
|
152
|
+
const salt = action.kind === "intimidate" ? SALT_INTIMIDATE
|
|
153
|
+
: action.kind === "persuade" ? SALT_PERSUADE
|
|
154
|
+
: SALT_DECEIVE;
|
|
155
|
+
const seed = eventSeed(ctx.worldSeed, ctx.tick, ctx.initiator.id, ctx.target.id, salt);
|
|
156
|
+
const rng = makeRng(seed, SCALE.Q);
|
|
157
|
+
if (rng.q01() < P) {
|
|
158
|
+
if (action.kind === "intimidate") {
|
|
159
|
+
return { result: "success", fearDelta: INTIMIDATE_FEAR_DELTA };
|
|
160
|
+
}
|
|
161
|
+
return { result: "success" };
|
|
162
|
+
}
|
|
163
|
+
// Failure branches
|
|
164
|
+
if (action.kind === "intimidate") {
|
|
165
|
+
// Fearless targets interpret intimidation as an insult.
|
|
166
|
+
if (ctx.target.condition.fearQ < ESCALATE_THRESHOLD) {
|
|
167
|
+
return { result: "escalate" };
|
|
168
|
+
}
|
|
169
|
+
return { result: "failure", cooldown_s: 30 };
|
|
170
|
+
}
|
|
171
|
+
const cooldown = action.kind === "persuade" ? 60 : 120;
|
|
172
|
+
return { result: "failure", cooldown_s: cooldown };
|
|
173
|
+
}
|
|
174
|
+
// ── Outcome application ───────────────────────────────────────────────────────
|
|
175
|
+
/**
|
|
176
|
+
* Write a successful outcome's deltas back to the target entity.
|
|
177
|
+
*
|
|
178
|
+
* - `fearDelta` — added directly to `target.condition.fearQ` (positive = more fear).
|
|
179
|
+
* - `moraleDelta` — subtracted from `target.condition.fearQ` (positive morale = less fear).
|
|
180
|
+
* - `setSurrendered` — sets `target.condition.surrendered = true`.
|
|
181
|
+
*
|
|
182
|
+
* No-op if `outcome.result !== "success"`.
|
|
183
|
+
*/
|
|
184
|
+
export function applyDialogueOutcome(outcome, target) {
|
|
185
|
+
if (outcome.result !== "success")
|
|
186
|
+
return;
|
|
187
|
+
if (outcome.fearDelta !== undefined) {
|
|
188
|
+
target.condition.fearQ = clampQ(target.condition.fearQ + outcome.fearDelta, 0, SCALE.Q);
|
|
189
|
+
}
|
|
190
|
+
if (outcome.moraleDelta !== undefined) {
|
|
191
|
+
target.condition.fearQ = clampQ(target.condition.fearQ - outcome.moraleDelta, 0, SCALE.Q);
|
|
192
|
+
}
|
|
193
|
+
if (outcome.setSurrendered) {
|
|
194
|
+
target.condition.surrendered = true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// ── Narrative ─────────────────────────────────────────────────────────────────
|
|
198
|
+
/** Short display label per action kind. */
|
|
199
|
+
function actionLabel(action) {
|
|
200
|
+
switch (action.kind) {
|
|
201
|
+
case "intimidate": return "Intimidation";
|
|
202
|
+
case "persuade": return "Persuasion";
|
|
203
|
+
case "deceive": return "Deception";
|
|
204
|
+
case "surrender": return "Surrender demand";
|
|
205
|
+
case "negotiate": return "Negotiation";
|
|
206
|
+
case "signal": return "Cross-species signal";
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/** Extended description for verbose mode. */
|
|
210
|
+
function verboseDetail(action, outcome) {
|
|
211
|
+
const res = outcome.result;
|
|
212
|
+
switch (action.kind) {
|
|
213
|
+
case "intimidate":
|
|
214
|
+
return res === "success" ? "the target was cowed by the show of force"
|
|
215
|
+
: res === "escalate" ? "the fearless target took it as an insult and attacked"
|
|
216
|
+
: "the target stood firm and was not intimidated";
|
|
217
|
+
case "persuade":
|
|
218
|
+
return res === "success" ? "the argument was accepted after careful consideration"
|
|
219
|
+
: "the target remained unconvinced";
|
|
220
|
+
case "deceive":
|
|
221
|
+
return res === "success" ? "the false claim was believed"
|
|
222
|
+
: "the target detected the deception";
|
|
223
|
+
case "surrender":
|
|
224
|
+
return res === "success" ? "the target laid down arms"
|
|
225
|
+
: "the target refused to surrender";
|
|
226
|
+
case "negotiate":
|
|
227
|
+
return res === "success" ? "both parties agreed to the exchange"
|
|
228
|
+
: "the offer was rejected as unfavourable";
|
|
229
|
+
case "signal":
|
|
230
|
+
return res === "success" ? `the ${action.targetSpecies} understood the ${action.intent} signal`
|
|
231
|
+
: res === "failure" && outcome.aggravated ? `the ${action.targetSpecies} was aggravated by the signal`
|
|
232
|
+
: `the ${action.targetSpecies} did not comprehend the signal`;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Produce a human-readable description of a dialogue action and its outcome.
|
|
237
|
+
*
|
|
238
|
+
* Verbosity levels:
|
|
239
|
+
* - `terse` — single word label + result token (shortest)
|
|
240
|
+
* - `normal` — one sentence with action and outcome context
|
|
241
|
+
* - `verbose` — entity names + extended description of what happened
|
|
242
|
+
*
|
|
243
|
+
* @param ids — optional initiator/target entity ids used to resolve names from `cfg.nameMap`
|
|
244
|
+
*/
|
|
245
|
+
export function narrateDialogue(action, outcome, cfg, ids) {
|
|
246
|
+
const label = actionLabel(action);
|
|
247
|
+
const res = outcome.result;
|
|
248
|
+
if (cfg.verbosity === "terse") {
|
|
249
|
+
return `${label}: ${res}`;
|
|
250
|
+
}
|
|
251
|
+
const iName = ids?.initiatorId !== undefined
|
|
252
|
+
? (cfg.nameMap?.get(ids.initiatorId) ?? `entity ${ids.initiatorId}`)
|
|
253
|
+
: "the initiator";
|
|
254
|
+
const tName = ids?.targetId !== undefined
|
|
255
|
+
? (cfg.nameMap?.get(ids.targetId) ?? `entity ${ids.targetId}`)
|
|
256
|
+
: "the target";
|
|
257
|
+
if (cfg.verbosity === "normal") {
|
|
258
|
+
const resultPhrase = res === "success" ? "succeeded"
|
|
259
|
+
: res === "escalate" ? "provoked hostility"
|
|
260
|
+
: "failed";
|
|
261
|
+
return `${iName}'s ${label.toLowerCase()} against ${tName} ${resultPhrase}.`;
|
|
262
|
+
}
|
|
263
|
+
// verbose
|
|
264
|
+
const detail = verboseDetail(action, outcome);
|
|
265
|
+
return `${iName} attempted ${label.toLowerCase()} on ${tName} — ${detail}.`;
|
|
266
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Q } from "./units.js";
|
|
2
|
+
export interface RngLike {
|
|
3
|
+
q01(): Q;
|
|
4
|
+
}
|
|
5
|
+
export declare function tri01(rng: RngLike): Q;
|
|
6
|
+
export declare function triSym(rng: RngLike): Q;
|
|
7
|
+
export declare function mulFromVariation(variationSym: Q, amplitude: Q): Q;
|
|
8
|
+
/**
|
|
9
|
+
* Symmetric triangular sample shifted by `bias × 0.5` of half-range, then
|
|
10
|
+
* clamped back to the natural triSym bounds `[−SCALE.Q/2, SCALE.Q/2]`.
|
|
11
|
+
*
|
|
12
|
+
* `bias` ∈ [−1, 1]:
|
|
13
|
+
* +1 strongly skews toward high-end attribute values
|
|
14
|
+
* −1 strongly skews toward low-end attribute values
|
|
15
|
+
* 0 is identical to an unbiased `triSym(rng)` call
|
|
16
|
+
*
|
|
17
|
+
* Used by `generateIndividual` to implement `NarrativeBias`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function biasedTriSym(rng: RngLike, bias: number): Q;
|
|
20
|
+
export declare function skewUp(mult: Q, steps: number): Q;
|