@its-not-rocket-science/ananke 0.1.41 → 0.1.42
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 +28 -0
- package/dist/src/famine.d.ts +120 -0
- package/dist/src/famine.js +201 -0
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,34 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [0.1.42] — 2026-03-26
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Phase 97 · Famine Relief & Rationing** (`src/famine.ts`)
|
|
14
|
+
- `FaminePhase`: `"none" | "shortage" | "famine" | "catastrophe"` — graduated severity above Phase-87 Granary's binary famine flag.
|
|
15
|
+
- `RationingPolicy`: `"none" | "tight" | "emergency" | "starvation_rations"` — active polity response.
|
|
16
|
+
- `FamineState { polityId, phase, daysInPhase, cumulativeSeverity_Q }` — per-polity mutable tracker stored externally.
|
|
17
|
+
- `FaminePressures { deathBonus_Q, migrationPush_Q, unrestPressure_Q }` — advisory bundle; callers pass fields into Phases 86/81/90.
|
|
18
|
+
- Phase thresholds: shortage < q(0.50), famine < q(0.20), catastrophe < q(0.05) of `computeFoodSupply_Q`.
|
|
19
|
+
- `FAMINE_PHASE_DEATH_Q`: +1%/year (shortage) → +3%/year (famine) → +7%/year (catastrophe); stacks with Phase-86 base famine death.
|
|
20
|
+
- `FAMINE_PHASE_MIGRATION_Q`: q(0.08) → q(0.25) → q(0.50) — feeds Phase-81.
|
|
21
|
+
- `RATIONING_REDUCTION_Q`: tight 20%, emergency 40%, starvation_rations 60% consumption cut.
|
|
22
|
+
- `RATIONING_UNREST_Q`: q(0.04) → q(0.12) → q(0.25) — rationing itself generates unrest.
|
|
23
|
+
- `SEVERITY_DELTA_PER_DAY`: none −5 (decay), shortage +2, famine +10, catastrophe +25 per day; `cumulativeSeverity_Q` models long-term famine damage.
|
|
24
|
+
- `createFamineState(polityId)` — factory.
|
|
25
|
+
- `computeFaminePhase(foodSupply_Q)` — classifies severity from granary output.
|
|
26
|
+
- `computeFaminePressures(state, policy?)` — combined famine + rationing advisory pressures.
|
|
27
|
+
- `stepFamine(state, foodSupply_Q, elapsedDays)` → `boolean` — advances state; returns `true` when phase changes.
|
|
28
|
+
- `computeRationedConsumption(polity, policy, elapsedDays)` — rationed su demand.
|
|
29
|
+
- `stepRationedGranary(polity, granary, policy, elapsedDays)` — replaces Phase-87 `stepGranaryConsumption` when rationing is active.
|
|
30
|
+
- `computeReliefImport(polity, granary, budget_cu, capacityCap_su)` — converts treasury into grain; mutates both in-place; capped by treasury, budget, and granary space.
|
|
31
|
+
- `isFamineActive(state)` / `isCatastrophicFamine(state)` — convenience predicates.
|
|
32
|
+
- Added `./famine` subpath export to `package.json`.
|
|
33
|
+
- 60 new tests; 5,082 total. Coverage: 100% statements/branches/functions/lines on `famine.ts`.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
9
37
|
## [0.1.41] — 2026-03-26
|
|
10
38
|
|
|
11
39
|
### Added
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { Q } from "./units.js";
|
|
2
|
+
import type { Polity } from "./polity.js";
|
|
3
|
+
import type { GranaryState } from "./granary.js";
|
|
4
|
+
/** Graduated severity of a food crisis. */
|
|
5
|
+
export type FaminePhase = "none" | "shortage" | "famine" | "catastrophe";
|
|
6
|
+
/** Polity policy for reducing per-capita food consumption below normal demand. */
|
|
7
|
+
export type RationingPolicy = "none" | "tight" | "emergency" | "starvation_rations";
|
|
8
|
+
/**
|
|
9
|
+
* Per-polity famine tracking state.
|
|
10
|
+
* Attach one to each polity; store externally (e.g. `Map<string, FamineState>`).
|
|
11
|
+
*/
|
|
12
|
+
export interface FamineState {
|
|
13
|
+
polityId: string;
|
|
14
|
+
phase: FaminePhase;
|
|
15
|
+
/** Days spent continuously in the current phase. Resets when phase changes. */
|
|
16
|
+
daysInPhase: number;
|
|
17
|
+
/**
|
|
18
|
+
* Long-term famine damage [0, SCALE.Q].
|
|
19
|
+
* Accrues during famine/catastrophe (depleted seed grain, dead workers, trauma).
|
|
20
|
+
* Decays slowly once food supply recovers to "none".
|
|
21
|
+
*/
|
|
22
|
+
cumulativeSeverity_Q: Q;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Advisory pressure bundle for downstream phases.
|
|
26
|
+
* All fields [0, SCALE.Q] unless noted.
|
|
27
|
+
*/
|
|
28
|
+
export interface FaminePressures {
|
|
29
|
+
/** Additional annual death rate. Supplement Phase-86 `deathPressure_Q`. */
|
|
30
|
+
deathBonus_Q: Q;
|
|
31
|
+
/** Migration push. Pass to Phase-81 `computePushPressure`. */
|
|
32
|
+
migrationPush_Q: Q;
|
|
33
|
+
/** Combined famine + rationing unrest. Pass to Phase-90 `computeUnrestLevel`. */
|
|
34
|
+
unrestPressure_Q: Q;
|
|
35
|
+
}
|
|
36
|
+
/** `foodSupply_Q` below this → shortage phase. */
|
|
37
|
+
export declare const SHORTAGE_THRESHOLD_Q: Q;
|
|
38
|
+
/** `foodSupply_Q` below this → famine phase. */
|
|
39
|
+
export declare const FAMINE_THRESHOLD_Q: Q;
|
|
40
|
+
/** `foodSupply_Q` below this → catastrophe phase. */
|
|
41
|
+
export declare const CATASTROPHE_THRESHOLD_Q: Q;
|
|
42
|
+
/**
|
|
43
|
+
* Additional annual death rate by famine phase [Q].
|
|
44
|
+
* Phase-86 already applies `FAMINE_DEATH_ANNUAL_Q = q(0.030)` at famine threshold;
|
|
45
|
+
* these bonuses are additive on top for graduated severity.
|
|
46
|
+
*/
|
|
47
|
+
export declare const FAMINE_PHASE_DEATH_Q: Record<FaminePhase, Q>;
|
|
48
|
+
/** Migration push pressure by famine phase [0, SCALE.Q]. */
|
|
49
|
+
export declare const FAMINE_PHASE_MIGRATION_Q: Record<FaminePhase, Q>;
|
|
50
|
+
/** Base unrest pressure by famine phase [0, SCALE.Q]. */
|
|
51
|
+
export declare const FAMINE_PHASE_UNREST_Q: Record<FaminePhase, Q>;
|
|
52
|
+
/**
|
|
53
|
+
* Consumption reduction fraction per rationing policy [0, SCALE.Q].
|
|
54
|
+
* Applied to `polity.population × elapsedDays` to give actual su demand.
|
|
55
|
+
*/
|
|
56
|
+
export declare const RATIONING_REDUCTION_Q: Record<RationingPolicy, Q>;
|
|
57
|
+
/** Unrest pressure added by rationing policy itself [0, SCALE.Q]. */
|
|
58
|
+
export declare const RATIONING_UNREST_Q: Record<RationingPolicy, Q>;
|
|
59
|
+
/** Treasury cost in cu per supply unit of emergency food import (1 su = 1 person-day). */
|
|
60
|
+
export declare const RELIEF_IMPORT_COST_CU_PER_SU = 2;
|
|
61
|
+
/**
|
|
62
|
+
* Cumulative severity change per day by famine phase [out of SCALE.Q].
|
|
63
|
+
* Negative values → decay; positive values → accrual.
|
|
64
|
+
*/
|
|
65
|
+
export declare const SEVERITY_DELTA_PER_DAY: Record<FaminePhase, number>;
|
|
66
|
+
/** Create a fresh `FamineState` for a polity (no active famine, zero severity). */
|
|
67
|
+
export declare function createFamineState(polityId: string): FamineState;
|
|
68
|
+
/**
|
|
69
|
+
* Classify the current famine phase from the granary food supply fraction.
|
|
70
|
+
*
|
|
71
|
+
* Obtain `foodSupply_Q` from Phase-87 `computeFoodSupply_Q(polity, granary)`.
|
|
72
|
+
*/
|
|
73
|
+
export declare function computeFaminePhase(foodSupply_Q: Q): FaminePhase;
|
|
74
|
+
/**
|
|
75
|
+
* Compute the advisory pressure bundle for the current famine state and rationing policy.
|
|
76
|
+
*
|
|
77
|
+
* `unrestPressure_Q` sums famine unrest with rationing unrest, clamped to SCALE.Q.
|
|
78
|
+
*/
|
|
79
|
+
export declare function computeFaminePressures(state: FamineState, policy?: RationingPolicy): FaminePressures;
|
|
80
|
+
/**
|
|
81
|
+
* Advance famine state by `elapsedDays`.
|
|
82
|
+
*
|
|
83
|
+
* - Reclassifies `phase` from the current `foodSupply_Q`.
|
|
84
|
+
* - Resets `daysInPhase` to 0 on phase change; otherwise increments.
|
|
85
|
+
* - Accrues or decays `cumulativeSeverity_Q` at `SEVERITY_DELTA_PER_DAY`.
|
|
86
|
+
*
|
|
87
|
+
* Returns `true` if the famine phase changed this step.
|
|
88
|
+
*/
|
|
89
|
+
export declare function stepFamine(state: FamineState, foodSupply_Q: Q, elapsedDays: number): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Compute food demand in supply units after applying the rationing reduction.
|
|
92
|
+
*
|
|
93
|
+
* Normal demand = `polity.population × elapsedDays` su.
|
|
94
|
+
* `RATIONING_REDUCTION_Q[policy]` fraction is subtracted before multiplication.
|
|
95
|
+
*/
|
|
96
|
+
export declare function computeRationedConsumption(polity: Polity, policy: RationingPolicy, elapsedDays: number): number;
|
|
97
|
+
/**
|
|
98
|
+
* Drain rationed consumption from a granary.
|
|
99
|
+
*
|
|
100
|
+
* Use in place of Phase-87 `stepGranaryConsumption` when a rationing policy is
|
|
101
|
+
* active. Grain is clamped to 0; returns the actual supply units consumed.
|
|
102
|
+
*/
|
|
103
|
+
export declare function stepRationedGranary(polity: Polity, granary: GranaryState, policy: RationingPolicy, elapsedDays: number): number;
|
|
104
|
+
/**
|
|
105
|
+
* Spend treasury to import emergency food.
|
|
106
|
+
*
|
|
107
|
+
* Converts up to `budget_cu` of `polity.treasury_cu` into grain at
|
|
108
|
+
* `RELIEF_IMPORT_COST_CU_PER_SU` cu/su, limited by remaining granary space.
|
|
109
|
+
*
|
|
110
|
+
* Mutates `polity.treasury_cu` and `granary.grain_su`.
|
|
111
|
+
* Returns the actual supply units added.
|
|
112
|
+
*
|
|
113
|
+
* @param budget_cu Max treasury to spend (e.g. pass `polity.treasury_cu` for all-in).
|
|
114
|
+
* @param capacityCap_su Max granary capacity; derive via Phase-87 `computeCapacity(polity)`.
|
|
115
|
+
*/
|
|
116
|
+
export declare function computeReliefImport(polity: Polity, granary: GranaryState, budget_cu: number, capacityCap_su: number): number;
|
|
117
|
+
/** Return `true` when the polity is in any active famine phase. */
|
|
118
|
+
export declare function isFamineActive(state: FamineState): boolean;
|
|
119
|
+
/** Return `true` when the polity has reached the most severe famine phase. */
|
|
120
|
+
export declare function isCatastrophicFamine(state: FamineState): boolean;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// src/famine.ts — Phase 97: Famine Relief & Rationing
|
|
2
|
+
//
|
|
3
|
+
// Graduated famine severity tracking, rationing policies, and emergency food
|
|
4
|
+
// relief for polities. Sits above Phase-87 (Granary) as the active response layer.
|
|
5
|
+
//
|
|
6
|
+
// Design:
|
|
7
|
+
// - Pure data layer — no Entity fields, no kernel changes.
|
|
8
|
+
// - `FamineState` tracks famine phase, days in phase, and cumulative severity.
|
|
9
|
+
// Store one per polity externally; pass to step functions each tick.
|
|
10
|
+
// - `computeFaminePressures` returns an advisory bundle; callers pass fields into
|
|
11
|
+
// Phase-86 (deathBonus), Phase-81 (migrationPush), and Phase-90 (unrest).
|
|
12
|
+
// - `stepRationedGranary` replaces Phase-87 `stepGranaryConsumption` when a
|
|
13
|
+
// rationing policy is active.
|
|
14
|
+
// - `computeReliefImport` converts `treasury_cu` to grain — treasury and granary
|
|
15
|
+
// are mutated in-place; caller supplies granary capacity.
|
|
16
|
+
//
|
|
17
|
+
// Integration:
|
|
18
|
+
// Phase 86 (Demography): deathBonus_Q supplements Phase-86 FAMINE_DEATH_ANNUAL_Q.
|
|
19
|
+
// Phase 87 (Granary): computeFoodSupply_Q → foodSupply_Q; stepRationedGranary.
|
|
20
|
+
// Phase 81 (Migration): migrationPush_Q passed to computePushPressure.
|
|
21
|
+
// Phase 90 (Unrest): unrestPressure_Q passed to computeUnrestLevel.
|
|
22
|
+
// Phase 96 (Climate): harvestYieldPenalty worsens foodSupply_Q that drives here.
|
|
23
|
+
import { q, SCALE, clampQ } from "./units.js";
|
|
24
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
25
|
+
/** `foodSupply_Q` below this → shortage phase. */
|
|
26
|
+
export const SHORTAGE_THRESHOLD_Q = q(0.50);
|
|
27
|
+
/** `foodSupply_Q` below this → famine phase. */
|
|
28
|
+
export const FAMINE_THRESHOLD_Q = q(0.20);
|
|
29
|
+
/** `foodSupply_Q` below this → catastrophe phase. */
|
|
30
|
+
export const CATASTROPHE_THRESHOLD_Q = q(0.05);
|
|
31
|
+
/**
|
|
32
|
+
* Additional annual death rate by famine phase [Q].
|
|
33
|
+
* Phase-86 already applies `FAMINE_DEATH_ANNUAL_Q = q(0.030)` at famine threshold;
|
|
34
|
+
* these bonuses are additive on top for graduated severity.
|
|
35
|
+
*/
|
|
36
|
+
export const FAMINE_PHASE_DEATH_Q = {
|
|
37
|
+
none: 0,
|
|
38
|
+
shortage: q(0.010), // +1%/year
|
|
39
|
+
famine: q(0.030), // +3%/year (stacks with Ph-86 +3%)
|
|
40
|
+
catastrophe: q(0.070), // +7%/year
|
|
41
|
+
};
|
|
42
|
+
/** Migration push pressure by famine phase [0, SCALE.Q]. */
|
|
43
|
+
export const FAMINE_PHASE_MIGRATION_Q = {
|
|
44
|
+
none: 0,
|
|
45
|
+
shortage: q(0.08),
|
|
46
|
+
famine: q(0.25),
|
|
47
|
+
catastrophe: q(0.50),
|
|
48
|
+
};
|
|
49
|
+
/** Base unrest pressure by famine phase [0, SCALE.Q]. */
|
|
50
|
+
export const FAMINE_PHASE_UNREST_Q = {
|
|
51
|
+
none: 0,
|
|
52
|
+
shortage: q(0.05),
|
|
53
|
+
famine: q(0.15),
|
|
54
|
+
catastrophe: q(0.30),
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Consumption reduction fraction per rationing policy [0, SCALE.Q].
|
|
58
|
+
* Applied to `polity.population × elapsedDays` to give actual su demand.
|
|
59
|
+
*/
|
|
60
|
+
export const RATIONING_REDUCTION_Q = {
|
|
61
|
+
none: 0,
|
|
62
|
+
tight: q(0.20),
|
|
63
|
+
emergency: q(0.40),
|
|
64
|
+
starvation_rations: q(0.60),
|
|
65
|
+
};
|
|
66
|
+
/** Unrest pressure added by rationing policy itself [0, SCALE.Q]. */
|
|
67
|
+
export const RATIONING_UNREST_Q = {
|
|
68
|
+
none: 0,
|
|
69
|
+
tight: q(0.04),
|
|
70
|
+
emergency: q(0.12),
|
|
71
|
+
starvation_rations: q(0.25),
|
|
72
|
+
};
|
|
73
|
+
/** Treasury cost in cu per supply unit of emergency food import (1 su = 1 person-day). */
|
|
74
|
+
export const RELIEF_IMPORT_COST_CU_PER_SU = 2;
|
|
75
|
+
/**
|
|
76
|
+
* Cumulative severity change per day by famine phase [out of SCALE.Q].
|
|
77
|
+
* Negative values → decay; positive values → accrual.
|
|
78
|
+
*/
|
|
79
|
+
export const SEVERITY_DELTA_PER_DAY = {
|
|
80
|
+
none: -5,
|
|
81
|
+
shortage: 2,
|
|
82
|
+
famine: 10,
|
|
83
|
+
catastrophe: 25,
|
|
84
|
+
};
|
|
85
|
+
// ── Factory ───────────────────────────────────────────────────────────────────
|
|
86
|
+
/** Create a fresh `FamineState` for a polity (no active famine, zero severity). */
|
|
87
|
+
export function createFamineState(polityId) {
|
|
88
|
+
return {
|
|
89
|
+
polityId,
|
|
90
|
+
phase: "none",
|
|
91
|
+
daysInPhase: 0,
|
|
92
|
+
cumulativeSeverity_Q: 0,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// ── Phase classification ───────────────────────────────────────────────────────
|
|
96
|
+
/**
|
|
97
|
+
* Classify the current famine phase from the granary food supply fraction.
|
|
98
|
+
*
|
|
99
|
+
* Obtain `foodSupply_Q` from Phase-87 `computeFoodSupply_Q(polity, granary)`.
|
|
100
|
+
*/
|
|
101
|
+
export function computeFaminePhase(foodSupply_Q) {
|
|
102
|
+
if (foodSupply_Q < CATASTROPHE_THRESHOLD_Q)
|
|
103
|
+
return "catastrophe";
|
|
104
|
+
if (foodSupply_Q < FAMINE_THRESHOLD_Q)
|
|
105
|
+
return "famine";
|
|
106
|
+
if (foodSupply_Q < SHORTAGE_THRESHOLD_Q)
|
|
107
|
+
return "shortage";
|
|
108
|
+
return "none";
|
|
109
|
+
}
|
|
110
|
+
// ── Pressure computation ──────────────────────────────────────────────────────
|
|
111
|
+
/**
|
|
112
|
+
* Compute the advisory pressure bundle for the current famine state and rationing policy.
|
|
113
|
+
*
|
|
114
|
+
* `unrestPressure_Q` sums famine unrest with rationing unrest, clamped to SCALE.Q.
|
|
115
|
+
*/
|
|
116
|
+
export function computeFaminePressures(state, policy = "none") {
|
|
117
|
+
const phase = state.phase;
|
|
118
|
+
const unrest = clampQ(FAMINE_PHASE_UNREST_Q[phase] + RATIONING_UNREST_Q[policy], 0, SCALE.Q);
|
|
119
|
+
return {
|
|
120
|
+
deathBonus_Q: FAMINE_PHASE_DEATH_Q[phase],
|
|
121
|
+
migrationPush_Q: FAMINE_PHASE_MIGRATION_Q[phase],
|
|
122
|
+
unrestPressure_Q: unrest,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
// ── State step ────────────────────────────────────────────────────────────────
|
|
126
|
+
/**
|
|
127
|
+
* Advance famine state by `elapsedDays`.
|
|
128
|
+
*
|
|
129
|
+
* - Reclassifies `phase` from the current `foodSupply_Q`.
|
|
130
|
+
* - Resets `daysInPhase` to 0 on phase change; otherwise increments.
|
|
131
|
+
* - Accrues or decays `cumulativeSeverity_Q` at `SEVERITY_DELTA_PER_DAY`.
|
|
132
|
+
*
|
|
133
|
+
* Returns `true` if the famine phase changed this step.
|
|
134
|
+
*/
|
|
135
|
+
export function stepFamine(state, foodSupply_Q, elapsedDays) {
|
|
136
|
+
const newPhase = computeFaminePhase(foodSupply_Q);
|
|
137
|
+
const phaseChanged = newPhase !== state.phase;
|
|
138
|
+
if (phaseChanged) {
|
|
139
|
+
state.phase = newPhase;
|
|
140
|
+
state.daysInPhase = 0;
|
|
141
|
+
}
|
|
142
|
+
state.daysInPhase += elapsedDays;
|
|
143
|
+
const delta = SEVERITY_DELTA_PER_DAY[state.phase] * elapsedDays;
|
|
144
|
+
state.cumulativeSeverity_Q = clampQ(state.cumulativeSeverity_Q + delta, 0, SCALE.Q);
|
|
145
|
+
return phaseChanged;
|
|
146
|
+
}
|
|
147
|
+
// ── Rationing ─────────────────────────────────────────────────────────────────
|
|
148
|
+
/**
|
|
149
|
+
* Compute food demand in supply units after applying the rationing reduction.
|
|
150
|
+
*
|
|
151
|
+
* Normal demand = `polity.population × elapsedDays` su.
|
|
152
|
+
* `RATIONING_REDUCTION_Q[policy]` fraction is subtracted before multiplication.
|
|
153
|
+
*/
|
|
154
|
+
export function computeRationedConsumption(polity, policy, elapsedDays) {
|
|
155
|
+
const reduction = RATIONING_REDUCTION_Q[policy];
|
|
156
|
+
const factor = SCALE.Q - reduction;
|
|
157
|
+
const dailyDemand = Math.round(polity.population * factor / SCALE.Q);
|
|
158
|
+
return dailyDemand * elapsedDays;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Drain rationed consumption from a granary.
|
|
162
|
+
*
|
|
163
|
+
* Use in place of Phase-87 `stepGranaryConsumption` when a rationing policy is
|
|
164
|
+
* active. Grain is clamped to 0; returns the actual supply units consumed.
|
|
165
|
+
*/
|
|
166
|
+
export function stepRationedGranary(polity, granary, policy, elapsedDays) {
|
|
167
|
+
const demand = computeRationedConsumption(polity, policy, elapsedDays);
|
|
168
|
+
const consumed = Math.min(demand, granary.grain_su);
|
|
169
|
+
granary.grain_su = Math.max(0, granary.grain_su - demand);
|
|
170
|
+
return consumed;
|
|
171
|
+
}
|
|
172
|
+
// ── Relief imports ─────────────────────────────────────────────────────────────
|
|
173
|
+
/**
|
|
174
|
+
* Spend treasury to import emergency food.
|
|
175
|
+
*
|
|
176
|
+
* Converts up to `budget_cu` of `polity.treasury_cu` into grain at
|
|
177
|
+
* `RELIEF_IMPORT_COST_CU_PER_SU` cu/su, limited by remaining granary space.
|
|
178
|
+
*
|
|
179
|
+
* Mutates `polity.treasury_cu` and `granary.grain_su`.
|
|
180
|
+
* Returns the actual supply units added.
|
|
181
|
+
*
|
|
182
|
+
* @param budget_cu Max treasury to spend (e.g. pass `polity.treasury_cu` for all-in).
|
|
183
|
+
* @param capacityCap_su Max granary capacity; derive via Phase-87 `computeCapacity(polity)`.
|
|
184
|
+
*/
|
|
185
|
+
export function computeReliefImport(polity, granary, budget_cu, capacityCap_su) {
|
|
186
|
+
const affordable = Math.floor(Math.min(budget_cu, polity.treasury_cu) / RELIEF_IMPORT_COST_CU_PER_SU);
|
|
187
|
+
const space = Math.max(0, capacityCap_su - granary.grain_su);
|
|
188
|
+
const added = Math.min(affordable, space);
|
|
189
|
+
granary.grain_su += added;
|
|
190
|
+
polity.treasury_cu -= added * RELIEF_IMPORT_COST_CU_PER_SU;
|
|
191
|
+
return added;
|
|
192
|
+
}
|
|
193
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
194
|
+
/** Return `true` when the polity is in any active famine phase. */
|
|
195
|
+
export function isFamineActive(state) {
|
|
196
|
+
return state.phase !== "none";
|
|
197
|
+
}
|
|
198
|
+
/** Return `true` when the polity has reached the most severe famine phase. */
|
|
199
|
+
export function isCatastrophicFamine(state) {
|
|
200
|
+
return state.phase === "catastrophe";
|
|
201
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@its-not-rocket-science/ananke",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.42",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deterministic lockstep-friendly SI-units RPG/physics core (fixed-point TS)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -150,6 +150,10 @@
|
|
|
150
150
|
"./climate": {
|
|
151
151
|
"import": "./dist/src/climate.js",
|
|
152
152
|
"types": "./dist/src/climate.d.ts"
|
|
153
|
+
},
|
|
154
|
+
"./famine": {
|
|
155
|
+
"import": "./dist/src/famine.js",
|
|
156
|
+
"types": "./dist/src/famine.d.ts"
|
|
153
157
|
}
|
|
154
158
|
},
|
|
155
159
|
"files": [
|