@its-not-rocket-science/ananke 0.1.44 → 0.1.47

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +71 -5
  2. package/dist/src/bridge/bridge-engine.js +0 -1
  3. package/dist/src/competence/acoustic.d.ts +1 -1
  4. package/dist/src/competence/acoustic.js +1 -7
  5. package/dist/src/competence/catalogue.js +1 -1
  6. package/dist/src/competence/engineering.js +0 -2
  7. package/dist/src/competence/framework.js +2 -4
  8. package/dist/src/competence/interspecies.js +0 -2
  9. package/dist/src/competence/language.js +0 -2
  10. package/dist/src/competence/naturalist.js +1 -1
  11. package/dist/src/crafting/index.d.ts +2 -2
  12. package/dist/src/crafting/index.js +6 -7
  13. package/dist/src/crafting/manufacturing.d.ts +11 -15
  14. package/dist/src/crafting/manufacturing.js +7 -11
  15. package/dist/src/crafting/materials.d.ts +1 -1
  16. package/dist/src/crafting/materials.js +12 -4
  17. package/dist/src/crafting/recipes.d.ts +1 -1
  18. package/dist/src/crafting/recipes.js +1 -1
  19. package/dist/src/item-durability.d.ts +1 -1
  20. package/dist/src/item-durability.js +1 -1
  21. package/dist/src/legend.d.ts +1 -1
  22. package/dist/src/legend.js +1 -1
  23. package/dist/src/modding.d.ts +11 -11
  24. package/dist/src/modding.js +10 -10
  25. package/dist/src/monetary.d.ts +104 -0
  26. package/dist/src/monetary.js +158 -0
  27. package/dist/src/narrative-render.js +3 -3
  28. package/dist/src/quest-generators.js +4 -4
  29. package/dist/src/settlement-services.js +2 -2
  30. package/dist/src/settlement.d.ts +1 -1
  31. package/dist/src/settlement.js +2 -2
  32. package/dist/src/sim/ai/behavior-trees.d.ts +2 -2
  33. package/dist/src/sim/ai/behavior-trees.js +2 -2
  34. package/dist/src/sim/bodyplan.d.ts +1 -1
  35. package/dist/src/sim/kernel.js +1 -1
  36. package/dist/src/sim/step/morale.js +1 -1
  37. package/dist/src/wonders.d.ts +125 -0
  38. package/dist/src/wonders.js +264 -0
  39. package/package.json +9 -1
@@ -8,13 +8,13 @@
8
8
  * mod file. The network replication layer (CE-11) compares fingerprints across clients
9
9
  * to guarantee all participants use identical mod definitions.
10
10
  *
11
- * **Layer 2 — Post-tick behavior hooks**
11
+ * **Layer 2 — Post-tick behaviour hooks**
12
12
  * `registerPostTickHook(id, fn)` registers an observer callback that the host fires
13
13
  * after each `stepWorld` call via `runPostTickHooks(world)`. Hooks are purely
14
14
  * observational — they MUST NOT mutate `WorldState` during the call. Because they run
15
15
  * outside the kernel path they cannot break determinism.
16
16
  *
17
- * **Layer 3 — AI behavior node overrides**
17
+ * **Layer 3 — AI behaviour node overrides**
18
18
  * `registerBehaviorNode(id, factory)` installs a named factory for custom
19
19
  * `BehaviorNode` implementations. `loadScenario` (CE-3) can reference them by id in
20
20
  * scenario JSON. AI overrides require explicit host opt-in.
@@ -117,7 +117,7 @@ const _behaviorNodes = new Map();
117
117
  * Register a named factory for a custom `BehaviorNode` implementation.
118
118
  *
119
119
  * The factory will be looked up by id when `loadScenario` (CE-3) encounters an
120
- * `"aiOverride"` reference in scenario JSON, or when a host builds a behavior
120
+ * `"aiOverride"` reference in scenario JSON, or when a host builds a behaviour
121
121
  * tree programmatically:
122
122
  *
123
123
  * ```typescript
@@ -129,7 +129,7 @@ const _behaviorNodes = new Map();
129
129
  * ```
130
130
  *
131
131
  * **Deterministic multiplayer**: AI overrides affect simulation output. All
132
- * clients must register the same behavior nodes (verified via `computeModManifest`)
132
+ * clients must register the same behaviour nodes (verified via `computeModManifest`)
133
133
  * before joining a session.
134
134
  *
135
135
  * Re-registering an existing id overwrites the previous factory.
@@ -140,30 +140,30 @@ export function registerBehaviorNode(id, factory) {
140
140
  _behaviorNodes.set(id, factory);
141
141
  }
142
142
  /**
143
- * Remove a previously registered behavior node factory.
143
+ * Remove a previously registered behaviour node factory.
144
144
  * Returns `true` if the factory existed and was removed.
145
145
  */
