@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,232 @@
|
|
|
1
|
+
// src/chronicle.ts — Phase 45: Emergent Story Generation — Chronicle System
|
|
2
|
+
//
|
|
3
|
+
// Transforms simulation events into coherent narratives through significance
|
|
4
|
+
// scoring and narrative templates.
|
|
5
|
+
// ── Significance Scoring ───────────────────────────────────────────────────────
|
|
6
|
+
/** Base significance scores by event type. */
|
|
7
|
+
export const SIGNIFICANCE_SCORES = {
|
|
8
|
+
entity_death: 80,
|
|
9
|
+
entity_birth: 30,
|
|
10
|
+
relationship_formed: 40,
|
|
11
|
+
relationship_broken: 50,
|
|
12
|
+
relationship_betrayal: 75,
|
|
13
|
+
quest_completed: 45,
|
|
14
|
+
quest_failed: 40,
|
|
15
|
+
quest_accepted: 20,
|
|
16
|
+
settlement_founded: 60,
|
|
17
|
+
settlement_upgraded: 50,
|
|
18
|
+
settlement_raided: 55,
|
|
19
|
+
settlement_destroyed: 90,
|
|
20
|
+
facility_completed: 35,
|
|
21
|
+
masterwork_crafted: 50,
|
|
22
|
+
first_contact: 70,
|
|
23
|
+
combat_victory: 25,
|
|
24
|
+
combat_defeat: 30,
|
|
25
|
+
rank_promotion: 35,
|
|
26
|
+
legendary_deed: 85,
|
|
27
|
+
tragic_event: 70,
|
|
28
|
+
};
|
|
29
|
+
/** Calculate significance score for an event. */
|
|
30
|
+
export function calculateSignificance(eventType, context) {
|
|
31
|
+
let score = SIGNIFICANCE_SCORES[eventType] ?? 20;
|
|
32
|
+
if (context) {
|
|
33
|
+
if (context.actorWasFamous)
|
|
34
|
+
score += 15;
|
|
35
|
+
if (context.wasFirst)
|
|
36
|
+
score += 20;
|
|
37
|
+
if (context.involvedMultipleParties)
|
|
38
|
+
score += 10;
|
|
39
|
+
if (context.unexpectedOutcome)
|
|
40
|
+
score += 15;
|
|
41
|
+
if (context.lastingConsequences)
|
|
42
|
+
score += 10;
|
|
43
|
+
}
|
|
44
|
+
return Math.min(100, score);
|
|
45
|
+
}
|
|
46
|
+
/** Check if an event is significant enough to record. */
|
|
47
|
+
export function isSignificant(eventType, threshold = 25, context) {
|
|
48
|
+
return calculateSignificance(eventType, context) >= threshold;
|
|
49
|
+
}
|
|
50
|
+
// ── Chronicle Management ───────────────────────────────────────────────────────
|
|
51
|
+
/** Create a new chronicle. */
|
|
52
|
+
export function createChronicle(chronicleId, scope, tick, ownerId) {
|
|
53
|
+
return {
|
|
54
|
+
chronicleId,
|
|
55
|
+
scope,
|
|
56
|
+
ownerId,
|
|
57
|
+
entries: [],
|
|
58
|
+
createdAtTick: tick,
|
|
59
|
+
lastEntryTick: tick,
|
|
60
|
+
detectedArcs: [],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Add an entry to a chronicle. */
|
|
64
|
+
export function addChronicleEntry(chronicle, entry, seed) {
|
|
65
|
+
const fullEntry = {
|
|
66
|
+
...entry,
|
|
67
|
+
entryId: generateEntryId(chronicle.chronicleId, chronicle.entries.length, seed ?? Date.now()),
|
|
68
|
+
};
|
|
69
|
+
chronicle.entries.push(fullEntry);
|
|
70
|
+
chronicle.lastEntryTick = entry.tick;
|
|
71
|
+
// Sort entries by tick
|
|
72
|
+
chronicle.entries.sort((a, b) => a.tick - b.tick);
|
|
73
|
+
return fullEntry;
|
|
74
|
+
}
|
|
75
|
+
function generateEntryId(chronicleId, index, seed) {
|
|
76
|
+
return `${chronicleId}_${seed}_${index}`;
|
|
77
|
+
}
|
|
78
|
+
/** Get entries above a significance threshold. */
|
|
79
|
+
export function getSignificantEntries(chronicle, minSignificance = 50) {
|
|
80
|
+
return chronicle.entries.filter(e => e.significance >= minSignificance);
|
|
81
|
+
}
|
|
82
|
+
/** Get entries within a tick range. */
|
|
83
|
+
export function getEntriesInRange(chronicle, startTick, endTick) {
|
|
84
|
+
return chronicle.entries.filter(e => e.tick >= startTick && e.tick <= endTick);
|
|
85
|
+
}
|
|
86
|
+
/** Get entries involving a specific entity. */
|
|
87
|
+
export function getEntriesForEntity(chronicle, entityId) {
|
|
88
|
+
return chronicle.entries.filter(e => e.actors.includes(entityId));
|
|
89
|
+
}
|
|
90
|
+
/** Get entries of a specific type. */
|
|
91
|
+
export function getEntriesByType(chronicle, eventType) {
|
|
92
|
+
return chronicle.entries.filter(e => e.eventType === eventType);
|
|
93
|
+
}
|
|
94
|
+
/** Create a new chronicle registry. */
|
|
95
|
+
export function createChronicleRegistry(tick) {
|
|
96
|
+
return {
|
|
97
|
+
worldChronicle: createChronicle("world", "world", tick),
|
|
98
|
+
entityChronicles: new Map(),
|
|
99
|
+
factionChronicles: new Map(),
|
|
100
|
+
settlementChronicles: new Map(),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Record an event to appropriate chronicles. */
|
|
104
|
+
export function recordEvent(registry, entry, options) {
|
|
105
|
+
// Always record to world chronicle if significant enough
|
|
106
|
+
if (entry.significance >= 30) {
|
|
107
|
+
addChronicleEntry(registry.worldChronicle, entry);
|
|
108
|
+
}
|
|
109
|
+
// Record to entity chronicles
|
|
110
|
+
if (options?.entityIds) {
|
|
111
|
+
for (const entityId of options.entityIds) {
|
|
112
|
+
let chronicle = registry.entityChronicles.get(entityId);
|
|
113
|
+
if (!chronicle) {
|
|
114
|
+
chronicle = createChronicle(`entity_${entityId}`, "entity", entry.tick, entityId);
|
|
115
|
+
registry.entityChronicles.set(entityId, chronicle);
|
|
116
|
+
}
|
|
117
|
+
addChronicleEntry(chronicle, entry);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Record to faction chronicles
|
|
121
|
+
if (options?.factionIds) {
|
|
122
|
+
for (const factionId of options.factionIds) {
|
|
123
|
+
let chronicle = registry.factionChronicles.get(factionId);
|
|
124
|
+
if (!chronicle) {
|
|
125
|
+
chronicle = createChronicle(`faction_${factionId}`, "faction", entry.tick, factionId);
|
|
126
|
+
registry.factionChronicles.set(factionId, chronicle);
|
|
127
|
+
}
|
|
128
|
+
addChronicleEntry(chronicle, entry);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Record to settlement chronicle
|
|
132
|
+
if (options?.settlementId) {
|
|
133
|
+
let chronicle = registry.settlementChronicles.get(options.settlementId);
|
|
134
|
+
if (!chronicle) {
|
|
135
|
+
chronicle = createChronicle(`settlement_${options.settlementId}`, "settlement", entry.tick, options.settlementId);
|
|
136
|
+
registry.settlementChronicles.set(options.settlementId, chronicle);
|
|
137
|
+
}
|
|
138
|
+
addChronicleEntry(chronicle, entry);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** Summarize a chronicle at different granularities. */
|
|
142
|
+
export function summarizeChronicle(chronicle, level = "chapter") {
|
|
143
|
+
let includedEntries;
|
|
144
|
+
let summaryText;
|
|
145
|
+
switch (level) {
|
|
146
|
+
case "full":
|
|
147
|
+
includedEntries = [...chronicle.entries];
|
|
148
|
+
summaryText = `Complete history: ${includedEntries.length} events recorded.`;
|
|
149
|
+
break;
|
|
150
|
+
case "chapter":
|
|
151
|
+
// Include only significant events and arc starts/ends
|
|
152
|
+
includedEntries = chronicle.entries.filter(e => e.significance >= 50 || chronicle.detectedArcs.some(a => a.entryIds.includes(e.entryId)));
|
|
153
|
+
summaryText = `Major events: ${includedEntries.length} significant moments across ${chronicle.detectedArcs.length} story arcs.`;
|
|
154
|
+
break;
|
|
155
|
+
case "synopsis":
|
|
156
|
+
// Just the highest significance events
|
|
157
|
+
includedEntries = getSignificantEntries(chronicle, 70);
|
|
158
|
+
summaryText = `Synopsis: ${includedEntries.length} pivotal events defined this ${chronicle.scope}.`;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
level,
|
|
163
|
+
totalEntries: chronicle.entries.length,
|
|
164
|
+
includedEntries: includedEntries.length,
|
|
165
|
+
summaryText,
|
|
166
|
+
keyEvents: includedEntries,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
// ── Serialization ─────────────────────────────────────────────────────────────
|
|
170
|
+
/** Serialize chronicle to JSON-friendly format. */
|
|
171
|
+
export function serializeChronicle(chronicle) {
|
|
172
|
+
return {
|
|
173
|
+
chronicleId: chronicle.chronicleId,
|
|
174
|
+
scope: chronicle.scope,
|
|
175
|
+
ownerId: chronicle.ownerId,
|
|
176
|
+
entries: chronicle.entries,
|
|
177
|
+
createdAtTick: chronicle.createdAtTick,
|
|
178
|
+
lastEntryTick: chronicle.lastEntryTick,
|
|
179
|
+
detectedArcs: chronicle.detectedArcs,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/** Deserialize chronicle. */
|
|
183
|
+
export function deserializeChronicle(data) {
|
|
184
|
+
const d = data;
|
|
185
|
+
return {
|
|
186
|
+
chronicleId: d.chronicleId ?? "",
|
|
187
|
+
scope: d.scope ?? "world",
|
|
188
|
+
ownerId: d.ownerId,
|
|
189
|
+
entries: Array.isArray(d.entries) ? d.entries : [],
|
|
190
|
+
createdAtTick: d.createdAtTick ?? 0,
|
|
191
|
+
lastEntryTick: d.lastEntryTick ?? 0,
|
|
192
|
+
detectedArcs: Array.isArray(d.detectedArcs) ? d.detectedArcs : [],
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/** Serialize chronicle registry. */
|
|
196
|
+
export function serializeChronicleRegistry(registry) {
|
|
197
|
+
return {
|
|
198
|
+
worldChronicle: serializeChronicle(registry.worldChronicle),
|
|
199
|
+
entityChronicles: Array.from(registry.entityChronicles.entries()).map(([id, c]) => [id, serializeChronicle(c)]),
|
|
200
|
+
factionChronicles: Array.from(registry.factionChronicles.entries()).map(([id, c]) => [id, serializeChronicle(c)]),
|
|
201
|
+
settlementChronicles: Array.from(registry.settlementChronicles.entries()).map(([id, c]) => [id, serializeChronicle(c)]),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/** Deserialize chronicle registry. */
|
|
205
|
+
export function deserializeChronicleRegistry(data) {
|
|
206
|
+
const d = data;
|
|
207
|
+
const registry = {
|
|
208
|
+
worldChronicle: createChronicle("world", "world", 0),
|
|
209
|
+
entityChronicles: new Map(),
|
|
210
|
+
factionChronicles: new Map(),
|
|
211
|
+
settlementChronicles: new Map(),
|
|
212
|
+
};
|
|
213
|
+
if (d.worldChronicle) {
|
|
214
|
+
registry.worldChronicle = deserializeChronicle(d.worldChronicle);
|
|
215
|
+
}
|
|
216
|
+
if (Array.isArray(d.entityChronicles)) {
|
|
217
|
+
for (const [id, c] of d.entityChronicles) {
|
|
218
|
+
registry.entityChronicles.set(id, deserializeChronicle(c));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (Array.isArray(d.factionChronicles)) {
|
|
222
|
+
for (const [id, c] of d.factionChronicles) {
|
|
223
|
+
registry.factionChronicles.set(id, deserializeChronicle(c));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (Array.isArray(d.settlementChronicles)) {
|
|
227
|
+
for (const [id, c] of d.settlementChronicles) {
|
|
228
|
+
registry.settlementChronicles.set(id, deserializeChronicle(c));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return registry;
|
|
232
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { type Q } from "./units.js";
|
|
2
|
+
import type { Entity } from "./sim/entity.js";
|
|
3
|
+
import type { Vec3 } from "./sim/vec3.js";
|
|
4
|
+
/** Kind of collective engineering project. */
|
|
5
|
+
export type CollectiveProjectKind = "field_fortification" | "siege_ramp" | "field_bridge" | "ritual_circle" | "trade_post";
|
|
6
|
+
/** One contributor's recorded work on a collective project. */
|
|
7
|
+
export interface CollectiveContributor {
|
|
8
|
+
entityId: number;
|
|
9
|
+
hoursWorked: number;
|
|
10
|
+
/** Effective competence at contribution time [Q]. */
|
|
11
|
+
competence_Q: Q;
|
|
12
|
+
}
|
|
13
|
+
/** A shared construction project that multiple entities contribute toward. */
|
|
14
|
+
export interface CollectiveProject {
|
|
15
|
+
projectId: string;
|
|
16
|
+
kind: CollectiveProjectKind;
|
|
17
|
+
label: string;
|
|
18
|
+
/** Current completion [Q 0..completionThreshold_Q]. */
|
|
19
|
+
progress_Q: Q;
|
|
20
|
+
/** Q threshold at which project is considered complete (usually SCALE.Q). */
|
|
21
|
+
completionThreshold_Q: Q;
|
|
22
|
+
/** Person-hours at q(1.0) competence required to reach completionThreshold_Q. */
|
|
23
|
+
requiredWorkHours: number;
|
|
24
|
+
contributors: CollectiveContributor[];
|
|
25
|
+
/** Tick at which progress first reached completionThreshold_Q; undefined if incomplete. */
|
|
26
|
+
completedAtTick?: number | undefined;
|
|
27
|
+
}
|
|
28
|
+
/** Result returned by `stepRitual`. */
|
|
29
|
+
export interface RitualResult {
|
|
30
|
+
/** Morale bonus applicable to all participants [Q 0..RITUAL_MAX_BONUS]. */
|
|
31
|
+
moraleBonus_Q: Q;
|
|
32
|
+
/** Fear threshold reduction for all participants [Q]. */
|
|
33
|
+
fearReduction_Q: Q;
|
|
34
|
+
/** Number of participants in this session. */
|
|
35
|
+
participantCount: number;
|
|
36
|
+
}
|
|
37
|
+
/** One waypoint in a caravan route. */
|
|
38
|
+
export interface CaravanWaypoint {
|
|
39
|
+
/** Identifier for the location (settlement ID, etc.). */
|
|
40
|
+
positionId: string;
|
|
41
|
+
position_m: Vec3;
|
|
42
|
+
/** Planned rest time at this waypoint [hours]. */
|
|
43
|
+
restHours: number;
|
|
44
|
+
}
|
|
45
|
+
/** Logistics plan produced by `planCaravanRoute`. */
|
|
46
|
+
export interface CaravanPlan {
|
|
47
|
+
/** Deterministic identifier derived from waypoint IDs. */
|
|
48
|
+
planId: string;
|
|
49
|
+
waypoints: CaravanWaypoint[];
|
|
50
|
+
/** Entity IDs of caravan participants. */
|
|
51
|
+
participantIds: number[];
|
|
52
|
+
/** Pure travel time excluding rest stops [seconds]. */
|
|
53
|
+
estimatedTravelSeconds: number;
|
|
54
|
+
/** Total journey time including rest stops [seconds]. */
|
|
55
|
+
estimatedTotalSeconds: number;
|
|
56
|
+
/** Food sufficiency for the journey [Q]: q(1.0) = fully provisioned. */
|
|
57
|
+
supplySufficiency_Q: Q;
|
|
58
|
+
/** Route quality from best navigator's logicalMathematical [Q]. */
|
|
59
|
+
routeQuality_Q: Q;
|
|
60
|
+
/** Trade bonus from best negotiator's interpersonal [Q]. */
|
|
61
|
+
negotiationBonus_Q: Q;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Seconds for a full-duration ritual to achieve maximum bonus.
|
|
65
|
+
* Shorter sessions scale linearly; longer sessions are capped at this duration.
|
|
66
|
+
*/
|
|
67
|
+
export declare const RITUAL_DURATION_s = 3600;
|
|
68
|
+
/**
|
|
69
|
+
* Maximum morale bonus a ritual can produce [Q].
|
|
70
|
+
* Even a perfect ensemble cannot exceed this per session.
|
|
71
|
+
*/
|
|
72
|
+
export declare const RITUAL_MAX_BONUS: Q;
|
|
73
|
+
/**
|
|
74
|
+
* Fraction of the morale bonus that converts to fear reduction [Q].
|
|
75
|
+
* fearReduction_Q = moraleBonus_Q × RITUAL_FEAR_REDUCTION_FRAC / SCALE.Q.
|
|
76
|
+
*/
|
|
77
|
+
export declare const RITUAL_FEAR_REDUCTION_FRAC: Q;
|
|
78
|
+
/**
|
|
79
|
+
* Walking-pace caravan speed [SCALE.mps].
|
|
80
|
+
* 1.2 m/s = 12 000 SCALE.mps.
|
|
81
|
+
*/
|
|
82
|
+
export declare const CARAVAN_BASE_SPEED_Smps = 12000;
|
|
83
|
+
/**
|
|
84
|
+
* Food rations required per person per day of caravan travel.
|
|
85
|
+
* Caller supplies all items in their food inventory; every item counts as one ration.
|
|
86
|
+
*/
|
|
87
|
+
export declare const CARAVAN_RATIONS_PER_PERSON_PER_DAY = 3;
|
|
88
|
+
/**
|
|
89
|
+
* Default engineering competence for entities without a cognition profile.
|
|
90
|
+
* Represents an average untrained labourer.
|
|
91
|
+
*/
|
|
92
|
+
export declare const DEFAULT_ENGINEERING_COMPETENCE: Q;
|
|
93
|
+
/**
|
|
94
|
+
* Derive an entity's engineering competence from their cognitive profile.
|
|
95
|
+
*
|
|
96
|
+
* Uses the average of logicalMathematical (planning) and bodilyKinesthetic
|
|
97
|
+
* (manual precision). Entities without a cognition profile return
|
|
98
|
+
* DEFAULT_ENGINEERING_COMPETENCE.
|
|
99
|
+
*/
|
|
100
|
+
export declare function deriveEngineeringCompetence(entity: Entity): Q;
|
|
101
|
+
/**
|
|
102
|
+
* Create a new collective project with zero progress.
|
|
103
|
+
*/
|
|
104
|
+
export declare function createCollectiveProject(projectId: string, kind: CollectiveProjectKind, label: string, requiredWorkHours: number): CollectiveProject;
|
|
105
|
+
/**
|
|
106
|
+
* Add a contribution to a collective project from one entity.
|
|
107
|
+
*
|
|
108
|
+
* Mutates `project.progress_Q` and appends to `project.contributors`.
|
|
109
|
+
* Progress delta = competence_Q × hoursWorked / requiredWorkHours (Q-scaled).
|
|
110
|
+
* Progress is clamped to [0, completionThreshold_Q].
|
|
111
|
+
*
|
|
112
|
+
* @param project The shared project to advance.
|
|
113
|
+
* @param entity The contributing entity.
|
|
114
|
+
* @param hoursWorked Hours of work this entity is contributing (must be > 0).
|
|
115
|
+
* @param tick Current world tick (used for completion timestamp).
|
|
116
|
+
* @returns The actual progress delta applied (Q units).
|
|
117
|
+
*/
|
|
118
|
+
export declare function contributeToCollectiveProject(project: CollectiveProject, entity: Entity, hoursWorked: number, tick: number): Q;
|
|
119
|
+
/**
|
|
120
|
+
* Return true if a project has reached its completion threshold.
|
|
121
|
+
*/
|
|
122
|
+
export declare function isProjectComplete(project: CollectiveProject): boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Compute the morale and fear-reduction effects of a communal ritual.
|
|
125
|
+
*
|
|
126
|
+
* Each participant contributes their average (intrapersonal + musical) / 2
|
|
127
|
+
* to a shared cognitive pool. The collective effect scales with sqrt(N)
|
|
128
|
+
* (diminishing returns for large groups). The time factor scales linearly
|
|
129
|
+
* from 0 at 0 s to 1 at RITUAL_DURATION_s; durations beyond that are capped.
|
|
130
|
+
*
|
|
131
|
+
* moraleBonus_Q is capped at RITUAL_MAX_BONUS regardless of group size.
|
|
132
|
+
* fearReduction_Q = moraleBonus_Q × RITUAL_FEAR_REDUCTION_FRAC / SCALE.Q.
|
|
133
|
+
*
|
|
134
|
+
* @param participants All entities taking part (pass only alive entities).
|
|
135
|
+
* @param elapsedSeconds Duration of the ritual session in seconds.
|
|
136
|
+
* @returns RitualResult to apply to all participants.
|
|
137
|
+
*/
|
|
138
|
+
export declare function stepRitual(participants: Entity[], elapsedSeconds: number): RitualResult;
|
|
139
|
+
/**
|
|
140
|
+
* Plan a trade caravan route, deriving logistics quality from participants'
|
|
141
|
+
* cognitive attributes and food sufficiency from the supplied inventory.
|
|
142
|
+
*
|
|
143
|
+
* Route quality (best logicalMathematical) shortens travel time:
|
|
144
|
+
* speedFactor = q(0.80) + routeQuality_Q × q(0.20) / SCALE.Q ∈ [q(0.80), q(1.00)]
|
|
145
|
+
* adjustedTravelSeconds = baseSeconds × SCALE.Q / speedFactor
|
|
146
|
+
*
|
|
147
|
+
* Supply sufficiency = totalRations / (adjustedTravelDays × participants × RATIONS/DAY).
|
|
148
|
+
*
|
|
149
|
+
* @param waypoints Ordered stops (position + rest hours).
|
|
150
|
+
* @param participants Entities travelling with the caravan.
|
|
151
|
+
* @param inventory Map<itemId, quantity> of food items available (all entries count).
|
|
152
|
+
* @returns CaravanPlan with derived logistics values.
|
|
153
|
+
*/
|
|
154
|
+
export declare function planCaravanRoute(waypoints: CaravanWaypoint[], participants: Entity[], inventory: Map<string, number>): CaravanPlan;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// src/collective-activities.ts — Phase 55: Collective Non-Combat Activities
|
|
2
|
+
//
|
|
3
|
+
// Three systems for formation/group-scale non-combat coordination:
|
|
4
|
+
//
|
|
5
|
+
// 1. Siege Engineering — field construction projects (fortifications, ramps, bridges)
|
|
6
|
+
// Progress = Σ(competence_Q × hoursWorked) / requiredWorkHours
|
|
7
|
+
// Competence derived from logicalMathematical + bodilyKinesthetic cognition.
|
|
8
|
+
//
|
|
9
|
+
// 2. Ritual & Ceremony — collective morale amplification via intrapersonal/musical pool
|
|
10
|
+
// moraleBonus_Q scales with average cognition and sqrt(N) for diminishing returns.
|
|
11
|
+
// Returns morale bonus and fear reduction applicable to all participants.
|
|
12
|
+
//
|
|
13
|
+
// 3. Trade Caravan Logistics — route planning with supply sufficiency and route quality
|
|
14
|
+
// routeQuality_Q from logicalMathematical (best navigator leads);
|
|
15
|
+
// negotiationBonus_Q from best interpersonal score;
|
|
16
|
+
// supplySufficiency_Q from inventory vs. estimated ration needs.
|
|
17
|
+
import { q, clampQ, SCALE } from "./units.js";
|
|
18
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
19
|
+
/**
|
|
20
|
+
* Seconds for a full-duration ritual to achieve maximum bonus.
|
|
21
|
+
* Shorter sessions scale linearly; longer sessions are capped at this duration.
|
|
22
|
+
*/
|
|
23
|
+
export const RITUAL_DURATION_s = 3_600; // 1 hour
|
|
24
|
+
/**
|
|
25
|
+
* Maximum morale bonus a ritual can produce [Q].
|
|
26
|
+
* Even a perfect ensemble cannot exceed this per session.
|
|
27
|
+
*/
|
|
28
|
+
export const RITUAL_MAX_BONUS = q(0.30); // 3000
|
|
29
|
+
/**
|
|
30
|
+
* Fraction of the morale bonus that converts to fear reduction [Q].
|
|
31
|
+
* fearReduction_Q = moraleBonus_Q × RITUAL_FEAR_REDUCTION_FRAC / SCALE.Q.
|
|
32
|
+
*/
|
|
33
|
+
export const RITUAL_FEAR_REDUCTION_FRAC = q(0.60); // 6000
|
|
34
|
+
/**
|
|
35
|
+
* Walking-pace caravan speed [SCALE.mps].
|
|
36
|
+
* 1.2 m/s = 12 000 SCALE.mps.
|
|
37
|
+
*/
|
|
38
|
+
export const CARAVAN_BASE_SPEED_Smps = 12_000;
|
|
39
|
+
/**
|
|
40
|
+
* Food rations required per person per day of caravan travel.
|
|
41
|
+
* Caller supplies all items in their food inventory; every item counts as one ration.
|
|
42
|
+
*/
|
|
43
|
+
export const CARAVAN_RATIONS_PER_PERSON_PER_DAY = 3;
|
|
44
|
+
/**
|
|
45
|
+
* Default engineering competence for entities without a cognition profile.
|
|
46
|
+
* Represents an average untrained labourer.
|
|
47
|
+
*/
|
|
48
|
+
export const DEFAULT_ENGINEERING_COMPETENCE = q(0.50); // 5000
|
|
49
|
+
// ── Siege Engineering ─────────────────────────────────────────────────────────
|
|
50
|
+
/**
|
|
51
|
+
* Derive an entity's engineering competence from their cognitive profile.
|
|
52
|
+
*
|
|
53
|
+
* Uses the average of logicalMathematical (planning) and bodilyKinesthetic
|
|
54
|
+
* (manual precision). Entities without a cognition profile return
|
|
55
|
+
* DEFAULT_ENGINEERING_COMPETENCE.
|
|
56
|
+
*/
|
|
57
|
+
export function deriveEngineeringCompetence(entity) {
|
|
58
|
+
const cog = entity.attributes.cognition;
|
|
59
|
+
if (!cog)
|
|
60
|
+
return DEFAULT_ENGINEERING_COMPETENCE;
|
|
61
|
+
return Math.round((cog.logicalMathematical + cog.bodilyKinesthetic) / 2);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Create a new collective project with zero progress.
|
|
65
|
+
*/
|
|
66
|
+
export function createCollectiveProject(projectId, kind, label, requiredWorkHours) {
|
|
67
|
+
return {
|
|
68
|
+
projectId,
|
|
69
|
+
kind,
|
|
70
|
+
label,
|
|
71
|
+
progress_Q: q(0),
|
|
72
|
+
completionThreshold_Q: SCALE.Q,
|
|
73
|
+
requiredWorkHours,
|
|
74
|
+
contributors: [],
|
|
75
|
+
completedAtTick: undefined,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Add a contribution to a collective project from one entity.
|
|
80
|
+
*
|
|
81
|
+
* Mutates `project.progress_Q` and appends to `project.contributors`.
|
|
82
|
+
* Progress delta = competence_Q × hoursWorked / requiredWorkHours (Q-scaled).
|
|
83
|
+
* Progress is clamped to [0, completionThreshold_Q].
|
|
84
|
+
*
|
|
85
|
+
* @param project The shared project to advance.
|
|
86
|
+
* @param entity The contributing entity.
|
|
87
|
+
* @param hoursWorked Hours of work this entity is contributing (must be > 0).
|
|
88
|
+
* @param tick Current world tick (used for completion timestamp).
|
|
89
|
+
* @returns The actual progress delta applied (Q units).
|
|
90
|
+
*/
|
|
91
|
+
export function contributeToCollectiveProject(project, entity, hoursWorked, tick) {
|
|
92
|
+
if (hoursWorked <= 0)
|
|
93
|
+
return q(0);
|
|
94
|
+
const competence_Q = deriveEngineeringCompetence(entity);
|
|
95
|
+
// delta = (competence_Q / SCALE.Q) × (hoursWorked / requiredWorkHours) × SCALE.Q
|
|
96
|
+
// = competence_Q × hoursWorked / requiredWorkHours
|
|
97
|
+
const delta = Math.round(competence_Q * hoursWorked / project.requiredWorkHours);
|
|
98
|
+
const prevProgress = project.progress_Q;
|
|
99
|
+
project.progress_Q = clampQ((project.progress_Q + delta), q(0), project.completionThreshold_Q);
|
|
100
|
+
project.contributors.push({ entityId: entity.id, hoursWorked, competence_Q });
|
|
101
|
+
// Record completion tick the first time the threshold is crossed.
|
|
102
|
+
if (project.completedAtTick === undefined &&
|
|
103
|
+
project.progress_Q >= project.completionThreshold_Q &&
|
|
104
|
+
prevProgress < project.completionThreshold_Q) {
|
|
105
|
+
project.completedAtTick = tick;
|
|
106
|
+
}
|
|
107
|
+
return (project.progress_Q - prevProgress);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Return true if a project has reached its completion threshold.
|
|
111
|
+
*/
|
|
112
|
+
export function isProjectComplete(project) {
|
|
113
|
+
return project.progress_Q >= project.completionThreshold_Q;
|
|
114
|
+
}
|
|
115
|
+
// ── Ritual & Ceremony ─────────────────────────────────────────────────────────
|
|
116
|
+
/**
|
|
117
|
+
* Compute the morale and fear-reduction effects of a communal ritual.
|
|
118
|
+
*
|
|
119
|
+
* Each participant contributes their average (intrapersonal + musical) / 2
|
|
120
|
+
* to a shared cognitive pool. The collective effect scales with sqrt(N)
|
|
121
|
+
* (diminishing returns for large groups). The time factor scales linearly
|
|
122
|
+
* from 0 at 0 s to 1 at RITUAL_DURATION_s; durations beyond that are capped.
|
|
123
|
+
*
|
|
124
|
+
* moraleBonus_Q is capped at RITUAL_MAX_BONUS regardless of group size.
|
|
125
|
+
* fearReduction_Q = moraleBonus_Q × RITUAL_FEAR_REDUCTION_FRAC / SCALE.Q.
|
|
126
|
+
*
|
|
127
|
+
* @param participants All entities taking part (pass only alive entities).
|
|
128
|
+
* @param elapsedSeconds Duration of the ritual session in seconds.
|
|
129
|
+
* @returns RitualResult to apply to all participants.
|
|
130
|
+
*/
|
|
131
|
+
export function stepRitual(participants, elapsedSeconds) {
|
|
132
|
+
const n = participants.length;
|
|
133
|
+
if (n === 0) {
|
|
134
|
+
return { moraleBonus_Q: q(0), fearReduction_Q: q(0), participantCount: 0 };
|
|
135
|
+
}
|
|
136
|
+
// Pool each participant's ritual cognitive capacity.
|
|
137
|
+
let sumPool = 0;
|
|
138
|
+
for (const p of participants) {
|
|
139
|
+
const intrap = p.attributes.cognition?.intrapersonal ?? 0;
|
|
140
|
+
const musical = p.attributes.cognition?.musical ?? 0;
|
|
141
|
+
sumPool += Math.trunc((intrap + musical) / 2);
|
|
142
|
+
}
|
|
143
|
+
const avgPool = Math.trunc(sumPool / n);
|
|
144
|
+
// sqrt(N) collective scaling in fixed-point.
|
|
145
|
+
// sqrtN_Q = floor(sqrt(N) × SCALE.Q) e.g. N=4 → sqrtN_Q = 20 000
|
|
146
|
+
const sqrtN_Q = Math.trunc(Math.sqrt(n) * SCALE.Q);
|
|
147
|
+
// Time factor [0..SCALE.Q]: fraction of full ritual duration completed.
|
|
148
|
+
const timeFrac = Math.min(SCALE.Q, Math.round(elapsedSeconds * SCALE.Q / RITUAL_DURATION_s));
|
|
149
|
+
// effectivePool = avgPool × sqrt(N) [Q-scaled]
|
|
150
|
+
const effectivePool = Math.round(avgPool * sqrtN_Q / SCALE.Q);
|
|
151
|
+
// rawBonus = effectivePool × timeFrac / SCALE.Q
|
|
152
|
+
const rawBonus = Math.round(effectivePool * timeFrac / SCALE.Q);
|
|
153
|
+
const moraleBonus_Q = clampQ(rawBonus, q(0), RITUAL_MAX_BONUS);
|
|
154
|
+
const fearReduction_Q = clampQ(Math.round(moraleBonus_Q * RITUAL_FEAR_REDUCTION_FRAC / SCALE.Q), q(0), moraleBonus_Q);
|
|
155
|
+
return { moraleBonus_Q, fearReduction_Q, participantCount: n };
|
|
156
|
+
}
|
|
157
|
+
// ── Trade Caravan Logistics ────────────────────────────────────────────────────
|
|
158
|
+
/** Compute total route distance (SCALE.m) from ordered waypoints. */
|
|
159
|
+
function routeTotalDistance(waypoints) {
|
|
160
|
+
let total = 0;
|
|
161
|
+
for (let i = 1; i < waypoints.length; i++) {
|
|
162
|
+
const a = waypoints[i - 1].position_m;
|
|
163
|
+
const b = waypoints[i].position_m;
|
|
164
|
+
const dx = Number(a.x) - Number(b.x);
|
|
165
|
+
const dy = Number(a.y) - Number(b.y);
|
|
166
|
+
const dz = Number(a.z) - Number(b.z);
|
|
167
|
+
total += Math.trunc(Math.sqrt(dx * dx + dy * dy + dz * dz));
|
|
168
|
+
}
|
|
169
|
+
return total;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Plan a trade caravan route, deriving logistics quality from participants'
|
|
173
|
+
* cognitive attributes and food sufficiency from the supplied inventory.
|
|
174
|
+
*
|
|
175
|
+
* Route quality (best logicalMathematical) shortens travel time:
|
|
176
|
+
* speedFactor = q(0.80) + routeQuality_Q × q(0.20) / SCALE.Q ∈ [q(0.80), q(1.00)]
|
|
177
|
+
* adjustedTravelSeconds = baseSeconds × SCALE.Q / speedFactor
|
|
178
|
+
*
|
|
179
|
+
* Supply sufficiency = totalRations / (adjustedTravelDays × participants × RATIONS/DAY).
|
|
180
|
+
*
|
|
181
|
+
* @param waypoints Ordered stops (position + rest hours).
|
|
182
|
+
* @param participants Entities travelling with the caravan.
|
|
183
|
+
* @param inventory Map<itemId, quantity> of food items available (all entries count).
|
|
184
|
+
* @returns CaravanPlan with derived logistics values.
|
|
185
|
+
*/
|
|
186
|
+
export function planCaravanRoute(waypoints, participants, inventory) {
|
|
187
|
+
const participantIds = participants.map(p => p.id);
|
|
188
|
+
// ── Route quality: best navigator's logicalMathematical leads the route ─────
|
|
189
|
+
let bestLogical = 0;
|
|
190
|
+
for (const p of participants) {
|
|
191
|
+
const lm = p.attributes.cognition?.logicalMathematical ?? 0;
|
|
192
|
+
if (lm > bestLogical)
|
|
193
|
+
bestLogical = lm;
|
|
194
|
+
}
|
|
195
|
+
const routeQuality_Q = participants.length > 0
|
|
196
|
+
? clampQ(bestLogical, q(0), SCALE.Q)
|
|
197
|
+
: q(0);
|
|
198
|
+
// ── Negotiation bonus: best interpersonal score ───────────────────────────
|
|
199
|
+
let bestInterpersonal = 0;
|
|
200
|
+
for (const p of participants) {
|
|
201
|
+
const ip = p.attributes.cognition?.interpersonal ?? 0;
|
|
202
|
+
if (ip > bestInterpersonal)
|
|
203
|
+
bestInterpersonal = ip;
|
|
204
|
+
}
|
|
205
|
+
const negotiationBonus_Q = clampQ(bestInterpersonal, q(0), SCALE.Q);
|
|
206
|
+
// ── Distance and base travel time ─────────────────────────────────────────
|
|
207
|
+
const totalDistance_Sm = routeTotalDistance(waypoints);
|
|
208
|
+
const baseTravelSeconds = CARAVAN_BASE_SPEED_Smps > 0
|
|
209
|
+
? Math.trunc(totalDistance_Sm / CARAVAN_BASE_SPEED_Smps)
|
|
210
|
+
: 0;
|
|
211
|
+
// speedFactor ∈ [q(0.80), q(1.00)]: higher quality → closer to q(1.00)
|
|
212
|
+
// At routeQuality_Q = 0 → speedFactor = q(0.80) → 25 % slower than optimal
|
|
213
|
+
// At routeQuality_Q = SCALE.Q → speedFactor = q(1.00) → full base speed
|
|
214
|
+
const speedFactor = (q(0.80) + Math.round(routeQuality_Q * q(0.20) / SCALE.Q));
|
|
215
|
+
const adjustedTravelSeconds = speedFactor > 0
|
|
216
|
+
? Math.round(baseTravelSeconds * SCALE.Q / speedFactor)
|
|
217
|
+
: baseTravelSeconds;
|
|
218
|
+
// ── Rest stops ────────────────────────────────────────────────────────────
|
|
219
|
+
const totalRestSeconds = waypoints.reduce((sum, w) => sum + Math.round(w.restHours * 3600), 0);
|
|
220
|
+
const estimatedTotalSeconds = adjustedTravelSeconds + totalRestSeconds;
|
|
221
|
+
// ── Supply sufficiency ────────────────────────────────────────────────────
|
|
222
|
+
let totalRations = 0;
|
|
223
|
+
for (const qty of inventory.values()) {
|
|
224
|
+
totalRations += Math.max(0, qty);
|
|
225
|
+
}
|
|
226
|
+
const travelDays = adjustedTravelSeconds / 86_400;
|
|
227
|
+
const rationsNeeded = travelDays * participants.length * CARAVAN_RATIONS_PER_PERSON_PER_DAY;
|
|
228
|
+
let supplySufficiency_Q;
|
|
229
|
+
if (rationsNeeded <= 0) {
|
|
230
|
+
supplySufficiency_Q = SCALE.Q;
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
supplySufficiency_Q = clampQ(Math.round(totalRations * SCALE.Q / rationsNeeded), q(0), SCALE.Q);
|
|
234
|
+
}
|
|
235
|
+
// ── Deterministic plan ID ─────────────────────────────────────────────────
|
|
236
|
+
const planId = `caravan_${waypoints.map(w => w.positionId).join("_")}`;
|
|
237
|
+
return {
|
|
238
|
+
planId,
|
|
239
|
+
waypoints,
|
|
240
|
+
participantIds,
|
|
241
|
+
estimatedTravelSeconds: adjustedTravelSeconds,
|
|
242
|
+
estimatedTotalSeconds,
|
|
243
|
+
supplySufficiency_Q,
|
|
244
|
+
routeQuality_Q,
|
|
245
|
+
negotiationBonus_Q,
|
|
246
|
+
};
|
|
247
|
+
}
|