@its-not-rocket-science/ananke 0.1.68 → 0.5.1
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 +274 -0
- package/README.md +150 -431
- package/STABLE_API.md +113 -490
- package/conformance/README.md +46 -44
- package/conformance/lockstep-sequence.json +395 -395
- package/conformance/phase-order.json +21 -0
- package/conformance/replay-parity.json +65 -65
- package/conformance/state-hash-regression.json +39 -0
- package/conformance/state-hash.json +23 -22
- package/dist/src/conformance.d.ts +8 -2
- package/dist/src/content/index.d.ts +3 -0
- package/dist/src/content/index.js +2 -0
- package/dist/src/content/injector.d.ts +19 -0
- package/dist/src/content/injector.js +61 -0
- package/dist/src/content/loader.d.ts +3 -0
- package/dist/src/content/loader.js +170 -0
- package/dist/src/content/types.d.ts +56 -0
- package/dist/src/content/types.js +1 -0
- package/dist/src/content/validator.d.ts +6 -0
- package/dist/src/content/validator.js +14 -0
- package/dist/src/content-pack.d.ts +3 -2
- package/dist/src/content-pack.js +205 -10
- package/dist/src/data-governance.d.ts +73 -0
- package/dist/src/data-governance.js +188 -0
- package/dist/src/debug.d.ts +6 -0
- package/dist/src/debug.js +9 -0
- package/dist/src/determinism.d.ts +12 -0
- package/dist/src/determinism.js +93 -0
- package/dist/src/dialogue.d.ts +41 -0
- package/dist/src/dialogue.js +113 -2
- package/dist/src/economy.d.ts +97 -3
- package/dist/src/economy.js +191 -37
- package/dist/src/history/autosave.d.ts +19 -0
- package/dist/src/history/autosave.js +98 -0
- package/dist/src/history/timetravel.d.ts +28 -0
- package/dist/src/history/timetravel.js +61 -0
- package/dist/src/index.d.ts +19 -31
- package/dist/src/index.js +20 -38
- package/dist/src/model3d.js +63 -2
- package/dist/src/narrative/combat-logger.d.ts +25 -0
- package/dist/src/narrative/combat-logger.js +97 -0
- package/dist/src/narrative/plausibility.d.ts +30 -0
- package/dist/src/narrative/plausibility.js +70 -0
- package/dist/src/narrative-layer.d.ts +2 -0
- package/dist/src/narrative-layer.js +2 -0
- package/dist/src/navigation/causal-chain.d.ts +26 -0
- package/dist/src/navigation/causal-chain.js +74 -0
- package/dist/src/netcode.d.ts +1 -1
- package/dist/src/netcode.js +1 -1
- package/dist/src/performance/adaptive-tick.d.ts +23 -0
- package/dist/src/performance/adaptive-tick.js +50 -0
- package/dist/src/plugins/loader.d.ts +23 -0
- package/dist/src/plugins/loader.js +86 -0
- package/dist/src/plugins/registry.d.ts +11 -0
- package/dist/src/plugins/registry.js +28 -0
- package/dist/src/plugins/types.d.ts +31 -0
- package/dist/src/plugins/types.js +7 -0
- package/dist/src/replay.js +5 -2
- package/dist/src/scenario.js +2 -1
- package/dist/src/serialization/binary.d.ts +5 -0
- package/dist/src/serialization/binary.js +193 -0
- package/dist/src/session.d.ts +115 -0
- package/dist/src/session.js +228 -0
- package/dist/src/sim/ai/decide.js +1 -1
- package/dist/src/sim/ai/personality.js +2 -2
- package/dist/src/sim/bodyplan.d.ts +30 -0
- package/dist/src/sim/context.d.ts +2 -0
- package/dist/src/sim/kernel.js +80 -1294
- package/dist/src/sim/normalization.d.ts +12 -0
- package/dist/src/sim/normalization.js +92 -0
- package/dist/src/sim/resolvers/attack-resolver.d.ts +30 -0
- package/dist/src/sim/resolvers/attack-resolver.js +310 -0
- package/dist/src/sim/resolvers/capability-resolver.d.ts +15 -0
- package/dist/src/sim/resolvers/capability-resolver.js +270 -0
- package/dist/src/sim/resolvers/grapple-resolver.d.ts +28 -0
- package/dist/src/sim/resolvers/grapple-resolver.js +70 -0
- package/dist/src/sim/resolvers/shoot-resolver.d.ts +27 -0
- package/dist/src/sim/resolvers/shoot-resolver.js +212 -0
- package/dist/src/sim/resolvers/treat-resolver.d.ts +16 -0
- package/dist/src/sim/resolvers/treat-resolver.js +77 -0
- package/dist/src/sim/step/apply/intents.d.ts +6 -0
- package/dist/src/sim/step/apply/intents.js +83 -0
- package/dist/src/sim/step/kernel-pipeline.d.ts +9 -0
- package/dist/src/sim/step/kernel-pipeline.js +15 -0
- package/dist/src/sim/step/phases/capability-phase.d.ts +8 -0
- package/dist/src/sim/step/phases/capability-phase.js +65 -0
- package/dist/src/sim/step/phases/cooldowns-phase.d.ts +3 -0
- package/dist/src/sim/step/phases/cooldowns-phase.js +37 -0
- package/dist/src/sim/step/phases/prepare-phase.d.ts +10 -0
- package/dist/src/sim/step/phases/prepare-phase.js +33 -0
- package/dist/src/sim/step/pipeline.d.ts +2 -0
- package/dist/src/sim/step/pipeline.js +15 -0
- package/dist/src/sim/step/resolvers/impact-resolver.d.ts +9 -0
- package/dist/src/sim/step/resolvers/impact-resolver.js +55 -0
- package/dist/src/sim/step/world-phases.d.ts +7 -0
- package/dist/src/sim/step/world-phases.js +15 -0
- package/dist/src/sim/step/world-step-context.d.ts +23 -0
- package/dist/src/sim/step/world-step-context.js +1 -0
- package/dist/src/sim/systemic-toxicology.js +1 -1
- package/dist/src/sim/team.js +2 -2
- package/dist/src/sim/testing.js +4 -3
- package/dist/src/sim/toxicology.d.ts +1 -1
- package/dist/src/sim/toxicology.js +1 -1
- package/dist/src/sim/world.d.ts +17 -24
- package/dist/src/snapshot.js +3 -6
- package/dist/src/tier2.d.ts +19 -0
- package/dist/src/tier2.js +21 -0
- package/dist/src/tier3.d.ts +10 -0
- package/dist/src/tier3.js +12 -0
- package/dist/src/version.d.ts +6 -0
- package/dist/src/version.js +8 -0
- package/dist/src/wasm/bridge.d.ts +14 -0
- package/dist/src/wasm/bridge.js +84 -0
- package/dist/src/wasm-kernel.d.ts +2 -0
- package/dist/src/wasm-kernel.js +1 -0
- package/dist/src/world-evolution-backend/engine.d.ts +4 -0
- package/dist/src/world-evolution-backend/engine.js +347 -0
- package/dist/src/world-evolution-backend/host-schema.d.ts +145 -0
- package/dist/src/world-evolution-backend/host-schema.js +375 -0
- package/dist/src/world-evolution-backend/index.d.ts +1 -0
- package/dist/src/world-evolution-backend/index.js +1 -0
- package/dist/src/world-evolution-backend/open-world-host-adapter.d.ts +96 -0
- package/dist/src/world-evolution-backend/open-world-host-adapter.js +208 -0
- package/dist/src/world-evolution-backend/profiles.d.ts +4 -0
- package/dist/src/world-evolution-backend/profiles.js +136 -0
- package/dist/src/world-evolution-backend/public.d.ts +11 -0
- package/dist/src/world-evolution-backend/public.js +7 -0
- package/dist/src/world-evolution-backend/reproducibility.d.ts +8 -0
- package/dist/src/world-evolution-backend/reproducibility.js +35 -0
- package/dist/src/world-evolution-backend/timeline.d.ts +6 -0
- package/dist/src/world-evolution-backend/timeline.js +337 -0
- package/dist/src/world-evolution-backend/types.d.ts +138 -0
- package/dist/src/world-evolution-backend/types.js +1 -0
- package/dist/src/world-evolution-host-backend.d.ts +51 -0
- package/dist/src/world-evolution-host-backend.js +97 -0
- package/dist/src/world-evolution.d.ts +201 -0
- package/dist/src/world-evolution.js +543 -0
- package/dist/src/world-factory.js +2 -1
- package/dist/tools/pack-cli.js +55 -2
- package/docs/bridge-contract.md +140 -332
- package/docs/completion-evidence-dashboard.md +63 -0
- package/docs/host-contract.md +86 -310
- package/docs/host-world-evolution-schema.md +155 -0
- package/docs/integration-primer.md +29 -22
- package/docs/migration-monolith-to-modular.md +102 -120
- package/docs/package-architecture.md +116 -323
- package/docs/plugins/README.md +42 -0
- package/docs/plugins/hooks.md +22 -0
- package/docs/plugins/publishing.md +29 -0
- package/docs/plugins/security-model.md +273 -0
- package/docs/project-overview.md +30 -30
- package/docs/release-dashboard.md +47 -0
- package/docs/session-api.md +127 -0
- package/docs/stable-api-manifest.json +52 -0
- package/docs/trust-dashboard.md +37 -0
- package/docs/versioning.md +256 -245
- package/docs/wire-protocol.md +140 -209
- package/docs/world-evolution-backend.md +200 -0
- package/docs/world-evolution-engine.md +150 -0
- package/docs/world-evolution-host-backend.md +70 -0
- package/docs/world-evolution-orchestration.md +248 -0
- package/package.json +125 -6
- package/schema/content-pack.schema.json +85 -0
- package/schema/plugin.schema.json +57 -0
- package/schema/world-evolution-input.schema.json +196 -0
package/dist/src/economy.js
CHANGED
|
@@ -17,6 +17,10 @@ export const WEAR_FUMBLE_THRESHOLD = q(0.70);
|
|
|
17
17
|
const FUMBLE_CHANCE_Q = q(0.20); // 20%
|
|
18
18
|
const DEFAULT_SELL_FRACTION = 0.40; // weapons/armour
|
|
19
19
|
const MEDICAL_SELL_FRACTION = 0.50;
|
|
20
|
+
const DEFAULT_DURABILITY_VALUE_POLICY = {
|
|
21
|
+
salvageFloorFraction: 0.05,
|
|
22
|
+
conditionExponent: 1.35,
|
|
23
|
+
};
|
|
20
24
|
// ── computeItemValue ──────────────────────────────────────────────────────────
|
|
21
25
|
/**
|
|
22
26
|
* Derive economic metadata for any item or medical resource.
|
|
@@ -63,19 +67,42 @@ export function computeItemValue(item, wear_Q = q(0)) {
|
|
|
63
67
|
return { itemId: it.id, baseValue: Math.floor(it.mass_kg / SCALE.kg * 8), condition_Q: q(1.0), sellFraction: DEFAULT_SELL_FRACTION };
|
|
64
68
|
}
|
|
65
69
|
/**
|
|
66
|
-
* Convert armour
|
|
70
|
+
* Convert armour state to condition fraction.
|
|
67
71
|
*
|
|
68
72
|
* ```
|
|
69
|
-
*
|
|
73
|
+
* condition = resistRemaining_J / resist_J
|
|
70
74
|
* ```
|
|
71
|
-
*
|
|
75
|
+
*
|
|
76
|
+
* The result can be converted to wear via `conditionToWearQ`.
|
|
72
77
|
*/
|
|
73
78
|
export function armourConditionQ(resist_J, resistRemaining_J) {
|
|
74
79
|
if (resist_J <= 0)
|
|
75
|
-
return q(
|
|
80
|
+
return q(0);
|
|
76
81
|
const fraction = Math.trunc((resistRemaining_J * SCALE.Q) / resist_J);
|
|
77
82
|
return clampQ(fraction, 0, SCALE.Q);
|
|
78
83
|
}
|
|
84
|
+
/** Convert condition to wear (`wear = 1 - condition`) in fixed-point space. */
|
|
85
|
+
export function conditionToWearQ(condition_Q) {
|
|
86
|
+
return clampQ(SCALE.Q - condition_Q, 0, SCALE.Q);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Convert an ItemValue into market-usable unit values.
|
|
90
|
+
*
|
|
91
|
+
* This keeps `computeItemValue` stable and lets hosts pick stricter/looser
|
|
92
|
+
* durability discounting.
|
|
93
|
+
*/
|
|
94
|
+
export function toMarketValue(itemValue, policy = DEFAULT_DURABILITY_VALUE_POLICY) {
|
|
95
|
+
const configured = {
|
|
96
|
+
salvageFloorFraction: policy.salvageFloorFraction ?? DEFAULT_DURABILITY_VALUE_POLICY.salvageFloorFraction,
|
|
97
|
+
conditionExponent: policy.conditionExponent ?? DEFAULT_DURABILITY_VALUE_POLICY.conditionExponent,
|
|
98
|
+
};
|
|
99
|
+
const condition = Math.max(0, Math.min(1, itemValue.condition_Q / SCALE.Q));
|
|
100
|
+
const curved = Math.pow(condition, Math.max(0.01, configured.conditionExponent));
|
|
101
|
+
const multiplier = configured.salvageFloorFraction + (1 - configured.salvageFloorFraction) * curved;
|
|
102
|
+
const buyValue = Math.max(0, Math.round(itemValue.baseValue * multiplier));
|
|
103
|
+
const sellValue = Math.max(0, Math.round(buyValue * itemValue.sellFraction));
|
|
104
|
+
return { buyValue, sellValue, conditionMultiplier: multiplier };
|
|
105
|
+
}
|
|
79
106
|
// ── applyWear ─────────────────────────────────────────────────────────────────
|
|
80
107
|
/**
|
|
81
108
|
* Apply one strike's worth of use-wear to a melee weapon.
|
|
@@ -104,42 +131,65 @@ export function applyWear(weapon, actionIntensity_Q, seed) {
|
|
|
104
131
|
return { wear_Q: newWear, broke, fumble, penaltyActive };
|
|
105
132
|
}
|
|
106
133
|
// ── resolveDrops ──────────────────────────────────────────────────────────────
|
|
134
|
+
function shouldResolveDrops(entity, config) {
|
|
135
|
+
if (entity.injury.dead)
|
|
136
|
+
return { shouldDrop: true, source: "dead" };
|
|
137
|
+
if (config?.dropOnIncapacitated ?? false)
|
|
138
|
+
return { shouldDrop: true, source: "incapacitated" };
|
|
139
|
+
return { shouldDrop: false, source: "none" };
|
|
140
|
+
}
|
|
107
141
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* Default behaviour:
|
|
111
|
-
* - Dead entity → all equipped weapons, ranged weapons, and armour drop (guaranteed).
|
|
112
|
-
* - Incapacitated but living → nothing drops (use `config.dropOnIncapacitated = true` to override).
|
|
113
|
-
*
|
|
114
|
-
* Additional items from `extra.guaranteed` always drop (when applicable).
|
|
115
|
-
* `extra.probabilistic` items are rolled deterministically from `seed`.
|
|
142
|
+
* Structured drop resolution for host-facing UI and telemetry.
|
|
116
143
|
*/
|
|
117
|
-
export function
|
|
118
|
-
const
|
|
119
|
-
if (!shouldDrop)
|
|
120
|
-
return
|
|
121
|
-
|
|
122
|
-
|
|
144
|
+
export function resolveDropsDetailed(entity, seed, extra, config) {
|
|
145
|
+
const dropState = shouldResolveDrops(entity, config);
|
|
146
|
+
if (!dropState.shouldDrop) {
|
|
147
|
+
return {
|
|
148
|
+
dropped: [],
|
|
149
|
+
guaranteed: [],
|
|
150
|
+
probabilistic: [],
|
|
151
|
+
preventedByState: true,
|
|
152
|
+
source: dropState.source,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const guaranteed = [];
|
|
156
|
+
const probabilistic = [];
|
|
123
157
|
for (const item of entity.loadout.items) {
|
|
124
158
|
if (item.kind === "weapon" || item.kind === "armour" || item.kind === "ranged") {
|
|
125
|
-
|
|
159
|
+
guaranteed.push(item.id);
|
|
126
160
|
}
|
|
127
161
|
}
|
|
128
162
|
if (extra) {
|
|
129
|
-
|
|
130
|
-
result.push(id);
|
|
131
|
-
}
|
|
132
|
-
// Probabilistic roll — deterministic per item index
|
|
163
|
+
guaranteed.push(...extra.guaranteed);
|
|
133
164
|
for (let i = 0; i < extra.probabilistic.length; i++) {
|
|
134
165
|
const entry = extra.probabilistic[i];
|
|
135
166
|
const rollSeed = eventSeed(seed, i, 0, 0, 0xD50A5);
|
|
136
167
|
const rng = makeRng(rollSeed, SCALE.Q);
|
|
137
168
|
if (rng.q01() < entry.chance_Q) {
|
|
138
|
-
|
|
169
|
+
probabilistic.push(entry.itemId);
|
|
139
170
|
}
|
|
140
171
|
}
|
|
141
172
|
}
|
|
142
|
-
return
|
|
173
|
+
return {
|
|
174
|
+
dropped: [...guaranteed, ...probabilistic],
|
|
175
|
+
guaranteed,
|
|
176
|
+
probabilistic,
|
|
177
|
+
preventedByState: false,
|
|
178
|
+
source: dropState.source,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Compute the list of item IDs dropped by an entity on death or incapacitation.
|
|
183
|
+
*
|
|
184
|
+
* Default behaviour:
|
|
185
|
+
* - Dead entity → all equipped weapons, ranged weapons, and armour drop (guaranteed).
|
|
186
|
+
* - Incapacitated but living → nothing drops (use `config.dropOnIncapacitated = true` to override).
|
|
187
|
+
*
|
|
188
|
+
* Additional items from `extra.guaranteed` always drop (when applicable).
|
|
189
|
+
* `extra.probabilistic` items are rolled deterministically from `seed`.
|
|
190
|
+
*/
|
|
191
|
+
export function resolveDrops(entity, seed, extra, config) {
|
|
192
|
+
return resolveDropsDetailed(entity, seed, extra, config).dropped;
|
|
143
193
|
}
|
|
144
194
|
// ── evaluateTradeOffer ────────────────────────────────────────────────────────
|
|
145
195
|
/**
|
|
@@ -153,22 +203,119 @@ export function resolveDrops(entity, seed, extra, config) {
|
|
|
153
203
|
* `feasible` — the accepting party has all `want` items in sufficient quantities.
|
|
154
204
|
*/
|
|
155
205
|
export function evaluateTradeOffer(offer, inventory) {
|
|
156
|
-
|
|
157
|
-
|
|
206
|
+
const detailed = evaluateTradeOfferDetailed(offer, inventory);
|
|
207
|
+
return { netValue: detailed.netValue, feasible: detailed.feasible };
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Inventory-aware trade evaluation that surfaces shortages and value breakdown.
|
|
211
|
+
*/
|
|
212
|
+
export function evaluateTradeOfferDetailed(offer, inventory) {
|
|
213
|
+
const snapshots = new Map();
|
|
214
|
+
const giveValue = offer.give.reduce((sum, g) => sum + g.count * g.unitValue, 0);
|
|
215
|
+
const wantValue = offer.want.reduce((sum, w) => sum + w.count * w.unitValue, 0);
|
|
216
|
+
const shortages = [];
|
|
158
217
|
for (const w of offer.want) {
|
|
159
218
|
const entry = inventory.get(w.itemId);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
219
|
+
const owned = entry?.count ?? 0;
|
|
220
|
+
const shortage = Math.max(0, w.count - owned);
|
|
221
|
+
if (shortage > 0)
|
|
222
|
+
shortages.push({ itemId: w.itemId, missingCount: shortage });
|
|
223
|
+
snapshots.set(w.itemId, {
|
|
224
|
+
ownedCount: owned,
|
|
225
|
+
shortageCount: shortage,
|
|
226
|
+
entryUnitValue: entry?.unitValue ?? 0,
|
|
227
|
+
offerUnitValue: w.unitValue,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
for (const g of offer.give) {
|
|
231
|
+
if (!snapshots.has(g.itemId)) {
|
|
232
|
+
snapshots.set(g.itemId, {
|
|
233
|
+
ownedCount: inventory.get(g.itemId)?.count ?? 0,
|
|
234
|
+
shortageCount: 0,
|
|
235
|
+
entryUnitValue: inventory.get(g.itemId)?.unitValue ?? 0,
|
|
236
|
+
offerUnitValue: g.unitValue,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const feasible = shortages.length === 0;
|
|
241
|
+
const netValue = giveValue - wantValue;
|
|
242
|
+
return { netValue, feasible, giveValue, wantValue, shortages, snapshots };
|
|
243
|
+
}
|
|
244
|
+
// ── host reporting helpers ────────────────────────────────────────────────────
|
|
245
|
+
/** Merge simple itemized counts into an ItemInventory map. */
|
|
246
|
+
export function mergeIntoInventory(inventory, items, resolver) {
|
|
247
|
+
for (const item of items) {
|
|
248
|
+
if (item.count <= 0)
|
|
249
|
+
continue;
|
|
250
|
+
const existing = inventory.get(item.itemId);
|
|
251
|
+
if (existing) {
|
|
252
|
+
existing.count += item.count;
|
|
253
|
+
continue;
|
|
163
254
|
}
|
|
255
|
+
inventory.set(item.itemId, { count: item.count, unitValue: resolver(item.itemId) });
|
|
164
256
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
257
|
+
return inventory;
|
|
258
|
+
}
|
|
259
|
+
/** Build a host-facing value report for any itemized list. */
|
|
260
|
+
export function createEconomyReport(label, entries, resolver, tags = []) {
|
|
261
|
+
const lines = entries
|
|
262
|
+
.filter(e => e.count > 0)
|
|
263
|
+
.map((entry) => {
|
|
264
|
+
const unitValue = Math.max(0, resolver(entry.itemId));
|
|
265
|
+
return {
|
|
266
|
+
itemId: entry.itemId,
|
|
267
|
+
count: entry.count,
|
|
268
|
+
unitValue,
|
|
269
|
+
totalValue: unitValue * entry.count,
|
|
270
|
+
};
|
|
271
|
+
});
|
|
272
|
+
const grossValue = lines.reduce((sum, line) => sum + line.totalValue, 0);
|
|
273
|
+
return {
|
|
274
|
+
label,
|
|
275
|
+
totals: {
|
|
276
|
+
grossValue,
|
|
277
|
+
netValue: grossValue,
|
|
278
|
+
lineCount: lines.length,
|
|
279
|
+
},
|
|
280
|
+
lines,
|
|
281
|
+
tags,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Example flow: post-battle loot screen.
|
|
286
|
+
*/
|
|
287
|
+
export function examplePostBattleLootFlow(entity, seed, resolver) {
|
|
288
|
+
const drops = resolveDropsDetailed(entity, seed);
|
|
289
|
+
const counts = countItems(drops.dropped);
|
|
290
|
+
return createEconomyReport("post_battle_loot", counts, resolver, ["loot", drops.source]);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Example flow: repair/reuse loop that compares pre-repair vs post-repair resale.
|
|
294
|
+
*/
|
|
295
|
+
export function exampleRepairReuseLoop(item, startingWear_Q, repairedWear_Q) {
|
|
296
|
+
const before = toMarketValue(computeItemValue(item, startingWear_Q));
|
|
297
|
+
const after = toMarketValue(computeItemValue(item, repairedWear_Q));
|
|
298
|
+
return {
|
|
299
|
+
label: "repair_reuse_loop",
|
|
300
|
+
totals: {
|
|
301
|
+
grossValue: after.sellValue,
|
|
302
|
+
netValue: after.sellValue - before.sellValue,
|
|
303
|
+
lineCount: 1,
|
|
304
|
+
},
|
|
305
|
+
lines: [{
|
|
306
|
+
itemId: item.id,
|
|
307
|
+
count: 1,
|
|
308
|
+
unitValue: after.sellValue,
|
|
309
|
+
totalValue: after.sellValue,
|
|
310
|
+
}],
|
|
311
|
+
tags: ["repair", "reuse", after.sellValue >= before.sellValue ? "improved" : "degraded"],
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Example flow: settlement/shop interaction, including affordability and shortages.
|
|
316
|
+
*/
|
|
317
|
+
export function exampleSettlementTradeFlow(offer, inventory) {
|
|
318
|
+
return evaluateTradeOfferDetailed(offer, inventory);
|
|
172
319
|
}
|
|
173
320
|
// ── totalInventoryValue ───────────────────────────────────────────────────────
|
|
174
321
|
/**
|
|
@@ -180,3 +327,10 @@ export function totalInventoryValue(inventory) {
|
|
|
180
327
|
total += entry.count * entry.unitValue;
|
|
181
328
|
return total;
|
|
182
329
|
}
|
|
330
|
+
function countItems(ids) {
|
|
331
|
+
const counts = new Map();
|
|
332
|
+
for (const id of ids) {
|
|
333
|
+
counts.set(id, (counts.get(id) ?? 0) + 1);
|
|
334
|
+
}
|
|
335
|
+
return [...counts.entries()].map(([itemId, count]) => ({ itemId, count }));
|
|
336
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { WorldState } from "../sim/world.js";
|
|
2
|
+
export interface AutosaveConfig {
|
|
3
|
+
everyNTicks?: number;
|
|
4
|
+
onEntityDeath?: boolean;
|
|
5
|
+
autosaveKey?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface AutosaveStorage {
|
|
8
|
+
save(key: string, bytes: Uint8Array): Promise<void>;
|
|
9
|
+
load(key: string): Promise<Uint8Array | undefined>;
|
|
10
|
+
}
|
|
11
|
+
export declare class AutosaveManager {
|
|
12
|
+
private readonly cfg;
|
|
13
|
+
private readonly storage;
|
|
14
|
+
constructor(storage?: AutosaveStorage, cfg?: AutosaveConfig);
|
|
15
|
+
maybeAutosave(world: WorldState, previous?: WorldState): Promise<void>;
|
|
16
|
+
saveNow(world: WorldState): Promise<void>;
|
|
17
|
+
recover(): Promise<WorldState | undefined>;
|
|
18
|
+
}
|
|
19
|
+
export declare function defaultAutosaveStorage(): AutosaveStorage;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { exportWorldState, importWorldState } from "../serialization/binary.js";
|
|
2
|
+
export class AutosaveManager {
|
|
3
|
+
cfg;
|
|
4
|
+
storage;
|
|
5
|
+
constructor(storage, cfg = {}) {
|
|
6
|
+
this.storage = storage ?? defaultAutosaveStorage();
|
|
7
|
+
this.cfg = {
|
|
8
|
+
everyNTicks: cfg.everyNTicks ?? 100,
|
|
9
|
+
onEntityDeath: cfg.onEntityDeath ?? true,
|
|
10
|
+
autosaveKey: cfg.autosaveKey ?? "ananke:last-autosave",
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
async maybeAutosave(world, previous) {
|
|
14
|
+
const tickPolicy = this.cfg.everyNTicks > 0 && world.tick % this.cfg.everyNTicks === 0;
|
|
15
|
+
const deathPolicy = this.cfg.onEntityDeath && previous ? hasDeath(previous, world) : false;
|
|
16
|
+
if (tickPolicy || deathPolicy) {
|
|
17
|
+
await this.saveNow(world);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async saveNow(world) {
|
|
21
|
+
await this.storage.save(this.cfg.autosaveKey, exportWorldState(world));
|
|
22
|
+
}
|
|
23
|
+
async recover() {
|
|
24
|
+
const bytes = await this.storage.load(this.cfg.autosaveKey);
|
|
25
|
+
if (!bytes)
|
|
26
|
+
return undefined;
|
|
27
|
+
return importWorldState(bytes);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function defaultAutosaveStorage() {
|
|
31
|
+
if (typeof window === "undefined")
|
|
32
|
+
return new NodeAutosaveStorage();
|
|
33
|
+
return new IndexedDbAutosaveStorage();
|
|
34
|
+
}
|
|
35
|
+
class NodeAutosaveStorage {
|
|
36
|
+
async save(key, bytes) {
|
|
37
|
+
const fs = await import("node:fs/promises");
|
|
38
|
+
const path = await import("node:path");
|
|
39
|
+
const os = await import("node:os");
|
|
40
|
+
const dir = path.join(os.homedir(), ".ananke", "autosave");
|
|
41
|
+
await fs.mkdir(dir, { recursive: true });
|
|
42
|
+
await fs.writeFile(path.join(dir, sanitizeKey(key) + ".bin"), bytes);
|
|
43
|
+
}
|
|
44
|
+
async load(key) {
|
|
45
|
+
const fs = await import("node:fs/promises");
|
|
46
|
+
const path = await import("node:path");
|
|
47
|
+
const os = await import("node:os");
|
|
48
|
+
try {
|
|
49
|
+
const bytes = await fs.readFile(path.join(os.homedir(), ".ananke", "autosave", sanitizeKey(key) + ".bin"));
|
|
50
|
+
return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
class IndexedDbAutosaveStorage {
|
|
58
|
+
dbName = "ananke-autosave";
|
|
59
|
+
storeName = "snapshots";
|
|
60
|
+
async save(key, bytes) {
|
|
61
|
+
const db = await this.open();
|
|
62
|
+
await new Promise((resolve, reject) => {
|
|
63
|
+
const tx = db.transaction(this.storeName, "readwrite");
|
|
64
|
+
tx.objectStore(this.storeName).put(bytes, key);
|
|
65
|
+
tx.oncomplete = () => resolve();
|
|
66
|
+
tx.onerror = () => reject(tx.error);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async load(key) {
|
|
70
|
+
const db = await this.open();
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const tx = db.transaction(this.storeName, "readonly");
|
|
73
|
+
const req = tx.objectStore(this.storeName).get(key);
|
|
74
|
+
req.onsuccess = () => resolve(req.result ? new Uint8Array(req.result) : undefined);
|
|
75
|
+
req.onerror = () => reject(req.error);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async open() {
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const req = indexedDB.open(this.dbName, 1);
|
|
81
|
+
req.onupgradeneeded = () => {
|
|
82
|
+
const db = req.result;
|
|
83
|
+
if (!db.objectStoreNames.contains(this.storeName))
|
|
84
|
+
db.createObjectStore(this.storeName);
|
|
85
|
+
};
|
|
86
|
+
req.onsuccess = () => resolve(req.result);
|
|
87
|
+
req.onerror = () => reject(req.error);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function hasDeath(previous, next) {
|
|
92
|
+
const prevAlive = previous.entities.filter(e => !e.injury.dead).length;
|
|
93
|
+
const nextAlive = next.entities.filter(e => !e.injury.dead).length;
|
|
94
|
+
return nextAlive < prevAlive;
|
|
95
|
+
}
|
|
96
|
+
function sanitizeKey(input) {
|
|
97
|
+
return input.replace(/[^a-z0-9_.-]/gi, "_");
|
|
98
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { WorldState } from "../sim/world.js";
|
|
2
|
+
import type { Command, CommandMap } from "../sim/commands.js";
|
|
3
|
+
import type { KernelContext } from "../sim/context.js";
|
|
4
|
+
export interface TimeTravelOptions {
|
|
5
|
+
bufferSizeTicks?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface TickRecord {
|
|
8
|
+
tick: number;
|
|
9
|
+
state: WorldState;
|
|
10
|
+
commands: Array<readonly [entityId: number, commands: readonly Command[]]>;
|
|
11
|
+
}
|
|
12
|
+
export interface TimeTravelWorld extends WorldState {
|
|
13
|
+
rewind(ticks: number): void;
|
|
14
|
+
fork(): WorldState;
|
|
15
|
+
}
|
|
16
|
+
export declare class TimeTravelController {
|
|
17
|
+
private readonly world;
|
|
18
|
+
private readonly ctx;
|
|
19
|
+
private readonly records;
|
|
20
|
+
private readonly bufferSizeTicks;
|
|
21
|
+
constructor(world: WorldState, ctx: KernelContext, opts?: TimeTravelOptions);
|
|
22
|
+
recordTick(commands: CommandMap): void;
|
|
23
|
+
step(commands: CommandMap): void;
|
|
24
|
+
rewind(ticks: number): void;
|
|
25
|
+
fork(): WorldState;
|
|
26
|
+
getHistory(): readonly TickRecord[];
|
|
27
|
+
}
|
|
28
|
+
export declare function enableTimeTravel(world: WorldState, ctx: KernelContext, opts?: TimeTravelOptions): TimeTravelController;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { stepWorld } from "../sim/kernel.js";
|
|
2
|
+
export class TimeTravelController {
|
|
3
|
+
world;
|
|
4
|
+
ctx;
|
|
5
|
+
records = [];
|
|
6
|
+
bufferSizeTicks;
|
|
7
|
+
constructor(world, ctx, opts = {}) {
|
|
8
|
+
this.world = world;
|
|
9
|
+
this.ctx = ctx;
|
|
10
|
+
const configured = opts.bufferSizeTicks ?? 600;
|
|
11
|
+
this.bufferSizeTicks = Math.max(60, Math.min(3600, configured));
|
|
12
|
+
this.records.push({ tick: world.tick, state: structuredClone(world), commands: [] });
|
|
13
|
+
attachMethods(world, this);
|
|
14
|
+
}
|
|
15
|
+
recordTick(commands) {
|
|
16
|
+
const record = {
|
|
17
|
+
tick: this.world.tick,
|
|
18
|
+
state: structuredClone(this.world),
|
|
19
|
+
commands: [...commands.entries()].map(([id, cmds]) => [id, [...cmds]]),
|
|
20
|
+
};
|
|
21
|
+
this.records.push(record);
|
|
22
|
+
while (this.records.length > this.bufferSizeTicks)
|
|
23
|
+
this.records.shift();
|
|
24
|
+
}
|
|
25
|
+
step(commands) {
|
|
26
|
+
this.recordTick(commands);
|
|
27
|
+
stepWorld(this.world, commands, this.ctx);
|
|
28
|
+
}
|
|
29
|
+
rewind(ticks) {
|
|
30
|
+
if (ticks <= 0)
|
|
31
|
+
return;
|
|
32
|
+
const targetTick = this.world.tick - ticks;
|
|
33
|
+
const candidate = [...this.records].reverse().find(r => r.tick <= targetTick);
|
|
34
|
+
if (!candidate) {
|
|
35
|
+
throw new Error(`timetravel: cannot rewind ${ticks} ticks with buffer size ${this.bufferSizeTicks}`);
|
|
36
|
+
}
|
|
37
|
+
mutateWorld(this.world, structuredClone(candidate.state));
|
|
38
|
+
}
|
|
39
|
+
fork() {
|
|
40
|
+
return structuredClone(this.world);
|
|
41
|
+
}
|
|
42
|
+
getHistory() {
|
|
43
|
+
return this.records;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function enableTimeTravel(world, ctx, opts = {}) {
|
|
47
|
+
return new TimeTravelController(world, ctx, opts);
|
|
48
|
+
}
|
|
49
|
+
function attachMethods(world, controller) {
|
|
50
|
+
const mutable = world;
|
|
51
|
+
mutable.rewind = (ticks) => controller.rewind(ticks);
|
|
52
|
+
mutable.fork = () => controller.fork();
|
|
53
|
+
}
|
|
54
|
+
function mutateWorld(target, source) {
|
|
55
|
+
for (const key of Object.keys(target)) {
|
|
56
|
+
if (!(key in source)) {
|
|
57
|
+
delete target[key];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
Object.assign(target, source);
|
|
61
|
+
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,31 +1,19 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export
|
|
8
|
-
export
|
|
9
|
-
export
|
|
10
|
-
export
|
|
11
|
-
export
|
|
12
|
-
export
|
|
13
|
-
export
|
|
14
|
-
export
|
|
15
|
-
export
|
|
16
|
-
export
|
|
17
|
-
export
|
|
18
|
-
export
|
|
19
|
-
export
|
|
20
|
-
export * from "./model3d.js";
|
|
21
|
-
export * from "./replay.js";
|
|
22
|
-
export * from "./bridge/index.js";
|
|
23
|
-
export * from "./world-factory.js";
|
|
24
|
-
export * from "./scenario.js";
|
|
25
|
-
export * from "./catalog.js";
|
|
26
|
-
export * from "./sim/formation-combat.js";
|
|
27
|
-
export * from "./sim/cover.js";
|
|
28
|
-
export * from "./sim/ai/behavior-trees.js";
|
|
29
|
-
export * from "./snapshot.js";
|
|
30
|
-
export * from "./parallel.js";
|
|
31
|
-
export * from "./modding.js";
|
|
1
|
+
export { SCALE, G_mps2, q, clampQ, qMul, qDiv, mulDiv, to, from, sqrtQ, } from "./units.js";
|
|
2
|
+
export type { I32, Q } from "./units.js";
|
|
3
|
+
export type { IndividualAttributes } from "./types.js";
|
|
4
|
+
export type { Entity } from "./sim/entity.js";
|
|
5
|
+
export type { WorldState } from "./sim/world.js";
|
|
6
|
+
export type { KernelContext } from "./sim/context.js";
|
|
7
|
+
export type { Command, CommandMap } from "./sim/commands.js";
|
|
8
|
+
export { createWorld } from "./world-factory.js";
|
|
9
|
+
export type { EntitySpec } from "./world-factory.js";
|
|
10
|
+
export { loadScenario, validateScenario } from "./scenario.js";
|
|
11
|
+
export type { AnankeScenario, AnankeScenarioEntity } from "./scenario.js";
|
|
12
|
+
export { stepWorld } from "./sim/kernel.js";
|
|
13
|
+
export { ReplayRecorder, replayTo, serializeReplay, deserializeReplay } from "./replay.js";
|
|
14
|
+
export type { Replay, ReplayFrame } from "./replay.js";
|
|
15
|
+
export { extractRigSnapshots, deriveAnimationHints } from "./model3d.js";
|
|
16
|
+
export type { RigSnapshot, AnimationHints } from "./model3d.js";
|
|
17
|
+
export { loadPlugin } from "./plugins/loader.js";
|
|
18
|
+
export { installPluginFromRegistry } from "./plugins/registry.js";
|
|
19
|
+
export type { PluginManifest, PluginPermission, PluginHookContext, PluginHooks, PluginRuntimeApi, PluginModule, LoadedPlugin, } from "./plugins/types.js";
|
package/dist/src/index.js
CHANGED
|
@@ -1,40 +1,22 @@
|
|
|
1
|
-
// ── Tier 1 — Stable host API
|
|
2
|
-
// This is the only import path
|
|
3
|
-
// import {
|
|
1
|
+
// ── Tier 1 — Stable host API (minimal contract) ─────────────────────────────
|
|
2
|
+
// This is the only import path hosts should treat as semver-stable:
|
|
3
|
+
// import { createWorld, stepWorld, q, SCALE } from "@its-not-rocket-science/ananke"
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
5
|
+
// Everything not exported here is intentionally Tier 2/3 and must be imported
|
|
6
|
+
// via explicit subpaths (e.g. "/character", "/combat", "/tier2", "/tier3").
|
|
7
7
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
export
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
export
|
|
20
|
-
|
|
21
|
-
export
|
|
22
|
-
export
|
|
23
|
-
export * from "./sim/entity.js"; // Entity (stable fields: id, pos, mass_kg, attributes…)
|
|
24
|
-
export * from "./sim/commands.js"; // CommandMap, Command, noMove()
|
|
25
|
-
export * from "./sim/kinds.js"; // CommandKinds, MoveModes, DefenceModes, EngageModes, HitArea
|
|
26
|
-
export * from "./sim/kernel.js"; // stepWorld(), applyImpactToInjury(), applyExplosion()
|
|
27
|
-
export * from "./sim/body.js"; // BodyPlan, BodySegment, humanoid / quadruped plans
|
|
28
|
-
export * from "./sim/world.js"; // WorldState, KernelContext
|
|
29
|
-
export * from "./model3d.js"; // extractRigSnapshots(), deriveAnimationHints(), RigSnapshot, AnimationHints
|
|
30
|
-
export * from "./replay.js"; // ReplayRecorder, replayTo(), serializeReplay(), deserializeReplay()
|
|
31
|
-
export * from "./bridge/index.js"; // BridgeEngine, InterpolatedState, BridgeConfig
|
|
32
|
-
export * from "./world-factory.js"; // createWorld(), EntitySpec, ARCHETYPE_MAP, ITEM_MAP
|
|
33
|
-
export * from "./scenario.js"; // loadScenario(), validateScenario(), AnankeScenario
|
|
34
|
-
export * from "./catalog.js"; // CE-12: registerArchetype(), registerWeapon(), registerArmour(), getCatalogEntry()
|
|
35
|
-
export * from "./sim/formation-combat.js"; // Phase 69: FormationUnit, TacticalEngagement, resolveTacticalEngagement()
|
|
36
|
-
export * from "./sim/cover.js"; // CE-15: CoverSegment, computeCoverProtection(), isLineOfSightBlocked(), applyExplosionToTerrain()
|
|
37
|
-
export * from "./sim/ai/behavior-trees.js"; // CE-10: BehaviorNode, FlankTarget, RetreatTo, ProtectAlly, GuardPosition, HealTarget, Sequence, Fallback
|
|
38
|
-
export * from "./snapshot.js"; // CE-9: diffWorldState(), applyDiff(), packDiff(), unpackDiff(), WorldStateDiff
|
|
39
|
-
export * from "./parallel.js"; // CE-7: partitionWorld(), mergePartitions(), detectBoundaryPairs(), assignEntitiesToPartitions()
|
|
40
|
-
export * from "./modding.js"; // CE-16: hashMod(), registerPostTickHook(), runPostTickHooks(), registerBehaviorNode(), computeModManifest()
|
|
8
|
+
// Source of truth: docs/stable-api-manifest.json
|
|
9
|
+
// Fixed-point utilities
|
|
10
|
+
export { SCALE, G_mps2, q, clampQ, qMul, qDiv, mulDiv, to, from, sqrtQ, } from "./units.js";
|
|
11
|
+
// World creation and scenario loading
|
|
12
|
+
export { createWorld } from "./world-factory.js";
|
|
13
|
+
export { loadScenario, validateScenario } from "./scenario.js";
|
|
14
|
+
// Stepping
|
|
15
|
+
export { stepWorld } from "./sim/kernel.js";
|
|
16
|
+
// Replay / serialization
|
|
17
|
+
export { ReplayRecorder, replayTo, serializeReplay, deserializeReplay } from "./replay.js";
|
|
18
|
+
// Bridge extraction
|
|
19
|
+
export { extractRigSnapshots, deriveAnimationHints } from "./model3d.js";
|
|
20
|
+
// Plugin SDK
|
|
21
|
+
export { loadPlugin } from "./plugins/loader.js";
|
|
22
|
+
export { installPluginFromRegistry } from "./plugins/registry.js";
|