146
146
  export function unregisterBehaviorNode(id) {
147
147
  return _behaviorNodes.delete(id);
148
148
  }
149
149
  /**
150
- * Look up a registered behavior node factory by id.
150
+ * Look up a registered behaviour node factory by id.
151
151
  * Returns `undefined` if not found.
152
152
  */
153
153
  export function getBehaviorNode(id) {
154
154
  return _behaviorNodes.get(id);
155
155
  }
156
- /** Return the ids of all registered behavior node factories in registration order. */
156
+ /** Return the ids of all registered behaviour node factories in registration order. */
157
157
  export function listBehaviorNodes() {
158
158
  return [..._behaviorNodes.keys()];
159
159
  }
160
- /** Remove all behavior node factories (useful for testing and hot-reload scenarios). */
160
+ /** Remove all behaviour node factories (useful for testing and hot-reload scenarios). */
161
161
  export function clearBehaviorNodes() {
162
162
  _behaviorNodes.clear();
163
163
  }
164
164
  /**
165
165
  * Compute a session manifest covering all active mods (CE-12 catalog entries,
166
- * post-tick hooks, and AI behavior node overrides).
166
+ * post-tick hooks, and AI behaviour node overrides).
167
167
  *
168
168
  * The `fingerprint` is a deterministic 8-char hex string suitable for
169
169
  * multiplayer session comparison. Clients are considered mod-compatible iff
@@ -181,7 +181,7 @@ export function computeModManifest(catalogIds = []) {
181
181
  const fingerprint = fnv1a32(combined).toString(16).padStart(8, "0");
182
182
  return { dataIds, hookIds, behaviorIds, fingerprint };
183
183
  }
184
- /** Remove all hooks and behavior node factories. Does not affect the CE-12 catalog. */
184
+ /** Remove all hooks and behaviour node factories. Does not affect the CE-12 catalog. */
185
185
  export function clearAllMods() {
186
186
  _hooks.clear();
187
187
  _behaviorNodes.clear();
@@ -0,0 +1,104 @@
1
+ import type { Q } from "./units.js";
2
+ import type { Polity } from "./polity.js";
3
+ /** Polity monetary policy tier. */
4
+ export type CoinagePolicy = "stable" | "slight_debasement" | "heavy_debasement" | "emergency_printing";
5
+ /**
6
+ * Per-polity monetary state.
7
+ * Store externally (e.g. `Map<string, MonetaryState>`); pass to step each tick.
8
+ */
9
+ export interface MonetaryState {
10
+ polityId: string;
11
+ /**
12
+ * Intrinsic coin purity [0, SCALE.Q].
13
+ * Starts at SCALE.Q (pure silver/gold). Debasement reduces it; stable
14
+ * policy restores it slowly. Trade partners assess coins by purity.
15
+ */
16
+ coinPurity_Q: Q;
17
+ /**
18
+ * Accumulated price inflation [0, SCALE.Q].
19
+ * Starts at 0 (no inflation). Rises with debasement; falls slowly under
20
+ * stable policy. Drives purchasing power loss and unrest.
21
+ */
22
+ inflationLevel_Q: Q;
23
+ /**
24
+ * `true` when `inflationLevel_Q >= MONETARY_CRISIS_THRESHOLD_Q`.
25
+ * In crisis: purchasing power collapses; trade partners sharply discount coins.
26
+ */
27
+ monetaryCrisis: boolean;
28
+ }
29
+ /** Inflation level at which monetary crisis activates [Q]. */
30
+ export declare const MONETARY_CRISIS_THRESHOLD_Q: Q;
31
+ /**
32
+ * Maximum unrest pressure from inflation at full inflation level [Q].
33
+ * Scales linearly: 0 at no inflation, `MONETARY_MAX_UNREST_Q` at SCALE.Q.
34
+ */
35
+ export declare const MONETARY_MAX_UNREST_Q: Q;
36
+ /**
37
+ * Minimum trade acceptance multiplier even with near-zero coin purity [Q].
38
+ * Barter / commodity exchange prevents complete trade collapse.
39
+ */
40
+ export declare const MONETARY_TRADE_FLOOR_Q: Q;
41
+ /**
42
+ * Coin purity change per day by policy [out of SCALE.Q].
43
+ * Positive = recovery; negative = degradation.
44
+ */
45
+ export declare const POLICY_PURITY_DELTA_PER_DAY: Record<CoinagePolicy, number>;
46
+ /**
47
+ * Inflation change per day by policy [out of SCALE.Q].
48
+ * Positive = inflation rising; negative = inflation falling.
49
+ */
50
+ export declare const POLICY_INFLATION_DELTA_PER_DAY: Record<CoinagePolicy, number>;
51
+ /**
52
+ * Extra coins minted per day as a fraction of current treasury [out of SCALE.Q].
53
+ * `gain_cu = round(treasury_cu × mintFrac × elapsedDays / SCALE.Q)`
54
+ */
55
+ export declare const POLICY_DAILY_MINT_FRAC_Q: Record<CoinagePolicy, number>;
56
+ /** Create a fresh `MonetaryState` with full purity and zero inflation. */
57
+ export declare function createMonetaryState(polityId: string): MonetaryState;
58
+ /**
59
+ * Compute the effective purchasing power of treasury coins [0, SCALE.Q].
60
+ *
61
+ * `purchasingPower = coinPurity_Q × (SCALE.Q − inflationLevel_Q) / SCALE.Q`
62
+ *
63
+ * Use to scale the real value of treasury income, mercenary wages, and
64
+ * construction costs. Returns q(0.05) minimum to avoid zero.
65
+ */
66
+ export declare function computePurchasingPower_Q(state: MonetaryState): Q;
67
+ /**
68
+ * Compute the trade acceptance multiplier [MONETARY_TRADE_FLOOR_Q, SCALE.Q].
69
+ *
70
+ * Foreign trade partners check coin purity:
71
+ * `multiplier = TRADE_FLOOR + mulDiv(SCALE.Q − TRADE_FLOOR, coinPurity_Q, SCALE.Q)`
72
+ *
73
+ * Pass as a multiplier on Phase-92 trade income.
74
+ */
75
+ export declare function computeMonetaryTradeMultiplier_Q(state: MonetaryState): Q;
76
+ /**
77
+ * Compute unrest pressure from inflation [0, MONETARY_MAX_UNREST_Q].
78
+ *
79
+ * `unrest = mulDiv(MONETARY_MAX_UNREST_Q, inflationLevel_Q, SCALE.Q)`
80
+ *
81
+ * Pass to Phase-90 `computeUnrestLevel`.
82
+ */
83
+ export declare function computeMonetaryUnrest_Q(state: MonetaryState): Q;
84
+ /**
85
+ * Compute the extra treasury that would be minted by a debasement step
86
+ * without mutating state. Advisory / preview function.
87
+ */
88
+ export declare function computeDebasementGain_cu(polity: Polity, policy: CoinagePolicy, elapsedDays: number): number;
89
+ /**
90
+ * Advance monetary state by `elapsedDays` under the given policy.
91
+ *
92
+ * 1. Mints extra coins: `treasury += computeDebasementGain_cu(polity, policy, elapsedDays)`.
93
+ * 2. Updates `coinPurity_Q` by `POLICY_PURITY_DELTA_PER_DAY × elapsedDays`; clamped [0, SCALE.Q].
94
+ * 3. Updates `inflationLevel_Q` by `POLICY_INFLATION_DELTA_PER_DAY × elapsedDays`; clamped [0, SCALE.Q].
95
+ * 4. Sets `monetaryCrisis = inflationLevel_Q >= MONETARY_CRISIS_THRESHOLD_Q`.
96
+ *
97
+ * Mutates `polity.treasury_cu`, `state.coinPurity_Q`, `state.inflationLevel_Q`,
98
+ * and `state.monetaryCrisis`.
99
+ */
100
+ export declare function stepMonetary(polity: Polity, state: MonetaryState, policy: CoinagePolicy, elapsedDays: number): void;
101
+ /** Return `true` when the polity is in monetary crisis (high inflation). */
102
+ export declare function isMonetaryCrisis(state: MonetaryState): boolean;
103
+ /** Return `true` when coin purity is at or above the given threshold. */
104
+ export declare function isCoinageSound(state: MonetaryState, threshold_Q?: Q): boolean;
@@ -0,0 +1,158 @@
1
+ // src/monetary.ts — Phase 101: Currency & Monetary Policy
2
+ //
3
+ // Models coin purity, inflation, and monetary crises at polity scale.
4
+ // Rulers can debase coinage (mint extra coins at lower purity) for short-term
5
+ // treasury gain, but sustained debasement causes inflation, trade rejection,
6
+ // and civil unrest.
7
+ //
8
+ // Design:
9
+ // - Pure data layer — no Entity fields, no kernel changes.
10
+ // - `MonetaryState` stores coin purity and inflation level separately:
11
+ // coinPurity_Q — intrinsic metal content; affects trade acceptance.
12
+ // inflationLevel_Q — accumulated price inflation; affects purchasing power and unrest.
13
+ // - `stepMonetary` mints extra coins (treasury gain), degrades purity, and accrues
14
+ // inflation; stable policy slowly restores both.
15
+ // - All derived metrics (`purchasingPower_Q`, `tradeMultiplier_Q`, `unrestPressure_Q`)
16
+ // are advisory; callers pass them to Phases 90/92/93 as needed.
17
+ //
18
+ // Integration:
19
+ // Phase 90 (Unrest): computeMonetaryUnrest_Q → unrestPressure_Q.
20
+ // Phase 92 (Taxation): computePurchasingPower_Q → real value of tax revenue.
21
+ // Phase 93 (Campaign): computeMonetaryTradeMultiplier_Q → tribute / supply costs.
22
+ // Phase 99 (Mercenaries): high inflation → real wage cost rises; host adjusts.
23
+ // Phase 100 (Wonders): construction costs inflated; host scales contribution.
24
+ import { q, SCALE, clampQ, mulDiv } from "./units.js";
25
+ // ── Constants ─────────────────────────────────────────────────────────────────
26
+ /** Inflation level at which monetary crisis activates [Q]. */
27
+ export const MONETARY_CRISIS_THRESHOLD_Q = q(0.60);
28
+ /**
29
+ * Maximum unrest pressure from inflation at full inflation level [Q].
30
+ * Scales linearly: 0 at no inflation, `MONETARY_MAX_UNREST_Q` at SCALE.Q.
31
+ */
32
+ export const MONETARY_MAX_UNREST_Q = q(0.25);
33
+ /**
34
+ * Minimum trade acceptance multiplier even with near-zero coin purity [Q].
35
+ * Barter / commodity exchange prevents complete trade collapse.
36
+ */
37
+ export const MONETARY_TRADE_FLOOR_Q = q(0.40);
38
+ /**
39
+ * Coin purity change per day by policy [out of SCALE.Q].
40
+ * Positive = recovery; negative = degradation.
41
+ */
42
+ export const POLICY_PURITY_DELTA_PER_DAY = {
43
+ stable: +3,
44
+ slight_debasement: -5,
45
+ heavy_debasement: -18,
46
+ emergency_printing: -40,
47
+ };
48
+ /**
49
+ * Inflation change per day by policy [out of SCALE.Q].
50
+ * Positive = inflation rising; negative = inflation falling.
51
+ */
52
+ export const POLICY_INFLATION_DELTA_PER_DAY = {
53
+ stable: -3,
54
+ slight_debasement: +6,
55
+ heavy_debasement: +20,
56
+ emergency_printing: +50,
57
+ };
58
+ /**
59
+ * Extra coins minted per day as a fraction of current treasury [out of SCALE.Q].
60
+ * `gain_cu = round(treasury_cu × mintFrac × elapsedDays / SCALE.Q)`
61
+ */
62
+ export const POLICY_DAILY_MINT_FRAC_Q = {
63
+ stable: 0,
64
+ slight_debasement: 3, // +0.03%/day ≈ +11%/year
65
+ heavy_debasement: 10, // +0.10%/day ≈ +37%/year
66
+ emergency_printing: 30, // +0.30%/day ≈ +110%/year
67
+ };
68
+ // ── Factory ───────────────────────────────────────────────────────────────────
69
+ /** Create a fresh `MonetaryState` with full purity and zero inflation. */
70
+ export function createMonetaryState(polityId) {
71
+ return {
72
+ polityId,
73
+ coinPurity_Q: SCALE.Q,
74
+ inflationLevel_Q: 0,
75
+ monetaryCrisis: false,
76
+ };
77
+ }
78
+ // ── Advisory metrics ──────────────────────────────────────────────────────────
79
+ /**
80
+ * Compute the effective purchasing power of treasury coins [0, SCALE.Q].
81
+ *
82
+ * `purchasingPower = coinPurity_Q × (SCALE.Q − inflationLevel_Q) / SCALE.Q`
83
+ *
84
+ * Use to scale the real value of treasury income, mercenary wages, and
85
+ * construction costs. Returns q(0.05) minimum to avoid zero.
86
+ */
87
+ export function computePurchasingPower_Q(state) {
88
+ const deflated = clampQ(SCALE.Q - state.inflationLevel_Q, 0, SCALE.Q);
89
+ const pp = mulDiv(state.coinPurity_Q, deflated, SCALE.Q);
90
+ return clampQ(pp, q(0.05), SCALE.Q);
91
+ }
92
+ /**
93
+ * Compute the trade acceptance multiplier [MONETARY_TRADE_FLOOR_Q, SCALE.Q].
94
+ *
95
+ * Foreign trade partners check coin purity:
96
+ * `multiplier = TRADE_FLOOR + mulDiv(SCALE.Q − TRADE_FLOOR, coinPurity_Q, SCALE.Q)`
97
+ *
98
+ * Pass as a multiplier on Phase-92 trade income.
99
+ */
100
+ export function computeMonetaryTradeMultiplier_Q(state) {
101
+ const floor = MONETARY_TRADE_FLOOR_Q;
102
+ const range = SCALE.Q - floor;
103
+ return clampQ(floor + mulDiv(range, state.coinPurity_Q, SCALE.Q), floor, SCALE.Q);
104
+ }
105
+ /**
106
+ * Compute unrest pressure from inflation [0, MONETARY_MAX_UNREST_Q].
107
+ *
108
+ * `unrest = mulDiv(MONETARY_MAX_UNREST_Q, inflationLevel_Q, SCALE.Q)`
109
+ *
110
+ * Pass to Phase-90 `computeUnrestLevel`.
111
+ */
112
+ export function computeMonetaryUnrest_Q(state) {
113
+ return clampQ(mulDiv(MONETARY_MAX_UNREST_Q, state.inflationLevel_Q, SCALE.Q), 0, MONETARY_MAX_UNREST_Q);
114
+ }
115
+ /**
116
+ * Compute the extra treasury that would be minted by a debasement step
117
+ * without mutating state. Advisory / preview function.
118
+ */
119
+ export function computeDebasementGain_cu(polity, policy, elapsedDays) {
120
+ const mintFrac = POLICY_DAILY_MINT_FRAC_Q[policy];
121
+ if (mintFrac === 0)
122
+ return 0;
123
+ return Math.round(polity.treasury_cu * mintFrac * elapsedDays / SCALE.Q);
124
+ }
125
+ // ── State step ────────────────────────────────────────────────────────────────
126
+ /**
127
+ * Advance monetary state by `elapsedDays` under the given policy.
128
+ *
129
+ * 1. Mints extra coins: `treasury += computeDebasementGain_cu(polity, policy, elapsedDays)`.
130
+ * 2. Updates `coinPurity_Q` by `POLICY_PURITY_DELTA_PER_DAY × elapsedDays`; clamped [0, SCALE.Q].
131
+ * 3. Updates `inflationLevel_Q` by `POLICY_INFLATION_DELTA_PER_DAY × elapsedDays`; clamped [0, SCALE.Q].
132
+ * 4. Sets `monetaryCrisis = inflationLevel_Q >= MONETARY_CRISIS_THRESHOLD_Q`.
133
+ *
134
+ * Mutates `polity.treasury_cu`, `state.coinPurity_Q`, `state.inflationLevel_Q`,
135
+ * and `state.monetaryCrisis`.
136
+ */
137
+ export function stepMonetary(polity, state, policy, elapsedDays) {
138
+ // Mint gain
139
+ const gain = computeDebasementGain_cu(polity, policy, elapsedDays);
140
+ polity.treasury_cu += gain;
141
+ // Purity update
142
+ const purityDelta = POLICY_PURITY_DELTA_PER_DAY[policy] * elapsedDays;
143
+ state.coinPurity_Q = clampQ(state.coinPurity_Q + purityDelta, 0, SCALE.Q);
144
+ // Inflation update
145
+ const inflDelta = POLICY_INFLATION_DELTA_PER_DAY[policy] * elapsedDays;
146
+ state.inflationLevel_Q = clampQ(state.inflationLevel_Q + inflDelta, 0, SCALE.Q);
147
+ // Crisis flag
148
+ state.monetaryCrisis = state.inflationLevel_Q >= MONETARY_CRISIS_THRESHOLD_Q;
149
+ }
150
+ // ── Helpers ───────────────────────────────────────────────────────────────────
151
+ /** Return `true` when the polity is in monetary crisis (high inflation). */
152
+ export function isMonetaryCrisis(state) {
153
+ return state.monetaryCrisis;
154
+ }
155
+ /** Return `true` when coin purity is at or above the given threshold. */
156
+ export function isCoinageSound(state, threshold_Q = q(0.80)) {
157
+ return state.coinPurity_Q >= threshold_Q;
158
+ }
@@ -31,9 +31,9 @@ export function renderArcSummary(arc) {
31
31
  rise_of_hero: (a) => `The hero's journey of ${actorNames(a)} spans ${entryCount(a)} pivotal moments.`,
32
32
  tragic_fall: (a) => `${actorNames(a)}'s descent from grace, marked by betrayal and loss.`,
33
33
  rivalry: (a) => `An enduring rivalry between ${actorNames(a)} unfolding across ${entryCount(a)} confrontations.`,
34
- great_migration: (a) => `A mass migration that reshaped the region.`,
35
- settlement_growth: (a) => `The rise of a settlement from humble beginnings to prosperity.`,
36
- fallen_settlement: (a) => `The tragic fall of a once-great settlement.`,
34
+ great_migration: (_a) => `A mass migration that reshaped the region.`,
35
+ settlement_growth: (_a) => `The rise of a settlement from humble beginnings to prosperity.`,
36
+ fallen_settlement: (_a) => `The tragic fall of a once-great settlement.`,
37
37
  legendary_craftsman: (a) => `${actorNames(a)}'s masterworks will be remembered for generations.`,
38
38
  notorious_villain: (a) => `The terrifying rise of ${actorNames(a)}.`,
39
39
  unlikely_friendship: (a) => `An unexpected bond formed between ${actorNames(a)} against all odds.`,
@@ -40,7 +40,7 @@ const DELIVERY_TEMPLATE = {
40
40
  currency: 50,
41
41
  },
42
42
  objectiveGenerators: [
43
- (ctx) => ({
43
+ (_ctx) => ({
44
44
  objectiveId: "collect_package",
45
45
  description: "Collect the package from the quest giver",
46
46
  type: "collect_item",
@@ -129,7 +129,7 @@ const INVESTIGATION_TEMPLATE = {
129
129
  state: "available",
130
130
  hidden: false,
131
131
  }),
132
- (ctx) => ({
132
+ (_ctx) => ({
133
133
  objectiveId: "report_findings",
134
134
  description: "Return and report your findings",
135
135
  type: "dialogue_choice",
@@ -185,7 +185,7 @@ const COLLECTION_TEMPLATE = {
185
185
  currency: 60,
186
186
  },
187
187
  objectiveGenerators: [
188
- (ctx) => ({
188
+ (_ctx) => ({
189
189
  objectiveId: "collect_items",
190
190
  description: "Collect the requested items",
191
191
  type: "collect_item",
@@ -224,7 +224,7 @@ const WAIT_TEMPLATE = {
224
224
  state: "available",
225
225
  hidden: false,
226
226
  }),
227
- (ctx) => ({
227
+ (_ctx) => ({
228
228
  objectiveId: "stand_watch",
229
229
  description: "Stand watch for the required duration",
230
230
  type: "wait_duration",
@@ -88,12 +88,12 @@ export function generateSettlementNeeds(settlement) {
88
88
  suggestedReward: 200,
89
89
  });
90
90
  }
91
- // Recent raid → defense need
91
+ // Recent raid → defence need
92
92
  if (settlement.safetyStatus.ticksSinceLastRaid < 100) {
93
93
  needs.push({
94
94
  type: "defense",
95
95
  priority: 8,
96
- description: "Recent raid requires improved defenses",
96
+ description: "Recent raid requires improved defences",
97
97
  suggestedReward: 300,
98
98
  });
99
99
  }
@@ -135,7 +135,7 @@ export interface AvailableServices {
135
135
  export declare function getAvailableServices(settlement: Settlement): AvailableServices;
136
136
  /** Record a raid/siege on a settlement. */
137
137
  export declare function recordRaid(settlement: Settlement, attackerFactionId: number, casualties: number, tick: number): void;
138
- /** Update settlement defenses. */
138
+ /** Update settlement defences. */
139
139
  export declare function updateDefenses(settlement: Settlement, hasDefenses: boolean): void;
140
140
  /** Serialize settlement to JSON-friendly format. */
141
141
  export declare function serializeSettlement(settlement: Settlement): unknown;
@@ -351,7 +351,7 @@ function getMedicalCareLevel(level) {
351
351
  default: return "none";
352
352
  }
353
353
  }
354
- // ── Settlement Defense ─────────────────────────────────────────────────────────
354
+ // ── Settlement Defence ─────────────────────────────────────────────────────────
355
355
  /** Record a raid/siege on a settlement. */
356
356
  export function recordRaid(settlement, attackerFactionId, casualties, tick) {
357
357
  settlement.safetyStatus.ticksSinceLastRaid = 0;
@@ -367,7 +367,7 @@ export function recordRaid(settlement, attackerFactionId, casualties, tick) {
367
367
  settlement.population = Math.max(0, settlement.population - casualties);
368
368
  }
369
369
  }
370
- /** Update settlement defenses. */
370
+ /** Update settlement defences. */
371
371
  export function updateDefenses(settlement, hasDefenses) {
372
372
  settlement.safetyStatus.hasDefenses = hasDefenses;
373
373
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * CE-10 — Pre-built AI Behavior Tree Library
2
+ * CE-10 — Pre-built AI Behaviour Tree Library
3
3
  *
4
4
  * A thin, composable layer over the existing AI decision system. Each
5
5
  * `BehaviorNode` receives the ticking entity, the current world state, and
@@ -31,7 +31,7 @@ import type { KernelContext } from "../context.js";
31
31
  import type { Command } from "../commands.js";
32
32
  import { type Q } from "../../units.js";
33
33
  /**
34
- * A single node in a behavior tree.
34
+ * A single node in a behaviour tree.
35
35
  *
36
36
  * `tick` is called once per AI frame (typically once per `stepWorld` tick).
37
37
  * Returns a `Command` if this node produces an action, or `null` if the node's
@@ -1,5 +1,5 @@
1
1
  /**
2
- * CE-10 — Pre-built AI Behavior Tree Library
2
+ * CE-10 — Pre-built AI Behaviour Tree Library
3
3
  *
4
4
  * A thin, composable layer over the existing AI decision system. Each
5
5
  * `BehaviorNode` receives the ticking entity, the current world state, and
@@ -302,7 +302,7 @@ export function WithProbability(probability_Q, inner, salt = 0) {
302
302
  },
303
303
  };
304
304
  }
305
- // ── Pre-built behavior tree presets ──────────────────────────────────────────
305
+ // ── Pre-built behaviour tree presets ─────────────────────────────────────────
306
306
  /**
307
307
  * Standard aggressive attacker: attack `targetId` at full intensity.
308
308
  * Falls back to retreat if badly shocked (shock ≥ q(0.70)).
@@ -81,7 +81,7 @@ export interface BodySegment {
81
81
  */
82
82
  regeneratesViaMolting?: boolean;
83
83
  /**
84
- * Intrinsic structural armor resist (joules) — energy absorbed by the shell
84
+ * Intrinsic structural armour resist (joules) — energy absorbed by the shell
85
85
  * before damage channels are allocated. Distinct from worn equipment armour.
86
86
  * Absent or 0 = no intrinsic resistance.
87
87
  */
@@ -1368,7 +1368,7 @@ export function applyImpactToInjury(target, wpn, energy_J, region, armoured, tra
1368
1368
  }
1369
1369
  }
1370
1370
  const armourShift = armoured ? q(0.75) : q(1.0);
1371
- // Phase 8C: intrinsic exoskeleton armor — absorbed before damage channels are allocated
1371
+ // Phase 8C: intrinsic exoskeleton armour — absorbed before damage channels are allocated
1372
1372
  if (seg?.intrinsicArmor_J !== undefined && seg.intrinsicArmor_J > 0) {
1373
1373
  energy_J = Math.max(0, energy_J - seg.intrinsicArmor_J);
1374
1374
  if (energy_J === 0)
@@ -48,7 +48,7 @@ export function stepMoraleForEntity(world, e, index, spatial, aliveBeforeTick, t
48
48
  }
49
49
  let fearQ = e.condition.fearQ;
50
50
  const wasRouting = isRouting(fearQ, distressTol);
51
- // 1. Suppression ticks add fear per tick — scaled by caliber multiplier (Feature 1)
51
+ // 1. Suppression ticks add fear per tick — scaled by calibre multiplier (Feature 1)
52
52
  if (e.condition.suppressedTicks > 0) {
53
53
  const supMul = e.condition.suppressionFearMul ?? SCALE.Q;
54
54
  fearQ = clampQ(fearQ + qMul(FEAR_PER_SUPPRESSION_TICK, supMul), 0, SCALE.Q);
@@ -0,0 +1,125 @@
1
+ import type { Q } from "./units.js";
2
+ import type { Polity } from "./polity.js";
3
+ /** Classification of wonder. */
4
+ export type WonderType = "great_pyramid" | "colosseum" | "grand_library" | "great_wall" | "grand_harbour" | "aqueduct_system" | "grand_temple";
5
+ /** In-progress wonder construction. */
6
+ export interface WonderProject {
7
+ projectId: string;
8
+ polityId: string;
9
+ type: WonderType;
10
+ /** Current build progress [0, SCALE.Q]. Complete at SCALE.Q. */
11
+ progress_Q: Q;
12
+ /** Cumulative treasury invested so far [cu]. */
13
+ investedCost_cu: number;
14
+ /** Tick at which construction started. */
15
+ startTick: number;
16
+ }
17
+ /** A completed wonder. */
18
+ export interface Wonder {
19
+ wonderId: string;
20
+ polityId: string;
21
+ type: WonderType;
22
+ /** Tick at which construction finished. */
23
+ completedAtTick: number;
24
+ /**
25
+ * Whether this wonder has been damaged by an earthquake (Phase-96) or
26
+ * siege (Phase-93). Damaged wonders provide `WONDER_DAMAGED_EFFECT_MUL`
27
+ * fraction of their normal effects until repaired.
28
+ */
29
+ damaged: boolean;
30
+ }
31
+ /**
32
+ * Advisory effect bundle from a wonder.
33
+ * Pass individual fields into the relevant downstream phase calls.
34
+ * All Q fields are [0, SCALE.Q]; `researchPointBonus` is raw points/day.
35
+ */
36
+ export interface WonderEffects {
37
+ /** Add to `polity.stabilityQ`. */
38
+ stabilityBonus_Q: Q;
39
+ /** Add to `polity.moraleQ`. */
40
+ moraleBonus_Q: Q;
41
+ /** Additional research points per day. Pass to Phase-91. */
42
+ researchPointBonus: number;
43
+ /** Subtract from Phase-90 unrest level. */
44
+ unrestReduction_Q: Q;
45
+ /** Trade income multiplier bonus. Pass to Phase-92. */
46
+ tradeIncomeBonus_Q: Q;
47
+ /** Additive defender strength bonus. Pass to Phase-93. */
48
+ defenseBonus_Q: Q;
49
+ /** Add to `healthCapacity_Q` in Phase-88 `stepEpidemic`. */
50
+ epidemicResistance_Q: Q;
51
+ }
52
+ /**
53
+ * Total treasury cost to construct each wonder type [cu].
54
+ * Grand library is fastest; great pyramid is a generational project.
55
+ */
56
+ export declare const WONDER_BASE_COST_CU: Record<WonderType, number>;
57
+ /**
58
+ * Estimated build time in days at average investment rate.
59
+ * Informational only — actual duration depends on how fast the host invests.
60
+ */
61
+ export declare const WONDER_TYPICAL_DAYS: Record<WonderType, number>;
62
+ /**
63
+ * Full effects for each wonder type at q(1.0) effectiveness.
64
+ * Damaged wonders multiply each field by `WONDER_DAMAGED_EFFECT_MUL`.
65
+ */
66
+ export declare const WONDER_BASE_EFFECTS: Record<WonderType, WonderEffects>;
67
+ /**
68
+ * Effect multiplier for a damaged wonder [0, SCALE.Q].
69
+ * Damaged wonders still provide partial benefit; repair restores full effects.
70
+ */
71
+ export declare const WONDER_DAMAGED_EFFECT_MUL: Q;
72
+ /**
73
+ * Treasury cost to repair a damaged wonder, as a fraction of `WONDER_BASE_COST_CU`.
74
+ * Repair = `round(baseCost × WONDER_REPAIR_COST_FRAC / SCALE.Q)`.
75
+ */
76
+ export declare const WONDER_REPAIR_COST_FRAC: Q;
77
+ /** Create a new wonder construction project. */
78
+ export declare function createWonderProject(projectId: string, polityId: string, type: WonderType, startTick: number): WonderProject;
79
+ /**
80
+ * Invest treasury into a wonder project.
81
+ *
82
+ * Deducts up to `contribution_cu` from `polity.treasury_cu` (capped by available
83
+ * treasury and remaining cost), advances `progress_Q`, and returns the new progress.
84
+ *
85
+ * Does not auto-complete — call `isWonderProjectComplete` then `completeWonder`.
86
+ */
87
+ export declare function contributeToWonder(project: WonderProject, polity: Polity, contribution_cu: number): Q;
88
+ /** Return `true` when the project has reached full completion. */
89
+ export declare function isWonderProjectComplete(project: WonderProject): boolean;
90
+ /**
91
+ * Finalise a completed project into a standing `Wonder`.
92
+ *
93
+ * The caller is responsible for checking `isWonderProjectComplete` first.
94
+ */
95
+ export declare function completeWonder(project: WonderProject, tick: number): Wonder;
96
+ /**
97
+ * Mark a wonder as damaged (earthquake, siege).
98
+ * Damaged wonders yield `WONDER_DAMAGED_EFFECT_MUL` fraction of full effects.
99
+ */
100
+ export declare function damageWonder(wonder: Wonder): void;
101
+ /**
102
+ * Repair a damaged wonder, spending `WONDER_REPAIR_COST_FRAC` of base cost.
103
+ *
104
+ * Mutates `polity.treasury_cu` and clears `wonder.damaged`.
105
+ * Returns `true` if repaired; `false` if the polity lacked funds.
106
+ * No-op if wonder is not damaged.
107
+ */
108
+ export declare function repairWonder(wonder: Wonder, polity: Polity): boolean;
109
+ /**
110
+ * Compute the `WonderEffects` advisory bundle for a single wonder.
111
+ *
112
+ * Damaged wonders: each numeric field is scaled by `WONDER_DAMAGED_EFFECT_MUL / SCALE.Q`.
113
+ */
114
+ export declare function computeWonderEffects(wonder: Wonder): WonderEffects;
115
+ /**
116
+ * Aggregate effects from multiple wonders.
117
+ *
118
+ * Q fields are summed and clamped to SCALE.Q.
119
+ * `researchPointBonus` is summed without capping.
120
+ */
121
+ export declare function aggregateWonderEffects(wonders: Wonder[]): WonderEffects;
122
+ /** Return `true` when the wonder is standing and undamaged. */
123
+ export declare function isWonderIntact(wonder: Wonder): boolean;
124
+ /** Compute treasury cost to repair a damaged wonder [cu]. */
125
+ export declare function computeRepairCost(type: WonderType): number;