@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,165 @@
|
|
|
1
|
+
// src/crafting/manufacturing.ts — Phase 61: Manufacturing System
|
|
2
|
+
//
|
|
3
|
+
// Batch production lines, progress accumulation, quality variance.
|
|
4
|
+
// Deterministic batch quality range based on workers, materials, workshop.
|
|
5
|
+
import { SCALE, q, clampQ, qMul, mulDiv } from "../units.js";
|
|
6
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
7
|
+
/** Default progress per worker per second (Q units). */
|
|
8
|
+
const PROGRESS_PER_WORKER_PER_SECOND = q(0.001);
|
|
9
|
+
/** Quality variance factor based on number of workers (more workers → less variance). */
|
|
10
|
+
const WORKER_VARIANCE_REDUCTION = q(0.10);
|
|
11
|
+
/** Minimum quality variance per batch. */
|
|
12
|
+
const MIN_QUALITY_VARIANCE = q(0.05);
|
|
13
|
+
// ── Production Line Management ────────────────────────────────────────────────
|
|
14
|
+
/**
|
|
15
|
+
* Initialize a production line for batch manufacturing.
|
|
16
|
+
*/
|
|
17
|
+
export function setupProductionLine(order, workers, seed) {
|
|
18
|
+
const workerIds = workers.map(w => w.id);
|
|
19
|
+
const qualityRange = calculateBatchQualityRange(workers, order.workshop, seed);
|
|
20
|
+
return {
|
|
21
|
+
lineId: `line_${order.orderId}`,
|
|
22
|
+
recipeId: order.recipeId,
|
|
23
|
+
batchSize: order.quantity,
|
|
24
|
+
itemsProduced: 0,
|
|
25
|
+
progress_Q: q(0),
|
|
26
|
+
assignedWorkers: workerIds,
|
|
27
|
+
priority: 1,
|
|
28
|
+
qualityRange,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Advance production line by deltaTime seconds.
|
|
33
|
+
* Progress accumulates based on number of workers and their skills.
|
|
34
|
+
* Returns new items completed and updated progress.
|
|
35
|
+
*/
|
|
36
|
+
export function advanceProduction(productionLine, deltaTime_s, workers, // Must match assignedWorkers IDs
|
|
37
|
+
workshop, seed) {
|
|
38
|
+
if (productionLine.itemsProduced >= productionLine.batchSize) {
|
|
39
|
+
return {
|
|
40
|
+
itemsCompleted: 0,
|
|
41
|
+
totalItemsProduced: productionLine.itemsProduced,
|
|
42
|
+
progress_Q: productionLine.progress_Q,
|
|
43
|
+
qualityRange: productionLine.qualityRange,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// Calculate total progress contribution from workers
|
|
47
|
+
let totalProgress = 0;
|
|
48
|
+
for (const worker of workers) {
|
|
49
|
+
const skill = worker.attributes.cognition?.bodilyKinesthetic ?? q(0.50);
|
|
50
|
+
// Progress = skill × time × base rate
|
|
51
|
+
const workerProgress = mulDiv(skill, deltaTime_s * SCALE.Q, 3600);
|
|
52
|
+
totalProgress += workerProgress;
|
|
53
|
+
}
|
|
54
|
+
// Apply workshop time reduction (not yet implemented)
|
|
55
|
+
const effectiveProgress = totalProgress; // placeholder
|
|
56
|
+
// Advance progress
|
|
57
|
+
let newProgress = productionLine.progress_Q + effectiveProgress;
|
|
58
|
+
let itemsCompleted = 0;
|
|
59
|
+
// Each item requires SCALE.Q progress
|
|
60
|
+
while (newProgress >= SCALE.Q && productionLine.itemsProduced < productionLine.batchSize) {
|
|
61
|
+
newProgress -= SCALE.Q;
|
|
62
|
+
itemsCompleted++;
|
|
63
|
+
productionLine.itemsProduced++;
|
|
64
|
+
}
|
|
65
|
+
productionLine.progress_Q = clampQ(newProgress, q(0), SCALE.Q);
|
|
66
|
+
// Update quality range based on remaining workers (variance reduces as more items produced)
|
|
67
|
+
const remainingItems = productionLine.batchSize - productionLine.itemsProduced;
|
|
68
|
+
const updatedRange = updateQualityRange(productionLine.qualityRange, workers, remainingItems, seed);
|
|
69
|
+
return {
|
|
70
|
+
itemsCompleted,
|
|
71
|
+
totalItemsProduced: productionLine.itemsProduced,
|
|
72
|
+
progress_Q: productionLine.progress_Q,
|
|
73
|
+
qualityRange: updatedRange,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Calculate predicted quality range for a batch based on workers, materials, workshop.
|
|
78
|
+
* Returns min, max, and average expected quality.
|
|
79
|
+
*/
|
|
80
|
+
export function calculateBatchQualityRange(workers, workshop, seed) {
|
|
81
|
+
if (workers.length === 0) {
|
|
82
|
+
return { min_Q: q(0), max_Q: q(0), avg_Q: q(0) };
|
|
83
|
+
}
|
|
84
|
+
// Average worker skill (bodilyKinesthetic)
|
|
85
|
+
let totalSkill = 0;
|
|
86
|
+
for (const worker of workers) {
|
|
87
|
+
totalSkill += worker.attributes.cognition?.bodilyKinesthetic ?? q(0.50);
|
|
88
|
+
}
|
|
89
|
+
const avgSkill = totalSkill / workers.length;
|
|
90
|
+
// Workshop quality bonus (placeholder)
|
|
91
|
+
const workshopBonus = q(1.0); // TODO: get from workshop
|
|
92
|
+
// Base average quality = avgSkill × workshopBonus
|
|
93
|
+
const avg_Q = clampQ(qMul(avgSkill, workshopBonus), q(0), SCALE.Q);
|
|
94
|
+
// Variance decreases with more workers
|
|
95
|
+
const variance = Math.max(MIN_QUALITY_VARIANCE, q(0.20) - mulDiv(WORKER_VARIANCE_REDUCTION, workers.length, 1));
|
|
96
|
+
const min_Q = clampQ((avg_Q - variance), q(0), SCALE.Q);
|
|
97
|
+
const max_Q = clampQ((avg_Q + variance), q(0), SCALE.Q);
|
|
98
|
+
return { min_Q, max_Q, avg_Q };
|
|
99
|
+
}
|
|
100
|
+
/** Update quality range as production progresses (variance may change). */
|
|
101
|
+
function updateQualityRange(currentRange, workers, remainingItems, seed) {
|
|
102
|
+
// For simplicity, keep range constant; could adjust based on worker fatigue, etc.
|
|
103
|
+
return currentRange;
|
|
104
|
+
}
|
|
105
|
+
// ── Multi‑Stage Assembly ──────────────────────────────────────────────────────
|
|
106
|
+
/**
|
|
107
|
+
* Create assembly steps for a complex recipe.
|
|
108
|
+
*/
|
|
109
|
+
export function createAssemblySteps(recipe) {
|
|
110
|
+
// Placeholder: generate steps based on recipe complexity
|
|
111
|
+
const steps = [];
|
|
112
|
+
const stepCount = Math.max(1, Math.round(recipe.complexity_Q / q(0.30)));
|
|
113
|
+
for (let i = 0; i < stepCount; i++) {
|
|
114
|
+
steps.push({
|
|
115
|
+
stepId: `step_${i}`,
|
|
116
|
+
description: `Step ${i + 1}`,
|
|
117
|
+
requiredSkill: i % 2 === 0 ? "bodilyKinesthetic" : "logicalMathematical",
|
|
118
|
+
timeFraction: Math.round(SCALE.Q / stepCount),
|
|
119
|
+
toolRequirements: i === 0 ? ["forge"] : [],
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return steps;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Advance a single assembly step.
|
|
126
|
+
*/
|
|
127
|
+
export function advanceAssemblyStep(step, worker, deltaTime_s, availableTools) {
|
|
128
|
+
const skill = (worker.attributes.cognition?.[step.requiredSkill] ?? q(0.50));
|
|
129
|
+
const toolBonus = step.toolRequirements.length > 0
|
|
130
|
+
? Math.max(...step.toolRequirements.map(t => availableTools.get(t) ?? q(0)))
|
|
131
|
+
: q(1.0);
|
|
132
|
+
const progress = mulDiv(skill, deltaTime_s * SCALE.Q, 3600);
|
|
133
|
+
const effectiveProgress = qMul(progress, toolBonus);
|
|
134
|
+
// Step requires timeFraction of total time to complete
|
|
135
|
+
const requiredProgress = step.timeFraction;
|
|
136
|
+
const newProgress = effectiveProgress; // accumulate across calls (needs state)
|
|
137
|
+
const completed = newProgress >= requiredProgress;
|
|
138
|
+
return { progress_Q: effectiveProgress, completed };
|
|
139
|
+
}
|
|
140
|
+
// ── Utility Functions ────────────────────────────────────────────────────────
|
|
141
|
+
/** Estimate time to complete a batch given workers and workshop. */
|
|
142
|
+
export function estimateBatchCompletionTime(batchSize, workers, workshop) {
|
|
143
|
+
if (workers.length === 0)
|
|
144
|
+
return Infinity;
|
|
145
|
+
let totalSkill = 0;
|
|
146
|
+
for (const worker of workers) {
|
|
147
|
+
totalSkill += worker.attributes.cognition?.bodilyKinesthetic ?? q(0.50);
|
|
148
|
+
}
|
|
149
|
+
const avgSkill = totalSkill / workers.length;
|
|
150
|
+
// Base time per item (placeholder: 1 hour)
|
|
151
|
+
const baseTimePerItem_s = 3600;
|
|
152
|
+
const workshopSpeedFactor = q(1.0); // TODO: get from workshop
|
|
153
|
+
const effectiveTimePerItem = Math.round(baseTimePerItem_s * SCALE.Q / avgSkill / workshopSpeedFactor);
|
|
154
|
+
return effectiveTimePerItem * batchSize / SCALE.Q;
|
|
155
|
+
}
|
|
156
|
+
/** Check if production line is complete. */
|
|
157
|
+
export function isProductionLineComplete(line) {
|
|
158
|
+
return line.itemsProduced >= line.batchSize;
|
|
159
|
+
}
|
|
160
|
+
/** Get progress percentage (0–1). */
|
|
161
|
+
export function getProductionLineProgress(line) {
|
|
162
|
+
const totalProgress = line.itemsProduced * SCALE.Q + line.progress_Q;
|
|
163
|
+
const totalRequired = line.batchSize * SCALE.Q;
|
|
164
|
+
return clampQ(Math.round(totalProgress * SCALE.Q / totalRequired), q(0), SCALE.Q);
|
|
165
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Q, I32 } from "../units.js";
|
|
2
|
+
import type { ItemBase } from "../equipment.js";
|
|
3
|
+
/** Physical properties of a material type. */
|
|
4
|
+
export interface MaterialType {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
density_kgPerM3: I32;
|
|
8
|
+
strength_Q: Q;
|
|
9
|
+
malleability_Q: Q;
|
|
10
|
+
conductivity_Q: Q;
|
|
11
|
+
baseQualityRange: {
|
|
12
|
+
min_Q: Q;
|
|
13
|
+
max_Q: Q;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** Material item kind extending ItemBase. */
|
|
17
|
+
export interface Material extends ItemBase {
|
|
18
|
+
kind: "material";
|
|
19
|
+
materialTypeId: string;
|
|
20
|
+
quality_Q: Q;
|
|
21
|
+
quantity_kg: I32;
|
|
22
|
+
}
|
|
23
|
+
/** Modifier to item stats based on material properties. */
|
|
24
|
+
export interface MaterialPropertyModifier {
|
|
25
|
+
durabilityMul?: Q;
|
|
26
|
+
damageMul?: Q;
|
|
27
|
+
weightMul?: Q;
|
|
28
|
+
valueMul?: Q;
|
|
29
|
+
}
|
|
30
|
+
export declare const MATERIAL_TYPES: MaterialType[];
|
|
31
|
+
/**
|
|
32
|
+
* Generate deterministic material quality within a material type's range.
|
|
33
|
+
* Uses seed to produce consistent quality for given inputs.
|
|
34
|
+
*/
|
|
35
|
+
export declare function deriveMaterialQuality(materialType: MaterialType, seed: number): Q;
|
|
36
|
+
/**
|
|
37
|
+
* Calculate material effect modifiers for an item based on material properties.
|
|
38
|
+
* Returns multipliers for various item stats.
|
|
39
|
+
*/
|
|
40
|
+
export declare function calculateMaterialEffect(item: ItemBase, // The base item (weapon, armour, etc.)
|
|
41
|
+
material: Material): MaterialPropertyModifier;
|
|
42
|
+
/**
|
|
43
|
+
* Extract materials from inventory items that are of kind "material".
|
|
44
|
+
* Returns map of materialTypeId to total quantity (kg) and average quality.
|
|
45
|
+
*/
|
|
46
|
+
export declare function getAvailableMaterials(inventory: any): Map<string, {
|
|
47
|
+
totalKg: number;
|
|
48
|
+
avgQuality_Q: Q;
|
|
49
|
+
}>;
|
|
50
|
+
/** Get material type by ID. */
|
|
51
|
+
export declare function getMaterialTypeById(id: string): MaterialType | undefined;
|
|
52
|
+
/** Create a material item instance. */
|
|
53
|
+
export declare function createMaterialItem(materialTypeId: string, quality_Q: Q, quantity_kg: I32, itemId: string, name?: string): Material;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// src/crafting/materials.ts — Phase 61: Material Catalog
|
|
2
|
+
//
|
|
3
|
+
// Material types with physical properties, and Material item kind.
|
|
4
|
+
// Deterministic quality generation, material property modifiers for crafted items.
|
|
5
|
+
import { SCALE, q, clampQ, mulDiv } from "../units.js";
|
|
6
|
+
import { makeRng } from "../rng.js";
|
|
7
|
+
// ── Material Type Catalogue ───────────────────────────────────────────────────
|
|
8
|
+
export const MATERIAL_TYPES = [
|
|
9
|
+
{
|
|
10
|
+
id: "iron",
|
|
11
|
+
name: "Iron",
|
|
12
|
+
density_kgPerM3: Math.round(7870 * SCALE.kg),
|
|
13
|
+
strength_Q: q(0.70),
|
|
14
|
+
malleability_Q: q(0.40),
|
|
15
|
+
conductivity_Q: q(0.45),
|
|
16
|
+
baseQualityRange: { min_Q: q(0.30), max_Q: q(0.80) },
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "steel",
|
|
20
|
+
name: "Steel",
|
|
21
|
+
density_kgPerM3: Math.round(7850 * SCALE.kg),
|
|
22
|
+
strength_Q: q(0.90),
|
|
23
|
+
malleability_Q: q(0.35),
|
|
24
|
+
conductivity_Q: q(0.40),
|
|
25
|
+
baseQualityRange: { min_Q: q(0.50), max_Q: q(0.95) },
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "wood",
|
|
29
|
+
name: "Wood",
|
|
30
|
+
density_kgPerM3: Math.round(600 * SCALE.kg),
|
|
31
|
+
strength_Q: q(0.30),
|
|
32
|
+
malleability_Q: q(0.60),
|
|
33
|
+
conductivity_Q: q(0.10),
|
|
34
|
+
baseQualityRange: { min_Q: q(0.20), max_Q: q(0.70) },
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "leather",
|
|
38
|
+
name: "Leather",
|
|
39
|
+
density_kgPerM3: Math.round(860 * SCALE.kg),
|
|
40
|
+
strength_Q: q(0.25),
|
|
41
|
+
malleability_Q: q(0.80),
|
|
42
|
+
conductivity_Q: q(0.15),
|
|
43
|
+
baseQualityRange: { min_Q: q(0.25), max_Q: q(0.75) },
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "bronze",
|
|
47
|
+
name: "Bronze",
|
|
48
|
+
density_kgPerM3: Math.round(8800 * SCALE.kg),
|
|
49
|
+
strength_Q: q(0.65),
|
|
50
|
+
malleability_Q: q(0.50),
|
|
51
|
+
conductivity_Q: q(0.55),
|
|
52
|
+
baseQualityRange: { min_Q: q(0.40), max_Q: q(0.85) },
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
// ── Quality Generation ────────────────────────────────────────────────────────
|
|
56
|
+
/**
|
|
57
|
+
* Generate deterministic material quality within a material type's range.
|
|
58
|
+
* Uses seed to produce consistent quality for given inputs.
|
|
59
|
+
*/
|
|
60
|
+
export function deriveMaterialQuality(materialType, seed) {
|
|
61
|
+
const rng = makeRng(seed, SCALE.Q);
|
|
62
|
+
const roll = rng.q01();
|
|
63
|
+
const range = materialType.baseQualityRange.max_Q - materialType.baseQualityRange.min_Q;
|
|
64
|
+
return clampQ(Math.round(materialType.baseQualityRange.min_Q + mulDiv(range, roll, SCALE.Q)), materialType.baseQualityRange.min_Q, materialType.baseQualityRange.max_Q);
|
|
65
|
+
}
|
|
66
|
+
// ── Material Property Effects ────────────────────────────────────────────────
|
|
67
|
+
/**
|
|
68
|
+
* Calculate material effect modifiers for an item based on material properties.
|
|
69
|
+
* Returns multipliers for various item stats.
|
|
70
|
+
*/
|
|
71
|
+
export function calculateMaterialEffect(item, // The base item (weapon, armour, etc.)
|
|
72
|
+
material) {
|
|
73
|
+
const materialType = MATERIAL_TYPES.find(mt => mt.id === material.materialTypeId);
|
|
74
|
+
if (!materialType) {
|
|
75
|
+
return {}; // No effect for unknown material
|
|
76
|
+
}
|
|
77
|
+
const qualityFactor = material.quality_Q / SCALE.Q; // 0–1
|
|
78
|
+
const modifiers = {};
|
|
79
|
+
// Strength affects durability and damage
|
|
80
|
+
modifiers.durabilityMul = clampQ(Math.round(q(0.80) + mulDiv(materialType.strength_Q, q(0.40), SCALE.Q)), q(0.50), q(1.50));
|
|
81
|
+
modifiers.damageMul = clampQ(Math.round(q(0.90) + mulDiv(materialType.strength_Q, q(0.20), SCALE.Q)), q(0.70), q(1.30));
|
|
82
|
+
// Density affects weight
|
|
83
|
+
const baseDensity = 1000; // water reference kg/m³
|
|
84
|
+
const densityFactor = materialType.density_kgPerM3 / (baseDensity * SCALE.kg);
|
|
85
|
+
modifiers.weightMul = clampQ(Math.round(densityFactor * SCALE.Q), q(0.50), q(2.00));
|
|
86
|
+
// Quality factor influences value
|
|
87
|
+
modifiers.valueMul = clampQ(Math.round(q(0.80) + mulDiv(material.quality_Q, q(0.40), SCALE.Q)), q(0.80), q(2.00));
|
|
88
|
+
return modifiers;
|
|
89
|
+
}
|
|
90
|
+
// ── Inventory Helpers ────────────────────────────────────────────────────────
|
|
91
|
+
/**
|
|
92
|
+
* Extract materials from inventory items that are of kind "material".
|
|
93
|
+
* Returns map of materialTypeId to total quantity (kg) and average quality.
|
|
94
|
+
*/
|
|
95
|
+
export function getAvailableMaterials(inventory) {
|
|
96
|
+
const map = new Map();
|
|
97
|
+
// TODO: iterate through inventory items, filter by kind === "material"
|
|
98
|
+
// For each material item, accumulate quantity and weighted quality.
|
|
99
|
+
return map;
|
|
100
|
+
}
|
|
101
|
+
// ── Utility Functions ────────────────────────────────────────────────────────
|
|
102
|
+
/** Get material type by ID. */
|
|
103
|
+
export function getMaterialTypeById(id) {
|
|
104
|
+
return MATERIAL_TYPES.find(mt => mt.id === id);
|
|
105
|
+
}
|
|
106
|
+
/** Create a material item instance. */
|
|
107
|
+
export function createMaterialItem(materialTypeId, quality_Q, quantity_kg, itemId, name) {
|
|
108
|
+
const materialType = getMaterialTypeById(materialTypeId);
|
|
109
|
+
const displayName = name ?? `${materialType?.name ?? materialTypeId} (${quality_Q})`;
|
|
110
|
+
return {
|
|
111
|
+
id: itemId,
|
|
112
|
+
kind: "material",
|
|
113
|
+
name: displayName,
|
|
114
|
+
mass_kg: Math.round(quantity_kg * SCALE.kg), // mass = quantity * density? Actually quantity is already mass.
|
|
115
|
+
bulk: q(1.0), // placeholder
|
|
116
|
+
materialTypeId,
|
|
117
|
+
quality_Q,
|
|
118
|
+
quantity_kg,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Q } from "../units.js";
|
|
2
|
+
import type { Entity } from "../sim/entity.js";
|
|
3
|
+
import type { Inventory } from "../inventory.js";
|
|
4
|
+
/** Skill requirement for a recipe. */
|
|
5
|
+
export interface SkillRequirement {
|
|
6
|
+
skillType: "bodilyKinesthetic" | "logicalMathematical" | "spatial" | "musical";
|
|
7
|
+
minLevel_Q: Q;
|
|
8
|
+
}
|
|
9
|
+
/** Tool requirement for a recipe. */
|
|
10
|
+
export interface ToolRequirement {
|
|
11
|
+
toolCategory: "forge" | "precision" | "bladed" | "blunt" | "needlework";
|
|
12
|
+
minQuality_Q?: Q;
|
|
13
|
+
}
|
|
14
|
+
/** Ingredient requirement for a recipe. */
|
|
15
|
+
export interface Ingredient {
|
|
16
|
+
itemId: string;
|
|
17
|
+
quantity: number;
|
|
18
|
+
materialType?: string;
|
|
19
|
+
minQuality_Q?: Q;
|
|
20
|
+
}
|
|
21
|
+
/** Recipe definition. */
|
|
22
|
+
export interface Recipe {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
outputItemId: string;
|
|
26
|
+
outputQuantity: number;
|
|
27
|
+
skillRequirements: SkillRequirement[];
|
|
28
|
+
toolRequirements: ToolRequirement[];
|
|
29
|
+
ingredients: Ingredient[];
|
|
30
|
+
baseTime_s: number;
|
|
31
|
+
complexity_Q: Q;
|
|
32
|
+
}
|
|
33
|
+
/** Result of recipe feasibility check. */
|
|
34
|
+
export interface FeasibilityResult {
|
|
35
|
+
feasible: boolean;
|
|
36
|
+
missingIngredients: string[];
|
|
37
|
+
missingSkills: SkillRequirement[];
|
|
38
|
+
missingTools: ToolRequirement[];
|
|
39
|
+
}
|
|
40
|
+
/** Result of recipe resolution. */
|
|
41
|
+
export interface RecipeResolutionResult {
|
|
42
|
+
success: boolean;
|
|
43
|
+
outputItemId: string;
|
|
44
|
+
outputQuantity: number;
|
|
45
|
+
quality_Q: Q;
|
|
46
|
+
timeTaken_s: number;
|
|
47
|
+
consumedIngredients: {
|
|
48
|
+
itemId: string;
|
|
49
|
+
quantity: number;
|
|
50
|
+
}[];
|
|
51
|
+
descriptor: "masterwork" | "fine" | "adequate" | "poor" | "ruined";
|
|
52
|
+
}
|
|
53
|
+
export declare const SAMPLE_RECIPES: Recipe[];
|
|
54
|
+
/**
|
|
55
|
+
* Validate whether a recipe can be crafted given entity skills, inventory, and tools.
|
|
56
|
+
* Tools are assumed to be available in the workshop; tool quality is checked separately.
|
|
57
|
+
*/
|
|
58
|
+
export declare function validateRecipeFeasibility(recipe: Recipe, inventory: Inventory, entity: Entity, availableToolQualities: Map<string, Q>): FeasibilityResult;
|
|
59
|
+
/**
|
|
60
|
+
* Calculate crafting cost (time, material consumption) based on recipe, materials, and workshop bonuses.
|
|
61
|
+
*/
|
|
62
|
+
export declare function calculateCraftingCost(recipe: Recipe, materialQualities: Map<string, Q>, // itemId -> quality_Q
|
|
63
|
+
workshopTimeReduction_Q?: Q, workshopQualityBonus_Q?: Q): {
|
|
64
|
+
time_s: number;
|
|
65
|
+
materialQualityAvg_Q: Q;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Resolve a recipe: consume ingredients, produce output with quality based on skills, tools, materials.
|
|
69
|
+
* Deterministic via seed.
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveRecipe(recipe: Recipe, entity: Entity, inventory: Inventory, availableToolQualities: Map<string, Q>, worldSeed: number, tick: number, entityId: number, salt: number): RecipeResolutionResult;
|
|
72
|
+
/** Get recipe by ID. */
|
|
73
|
+
export declare function getRecipeById(id: string): Recipe | undefined;
|
|
74
|
+
/** Get all recipes. */
|
|
75
|
+
export declare function getAllRecipes(): Recipe[];
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// src/crafting/recipes.ts — Phase 61: Recipe System
|
|
2
|
+
//
|
|
3
|
+
// Recipe definitions with ingredient requirements, skill checks, and crafting resolution.
|
|
4
|
+
// Deterministic via eventSeed/makeRng, fixed-point arithmetic only.
|
|
5
|
+
import { SCALE, q, clampQ, qMul, mulDiv } from "../units.js";
|
|
6
|
+
import { getItemCountByTemplateId } from "../inventory.js";
|
|
7
|
+
import { makeRng } from "../rng.js";
|
|
8
|
+
import { eventSeed, hashString } from "../sim/seeds.js";
|
|
9
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
10
|
+
/** Default tool quality if not specified. */
|
|
11
|
+
const DEFAULT_TOOL_QUALITY_Q = q(0.60);
|
|
12
|
+
/** Complexity penalty scaling factor. */
|
|
13
|
+
const COMPLEXITY_PENALTY_FACTOR = q(0.30);
|
|
14
|
+
/** Base success chance modifier for complexity. */
|
|
15
|
+
const BASE_SUCCESS_MOD = q(0.80);
|
|
16
|
+
// ── Recipe Catalogue (Example) ────────────────────────────────────────────────
|
|
17
|
+
export const SAMPLE_RECIPES = [
|
|
18
|
+
{
|
|
19
|
+
id: "recipe_shortsword",
|
|
20
|
+
name: "Shortsword",
|
|
21
|
+
outputItemId: "wpn_knife",
|
|
22
|
+
outputQuantity: 1,
|
|
23
|
+
skillRequirements: [
|
|
24
|
+
{ skillType: "bodilyKinesthetic", minLevel_Q: q(0.40) },
|
|
25
|
+
{ skillType: "logicalMathematical", minLevel_Q: q(0.25) },
|
|
26
|
+
],
|
|
27
|
+
toolRequirements: [
|
|
28
|
+
{ toolCategory: "forge", minQuality_Q: q(0.50) },
|
|
29
|
+
{ toolCategory: "bladed", minQuality_Q: q(0.30) },
|
|
30
|
+
],
|
|
31
|
+
ingredients: [
|
|
32
|
+
{ itemId: "material_iron", quantity: 2, materialType: "iron" },
|
|
33
|
+
{ itemId: "material_wood", quantity: 1, materialType: "wood" },
|
|
34
|
+
],
|
|
35
|
+
baseTime_s: 3600, // 1 hour
|
|
36
|
+
complexity_Q: q(0.60),
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "recipe_leather_armour",
|
|
40
|
+
name: "Leather armour",
|
|
41
|
+
outputItemId: "arm_leather",
|
|
42
|
+
outputQuantity: 1,
|
|
43
|
+
skillRequirements: [
|
|
44
|
+
{ skillType: "bodilyKinesthetic", minLevel_Q: q(0.30) },
|
|
45
|
+
],
|
|
46
|
+
toolRequirements: [
|
|
47
|
+
{ toolCategory: "needlework", minQuality_Q: q(0.20) },
|
|
48
|
+
],
|
|
49
|
+
ingredients: [
|
|
50
|
+
{ itemId: "material_leather", quantity: 5, materialType: "leather" },
|
|
51
|
+
{ itemId: "material_sinew", quantity: 2 },
|
|
52
|
+
],
|
|
53
|
+
baseTime_s: 7200, // 2 hours
|
|
54
|
+
complexity_Q: q(0.45),
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
// ── Feasibility Checking ──────────────────────────────────────────────────────
|
|
58
|
+
/**
|
|
59
|
+
* Validate whether a recipe can be crafted given entity skills, inventory, and tools.
|
|
60
|
+
* Tools are assumed to be available in the workshop; tool quality is checked separately.
|
|
61
|
+
*/
|
|
62
|
+
export function validateRecipeFeasibility(recipe, inventory, entity, availableToolQualities) {
|
|
63
|
+
const missingIngredients = [];
|
|
64
|
+
const missingSkills = [];
|
|
65
|
+
const missingTools = [];
|
|
66
|
+
// Check ingredients
|
|
67
|
+
for (const ing of recipe.ingredients) {
|
|
68
|
+
// Count items with matching templateId (material type ignored for now)
|
|
69
|
+
const total = getItemCountByTemplateId(inventory, ing.itemId);
|
|
70
|
+
if (total < ing.quantity) {
|
|
71
|
+
missingIngredients.push(ing.itemId);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Check skills
|
|
75
|
+
for (const skillReq of recipe.skillRequirements) {
|
|
76
|
+
const skillQ = getEntitySkill(entity, skillReq.skillType);
|
|
77
|
+
if (skillQ < skillReq.minLevel_Q) {
|
|
78
|
+
missingSkills.push(skillReq);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Check tools
|
|
82
|
+
for (const toolReq of recipe.toolRequirements) {
|
|
83
|
+
const toolQ = availableToolQualities.get(toolReq.toolCategory) ?? q(0);
|
|
84
|
+
const requiredQ = toolReq.minQuality_Q ?? DEFAULT_TOOL_QUALITY_Q;
|
|
85
|
+
if (toolQ < requiredQ) {
|
|
86
|
+
missingTools.push(toolReq);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
feasible: missingIngredients.length === 0 && missingSkills.length === 0 && missingTools.length === 0,
|
|
91
|
+
missingIngredients,
|
|
92
|
+
missingSkills,
|
|
93
|
+
missingTools,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** Get entity skill level for a given skill type. */
|
|
97
|
+
function getEntitySkill(entity, skillType) {
|
|
98
|
+
const cognition = entity.attributes.cognition;
|
|
99
|
+
if (!cognition)
|
|
100
|
+
return q(0.50);
|
|
101
|
+
switch (skillType) {
|
|
102
|
+
case "bodilyKinesthetic":
|
|
103
|
+
return (cognition.bodilyKinesthetic ?? q(0.50));
|
|
104
|
+
case "logicalMathematical":
|
|
105
|
+
return (cognition.logicalMathematical ?? q(0.50));
|
|
106
|
+
case "spatial":
|
|
107
|
+
return (cognition.spatial ?? q(0.50));
|
|
108
|
+
case "musical":
|
|
109
|
+
return (cognition.musical ?? q(0.50));
|
|
110
|
+
default:
|
|
111
|
+
return q(0.50);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// ── Crafting Resolution ───────────────────────────────────────────────────────
|
|
115
|
+
/**
|
|
116
|
+
* Calculate crafting cost (time, material consumption) based on recipe, materials, and workshop bonuses.
|
|
117
|
+
*/
|
|
118
|
+
export function calculateCraftingCost(recipe, materialQualities, // itemId -> quality_Q
|
|
119
|
+
workshopTimeReduction_Q = q(1.0), workshopQualityBonus_Q = q(0)) {
|
|
120
|
+
// Average material quality (default q(0.50))
|
|
121
|
+
let totalQuality = 0;
|
|
122
|
+
let count = 0;
|
|
123
|
+
for (const ing of recipe.ingredients) {
|
|
124
|
+
const qual = materialQualities.get(ing.itemId) ?? q(0.50);
|
|
125
|
+
totalQuality += qual;
|
|
126
|
+
count++;
|
|
127
|
+
}
|
|
128
|
+
const materialQualityAvg_Q = count > 0 ? Math.round(totalQuality / count) : q(0.50);
|
|
129
|
+
// Time reduced by workshop efficiency
|
|
130
|
+
const time_s = Math.round(recipe.baseTime_s * SCALE.Q / workshopTimeReduction_Q);
|
|
131
|
+
return { time_s, materialQualityAvg_Q };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Resolve a recipe: consume ingredients, produce output with quality based on skills, tools, materials.
|
|
135
|
+
* Deterministic via seed.
|
|
136
|
+
*/
|
|
137
|
+
export function resolveRecipe(recipe, entity, inventory, availableToolQualities, worldSeed, tick, entityId, salt) {
|
|
138
|
+
const seed = eventSeed(worldSeed, tick, entityId, hashString(recipe.id), salt);
|
|
139
|
+
// Feasibility check (should be done before calling, but we double-check)
|
|
140
|
+
const feasibility = validateRecipeFeasibility(recipe, inventory, entity, availableToolQualities);
|
|
141
|
+
if (!feasibility.feasible) {
|
|
142
|
+
return {
|
|
143
|
+
success: false,
|
|
144
|
+
outputItemId: recipe.outputItemId,
|
|
145
|
+
outputQuantity: 0,
|
|
146
|
+
quality_Q: q(0),
|
|
147
|
+
timeTaken_s: recipe.baseTime_s,
|
|
148
|
+
consumedIngredients: [],
|
|
149
|
+
descriptor: "ruined",
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
// Determine material qualities (for now assume average q(0.50))
|
|
153
|
+
const materialQualities = new Map();
|
|
154
|
+
for (const ing of recipe.ingredients) {
|
|
155
|
+
materialQualities.set(ing.itemId, q(0.50));
|
|
156
|
+
}
|
|
157
|
+
// Calculate crafting cost (no workshop bonuses yet)
|
|
158
|
+
const { time_s, materialQualityAvg_Q } = calculateCraftingCost(recipe, materialQualities);
|
|
159
|
+
// Compute skill factor: average of required skills weighted by their minimum levels
|
|
160
|
+
let skillFactor = 0;
|
|
161
|
+
let totalWeight = 0;
|
|
162
|
+
for (const skillReq of recipe.skillRequirements) {
|
|
163
|
+
const skillQ = getEntitySkill(entity, skillReq.skillType);
|
|
164
|
+
// How much above minimum? Normalized to [0,1]
|
|
165
|
+
const excess = Math.max(0, skillQ - skillReq.minLevel_Q);
|
|
166
|
+
const weight = skillReq.minLevel_Q; // higher minimum => more important
|
|
167
|
+
skillFactor += excess * weight;
|
|
168
|
+
totalWeight += weight;
|
|
169
|
+
}
|
|
170
|
+
const skillBonus = totalWeight > 0 ? skillFactor / totalWeight : q(0);
|
|
171
|
+
// Compute tool factor: average tool quality relative to requirements
|
|
172
|
+
let toolFactor = 0;
|
|
173
|
+
let toolCount = 0;
|
|
174
|
+
for (const toolReq of recipe.toolRequirements) {
|
|
175
|
+
const toolQ = availableToolQualities.get(toolReq.toolCategory) ?? q(0);
|
|
176
|
+
const requiredQ = toolReq.minQuality_Q ?? DEFAULT_TOOL_QUALITY_Q;
|
|
177
|
+
const excess = Math.max(0, toolQ - requiredQ);
|
|
178
|
+
toolFactor += excess;
|
|
179
|
+
toolCount++;
|
|
180
|
+
}
|
|
181
|
+
const toolBonus = toolCount > 0 ? toolFactor / toolCount : q(0);
|
|
182
|
+
// Complexity penalty: higher complexity reduces success chance
|
|
183
|
+
const complexityPenalty = mulDiv(recipe.complexity_Q, COMPLEXITY_PENALTY_FACTOR, SCALE.Q);
|
|
184
|
+
// Base success chance = skillBonus + toolBonus - complexityPenalty + BASE_SUCCESS_MOD
|
|
185
|
+
const rawSuccess = skillBonus + toolBonus - complexityPenalty + BASE_SUCCESS_MOD;
|
|
186
|
+
const successChance = clampQ(rawSuccess, q(0), SCALE.Q);
|
|
187
|
+
// Deterministic roll
|
|
188
|
+
const rng = makeRng(seed, SCALE.Q);
|
|
189
|
+
const roll = rng.q01();
|
|
190
|
+
const success = roll < successChance;
|
|
191
|
+
// Quality calculation: material quality × skill factor × tool factor ± variance
|
|
192
|
+
const expectedQuality = qMul(materialQualityAvg_Q, qMul(skillBonus, toolBonus));
|
|
193
|
+
const variance = mulDiv(rng.q01() - SCALE.Q / 2, q(0.20), SCALE.Q); // ±20%
|
|
194
|
+
const quality_Q = clampQ((expectedQuality + variance), q(0), SCALE.Q);
|
|
195
|
+
// Time taken (inverse of skill factor)
|
|
196
|
+
const timeTaken_s = Math.round(time_s * q(0.50) / (skillBonus > 0 ? skillBonus : q(0.50)));
|
|
197
|
+
// Determine descriptor
|
|
198
|
+
const descriptor = qualityToDescriptor(quality_Q);
|
|
199
|
+
// Consume ingredients (placeholder)
|
|
200
|
+
const consumedIngredients = recipe.ingredients.map(ing => ({
|
|
201
|
+
itemId: ing.itemId,
|
|
202
|
+
quantity: ing.quantity,
|
|
203
|
+
}));
|
|
204
|
+
return {
|
|
205
|
+
success,
|
|
206
|
+
outputItemId: recipe.outputItemId,
|
|
207
|
+
outputQuantity: success ? recipe.outputQuantity : 0,
|
|
208
|
+
quality_Q,
|
|
209
|
+
timeTaken_s,
|
|
210
|
+
consumedIngredients,
|
|
211
|
+
descriptor,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function qualityToDescriptor(quality_Q) {
|
|
215
|
+
if (quality_Q >= q(0.85))
|
|
216
|
+
return "masterwork";
|
|
217
|
+
if (quality_Q >= q(0.65))
|
|
218
|
+
return "fine";
|
|
219
|
+
if (quality_Q >= q(0.40))
|
|
220
|
+
return "adequate";
|
|
221
|
+
if (quality_Q >= q(0.20))
|
|
222
|
+
return "poor";
|
|
223
|
+
return "ruined";
|
|
224
|
+
}
|
|
225
|
+
// ── Utility Functions ─────────────────────────────────────────────────────────
|
|
226
|
+
/** Get recipe by ID. */
|
|
227
|
+
export function getRecipeById(id) {
|
|
228
|
+
return SAMPLE_RECIPES.find(r => r.id === id);
|
|
229
|
+
}
|
|
230
|
+
/** Get all recipes. */
|
|
231
|
+
export function getAllRecipes() {
|
|
232
|
+
return SAMPLE_RECIPES;
|
|
233
|
+
}
|