@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,47 @@
|
|
|
1
|
+
import type { WorldState } from "./sim/world.js";
|
|
2
|
+
import type { CommandMap, Command } from "./sim/commands.js";
|
|
3
|
+
import type { KernelContext } from "./sim/context.js";
|
|
4
|
+
/** One recorded tick: the tick number and the commands dispatched that tick. */
|
|
5
|
+
export interface ReplayFrame {
|
|
6
|
+
tick: number;
|
|
7
|
+
commands: ReadonlyArray<readonly [entityId: number, cmds: ReadonlyArray<Command>]>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A complete replay: the initial world snapshot plus one frame per recorded tick.
|
|
11
|
+
* Replaying from `initialState` and re-applying `frames` in order deterministically
|
|
12
|
+
* reproduces the original simulation.
|
|
13
|
+
*/
|
|
14
|
+
export interface Replay {
|
|
15
|
+
/** Deep clone of the WorldState before the first stepWorld call. */
|
|
16
|
+
initialState: WorldState;
|
|
17
|
+
frames: readonly ReplayFrame[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Records commands applied each tick so the simulation can be replayed later.
|
|
21
|
+
*
|
|
22
|
+
* Usage:
|
|
23
|
+
* const recorder = new ReplayRecorder(world); // snapshot before first step
|
|
24
|
+
* recorder.record(world.tick, cmds); // call once per tick
|
|
25
|
+
* stepWorld(world, cmds, ctx);
|
|
26
|
+
* const replay = recorder.toReplay();
|
|
27
|
+
*/
|
|
28
|
+
export declare class ReplayRecorder {
|
|
29
|
+
private readonly _initialState;
|
|
30
|
+
private readonly _frames;
|
|
31
|
+
constructor(world: WorldState);
|
|
32
|
+
/** Record the commands dispatched for one tick. Call once per tick, before or after stepWorld. */
|
|
33
|
+
record(tick: number, commands: CommandMap): void;
|
|
34
|
+
toReplay(): Replay;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Replay a recorded simulation up to (and including) `targetTick`.
|
|
38
|
+
* Returns the reconstructed WorldState at that tick.
|
|
39
|
+
* Does NOT mutate the Replay.
|
|
40
|
+
*
|
|
41
|
+
* Pass `ctx.trace` to collect all replayed events for analysis.
|
|
42
|
+
*/
|
|
43
|
+
export declare function replayTo(replay: Replay, targetTick: number, ctx: KernelContext): WorldState;
|
|
44
|
+
/** Serialize a Replay to a JSON string (handles Maps). */
|
|
45
|
+
export declare function serializeReplay(replay: Replay): string;
|
|
46
|
+
/** Deserialize a JSON string produced by `serializeReplay` back into a Replay. */
|
|
47
|
+
export declare function deserializeReplay(json: string): Replay;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// src/replay.ts — Phase 13: deterministic replay system
|
|
2
|
+
//
|
|
3
|
+
// Deterministic replays work because stepWorld is a pure function of:
|
|
4
|
+
// (WorldState, CommandMap, KernelContext)
|
|
5
|
+
//
|
|
6
|
+
// Recording: snapshot the initial WorldState, then log commands per tick.
|
|
7
|
+
// Replaying: restore the snapshot, apply logged commands in order.
|
|
8
|
+
import { stepWorld } from "./sim/kernel.js";
|
|
9
|
+
/**
|
|
10
|
+
* Records commands applied each tick so the simulation can be replayed later.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* const recorder = new ReplayRecorder(world); // snapshot before first step
|
|
14
|
+
* recorder.record(world.tick, cmds); // call once per tick
|
|
15
|
+
* stepWorld(world, cmds, ctx);
|
|
16
|
+
* const replay = recorder.toReplay();
|
|
17
|
+
*/
|
|
18
|
+
export class ReplayRecorder {
|
|
19
|
+
_initialState;
|
|
20
|
+
_frames = [];
|
|
21
|
+
constructor(world) {
|
|
22
|
+
// structuredClone handles Maps (armourState, capabilityCooldowns) correctly.
|
|
23
|
+
this._initialState = structuredClone(world);
|
|
24
|
+
}
|
|
25
|
+
/** Record the commands dispatched for one tick. Call once per tick, before or after stepWorld. */
|
|
26
|
+
record(tick, commands) {
|
|
27
|
+
this._frames.push({
|
|
28
|
+
tick,
|
|
29
|
+
commands: [...commands.entries()].map(([id, cmds]) => [id, [...cmds]]),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
toReplay() {
|
|
33
|
+
return {
|
|
34
|
+
initialState: structuredClone(this._initialState),
|
|
35
|
+
frames: this._frames.map(f => ({ ...f })),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Replay a recorded simulation up to (and including) `targetTick`.
|
|
41
|
+
* Returns the reconstructed WorldState at that tick.
|
|
42
|
+
* Does NOT mutate the Replay.
|
|
43
|
+
*
|
|
44
|
+
* Pass `ctx.trace` to collect all replayed events for analysis.
|
|
45
|
+
*/
|
|
46
|
+
export function replayTo(replay, targetTick, ctx) {
|
|
47
|
+
const world = structuredClone(replay.initialState);
|
|
48
|
+
for (const frame of replay.frames) {
|
|
49
|
+
if (frame.tick > targetTick)
|
|
50
|
+
break;
|
|
51
|
+
const cmds = new Map(frame.commands.map(([id, cmds]) => [id, cmds]));
|
|
52
|
+
stepWorld(world, cmds, ctx);
|
|
53
|
+
}
|
|
54
|
+
return world;
|
|
55
|
+
}
|
|
56
|
+
// ── JSON serialisation ────────────────────────────────────────────────────────
|
|
57
|
+
//
|
|
58
|
+
// WorldState contains Map fields (entity.armourState, action.capabilityCooldowns).
|
|
59
|
+
// Standard JSON.stringify drops Map entries. We use a marker-based replacer/reviver.
|
|
60
|
+
const MAP_MARKER = "__ananke_map__";
|
|
61
|
+
function mapAwareReplacer(_key, value) {
|
|
62
|
+
if (value instanceof Map) {
|
|
63
|
+
return { [MAP_MARKER]: true, entries: [...value.entries()] };
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
function mapAwareReviver(_key, value) {
|
|
68
|
+
if (value !== null &&
|
|
69
|
+
typeof value === "object" &&
|
|
70
|
+
value[MAP_MARKER] === true) {
|
|
71
|
+
return new Map(value.entries);
|
|
72
|
+
}
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
/** Serialize a Replay to a JSON string (handles Maps). */
|
|
76
|
+
export function serializeReplay(replay) {
|
|
77
|
+
return JSON.stringify(replay, mapAwareReplacer);
|
|
78
|
+
}
|
|
79
|
+
/** Deserialize a JSON string produced by `serializeReplay` back into a Replay. */
|
|
80
|
+
export function deserializeReplay(json) {
|
|
81
|
+
return JSON.parse(json, mapAwareReviver);
|
|
82
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type U32 = number;
|
|
2
|
+
export declare function splitmix32(seed: U32): () => U32;
|
|
3
|
+
export declare function sfc32(a: U32, b: U32, c: U32, d: U32): {
|
|
4
|
+
nextU32: () => number;
|
|
5
|
+
};
|
|
6
|
+
export declare function makeRng(seed: U32, scaleQ: number): {
|
|
7
|
+
u32: () => number;
|
|
8
|
+
q01: () => number;
|
|
9
|
+
};
|
package/dist/src/rng.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const u32 = (x) => (x >>> 0);
|
|
2
|
+
export function splitmix32(seed) {
|
|
3
|
+
let x = u32(seed);
|
|
4
|
+
return () => {
|
|
5
|
+
x = u32(x + 0x9E3779B9);
|
|
6
|
+
let z = x;
|
|
7
|
+
z = u32((z ^ (z >>> 16)) * 0x85EBCA6B);
|
|
8
|
+
z = u32((z ^ (z >>> 13)) * 0xC2B2AE35);
|
|
9
|
+
z = u32(z ^ (z >>> 16));
|
|
10
|
+
return z;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function sfc32(a, b, c, d) {
|
|
14
|
+
let A = u32(a), B = u32(b), C = u32(c), D = u32(d);
|
|
15
|
+
const nextU32 = () => {
|
|
16
|
+
const t = u32(A + B);
|
|
17
|
+
A = u32(B ^ (B >>> 9));
|
|
18
|
+
B = u32(C + (C << 3));
|
|
19
|
+
C = u32((C << 21) | (C >>> 11));
|
|
20
|
+
D = u32(D + 1);
|
|
21
|
+
const out = u32(t + D);
|
|
22
|
+
C = u32(C + out);
|
|
23
|
+
return out;
|
|
24
|
+
};
|
|
25
|
+
return { nextU32 };
|
|
26
|
+
}
|
|
27
|
+
export function makeRng(seed, scaleQ) {
|
|
28
|
+
const sm = splitmix32(seed);
|
|
29
|
+
const r = sfc32(sm(), sm(), sm(), sm());
|
|
30
|
+
return {
|
|
31
|
+
u32: r.nextU32,
|
|
32
|
+
q01: () => {
|
|
33
|
+
const x = r.nextU32();
|
|
34
|
+
return Math.min(scaleQ - 1, Math.trunc((x / 0x1_0000_0000) * scaleQ));
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Q } from "./units.js";
|
|
2
|
+
import type { Settlement } from "./settlement.js";
|
|
3
|
+
export interface ServicePricing {
|
|
4
|
+
baseCost: number;
|
|
5
|
+
currency: string;
|
|
6
|
+
settlementMultiplier: number;
|
|
7
|
+
availability: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** Get repair service pricing for a settlement. */
|
|
10
|
+
export declare function getRepairPricing(settlement: Settlement, itemValue: number, damageLevel: Q): {
|
|
11
|
+
cost: number;
|
|
12
|
+
canRepair: boolean;
|
|
13
|
+
qualityBonus_Q: Q;
|
|
14
|
+
};
|
|
15
|
+
/** Get medical service pricing. */
|
|
16
|
+
export declare function getMedicalPricing(settlement: Settlement, careLevel: "treatment" | "surgery" | "recovery"): {
|
|
17
|
+
cost: number;
|
|
18
|
+
available: boolean;
|
|
19
|
+
careQuality: number;
|
|
20
|
+
};
|
|
21
|
+
/** Get training service pricing. */
|
|
22
|
+
export declare function getTrainingPricing(settlement: Settlement, hours: number): {
|
|
23
|
+
cost: number;
|
|
24
|
+
available: boolean;
|
|
25
|
+
xpBonus_Q: Q;
|
|
26
|
+
};
|
|
27
|
+
export interface SettlementQuestNeed {
|
|
28
|
+
type: string;
|
|
29
|
+
priority: number;
|
|
30
|
+
description: string;
|
|
31
|
+
suggestedReward: number;
|
|
32
|
+
}
|
|
33
|
+
/** Generate settlement needs that can become quests. */
|
|
34
|
+
export declare function generateSettlementNeeds(settlement: Settlement): SettlementQuestNeed[];
|
|
35
|
+
/** Select the highest priority need and convert to quest template. */
|
|
36
|
+
export declare function selectQuestNeed(settlement: Settlement, needs?: SettlementQuestNeed[]): SettlementQuestNeed | undefined;
|
|
37
|
+
export interface ServiceDescription {
|
|
38
|
+
name: string;
|
|
39
|
+
description: string;
|
|
40
|
+
available: boolean;
|
|
41
|
+
quality: string;
|
|
42
|
+
costEstimate: string;
|
|
43
|
+
}
|
|
44
|
+
/** Get descriptions of all available services for UI/display. */
|
|
45
|
+
export declare function getServiceDescriptions(settlement: Settlement): ServiceDescription[];
|
|
46
|
+
export interface SettlementInfo {
|
|
47
|
+
id: string;
|
|
48
|
+
name: string;
|
|
49
|
+
tier: string;
|
|
50
|
+
population: number;
|
|
51
|
+
populationCap: number;
|
|
52
|
+
faction?: string | undefined;
|
|
53
|
+
services: string[];
|
|
54
|
+
activeProjects: number;
|
|
55
|
+
hasQuests: boolean;
|
|
56
|
+
}
|
|
57
|
+
/** Get summary info for settlement listing. */
|
|
58
|
+
export declare function getSettlementInfo(settlement: Settlement): SettlementInfo;
|
|
59
|
+
/** Check if entity can use a service at a settlement. */
|
|
60
|
+
export declare function canUseService(settlement: Settlement, entityId: number, serviceType: "repair" | "medical" | "training" | "market"): {
|
|
61
|
+
allowed: boolean;
|
|
62
|
+
reason?: string;
|
|
63
|
+
};
|
|
64
|
+
/** Calculate total investment in a settlement (sum of all facility levels). */
|
|
65
|
+
export declare function calculateSettlementInvestment(settlement: Settlement): number;
|
|
66
|
+
/** Get settlement attractiveness score for immigration. */
|
|
67
|
+
export declare function getSettlementAttractiveness(settlement: Settlement): number;
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// src/settlement-services.ts — Phase 44: Settlement Services Integration
|
|
2
|
+
//
|
|
3
|
+
// Service availability, pricing, and quest generation for settlements.
|
|
4
|
+
// Integrates with economy (Phase 25), quests (Phase 41), and competence (Phase 40).
|
|
5
|
+
import { q, SCALE } from "./units.js";
|
|
6
|
+
import { getAvailableServices, SETTLEMENT_TIER_NAMES } from "./settlement.js";
|
|
7
|
+
/** Get repair service pricing for a settlement. */
|
|
8
|
+
export function getRepairPricing(settlement, itemValue, damageLevel) {
|
|
9
|
+
const services = getAvailableServices(settlement);
|
|
10
|
+
if (!services.repair) {
|
|
11
|
+
return { cost: 0, canRepair: false, qualityBonus_Q: q(0) };
|
|
12
|
+
}
|
|
13
|
+
// Base cost scales with item value and damage
|
|
14
|
+
const baseCost = Math.round(itemValue * 0.1 * (damageLevel / SCALE.Q));
|
|
15
|
+
// Apply market discount
|
|
16
|
+
const discountMul = SCALE.Q - services.marketDiscount_Q;
|
|
17
|
+
const cost = Math.round((baseCost * discountMul) / SCALE.Q);
|
|
18
|
+
return {
|
|
19
|
+
cost: Math.max(1, cost),
|
|
20
|
+
canRepair: true,
|
|
21
|
+
qualityBonus_Q: services.repairQualityBonus_Q,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Get medical service pricing. */
|
|
25
|
+
export function getMedicalPricing(settlement, careLevel) {
|
|
26
|
+
const services = getAvailableServices(settlement);
|
|
27
|
+
if (services.medicalCare === "none") {
|
|
28
|
+
return { cost: 0, available: false, careQuality: 0 };
|
|
29
|
+
}
|
|
30
|
+
const careQualityMap = {
|
|
31
|
+
none: 0,
|
|
32
|
+
basic: 1,
|
|
33
|
+
skilled: 2,
|
|
34
|
+
expert: 3,
|
|
35
|
+
master: 4,
|
|
36
|
+
};
|
|
37
|
+
const baseCosts = {
|
|
38
|
+
treatment: 50,
|
|
39
|
+
surgery: 200,
|
|
40
|
+
recovery: 20,
|
|
41
|
+
};
|
|
42
|
+
const careQuality = careQualityMap[services.medicalCare] ?? 0;
|
|
43
|
+
const costMultiplier = careLevel === "surgery" ? careQuality * 0.5 + 1 : 1;
|
|
44
|
+
return {
|
|
45
|
+
cost: Math.round(baseCosts[careLevel] * costMultiplier),
|
|
46
|
+
available: true,
|
|
47
|
+
careQuality: careQuality,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Get training service pricing. */
|
|
51
|
+
export function getTrainingPricing(settlement, hours) {
|
|
52
|
+
const services = getAvailableServices(settlement);
|
|
53
|
+
if (!services.training) {
|
|
54
|
+
return { cost: 0, available: false, xpBonus_Q: q(0) };
|
|
55
|
+
}
|
|
56
|
+
const hourlyRate = 10; // Currency per hour
|
|
57
|
+
const discountMul = SCALE.Q - services.marketDiscount_Q;
|
|
58
|
+
const cost = Math.round((hourlyRate * hours * discountMul) / SCALE.Q);
|
|
59
|
+
return {
|
|
60
|
+
cost: Math.max(1, cost),
|
|
61
|
+
available: true,
|
|
62
|
+
xpBonus_Q: services.trainingBonus_Q,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Generate settlement needs that can become quests. */
|
|
66
|
+
export function generateSettlementNeeds(settlement) {
|
|
67
|
+
const needs = [];
|
|
68
|
+
// Check for low facilities that need upgrading
|
|
69
|
+
const facilities = settlement.facilities;
|
|
70
|
+
const facilityNames = ["forge", "medical", "market", "barracks", "temple"];
|
|
71
|
+
for (const facility of facilityNames) {
|
|
72
|
+
const level = facilities[facility];
|
|
73
|
+
if (level < 2 && settlement.tier >= 2) {
|
|
74
|
+
needs.push({
|
|
75
|
+
type: "construction",
|
|
76
|
+
priority: 5 + (2 - level) * 2,
|
|
77
|
+
description: `Upgrade ${facility} to support growing population`,
|
|
78
|
+
suggestedReward: 100 * (level + 1),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Food shortage
|
|
83
|
+
if (settlement.foodSurplus_Q < q(0.3)) {
|
|
84
|
+
needs.push({
|
|
85
|
+
type: "supply",
|
|
86
|
+
priority: 9,
|
|
87
|
+
description: "Food shortage threatening population",
|
|
88
|
+
suggestedReward: 200,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
// Recent raid → defense need
|
|
92
|
+
if (settlement.safetyStatus.ticksSinceLastRaid < 100) {
|
|
93
|
+
needs.push({
|
|
94
|
+
type: "defense",
|
|
95
|
+
priority: 8,
|
|
96
|
+
description: "Recent raid requires improved defenses",
|
|
97
|
+
suggestedReward: 300,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
// Medical need if high population, low medical facility
|
|
101
|
+
if (settlement.population > 100 && facilities.medical < 2) {
|
|
102
|
+
needs.push({
|
|
103
|
+
type: "medical",
|
|
104
|
+
priority: 6,
|
|
105
|
+
description: "Population needs better medical facilities",
|
|
106
|
+
suggestedReward: 150,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
// Generic settlement needs based on tier
|
|
110
|
+
if (settlement.tier >= 1) {
|
|
111
|
+
needs.push({
|
|
112
|
+
type: "patrol",
|
|
113
|
+
priority: 4,
|
|
114
|
+
description: `Patrol roads near ${settlement.name}`,
|
|
115
|
+
suggestedReward: 100,
|
|
116
|
+
});
|
|
117
|
+
needs.push({
|
|
118
|
+
type: "delivery",
|
|
119
|
+
priority: 3,
|
|
120
|
+
description: "Deliver goods to nearby settlement",
|
|
121
|
+
suggestedReward: 75,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return needs.sort((a, b) => b.priority - a.priority);
|
|
125
|
+
}
|
|
126
|
+
/** Select the highest priority need and convert to quest template. */
|
|
127
|
+
export function selectQuestNeed(settlement, needs) {
|
|
128
|
+
const settlementNeeds = needs ?? generateSettlementNeeds(settlement);
|
|
129
|
+
if (settlementNeeds.length === 0)
|
|
130
|
+
return undefined;
|
|
131
|
+
// Return highest priority need
|
|
132
|
+
return settlementNeeds[0];
|
|
133
|
+
}
|
|
134
|
+
/** Get descriptions of all available services for UI/display. */
|
|
135
|
+
export function getServiceDescriptions(settlement) {
|
|
136
|
+
const services = getAvailableServices(settlement);
|
|
137
|
+
const descriptions = [];
|
|
138
|
+
// Repair
|
|
139
|
+
if (services.repair) {
|
|
140
|
+
descriptions.push({
|
|
141
|
+
name: "Repair Services",
|
|
142
|
+
description: "Weapon and armour repair",
|
|
143
|
+
available: true,
|
|
144
|
+
quality: getQualityLabel(services.repairQualityBonus_Q),
|
|
145
|
+
costEstimate: "10% of item value",
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
// Medical
|
|
149
|
+
if (services.medicalCare !== "none") {
|
|
150
|
+
descriptions.push({
|
|
151
|
+
name: "Medical Care",
|
|
152
|
+
description: "Wound treatment and surgery",
|
|
153
|
+
available: true,
|
|
154
|
+
quality: services.medicalCare,
|
|
155
|
+
costEstimate: services.medicalCare === "basic" ? "50-200" : "100-500",
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
// Training
|
|
159
|
+
if (services.training) {
|
|
160
|
+
descriptions.push({
|
|
161
|
+
name: "Training Grounds",
|
|
162
|
+
description: "Combat skill training",
|
|
163
|
+
available: true,
|
|
164
|
+
quality: getQualityLabel(services.trainingBonus_Q),
|
|
165
|
+
costEstimate: "10 per hour",
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
// Market
|
|
169
|
+
if (services.market) {
|
|
170
|
+
const discount = Math.round((services.marketDiscount_Q / SCALE.Q) * 100);
|
|
171
|
+
descriptions.push({
|
|
172
|
+
name: "Market",
|
|
173
|
+
description: "Buy and sell goods",
|
|
174
|
+
available: true,
|
|
175
|
+
quality: `${discount}% discount`,
|
|
176
|
+
costEstimate: "Variable",
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return descriptions;
|
|
180
|
+
}
|
|
181
|
+
function getQualityLabel(bonus_Q) {
|
|
182
|
+
const percent = Math.round((bonus_Q / SCALE.Q) * 100);
|
|
183
|
+
if (percent >= 15)
|
|
184
|
+
return "Excellent (+" + percent + "%)";
|
|
185
|
+
if (percent >= 10)
|
|
186
|
+
return "Good (+" + percent + "%)";
|
|
187
|
+
if (percent >= 5)
|
|
188
|
+
return "Fair (+" + percent + "%)";
|
|
189
|
+
return "Basic";
|
|
190
|
+
}
|
|
191
|
+
/** Get summary info for settlement listing. */
|
|
192
|
+
export function getSettlementInfo(settlement) {
|
|
193
|
+
const services = getAvailableServices(settlement);
|
|
194
|
+
const availableServices = [];
|
|
195
|
+
if (services.repair)
|
|
196
|
+
availableServices.push("Repair");
|
|
197
|
+
if (services.medicalCare !== "none")
|
|
198
|
+
availableServices.push("Medical");
|
|
199
|
+
if (services.training)
|
|
200
|
+
availableServices.push("Training");
|
|
201
|
+
if (services.market)
|
|
202
|
+
availableServices.push("Market");
|
|
203
|
+
return {
|
|
204
|
+
id: settlement.settlementId,
|
|
205
|
+
name: settlement.name,
|
|
206
|
+
tier: SETTLEMENT_TIER_NAMES[settlement.tier],
|
|
207
|
+
population: settlement.population,
|
|
208
|
+
populationCap: settlement.populationCap,
|
|
209
|
+
faction: settlement.factionId?.toString(),
|
|
210
|
+
services: availableServices,
|
|
211
|
+
activeProjects: settlement.activeProjects.length,
|
|
212
|
+
hasQuests: services.questGeneration,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
// ── Integration Helpers ────────────────────────────────────────────────────────
|
|
216
|
+
/** Check if entity can use a service at a settlement. */
|
|
217
|
+
export function canUseService(settlement, entityId, serviceType) {
|
|
218
|
+
// Check if service exists
|
|
219
|
+
const services = getAvailableServices(settlement);
|
|
220
|
+
switch (serviceType) {
|
|
221
|
+
case "repair":
|
|
222
|
+
if (!services.repair)
|
|
223
|
+
return { allowed: false, reason: "no_forge" };
|
|
224
|
+
break;
|
|
225
|
+
case "medical":
|
|
226
|
+
if (services.medicalCare === "none")
|
|
227
|
+
return { allowed: false, reason: "no_medical" };
|
|
228
|
+
break;
|
|
229
|
+
case "training":
|
|
230
|
+
if (!services.training)
|
|
231
|
+
return { allowed: false, reason: "no_barracks" };
|
|
232
|
+
break;
|
|
233
|
+
case "market":
|
|
234
|
+
if (!services.market)
|
|
235
|
+
return { allowed: false, reason: "no_market" };
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
// Entity can use service
|
|
239
|
+
return { allowed: true };
|
|
240
|
+
}
|
|
241
|
+
/** Calculate total investment in a settlement (sum of all facility levels). */
|
|
242
|
+
export function calculateSettlementInvestment(settlement) {
|
|
243
|
+
return Object.values(settlement.facilities).reduce((sum, level) => sum + level, 0);
|
|
244
|
+
}
|
|
245
|
+
/** Get settlement attractiveness score for immigration. */
|
|
246
|
+
export function getSettlementAttractiveness(settlement) {
|
|
247
|
+
let score = settlement.tier * 10;
|
|
248
|
+
// Facilities add attractiveness
|
|
249
|
+
score += calculateSettlementInvestment(settlement) * 2;
|
|
250
|
+
// Safety penalty
|
|
251
|
+
if (settlement.safetyStatus.ticksSinceLastRaid < 500) {
|
|
252
|
+
score -= 10;
|
|
253
|
+
}
|
|
254
|
+
// Food bonus
|
|
255
|
+
if (settlement.foodSurplus_Q > q(0.5)) {
|
|
256
|
+
score += 5;
|
|
257
|
+
}
|
|
258
|
+
// Population pressure (less attractive if overcrowded)
|
|
259
|
+
const occupancy = settlement.population / settlement.populationCap;
|
|
260
|
+
if (occupancy > 0.9) {
|
|
261
|
+
score -= 15;
|
|
262
|
+
}
|
|
263
|
+
else if (occupancy < 0.5) {
|
|
264
|
+
score += 5; // Room to grow
|
|
265
|
+
}
|
|
266
|
+
return Math.max(0, score);
|
|
267
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { Q } from "./units.js";
|
|
2
|
+
import type { Inventory } from "./inventory.js";
|
|
3
|
+
/** Facility tier levels. */
|
|
4
|
+
export type FacilityLevel = 0 | 1 | 2 | 3 | 4;
|
|
5
|
+
/** Settlement tiers from smallest to largest. */
|
|
6
|
+
export type SettlementTier = 0 | 1 | 2 | 3 | 4;
|
|
7
|
+
export declare const SETTLEMENT_TIER_NAMES: Record<SettlementTier, string>;
|
|
8
|
+
/** Settlement definition. */
|
|
9
|
+
export interface Settlement {
|
|
10
|
+
settlementId: string;
|
|
11
|
+
name: string;
|
|
12
|
+
position: {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
};
|
|
16
|
+
tier: SettlementTier;
|
|
17
|
+
/** Facilities determine available services. */
|
|
18
|
+
facilities: {
|
|
19
|
+
forge: FacilityLevel;
|
|
20
|
+
medical: FacilityLevel;
|
|
21
|
+
market: FacilityLevel;
|
|
22
|
+
barracks: FacilityLevel;
|
|
23
|
+
temple: FacilityLevel;
|
|
24
|
+
};
|
|
25
|
+
population: number;
|
|
26
|
+
populationCap: number;
|
|
27
|
+
factionId?: number | undefined;
|
|
28
|
+
/** Shared storage for guild/faction. */
|
|
29
|
+
sharedStorage?: Inventory | undefined;
|
|
30
|
+
/** Active construction projects. */
|
|
31
|
+
activeProjects: ConstructionProject[];
|
|
32
|
+
/** Settlement history for chronicle generation. */
|
|
33
|
+
history: SettlementEvent[];
|
|
34
|
+
/** Safety status (affects population growth). */
|
|
35
|
+
safetyStatus: SafetyStatus;
|
|
36
|
+
/** Food surplus metric (affects population growth). */
|
|
37
|
+
foodSurplus_Q: Q;
|
|
38
|
+
/** Tick when settlement was founded. */
|
|
39
|
+
foundedAtTick: number;
|
|
40
|
+
/** Last time settlement was updated. */
|
|
41
|
+
lastUpdateTick: number;
|
|
42
|
+
}
|
|
43
|
+
/** Safety status affecting population growth. */
|
|
44
|
+
export interface SafetyStatus {
|
|
45
|
+
/** Number of ticks since last raid/attack. */
|
|
46
|
+
ticksSinceLastRaid: number;
|
|
47
|
+
/** Whether settlement has defensive structures. */
|
|
48
|
+
hasDefenses: boolean;
|
|
49
|
+
/** Recent casualties from raids. */
|
|
50
|
+
recentCasualties: number;
|
|
51
|
+
}
|
|
52
|
+
/** Construction project for upgrading facilities. */
|
|
53
|
+
export interface ConstructionProject {
|
|
54
|
+
projectId: string;
|
|
55
|
+
targetFacility: keyof Settlement["facilities"];
|
|
56
|
+
targetLevel: FacilityLevel;
|
|
57
|
+
requiredResources: Record<string, number>;
|
|
58
|
+
progress_Q: Q;
|
|
59
|
+
contributors: number[];
|
|
60
|
+
startedAtTick: number;
|
|
61
|
+
estimatedCompletionTick?: number | undefined;
|
|
62
|
+
}
|
|
63
|
+
/** Settlement event for history/chronicle. */
|
|
64
|
+
export interface SettlementEvent {
|
|
65
|
+
tick: number;
|
|
66
|
+
type: SettlementEventType;
|
|
67
|
+
description: string;
|
|
68
|
+
entityIds?: number[] | undefined;
|
|
69
|
+
data?: Record<string, unknown> | undefined;
|
|
70
|
+
}
|
|
71
|
+
export type SettlementEventType = "founded" | "tier_upgraded" | "facility_upgraded" | "project_started" | "project_completed" | "population_changed" | "raid" | "siege_started" | "siege_ended" | "faction_changed" | "quest_generated";
|
|
72
|
+
/** Settlement registry for world management. */
|
|
73
|
+
export interface SettlementRegistry {
|
|
74
|
+
settlements: Map<string, Settlement>;
|
|
75
|
+
byPosition: Map<string, string>;
|
|
76
|
+
byFaction: Map<number, Set<string>>;
|
|
77
|
+
}
|
|
78
|
+
/** Create a new settlement. */
|
|
79
|
+
export declare function createSettlement(settlementId: string, name: string, position: {
|
|
80
|
+
x: number;
|
|
81
|
+
y: number;
|
|
82
|
+
}, tick: number, tier?: SettlementTier, factionId?: number): Settlement;
|
|
83
|
+
/** Calculate population cap based on tier and facilities. */
|
|
84
|
+
export declare function calculatePopulationCap(tier: SettlementTier, facilities: Settlement["facilities"]): number;
|
|
85
|
+
/** Create a new settlement registry. */
|
|
86
|
+
export declare function createSettlementRegistry(): SettlementRegistry;
|
|
87
|
+
/** Register a settlement in the registry. */
|
|
88
|
+
export declare function registerSettlement(registry: SettlementRegistry, settlement: Settlement): void;
|
|
89
|
+
/** Remove a settlement from the registry. */
|
|
90
|
+
export declare function unregisterSettlement(registry: SettlementRegistry, settlementId: string): boolean;
|
|
91
|
+
/** Get settlement by position. */
|
|
92
|
+
export declare function getSettlementAtPosition(registry: SettlementRegistry, x: number, y: number): Settlement | undefined;
|
|
93
|
+
/** Get all settlements for a faction. */
|
|
94
|
+
export declare function getFactionSettlements(registry: SettlementRegistry, factionId: number): Settlement[];
|
|
95
|
+
/** Find nearest settlement to a position. */
|
|
96
|
+
export declare function findNearestSettlement(registry: SettlementRegistry, x: number, y: number): {
|
|
97
|
+
settlement: Settlement;
|
|
98
|
+
distance: number;
|
|
99
|
+
} | undefined;
|
|
100
|
+
/** Facility upgrade costs and requirements. */
|
|
101
|
+
export declare const FACILITY_UPGRADE_COSTS: Record<keyof Settlement["facilities"], Record<FacilityLevel, {
|
|
102
|
+
materials: Record<string, number>;
|
|
103
|
+
laborHours: number;
|
|
104
|
+
}>>;
|
|
105
|
+
/** Start a construction project. */
|
|
106
|
+
export declare function startConstructionProject(settlement: Settlement, facility: keyof Settlement["facilities"], targetLevel: FacilityLevel, tick: number): {
|
|
107
|
+
success: boolean;
|
|
108
|
+
reason?: string;
|
|
109
|
+
project?: ConstructionProject;
|
|
110
|
+
};
|
|
111
|
+
/** Contribute work to a construction project. */
|
|
112
|
+
export declare function contributeToProject(settlement: Settlement, projectId: string, entityId: number, competenceQuality_Q: Q, // From Phase 40 competence system
|
|
113
|
+
hoursWorked: number, tick: number): {
|
|
114
|
+
success: boolean;
|
|
115
|
+
reason?: string;
|
|
116
|
+
completed?: boolean;
|
|
117
|
+
};
|
|
118
|
+
/** Update settlement population based on conditions. */
|
|
119
|
+
export declare function updateSettlementPopulation(settlement: Settlement, tick: number): {
|
|
120
|
+
growth: number;
|
|
121
|
+
reason: string;
|
|
122
|
+
};
|
|
123
|
+
/** Services available based on facility levels. */
|
|
124
|
+
export interface AvailableServices {
|
|
125
|
+
repair: boolean;
|
|
126
|
+
repairQualityBonus_Q: Q;
|
|
127
|
+
medicalCare: "none" | "basic" | "skilled" | "expert" | "master";
|
|
128
|
+
training: boolean;
|
|
129
|
+
trainingBonus_Q: Q;
|
|
130
|
+
market: boolean;
|
|
131
|
+
marketDiscount_Q: Q;
|
|
132
|
+
questGeneration: boolean;
|
|
133
|
+
}
|
|
134
|
+
/** Get available services for a settlement. */
|
|
135
|
+
export declare function getAvailableServices(settlement: Settlement): AvailableServices;
|
|
136
|
+
/** Record a raid/siege on a settlement. */
|
|
137
|
+
export declare function recordRaid(settlement: Settlement, attackerFactionId: number, casualties: number, tick: number): void;
|
|
138
|
+
/** Update settlement defenses. */
|
|
139
|
+
export declare function updateDefenses(settlement: Settlement, hasDefenses: boolean): void;
|
|
140
|
+
/** Serialize settlement to JSON-friendly format. */
|
|
141
|
+
export declare function serializeSettlement(settlement: Settlement): unknown;
|
|
142
|
+
/** Deserialize settlement. */
|
|
143
|
+
export declare function deserializeSettlement(data: unknown): Settlement;
|