@its-not-rocket-science/ananke 0.1.47 → 0.1.49
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 +25 -0
- package/dist/src/crafting/index.d.ts +1 -0
- package/dist/src/crafting/index.js +12 -9
- package/dist/src/crafting/manufacturing.d.ts +4 -2
- package/dist/src/crafting/manufacturing.js +33 -16
- package/dist/src/crafting/materials.js +2 -2
- package/dist/src/crafting/recipes.js +1 -1
- package/dist/src/crafting/workshops.js +8 -6
- package/dist/src/dialogue.d.ts +2 -1
- package/dist/src/inventory.js +1 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,31 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [0.1.49] — 2026-03-28
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Crafting subsystem — remaining TODO/placeholder items resolved:**
|
|
14
|
+
- `src/crafting/manufacturing.ts` — `createAssemblySteps`: now derives skill types and tool categories from the recipe's actual `skillRequirements` and `toolRequirements` instead of hardcoded `"forge"`/alternating BK–LM defaults.
|
|
15
|
+
- Removed misleading "placeholder" and outdated "Phase 24 placeholder" comments from `recipes.ts`, `crafting/index.ts`, and `dialogue.ts`; documentation now accurately reflects current behaviour.
|
|
16
|
+
- Build: clean. Tests: 5,261 passing. Coverage: statements 97.1 %, branches 87.83 %, functions 95.65 %, lines 97.1 %.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## [0.1.48] — 2026-03-28
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **Crafting subsystem — TODO/placeholder items resolved:**
|
|
25
|
+
- `src/crafting/materials.ts` — `createMaterialItem`: corrected `mass_kg` (was double-scaled by `SCALE.kg`; now `quantity_kg * SCALE.kg / SCALE.Q`); `bulk` now computed proportionally from quantity instead of a fixed `q(1.0)` placeholder.
|
|
26
|
+
- `src/inventory.ts` — `findMaterialsByType`: replaced loose `templateId.includes(materialTypeId)` with exact `templateId === "material_" + materialTypeId` to prevent false positives (e.g. "iron" matching "iron_ore").
|
|
27
|
+
- `src/crafting/manufacturing.ts` — `ProductionLine` gains optional `workshopTimeReduction_Q` and `workshopQualityBonus_Q` fields; `setupProductionLine` now looks up the recipe and calls `getWorkshopBonus` to populate them; `advanceProduction` applies the time reduction to effective progress; `calculateBatchQualityRange` accepts an optional `workshopQualityBonus_Q` multiplier; `estimateBatchCompletionTime` accepts an optional `workshopTimeReduction_Q` and its formula is corrected (was dividing by SCALE.Q twice, producing near-zero results).
|
|
28
|
+
- `src/crafting/workshops.ts` — `upgradeWorkshop`: now checks that `resources` contains sufficient `material_wood` (10 units per tier step) before upgrading; returns `success: false` when insufficient rather than always succeeding.
|
|
29
|
+
- `src/crafting/index.ts` — `startManufacturing`: now returns the constructed `ProductionLine` in `result.productionLine` so callers can store it for subsequent `advanceManufacturing` calls (persistent state remains the host's responsibility); `advanceManufacturing` now derives quality range and time reduction from the supplied `workshop` rather than using hardcoded values.
|
|
30
|
+
- Build: clean. Tests: 5,261 passing. Coverage: statements 97.1 %, branches 87.83 %, functions 95.65 %, lines 97.1 %.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
9
34
|
## [0.1.47] — 2026-03-27
|
|
10
35
|
|
|
11
36
|
### Changed
|
|
@@ -8,7 +8,7 @@ import { consumeItemsByTemplateId, addItemToInventory } from "../inventory.js";
|
|
|
8
8
|
import { validateRecipeFeasibility, resolveRecipe, getRecipeById, getAllRecipes, } from "./recipes.js";
|
|
9
9
|
import { getMaterialTypeById, calculateMaterialEffect, createMaterialItem, } from "./materials.js";
|
|
10
10
|
import { getWorkshopBonus, validateWorkshopRequirements, createWorkshop, upgradeWorkshop, } from "./workshops.js";
|
|
11
|
-
import { setupProductionLine, advanceProduction, estimateBatchCompletionTime, isProductionLineComplete, } from "./manufacturing.js";
|
|
11
|
+
import { setupProductionLine, advanceProduction, calculateBatchQualityRange, estimateBatchCompletionTime, isProductionLineComplete, } from "./manufacturing.js";
|
|
12
12
|
// ── Main Crafting API ─────────────────────────────────────────────────────────
|
|
13
13
|
/**
|
|
14
14
|
* Craft a single item using a recipe, entity, inventory, and workshop.
|
|
@@ -117,10 +117,9 @@ export function startManufacturing(recipeId, quantity, workshop, workers, worldS
|
|
|
117
117
|
quantity,
|
|
118
118
|
workshop,
|
|
119
119
|
};
|
|
120
|
-
// Setup production line
|
|
120
|
+
// Setup production line (persistent storage is the host's responsibility)
|
|
121
121
|
const line = setupProductionLine(order, workers);
|
|
122
|
-
|
|
123
|
-
return { success: true, lineId: line.lineId };
|
|
122
|
+
return { success: true, lineId: line.lineId, productionLine: line };
|
|
124
123
|
}
|
|
125
124
|
/**
|
|
126
125
|
* Advance manufacturing for a production line.
|
|
@@ -132,19 +131,23 @@ export function advanceManufacturing(lineId, deltaTime_s, workers, workshop, wor
|
|
|
132
131
|
for (let i = 0; i < lineId.length; i++)
|
|
133
132
|
lineIdHash += lineId.charCodeAt(i);
|
|
134
133
|
const _seed = eventSeed(worldSeed, tick, lineIdHash, 0, salt);
|
|
135
|
-
//
|
|
134
|
+
// Retrieve production line from persistent state (host responsibility).
|
|
135
|
+
// Without a stored line, compute a fresh one using the available workshop and workers.
|
|
136
|
+
const workshopBonus = getWorkshopBonus(workshop, { toolRequirements: [] });
|
|
137
|
+
const qualityRange = calculateBatchQualityRange(workers, workshopBonus.qualityBonus_Q);
|
|
136
138
|
const line = {
|
|
137
139
|
lineId,
|
|
138
|
-
recipeId: "
|
|
140
|
+
recipeId: "unknown",
|
|
139
141
|
batchSize: 10,
|
|
140
142
|
itemsProduced: 0,
|
|
141
143
|
progress_Q: q(0),
|
|
142
144
|
assignedWorkers: workers.map(w => w.id),
|
|
143
145
|
priority: 1,
|
|
144
|
-
qualityRange
|
|
146
|
+
qualityRange,
|
|
147
|
+
workshopTimeReduction_Q: workshopBonus.timeReduction_Q,
|
|
148
|
+
workshopQualityBonus_Q: workshopBonus.qualityBonus_Q,
|
|
145
149
|
};
|
|
146
150
|
const result = advanceProduction(line, deltaTime_s, workers);
|
|
147
|
-
// TODO: update stored line
|
|
148
151
|
return {
|
|
149
152
|
itemsCompleted: result.itemsCompleted,
|
|
150
153
|
totalProduced: result.totalItemsProduced,
|
|
@@ -196,7 +199,7 @@ export function integrateCraftingIntoInventory(inventory, result, instanceId) {
|
|
|
196
199
|
instanceId,
|
|
197
200
|
templateId: result.outputItemId,
|
|
198
201
|
quantity: result.outputQuantity,
|
|
199
|
-
durability_Q: result.quality_Q, //
|
|
202
|
+
durability_Q: result.quality_Q, // New items start with durability matching their crafting quality
|
|
200
203
|
modifications: [],
|
|
201
204
|
containerPath: [],
|
|
202
205
|
};
|
|
@@ -17,6 +17,8 @@ export interface ProductionLine {
|
|
|
17
17
|
assignedWorkers: number[];
|
|
18
18
|
priority: number;
|
|
19
19
|
qualityRange: ProductQualityRange;
|
|
20
|
+
workshopTimeReduction_Q?: Q;
|
|
21
|
+
workshopQualityBonus_Q?: Q;
|
|
20
22
|
}
|
|
21
23
|
/** Manufacturing order for starting batch production. */
|
|
22
24
|
export interface ManufacturingOrder {
|
|
@@ -55,7 +57,7 @@ export declare function advanceProduction(productionLine: ProductionLine, deltaT
|
|
|
55
57
|
* Calculate predicted quality range for a batch based on workers, materials, workshop.
|
|
56
58
|
* Returns min, max, and average expected quality.
|
|
57
59
|
*/
|
|
58
|
-
export declare function calculateBatchQualityRange(workers: Entity[]): {
|
|
60
|
+
export declare function calculateBatchQualityRange(workers: Entity[], workshopQualityBonus_Q?: Q): {
|
|
59
61
|
min_Q: Q;
|
|
60
62
|
max_Q: Q;
|
|
61
63
|
avg_Q: Q;
|
|
@@ -72,7 +74,7 @@ export declare function advanceAssemblyStep(step: AssemblyStep, worker: Entity,
|
|
|
72
74
|
completed: boolean;
|
|
73
75
|
};
|
|
74
76
|
/** Estimate time to complete a batch given workers and workshop. */
|
|
75
|
-
export declare function estimateBatchCompletionTime(batchSize: number, workers: Entity[]): number;
|
|
77
|
+
export declare function estimateBatchCompletionTime(batchSize: number, workers: Entity[], workshopTimeReduction_Q?: Q): number;
|
|
76
78
|
/** Check if production line is complete. */
|
|
77
79
|
export declare function isProductionLineComplete(line: ProductionLine): boolean;
|
|
78
80
|
/** Get progress percentage (0–1). */
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// Batch production lines, progress accumulation, quality variance.
|
|
4
4
|
// Deterministic batch quality range based on workers, materials, workshop.
|
|
5
5
|
import { SCALE, q, clampQ, qMul, mulDiv } from "../units.js";
|
|
6
|
+
import { getWorkshopBonus } from "./workshops.js";
|
|
7
|
+
import { getRecipeById } from "./recipes.js";
|
|
6
8
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
7
9
|
/** Quality variance factor based on number of workers (more workers → less variance). */
|
|
8
10
|
const WORKER_VARIANCE_REDUCTION = q(0.10);
|
|
@@ -13,8 +15,12 @@ const MIN_QUALITY_VARIANCE = q(0.05);
|
|
|
13
15
|
* Initialize a production line for batch manufacturing.
|
|
14
16
|
*/
|
|
15
17
|
export function setupProductionLine(order, workers) {
|
|
18
|
+
const recipe = getRecipeById(order.recipeId);
|
|
19
|
+
const workshopBonus = recipe
|
|
20
|
+
? getWorkshopBonus(order.workshop, recipe)
|
|
21
|
+
: { toolBonus_Q: q(0), timeReduction_Q: q(1.0), qualityBonus_Q: q(1.0) };
|
|
16
22
|
const workerIds = workers.map(w => w.id);
|
|
17
|
-
const qualityRange = calculateBatchQualityRange(workers);
|
|
23
|
+
const qualityRange = calculateBatchQualityRange(workers, workshopBonus.qualityBonus_Q);
|
|
18
24
|
return {
|
|
19
25
|
lineId: `line_${order.orderId}`,
|
|
20
26
|
recipeId: order.recipeId,
|
|
@@ -24,6 +30,8 @@ export function setupProductionLine(order, workers) {
|
|
|
24
30
|
assignedWorkers: workerIds,
|
|
25
31
|
priority: 1,
|
|
26
32
|
qualityRange,
|
|
33
|
+
workshopTimeReduction_Q: workshopBonus.timeReduction_Q,
|
|
34
|
+
workshopQualityBonus_Q: workshopBonus.qualityBonus_Q,
|
|
27
35
|
};
|
|
28
36
|
}
|
|
29
37
|
/**
|
|
@@ -48,8 +56,11 @@ export function advanceProduction(productionLine, deltaTime_s, workers) {
|
|
|
48
56
|
const workerProgress = mulDiv(skill, deltaTime_s * SCALE.Q, 3600);
|
|
49
57
|
totalProgress += workerProgress;
|
|
50
58
|
}
|
|
51
|
-
// Apply workshop time reduction
|
|
52
|
-
const
|
|
59
|
+
// Apply workshop time reduction: timeReduction_Q < SCALE.Q means faster production
|
|
60
|
+
const timeReduction = productionLine.workshopTimeReduction_Q ?? SCALE.Q;
|
|
61
|
+
const effectiveProgress = timeReduction > 0
|
|
62
|
+
? Math.round(totalProgress * SCALE.Q / timeReduction)
|
|
63
|
+
: totalProgress;
|
|
53
64
|
// Advance progress
|
|
54
65
|
let newProgress = productionLine.progress_Q + effectiveProgress;
|
|
55
66
|
let itemsCompleted = 0;
|
|
@@ -73,7 +84,7 @@ export function advanceProduction(productionLine, deltaTime_s, workers) {
|
|
|
73
84
|
* Calculate predicted quality range for a batch based on workers, materials, workshop.
|
|
74
85
|
* Returns min, max, and average expected quality.
|
|
75
86
|
*/
|
|
76
|
-
export function calculateBatchQualityRange(workers) {
|
|
87
|
+
export function calculateBatchQualityRange(workers, workshopQualityBonus_Q = q(1.0)) {
|
|
77
88
|
if (workers.length === 0) {
|
|
78
89
|
return { min_Q: q(0), max_Q: q(0), avg_Q: q(0) };
|
|
79
90
|
}
|
|
@@ -83,10 +94,8 @@ export function calculateBatchQualityRange(workers) {
|
|
|
83
94
|
totalSkill += worker.attributes.cognition?.bodilyKinesthetic ?? q(0.50);
|
|
84
95
|
}
|
|
85
96
|
const avgSkill = totalSkill / workers.length;
|
|
86
|
-
//
|
|
87
|
-
const
|
|
88
|
-
// Base average quality = avgSkill × workshopBonus
|
|
89
|
-
const avg_Q = clampQ(qMul(avgSkill, workshopBonus), q(0), SCALE.Q);
|
|
97
|
+
// Base average quality = avgSkill × workshopQualityBonus
|
|
98
|
+
const avg_Q = clampQ(qMul(avgSkill, workshopQualityBonus_Q), q(0), SCALE.Q);
|
|
90
99
|
// Variance decreases with more workers
|
|
91
100
|
const variance = Math.max(MIN_QUALITY_VARIANCE, q(0.20) - mulDiv(WORKER_VARIANCE_REDUCTION, workers.length, 1));
|
|
92
101
|
const min_Q = clampQ((avg_Q - variance), q(0), SCALE.Q);
|
|
@@ -103,16 +112,20 @@ function updateQualityRange(currentRange) {
|
|
|
103
112
|
* Create assembly steps for a complex recipe.
|
|
104
113
|
*/
|
|
105
114
|
export function createAssemblySteps(recipe) {
|
|
106
|
-
// Placeholder: generate steps based on recipe complexity
|
|
107
115
|
const steps = [];
|
|
108
116
|
const stepCount = Math.max(1, Math.round(recipe.complexity_Q / q(0.30)));
|
|
117
|
+
// Derive skills and tools from recipe requirements
|
|
118
|
+
const skillTypes = recipe.skillRequirements.length > 0
|
|
119
|
+
? recipe.skillRequirements.map(sr => sr.skillType)
|
|
120
|
+
: ["bodilyKinesthetic", "logicalMathematical"];
|
|
121
|
+
const toolCategories = recipe.toolRequirements.map(tr => tr.toolCategory);
|
|
109
122
|
for (let i = 0; i < stepCount; i++) {
|
|
110
123
|
steps.push({
|
|
111
124
|
stepId: `step_${i}`,
|
|
112
125
|
description: `Step ${i + 1}`,
|
|
113
|
-
requiredSkill: i %
|
|
126
|
+
requiredSkill: skillTypes[i % skillTypes.length],
|
|
114
127
|
timeFraction: Math.round(SCALE.Q / stepCount),
|
|
115
|
-
toolRequirements: i === 0 ? [
|
|
128
|
+
toolRequirements: i === 0 && toolCategories.length > 0 ? [toolCategories[0]] : [],
|
|
116
129
|
});
|
|
117
130
|
}
|
|
118
131
|
return steps;
|
|
@@ -135,19 +148,23 @@ export function advanceAssemblyStep(step, worker, deltaTime_s, availableTools) {
|
|
|
135
148
|
}
|
|
136
149
|
// ── Utility Functions ────────────────────────────────────────────────────────
|
|
137
150
|
/** Estimate time to complete a batch given workers and workshop. */
|
|
138
|
-
export function estimateBatchCompletionTime(batchSize, workers) {
|
|
151
|
+
export function estimateBatchCompletionTime(batchSize, workers, workshopTimeReduction_Q = q(1.0)) {
|
|
139
152
|
if (workers.length === 0)
|
|
140
153
|
return Infinity;
|
|
154
|
+
if (batchSize === 0)
|
|
155
|
+
return 0;
|
|
141
156
|
let totalSkill = 0;
|
|
142
157
|
for (const worker of workers) {
|
|
143
158
|
totalSkill += worker.attributes.cognition?.bodilyKinesthetic ?? q(0.50);
|
|
144
159
|
}
|
|
145
160
|
const avgSkill = totalSkill / workers.length;
|
|
146
|
-
|
|
161
|
+
if (avgSkill <= 0)
|
|
162
|
+
return Infinity;
|
|
163
|
+
// At skill q(1.0), one item takes baseTimePerItem_s seconds.
|
|
164
|
+
// With time reduction q(0.90), items take 90% as long (10% faster).
|
|
147
165
|
const baseTimePerItem_s = 3600;
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
return effectiveTimePerItem * batchSize / SCALE.Q;
|
|
166
|
+
const timePerItem_s = Math.round(baseTimePerItem_s * workshopTimeReduction_Q / avgSkill);
|
|
167
|
+
return timePerItem_s * batchSize;
|
|
151
168
|
}
|
|
152
169
|
/** Check if production line is complete. */
|
|
153
170
|
export function isProductionLineComplete(line) {
|
|
@@ -119,8 +119,8 @@ export function createMaterialItem(materialTypeId, quality_Q, quantity_kg, itemI
|
|
|
119
119
|
id: itemId,
|
|
120
120
|
kind: "material",
|
|
121
121
|
name: displayName,
|
|
122
|
-
mass_kg: Math.round(quantity_kg * SCALE.kg
|
|
123
|
-
bulk: q(
|
|
122
|
+
mass_kg: Math.round(quantity_kg * SCALE.kg / SCALE.Q),
|
|
123
|
+
bulk: clampQ(Math.round(quantity_kg / 10), q(0.05), 5 * SCALE.Q),
|
|
124
124
|
materialTypeId,
|
|
125
125
|
quality_Q,
|
|
126
126
|
quantity_kg,
|
|
@@ -196,7 +196,7 @@ export function resolveRecipe(recipe, entity, inventory, availableToolQualities,
|
|
|
196
196
|
const timeTaken_s = Math.round(time_s * q(0.50) / (skillBonus > 0 ? skillBonus : q(0.50)));
|
|
197
197
|
// Determine descriptor
|
|
198
198
|
const descriptor = qualityToDescriptor(quality_Q);
|
|
199
|
-
//
|
|
199
|
+
// Build consumed ingredients list (actual inventory mutation is handled by the caller)
|
|
200
200
|
const consumedIngredients = recipe.ingredients.map(ing => ({
|
|
201
201
|
itemId: ing.itemId,
|
|
202
202
|
quantity: ing.quantity,
|
|
@@ -125,17 +125,19 @@ targetLevel) {
|
|
|
125
125
|
if (targetTier <= currentTier) {
|
|
126
126
|
return { success: false, upgradedWorkshop: workshop, consumedResources: new Map() };
|
|
127
127
|
}
|
|
128
|
-
//
|
|
129
|
-
|
|
128
|
+
// Check resource requirements: 10 units of "material_wood" per tier step
|
|
129
|
+
const tierSteps = targetTier - currentTier;
|
|
130
|
+
const woodRequired = 10 * tierSteps;
|
|
131
|
+
const woodAvailable = resources.get("material_wood") ?? 0;
|
|
132
|
+
if (woodAvailable < woodRequired) {
|
|
133
|
+
return { success: false, upgradedWorkshop: workshop, consumedResources: new Map() };
|
|
134
|
+
}
|
|
130
135
|
const upgradedWorkshop = {
|
|
131
136
|
...workshop,
|
|
132
137
|
facilityLevel: targetLevel,
|
|
133
138
|
};
|
|
134
|
-
// Consume resources (placeholder)
|
|
135
139
|
const consumedResources = new Map();
|
|
136
|
-
|
|
137
|
-
const tierSteps = targetTier - currentTier;
|
|
138
|
-
consumedResources.set("material_wood", 10 * tierSteps);
|
|
140
|
+
consumedResources.set("material_wood", woodRequired);
|
|
139
141
|
return { success: true, upgradedWorkshop, consumedResources };
|
|
140
142
|
}
|
|
141
143
|
// ── Workshop Creation ─────────────────────────────────────────────────────────
|
package/dist/src/dialogue.d.ts
CHANGED
|
@@ -49,7 +49,8 @@ export type DialogueOutcome = {
|
|
|
49
49
|
/**
|
|
50
50
|
* Context for a dialogue resolution.
|
|
51
51
|
*
|
|
52
|
-
* `sharedFaction` —
|
|
52
|
+
* `sharedFaction` — when true, applies a `PERSUADE_FACTION_BONUS` to persuasion rolls;
|
|
53
|
+
* set by the host when entities belong to the same faction.
|
|
53
54
|
* `priorFailedAttempts` — cumulative failed persuasion attempts by this initiator against
|
|
54
55
|
* this target; each one imposes a PERSUADE_FAILURE_PENALTY.
|
|
55
56
|
*/
|
package/dist/src/inventory.js
CHANGED
|
@@ -381,12 +381,7 @@ export function findMaterialsByType(inventory, materialTypeId) {
|
|
|
381
381
|
const results = [];
|
|
382
382
|
// Helper to check and add
|
|
383
383
|
const checkItem = (item) => {
|
|
384
|
-
|
|
385
|
-
// Since ItemInstance doesn't have materialTypeId, we need to rely on the templateId mapping
|
|
386
|
-
// or assume that the item is a Material (which extends ItemBase).
|
|
387
|
-
// For now, we'll assume that templateId indicates material type (e.g., "material_iron").
|
|
388
|
-
// This is a placeholder; we need to integrate with crafting material system.
|
|
389
|
-
if (item.templateId.startsWith("material_") && item.templateId.includes(materialTypeId)) {
|
|
384
|
+
if (item.templateId === `material_${materialTypeId}`) {
|
|
390
385
|
results.push(item);
|
|
391
386
|
}
|
|
392
387
|
};
|