@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,187 @@
|
|
|
1
|
+
// src/competence/naturalist.ts — Phase 35: Naturalist Intelligence & Animal Handling
|
|
2
|
+
//
|
|
3
|
+
// Naturalist intelligence governs pattern recognition in living organisms:
|
|
4
|
+
// - Tracking: following quarry via environmental signs
|
|
5
|
+
// - Foraging: finding edible/medicinal plants, avoiding misidentification
|
|
6
|
+
// - Taming: building trust with animals across species boundaries
|
|
7
|
+
//
|
|
8
|
+
// No kernel import — pure resolution module.
|
|
9
|
+
import { SCALE, q, clampQ, qMul, mulDiv } from "../units.js";
|
|
10
|
+
import { makeRng } from "../rng.js";
|
|
11
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
12
|
+
/** Base track visibility range in metres at naturalist q(0.50), ideal conditions. */
|
|
13
|
+
const BASE_TRACK_RANGE_m = 100;
|
|
14
|
+
/** Age degradation: track freshness multiplier (seconds). */
|
|
15
|
+
const AGE_MUL = {
|
|
16
|
+
ideal: { decayPerHour: 0.05, floor: q(0.80) },
|
|
17
|
+
rain: { decayPerHour: 0.25, floor: q(0.30) },
|
|
18
|
+
urban: { decayPerHour: 0.15, floor: q(0.40) },
|
|
19
|
+
deep_water: { decayPerHour: 0.50, floor: q(0.10) },
|
|
20
|
+
forest: { decayPerHour: 0.10, floor: q(0.50) }, // leaves/debris preserve tracks moderately
|
|
21
|
+
};
|
|
22
|
+
/** Species difficulty multiplier for tracking (default q(1.0)). */
|
|
23
|
+
const SPECIES_TRACK_MUL = {
|
|
24
|
+
human: q(1.00),
|
|
25
|
+
elf: q(1.10),
|
|
26
|
+
dwarf: q(0.90),
|
|
27
|
+
halfling: q(0.85),
|
|
28
|
+
orc: q(1.05),
|
|
29
|
+
ogre: q(1.20),
|
|
30
|
+
goblin: q(0.80),
|
|
31
|
+
troll: q(1.30),
|
|
32
|
+
dragon: q(0.70), // rare, distinctive scent
|
|
33
|
+
wolf: q(1.15),
|
|
34
|
+
deer: q(1.10),
|
|
35
|
+
bear: q(1.25),
|
|
36
|
+
};
|
|
37
|
+
/** Species affinity bonus for tracking known species. */
|
|
38
|
+
const SPECIES_AFFINITY_BONUS = q(0.15);
|
|
39
|
+
/** Biome yield multipliers (base items per hour at naturalist q(0.50)). */
|
|
40
|
+
const BIOME_YIELD_BASE = {
|
|
41
|
+
forest: 3.0,
|
|
42
|
+
plains: 2.0,
|
|
43
|
+
desert: 0.5,
|
|
44
|
+
swamp: 2.5,
|
|
45
|
+
mountain: 1.0,
|
|
46
|
+
};
|
|
47
|
+
/** Season multipliers affecting foraging yield. */
|
|
48
|
+
const SEASON_MUL = {
|
|
49
|
+
spring: q(1.20),
|
|
50
|
+
summer: q(1.00),
|
|
51
|
+
autumn: q(0.90),
|
|
52
|
+
winter: q(0.40),
|
|
53
|
+
};
|
|
54
|
+
/** Threshold for reliable tracking confidence. */
|
|
55
|
+
const TRACKING_CONFIDENCE_THRESHOLD = q(0.60);
|
|
56
|
+
/** Trust threshold for fully tamed animal. */
|
|
57
|
+
const FULLY_TAMED_THRESHOLD = q(0.90);
|
|
58
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
59
|
+
/**
|
|
60
|
+
* Get entity's species affinity list (for tracking/taming bonuses).
|
|
61
|
+
* Currently derived from species id in a simplified manner.
|
|
62
|
+
*/
|
|
63
|
+
function getSpeciesAffinity(entity) {
|
|
64
|
+
// Simplified: could be expanded with a proper affinity system
|
|
65
|
+
const baseAffinity = [];
|
|
66
|
+
// Add natural affinities based on archetype patterns
|
|
67
|
+
if (entity.attributes.cognition?.naturalist ?? 0 > q(0.60)) {
|
|
68
|
+
baseAffinity.push("wolf", "deer", "bear"); // high naturalist = forest affinity
|
|
69
|
+
}
|
|
70
|
+
return baseAffinity;
|
|
71
|
+
}
|
|
72
|
+
// ── Public API ────────────────────────────────────────────────────────────────
|
|
73
|
+
/**
|
|
74
|
+
* Resolve a tracking attempt.
|
|
75
|
+
*
|
|
76
|
+
* Formula:
|
|
77
|
+
* confidence_Q = naturalist × ageMul × terrainMul × speciesMul
|
|
78
|
+
* If quarrySpecies in entity's speciesAffinity: +q(0.15)
|
|
79
|
+
* trackRange_m = baseRange × confidence_Q (with floor at naturalist × 10m)
|
|
80
|
+
*
|
|
81
|
+
* @param entity - The tracker; uses `cognition.naturalist`.
|
|
82
|
+
* @param spec - Tracking specification.
|
|
83
|
+
* @param _seed - Reserved for future variance; currently unused.
|
|
84
|
+
*/
|
|
85
|
+
export function resolveTracking(entity, spec, _seed) {
|
|
86
|
+
const naturalist = (entity.attributes.cognition?.naturalist ?? q(0.50));
|
|
87
|
+
// Age multiplier: degrades over time, with terrain-specific floor
|
|
88
|
+
const hoursOld = spec.trackAge_s / 3600;
|
|
89
|
+
const ageConfig = AGE_MUL[spec.terrain];
|
|
90
|
+
const ageDegradation = Math.min(1.0, hoursOld * ageConfig.decayPerHour);
|
|
91
|
+
const ageMul = clampQ((q(1.0) - Math.trunc(ageDegradation * (q(1.0) - ageConfig.floor))), q(0.10), q(1.0));
|
|
92
|
+
// Species multiplier
|
|
93
|
+
const speciesMul = SPECIES_TRACK_MUL[spec.quarrySpecies] ?? q(1.0);
|
|
94
|
+
// Base confidence
|
|
95
|
+
let confidence_Q = qMul(qMul(naturalist, ageMul), speciesMul);
|
|
96
|
+
// Species affinity bonus
|
|
97
|
+
const affinity = getSpeciesAffinity(entity);
|
|
98
|
+
if (affinity.includes(spec.quarrySpecies)) {
|
|
99
|
+
confidence_Q = clampQ((confidence_Q + SPECIES_AFFINITY_BONUS), q(0), q(1.0));
|
|
100
|
+
}
|
|
101
|
+
// Track range scales with confidence and naturalist ability
|
|
102
|
+
// Floor ensures even low-confidence trackers can follow obvious trails
|
|
103
|
+
const rangeMul = Math.max(confidence_Q, mulDiv(naturalist, q(0.20), SCALE.Q));
|
|
104
|
+
const trackRange_m = Math.round(BASE_TRACK_RANGE_m * rangeMul / SCALE.Q);
|
|
105
|
+
return { confidence_Q, trackRange_m };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Resolve a foraging/herbalism attempt.
|
|
109
|
+
*
|
|
110
|
+
* Formula:
|
|
111
|
+
* yield = baseYield × naturalist × seasonMul × searchHours
|
|
112
|
+
* herbQuality_Q = clamp(naturalist × randomFactor, 0, 1)
|
|
113
|
+
* P_misidentified = max(0, 0.30 − naturalist × 0.40)
|
|
114
|
+
* (troll naturalist 0.50 → ~10%; elf naturalist 0.78 → ~0%)
|
|
115
|
+
*
|
|
116
|
+
* @param entity - The forager; uses `cognition.naturalist`.
|
|
117
|
+
* @param spec - Foraging specification.
|
|
118
|
+
* @param seed - Deterministic seed for quality variance.
|
|
119
|
+
*/
|
|
120
|
+
export function resolveForaging(entity, spec, seed) {
|
|
121
|
+
const naturalist = (entity.attributes.cognition?.naturalist ?? q(0.50));
|
|
122
|
+
// Base yield calculation
|
|
123
|
+
const baseYield = BIOME_YIELD_BASE[spec.biome];
|
|
124
|
+
const seasonQ = SEASON_MUL[spec.season];
|
|
125
|
+
// itemsFound = base × naturalist(normalized) × season × hours
|
|
126
|
+
const naturalistNorm = mulDiv(naturalist, q(1.0), SCALE.Q); // 0-1 range
|
|
127
|
+
const yieldFloat = baseYield * naturalistNorm * (seasonQ / SCALE.Q) * spec.searchHours;
|
|
128
|
+
const itemsFound = Math.max(0, Math.round(yieldFloat * 10) / 10); // 1 decimal place
|
|
129
|
+
// Herb quality: naturalist × RNG factor (roll in [0.70, 1.10] range)
|
|
130
|
+
const rng = makeRng(seed, SCALE.Q);
|
|
131
|
+
const q01Roll = rng.q01(); // [0, SCALE.Q-1]
|
|
132
|
+
const qualityRoll = clampQ((q(0.70) + mulDiv(q01Roll, q(0.40), SCALE.Q)), q(0.70), q(1.10));
|
|
133
|
+
const herbQuality_Q = clampQ(mulDiv(naturalist, qualityRoll, SCALE.Q), q(0), q(1.0));
|
|
134
|
+
// Misidentification probability: max(0, 0.30 − naturalist × 0.40)
|
|
135
|
+
// At naturalist 0.50 (q(0.50)=5000): 0.30 − 0.50 × 0.40 = 0.30 − 0.20 = 0.10 (10%)
|
|
136
|
+
// At naturalist 0.75: 0.30 − 0.30 = 0 (0%)
|
|
137
|
+
const naturalistFloat = naturalistNorm;
|
|
138
|
+
const pMisidentify = Math.max(0, 0.30 - naturalistFloat * 0.40);
|
|
139
|
+
const misidentifyRoll = (rng.q01() / SCALE.Q);
|
|
140
|
+
const misidentified = misidentifyRoll < pMisidentify;
|
|
141
|
+
return { itemsFound, herbQuality_Q, misidentified };
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Resolve an animal taming attempt.
|
|
145
|
+
*
|
|
146
|
+
* Formula:
|
|
147
|
+
* trust_Q = clamp(naturalist × interSpecies × effortFactor − animalFearQ × 0.50, 0, 1)
|
|
148
|
+
* attacked = RNG < (animalFearQ − trust_Q) × 0.30 (high fear + low trust = danger)
|
|
149
|
+
*
|
|
150
|
+
* Full taming (trust_Q ≥ q(0.90)) makes the animal available as an ally.
|
|
151
|
+
*
|
|
152
|
+
* @param entity - The handler; uses `cognition.naturalist` and `cognition.interSpecies`.
|
|
153
|
+
* @param spec - Taming specification.
|
|
154
|
+
* @param seed - Deterministic seed for attack check.
|
|
155
|
+
*/
|
|
156
|
+
export function resolveTaming(entity, spec, seed) {
|
|
157
|
+
const naturalist = (entity.attributes.cognition?.naturalist ?? q(0.50));
|
|
158
|
+
const interSpecies = (entity.attributes.cognition?.interSpecies ?? q(0.35));
|
|
159
|
+
// Base trust from naturalist × interSpecies × effort
|
|
160
|
+
const baseTrust = qMul(qMul(naturalist, interSpecies), spec.effortFactor);
|
|
161
|
+
// Fear penalty: high fear reduces trust
|
|
162
|
+
const fearPenalty = mulDiv(spec.animalFearQ, q(0.50), SCALE.Q);
|
|
163
|
+
// Prior successes bonus: +5% per success, max +25%
|
|
164
|
+
const experienceBonus = Math.min(spec.priorSuccesses * q(0.05), q(0.25));
|
|
165
|
+
let trust_Q = clampQ((baseTrust - fearPenalty + experienceBonus), q(0), q(1.0));
|
|
166
|
+
// Attack check: high fear with low trust is dangerous
|
|
167
|
+
// P(attack) = max(0, (animalFearQ − trust_Q) × 0.30)
|
|
168
|
+
const fearFloat = mulDiv(spec.animalFearQ, q(1.0), SCALE.Q);
|
|
169
|
+
const trustFloat = mulDiv(trust_Q, q(1.0), SCALE.Q);
|
|
170
|
+
const pAttack = Math.max(0, (fearFloat - trustFloat) * 0.30);
|
|
171
|
+
const rng = makeRng(seed, SCALE.Q);
|
|
172
|
+
const attackRoll = rng.q01() / SCALE.Q;
|
|
173
|
+
const attacked = attackRoll < pAttack;
|
|
174
|
+
return { trust_Q, attacked };
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Check if an animal is fully tamed (available as ally).
|
|
178
|
+
*/
|
|
179
|
+
export function isFullyTamed(trust_Q) {
|
|
180
|
+
return trust_Q >= FULLY_TAMED_THRESHOLD;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Check if tracking confidence is reliable.
|
|
184
|
+
*/
|
|
185
|
+
export function isTrackingReliable(confidence_Q) {
|
|
186
|
+
return confidence_Q >= TRACKING_CONFIDENCE_THRESHOLD;
|
|
187
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Q } from "../units.js";
|
|
2
|
+
import type { Entity } from "../sim/entity.js";
|
|
3
|
+
export interface NavigationSpec {
|
|
4
|
+
/** Journey distance in metres (real number). */
|
|
5
|
+
distance_m: number;
|
|
6
|
+
terrain: "road" | "forest" | "mountain" | "urban" | "sea";
|
|
7
|
+
/** True = navigator has a map or chart of the area. */
|
|
8
|
+
hasMap: boolean;
|
|
9
|
+
visibility: "clear" | "fog" | "night";
|
|
10
|
+
}
|
|
11
|
+
export interface NavigationOutcome {
|
|
12
|
+
/** 1.0 = optimal route; lower = detours taken. Clamped to [q(0.50), q(1.0)]. */
|
|
13
|
+
routeEfficiency: Q;
|
|
14
|
+
/** Additional seconds lost to detours vs the optimal route. */
|
|
15
|
+
timeLost_s: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Resolve a navigation attempt.
|
|
19
|
+
*
|
|
20
|
+
* @param entity - The navigator; uses `cognition.spatial`.
|
|
21
|
+
* @param spec - Navigation specification.
|
|
22
|
+
* @param seed - Reserved for future variance; currently unused.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveNavigation(entity: Entity, spec: NavigationSpec, _seed: number): NavigationOutcome;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// src/competence/navigation.ts — Phase 34: Spatial Intelligence Non-Combat Applications
|
|
2
|
+
//
|
|
3
|
+
// Navigation and pathfinding model:
|
|
4
|
+
// routeEfficiency = clamp(spatial × terrainQ × mapBonus × visibilityQ, q(0.50), q(1.0))
|
|
5
|
+
// timeLost_s = baseTime_s × (1 / routeEfficiency − 1)
|
|
6
|
+
// where baseTime_s = distance_m / BASE_TRAVEL_SPEED_mps
|
|
7
|
+
//
|
|
8
|
+
// No kernel import — pure resolution module.
|
|
9
|
+
import { SCALE, q, clampQ, qMul, mulDiv } from "../units.js";
|
|
10
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
11
|
+
const TERRAIN_Q = {
|
|
12
|
+
road: q(1.00),
|
|
13
|
+
urban: q(0.95),
|
|
14
|
+
forest: q(0.85),
|
|
15
|
+
mountain: q(0.75),
|
|
16
|
+
sea: q(0.70),
|
|
17
|
+
};
|
|
18
|
+
const VIS_Q = {
|
|
19
|
+
clear: q(1.00),
|
|
20
|
+
fog: q(0.85),
|
|
21
|
+
night: q(0.75),
|
|
22
|
+
};
|
|
23
|
+
/** Default foot-travel speed [m/s] used to derive base journey time. */
|
|
24
|
+
const BASE_TRAVEL_SPEED_mps = 1.4;
|
|
25
|
+
// ── Public API ────────────────────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* Resolve a navigation attempt.
|
|
28
|
+
*
|
|
29
|
+
* @param entity - The navigator; uses `cognition.spatial`.
|
|
30
|
+
* @param spec - Navigation specification.
|
|
31
|
+
* @param seed - Reserved for future variance; currently unused.
|
|
32
|
+
*/
|
|
33
|
+
export function resolveNavigation(entity, spec, _seed) {
|
|
34
|
+
const spatial = (entity.attributes.cognition?.spatial ?? q(0.50));
|
|
35
|
+
const terrainQ = TERRAIN_Q[spec.terrain];
|
|
36
|
+
const visQ = VIS_Q[spec.visibility];
|
|
37
|
+
const mapBonus = spec.hasMap ? q(1.10) : q(0.90);
|
|
38
|
+
// Compose all modifiers: spatial × mapBonus × terrainQ × visibilityQ
|
|
39
|
+
const rawEfficiency = qMul(qMul(qMul(spatial, mapBonus), terrainQ), visQ);
|
|
40
|
+
const routeEfficiency = clampQ(rawEfficiency, q(0.50), q(1.0));
|
|
41
|
+
// timeLost = baseTravelTime × (1/efficiency − 1)
|
|
42
|
+
// = baseTravelTime × (SCALE.Q − efficiency) / efficiency
|
|
43
|
+
const baseTime_s = spec.distance_m / BASE_TRAVEL_SPEED_mps;
|
|
44
|
+
const timeLost_s = spec.distance_m > 0
|
|
45
|
+
? Math.round(baseTime_s * mulDiv(SCALE.Q - routeEfficiency, SCALE.Q, routeEfficiency) / SCALE.Q)
|
|
46
|
+
: 0;
|
|
47
|
+
return { routeEfficiency, timeLost_s };
|
|
48
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { Q } from "../units.js";
|
|
2
|
+
import type { Entity } from "../sim/entity.js";
|
|
3
|
+
import type { WillpowerState } from "./willpower.js";
|
|
4
|
+
/** Performance type affecting the style of morale bonus. */
|
|
5
|
+
export type PerformanceType = "march" | "rally" | "dirge" | "celebration" | "lament";
|
|
6
|
+
/** Specification for a performance. */
|
|
7
|
+
export interface PerformanceSpec {
|
|
8
|
+
/** Type of performance. */
|
|
9
|
+
performanceType: PerformanceType;
|
|
10
|
+
/** Duration in seconds. */
|
|
11
|
+
duration_s: number;
|
|
12
|
+
/** Number of allies in range (affects total willpower drain). */
|
|
13
|
+
audienceCount: number;
|
|
14
|
+
/** Base range of the performance in metres. */
|
|
15
|
+
range_m: number;
|
|
16
|
+
}
|
|
17
|
+
/** Outcome of a performance. */
|
|
18
|
+
export interface PerformanceOutcome {
|
|
19
|
+
/** Fear decay bonus per tick (adds to normal fear decay). */
|
|
20
|
+
fearDecayBonus_Q: Q;
|
|
21
|
+
/** Cohesion bonus for the group (0–1). */
|
|
22
|
+
cohesionBonus_Q: Q;
|
|
23
|
+
/** Total willpower drained during performance. */
|
|
24
|
+
willpowerDrained_J: number;
|
|
25
|
+
/** Performance quality descriptor. */
|
|
26
|
+
descriptor: "exceptional" | "good" | "adequate" | "poor";
|
|
27
|
+
/** Whether performance was maintained for full duration. */
|
|
28
|
+
completed: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** Active performance state for ongoing morale effects. */
|
|
31
|
+
export interface ActivePerformance {
|
|
32
|
+
/** Performer entity ID. */
|
|
33
|
+
performerId: number;
|
|
34
|
+
/** Performance type. */
|
|
35
|
+
performanceType: PerformanceType;
|
|
36
|
+
/** Remaining duration in seconds. */
|
|
37
|
+
remaining_s: number;
|
|
38
|
+
/** Fear decay bonus per tick. */
|
|
39
|
+
fearDecayBonus_Q: Q;
|
|
40
|
+
/** Range in metres. */
|
|
41
|
+
range_m: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolve a musical performance.
|
|
45
|
+
*
|
|
46
|
+
* Performance generates sustained morale auras scaled by musical intelligence,
|
|
47
|
+
* draining willpower (Phase 38) from the performer.
|
|
48
|
+
*
|
|
49
|
+
* Formulas:
|
|
50
|
+
* fearDecayBonus = musical × BASE_FEAR_DECAY_BONUS × typeMul
|
|
51
|
+
* cohesionBonus = musical × BASE_COHESION_BONUS × typeMul
|
|
52
|
+
* willpowerDrained = (BASE_DRAIN + audience × AUDIENCE_DRAIN) × duration × typeDrainMul
|
|
53
|
+
*
|
|
54
|
+
* Satyr bard (musical 0.95) → fearDecayBonus ≈ q(0.019), nearly matching a leader aura.
|
|
55
|
+
*
|
|
56
|
+
* @param performer - The entity performing; uses `cognition.musical`.
|
|
57
|
+
* @param spec - Performance specification.
|
|
58
|
+
* @returns Performance outcome with morale bonuses and willpower cost.
|
|
59
|
+
*/
|
|
60
|
+
export declare function resolvePerformance(performer: Entity, spec: PerformanceSpec): PerformanceOutcome;
|
|
61
|
+
/**
|
|
62
|
+
* Step an active performance for one tick.
|
|
63
|
+
* Deducts willpower and returns whether performance can continue.
|
|
64
|
+
*
|
|
65
|
+
* @param performance - Active performance state (mutated).
|
|
66
|
+
* @param willpower - Performer's willpower state (mutated).
|
|
67
|
+
* @param delta_s - Time step in seconds.
|
|
68
|
+
* @returns True if performance can continue, false if willpower depleted.
|
|
69
|
+
*/
|
|
70
|
+
export declare function stepPerformance(performance: ActivePerformance, willpower: WillpowerState, delta_s: number): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Calculate the effective range of a performance.
|
|
73
|
+
*
|
|
74
|
+
* @param performer - The performing entity.
|
|
75
|
+
* @param baseRange - Base range in metres.
|
|
76
|
+
* @returns Effective range in metres.
|
|
77
|
+
*/
|
|
78
|
+
export declare function calculatePerformanceRange(performer: Entity, baseRange?: number): number;
|
|
79
|
+
/**
|
|
80
|
+
* Check if an entity is in range of a performance.
|
|
81
|
+
*
|
|
82
|
+
* @param performance - Active performance.
|
|
83
|
+
* @param performerPos - Performer's position.
|
|
84
|
+
* @param listenerPos - Potential listener's position.
|
|
85
|
+
* @returns True if listener is within performance range.
|
|
86
|
+
*/
|
|
87
|
+
export declare function isInPerformanceRange(performance: ActivePerformance, performerPos: {
|
|
88
|
+
x: number;
|
|
89
|
+
y: number;
|
|
90
|
+
z: number;
|
|
91
|
+
}, listenerPos: {
|
|
92
|
+
x: number;
|
|
93
|
+
y: number;
|
|
94
|
+
z: number;
|
|
95
|
+
}): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Create an active performance state.
|
|
98
|
+
*
|
|
99
|
+
* @param performerId - Entity ID of the performer.
|
|
100
|
+
* @param performanceType - Type of performance.
|
|
101
|
+
* @param duration_s - Duration in seconds.
|
|
102
|
+
* @param performer - The performing entity (for musical bonus calculation).
|
|
103
|
+
* @returns Active performance state.
|
|
104
|
+
*/
|
|
105
|
+
export declare function createActivePerformance(performerId: number, performanceType: PerformanceType, duration_s: number, performer: Entity): ActivePerformance;
|
|
106
|
+
/**
|
|
107
|
+
* Check if an entity can effectively perform.
|
|
108
|
+
* Requires minimum musical intelligence and available willpower.
|
|
109
|
+
*
|
|
110
|
+
* @param entity - The potential performer.
|
|
111
|
+
* @param willpower - The entity's willpower state.
|
|
112
|
+
* @param minMusical - Minimum musical intelligence required.
|
|
113
|
+
* @returns True if entity can perform.
|
|
114
|
+
*/
|
|
115
|
+
export declare function canPerform(entity: Entity, willpower: WillpowerState, minMusical?: Q): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Estimate performance effectiveness without consuming resources.
|
|
118
|
+
*
|
|
119
|
+
* @param performer - The potential performer.
|
|
120
|
+
* @param performanceType - Type of performance.
|
|
121
|
+
* @param duration_s - Expected duration.
|
|
122
|
+
* @param audienceCount - Expected audience size.
|
|
123
|
+
* @returns Estimated outcome values.
|
|
124
|
+
*/
|
|
125
|
+
export declare function estimatePerformance(performer: Entity, performanceType: PerformanceType, duration_s: number, audienceCount: number): Omit<PerformanceOutcome, "completed">;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// src/competence/performance.ts — Phase 39: Musical Performance as Morale Vector
|
|
2
|
+
//
|
|
3
|
+
// Performance generates sustained morale effects scaled by musical intelligence,
|
|
4
|
+
// draining willpower (Phase 38) to maintain.
|
|
5
|
+
//
|
|
6
|
+
// No kernel import — pure resolution module.
|
|
7
|
+
import { SCALE, q, clampQ } from "../units.js";
|
|
8
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
9
|
+
/** Base fear decay bonus per tick at musical q(1.0). */
|
|
10
|
+
const BASE_FEAR_DECAY_BONUS = q(0.020);
|
|
11
|
+
/** Base cohesion bonus per performance. */
|
|
12
|
+
const BASE_COHESION_BONUS = q(0.010);
|
|
13
|
+
/** Willpower drain per second of performance (at base quality). */
|
|
14
|
+
const BASE_WILLPOWER_DRAIN_PER_SECOND = 50; // 50 J/s
|
|
15
|
+
/** Willpower drain per audience member per second. */
|
|
16
|
+
const AUDIENCE_DRAIN_PER_SECOND = 10; // 10 J/s per ally
|
|
17
|
+
/** Maximum effective range for performance effects. */
|
|
18
|
+
const MAX_PERFORMANCE_RANGE_m = 100;
|
|
19
|
+
/** Minimum range for any performance effect. */
|
|
20
|
+
const MIN_PERFORMANCE_RANGE_m = 10;
|
|
21
|
+
// Performance type multipliers
|
|
22
|
+
const PERFORMANCE_TYPE_MULTIPLIERS = {
|
|
23
|
+
march: { fearMul: 0.5, cohesionMul: 1.5, drainMul: 0.8 },
|
|
24
|
+
rally: { fearMul: 1.5, cohesionMul: 1.0, drainMul: 1.2 },
|
|
25
|
+
dirge: { fearMul: 0.8, cohesionMul: 0.5, drainMul: 0.6 },
|
|
26
|
+
celebration: { fearMul: 1.0, cohesionMul: 1.5, drainMul: 1.0 },
|
|
27
|
+
lament: { fearMul: 1.2, cohesionMul: 0.3, drainMul: 0.7 },
|
|
28
|
+
};
|
|
29
|
+
// ── Public API ────────────────────────────────────────────────────────────────
|
|
30
|
+
/**
|
|
31
|
+
* Resolve a musical performance.
|
|
32
|
+
*
|
|
33
|
+
* Performance generates sustained morale auras scaled by musical intelligence,
|
|
34
|
+
* draining willpower (Phase 38) from the performer.
|
|
35
|
+
*
|
|
36
|
+
* Formulas:
|
|
37
|
+
* fearDecayBonus = musical × BASE_FEAR_DECAY_BONUS × typeMul
|
|
38
|
+
* cohesionBonus = musical × BASE_COHESION_BONUS × typeMul
|
|
39
|
+
* willpowerDrained = (BASE_DRAIN + audience × AUDIENCE_DRAIN) × duration × typeDrainMul
|
|
40
|
+
*
|
|
41
|
+
* Satyr bard (musical 0.95) → fearDecayBonus ≈ q(0.019), nearly matching a leader aura.
|
|
42
|
+
*
|
|
43
|
+
* @param performer - The entity performing; uses `cognition.musical`.
|
|
44
|
+
* @param spec - Performance specification.
|
|
45
|
+
* @returns Performance outcome with morale bonuses and willpower cost.
|
|
46
|
+
*/
|
|
47
|
+
export function resolvePerformance(performer, spec) {
|
|
48
|
+
const musical = (performer.attributes.cognition?.musical ?? q(0.50));
|
|
49
|
+
const musicalNorm = musical / SCALE.Q;
|
|
50
|
+
// Type multipliers
|
|
51
|
+
const typeMuls = PERFORMANCE_TYPE_MULTIPLIERS[spec.performanceType];
|
|
52
|
+
// Calculate fear decay bonus
|
|
53
|
+
const rawFearBonus = musicalNorm * (BASE_FEAR_DECAY_BONUS / SCALE.Q) * typeMuls.fearMul;
|
|
54
|
+
const fearDecayBonus_Q = clampQ(Math.round(rawFearBonus * SCALE.Q), q(0), q(0.050));
|
|
55
|
+
// Calculate cohesion bonus
|
|
56
|
+
const rawCohesionBonus = musicalNorm * (BASE_COHESION_BONUS / SCALE.Q) * typeMuls.cohesionMul;
|
|
57
|
+
const cohesionBonus_Q = clampQ(Math.round(rawCohesionBonus * SCALE.Q), q(0), SCALE.Q);
|
|
58
|
+
// Calculate willpower drain
|
|
59
|
+
const baseDrain = BASE_WILLPOWER_DRAIN_PER_SECOND + spec.audienceCount * AUDIENCE_DRAIN_PER_SECOND;
|
|
60
|
+
const willpowerDrained_J = Math.round(baseDrain * spec.duration_s * typeMuls.drainMul);
|
|
61
|
+
// Determine descriptor based on musical skill
|
|
62
|
+
let descriptor;
|
|
63
|
+
if (musical >= q(0.85)) {
|
|
64
|
+
descriptor = "exceptional";
|
|
65
|
+
}
|
|
66
|
+
else if (musical >= q(0.65)) {
|
|
67
|
+
descriptor = "good";
|
|
68
|
+
}
|
|
69
|
+
else if (musical >= q(0.45)) {
|
|
70
|
+
descriptor = "adequate";
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
descriptor = "poor";
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
fearDecayBonus_Q,
|
|
77
|
+
cohesionBonus_Q,
|
|
78
|
+
willpowerDrained_J,
|
|
79
|
+
descriptor,
|
|
80
|
+
completed: true, // assumes full duration completed
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Step an active performance for one tick.
|
|
85
|
+
* Deducts willpower and returns whether performance can continue.
|
|
86
|
+
*
|
|
87
|
+
* @param performance - Active performance state (mutated).
|
|
88
|
+
* @param willpower - Performer's willpower state (mutated).
|
|
89
|
+
* @param delta_s - Time step in seconds.
|
|
90
|
+
* @returns True if performance can continue, false if willpower depleted.
|
|
91
|
+
*/
|
|
92
|
+
export function stepPerformance(performance, willpower, delta_s) {
|
|
93
|
+
const typeMuls = PERFORMANCE_TYPE_MULTIPLIERS[performance.performanceType];
|
|
94
|
+
const drainRate = BASE_WILLPOWER_DRAIN_PER_SECOND * typeMuls.drainMul;
|
|
95
|
+
const drainAmount = Math.round(drainRate * delta_s);
|
|
96
|
+
if (willpower.current_J < drainAmount) {
|
|
97
|
+
// Insufficient willpower - performance stops
|
|
98
|
+
willpower.current_J = Math.max(0, willpower.current_J);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
willpower.current_J -= drainAmount;
|
|
102
|
+
performance.remaining_s = Math.max(0, performance.remaining_s - delta_s);
|
|
103
|
+
return performance.remaining_s > 0;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Calculate the effective range of a performance.
|
|
107
|
+
*
|
|
108
|
+
* @param performer - The performing entity.
|
|
109
|
+
* @param baseRange - Base range in metres.
|
|
110
|
+
* @returns Effective range in metres.
|
|
111
|
+
*/
|
|
112
|
+
export function calculatePerformanceRange(performer, baseRange = 50) {
|
|
113
|
+
const musical = (performer.attributes.cognition?.musical ?? q(0.50));
|
|
114
|
+
const musicalNorm = musical / SCALE.Q;
|
|
115
|
+
// Musical skill increases effective range
|
|
116
|
+
const rangeMultiplier = 0.5 + musicalNorm; // 0.5 to 1.5x range
|
|
117
|
+
const effectiveRange = Math.round(baseRange * rangeMultiplier);
|
|
118
|
+
return clampQ(effectiveRange, MIN_PERFORMANCE_RANGE_m, MAX_PERFORMANCE_RANGE_m);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Check if an entity is in range of a performance.
|
|
122
|
+
*
|
|
123
|
+
* @param performance - Active performance.
|
|
124
|
+
* @param performerPos - Performer's position.
|
|
125
|
+
* @param listenerPos - Potential listener's position.
|
|
126
|
+
* @returns True if listener is within performance range.
|
|
127
|
+
*/
|
|
128
|
+
export function isInPerformanceRange(performance, performerPos, listenerPos) {
|
|
129
|
+
const dx = (performerPos.x - listenerPos.x) / SCALE.m;
|
|
130
|
+
const dy = (performerPos.y - listenerPos.y) / SCALE.m;
|
|
131
|
+
const dz = (performerPos.z - listenerPos.z) / SCALE.m;
|
|
132
|
+
const dist_m = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
133
|
+
return dist_m <= performance.range_m;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Create an active performance state.
|
|
137
|
+
*
|
|
138
|
+
* @param performerId - Entity ID of the performer.
|
|
139
|
+
* @param performanceType - Type of performance.
|
|
140
|
+
* @param duration_s - Duration in seconds.
|
|
141
|
+
* @param performer - The performing entity (for musical bonus calculation).
|
|
142
|
+
* @returns Active performance state.
|
|
143
|
+
*/
|
|
144
|
+
export function createActivePerformance(performerId, performanceType, duration_s, performer) {
|
|
145
|
+
const outcome = resolvePerformance(performer, {
|
|
146
|
+
performanceType,
|
|
147
|
+
duration_s,
|
|
148
|
+
audienceCount: 0, // Will be updated dynamically
|
|
149
|
+
range_m: calculatePerformanceRange(performer),
|
|
150
|
+
});
|
|
151
|
+
return {
|
|
152
|
+
performerId,
|
|
153
|
+
performanceType,
|
|
154
|
+
remaining_s: duration_s,
|
|
155
|
+
fearDecayBonus_Q: outcome.fearDecayBonus_Q,
|
|
156
|
+
range_m: calculatePerformanceRange(performer),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Check if an entity can effectively perform.
|
|
161
|
+
* Requires minimum musical intelligence and available willpower.
|
|
162
|
+
*
|
|
163
|
+
* @param entity - The potential performer.
|
|
164
|
+
* @param willpower - The entity's willpower state.
|
|
165
|
+
* @param minMusical - Minimum musical intelligence required.
|
|
166
|
+
* @returns True if entity can perform.
|
|
167
|
+
*/
|
|
168
|
+
export function canPerform(entity, willpower, minMusical = q(0.35)) {
|
|
169
|
+
const musical = (entity.attributes.cognition?.musical ?? q(0.50));
|
|
170
|
+
return musical >= minMusical && willpower.current_J > BASE_WILLPOWER_DRAIN_PER_SECOND * 10;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Estimate performance effectiveness without consuming resources.
|
|
174
|
+
*
|
|
175
|
+
* @param performer - The potential performer.
|
|
176
|
+
* @param performanceType - Type of performance.
|
|
177
|
+
* @param duration_s - Expected duration.
|
|
178
|
+
* @param audienceCount - Expected audience size.
|
|
179
|
+
* @returns Estimated outcome values.
|
|
180
|
+
*/
|
|
181
|
+
export function estimatePerformance(performer, performanceType, duration_s, audienceCount) {
|
|
182
|
+
const musical = (performer.attributes.cognition?.musical ?? q(0.50));
|
|
183
|
+
const musicalNorm = musical / SCALE.Q;
|
|
184
|
+
const typeMuls = PERFORMANCE_TYPE_MULTIPLIERS[performanceType];
|
|
185
|
+
const rawFearBonus = musicalNorm * (BASE_FEAR_DECAY_BONUS / SCALE.Q) * typeMuls.fearMul;
|
|
186
|
+
const fearDecayBonus_Q = clampQ(Math.round(rawFearBonus * SCALE.Q), q(0), q(0.050));
|
|
187
|
+
const rawCohesionBonus = musicalNorm * (BASE_COHESION_BONUS / SCALE.Q) * typeMuls.cohesionMul;
|
|
188
|
+
const cohesionBonus_Q = clampQ(Math.round(rawCohesionBonus * SCALE.Q), q(0), SCALE.Q);
|
|
189
|
+
const baseDrain = BASE_WILLPOWER_DRAIN_PER_SECOND + audienceCount * AUDIENCE_DRAIN_PER_SECOND;
|
|
190
|
+
const willpowerDrained_J = Math.round(baseDrain * duration_s * typeMuls.drainMul);
|
|
191
|
+
let descriptor;
|
|
192
|
+
if (musical >= q(0.85)) {
|
|
193
|
+
descriptor = "exceptional";
|
|
194
|
+
}
|
|
195
|
+
else if (musical >= q(0.65)) {
|
|
196
|
+
descriptor = "good";
|
|
197
|
+
}
|
|
198
|
+
else if (musical >= q(0.45)) {
|
|
199
|
+
descriptor = "adequate";
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
descriptor = "poor";
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
fearDecayBonus_Q,
|
|
206
|
+
cohesionBonus_Q,
|
|
207
|
+
willpowerDrained_J,
|
|
208
|
+
descriptor,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Q } from "../units.js";
|
|
2
|
+
import type { Entity } from "../sim/entity.js";
|
|
3
|
+
import type { SkillId } from "../sim/skills.js";
|
|
4
|
+
export interface TeachingSpec {
|
|
5
|
+
/** Skill domain being taught. */
|
|
6
|
+
domain: SkillId;
|
|
7
|
+
/** Hours of teaching session. */
|
|
8
|
+
hours: number;
|
|
9
|
+
/** Teacher's interpersonal skill quality (optional override). */
|
|
10
|
+
teacherInterpersonal_Q?: Q;
|
|
11
|
+
/** Learner's natural learning rate (optional override). */
|
|
12
|
+
learnerLearningRate_Q?: Q;
|
|
13
|
+
}
|
|
14
|
+
export interface TeachingOutcome {
|
|
15
|
+
/** XP gained by learner. */
|
|
16
|
+
xpGained: number;
|
|
17
|
+
/** Teacher fatigue in joules. */
|
|
18
|
+
teacherFatigueJ: number;
|
|
19
|
+
/** Teaching quality multiplier (0–1). */
|
|
20
|
+
teachingQuality_Q: Q;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a teaching session and compute XP gain.
|
|
24
|
+
*
|
|
25
|
+
* Formula:
|
|
26
|
+
* xpGained = hours × BASE_XP_RATE × interpersonal(teacher) × learningRate(learner)
|
|
27
|
+
* teacherFatigueJ = hours × BASE_FATIGUE_PER_HOUR × FATIGUE_MULTIPLIER
|
|
28
|
+
*
|
|
29
|
+
* @param teacher - The entity providing instruction.
|
|
30
|
+
* @param learner - The entity receiving instruction.
|
|
31
|
+
* @param spec - Teaching specification.
|
|
32
|
+
* @returns Teaching outcome with XP gained and teacher fatigue.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveTeaching(teacher: Entity, learner: Entity, spec: TeachingSpec): TeachingOutcome;
|
|
35
|
+
/**
|
|
36
|
+
* Compute maximum effective teaching hours based on teacher stamina.
|
|
37
|
+
*/
|
|
38
|
+
export declare function computeMaxTeachingHours(teacher: Entity, availableEnergyJ: number): number;
|
|
39
|
+
/**
|
|
40
|
+
* Check if teacher is qualified to teach a skill.
|
|
41
|
+
* Teacher should have at least journeyman level (skill delta showing improvement).
|
|
42
|
+
*/
|
|
43
|
+
export declare function isQualifiedTeacher(teacher: Entity, domain: SkillId, minimumProficiency?: Q): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Compute probability of detecting deception.
|
|
46
|
+
*
|
|
47
|
+
* Formula:
|
|
48
|
+
* P_detect = clamp(
|
|
49
|
+
* attentionDepth × 0.50 + interpersonal × 0.50 − plausibility,
|
|
50
|
+
* 0, 1)
|
|
51
|
+
*
|
|
52
|
+
* @param detector - The entity attempting to detect deception.
|
|
53
|
+
* @param plausibility_Q - Plausibility of the deception (0–1).
|
|
54
|
+
* @returns Detection probability (0–1).
|
|
55
|
+
*/
|
|
56
|
+
export declare function computeDeceptionDetectionProbability(detector: Entity, plausibility_Q: Q): Q;
|
|
57
|
+
/**
|
|
58
|
+
* Resolve deception detection.
|
|
59
|
+
* @returns True if deception was detected.
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveDeceptionDetection(detector: Entity, plausibility_Q: Q, seed: number): {
|
|
62
|
+
detected: boolean;
|
|
63
|
+
confidence_Q: Q;
|
|
64
|
+
};
|