@its-not-rocket-science/ananke 0.1.30 → 0.1.32
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 +39 -0
- package/dist/src/demography.d.ts +122 -0
- package/dist/src/demography.js +190 -0
- package/dist/src/granary.d.ts +118 -0
- package/dist/src/granary.js +180 -0
- package/package.json +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,45 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [0.1.32] — 2026-03-26
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Phase 87 · Granary & Food Supply** (`src/granary.ts`)
|
|
14
|
+
- `GranaryState { polityId, grain_su }` — grain reserves in supply units (1 su = food for 1 person for 1 day); capacity derived dynamically from `polity.population × GRANARY_CAPACITY_DAYS = 730`.
|
|
15
|
+
- `createGranary(polity)` — initialises with one year of consumption.
|
|
16
|
+
- `computeCapacity(polity)` → integer; `computeFoodSupply_Q(polity, granary)` → Q [0, SCALE.Q] — feeds directly into Phase-86 `stepPolityPopulation(foodSupply_Q)`.
|
|
17
|
+
- **Harvest yield**: `HARVEST_BASE_SU_PER_CAPITA = 250` su/person/harvest; `HARVEST_YIELD_BASE_Q = q(0.70)` floor; `HARVEST_STABILITY_BONUS_Q = q(0.30)` max bonus from stability. `deriveHarvestYieldFactor(polity, season_Q?)` integrates Phase-78 seasonal multiplier.
|
|
18
|
+
- `computeHarvestYield(polity, yieldFactor_Q?)` → su; `triggerHarvest(polity, granary, yieldFactor_Q?)` → added su (clamped to capacity).
|
|
19
|
+
- `stepGranaryConsumption(polity, granary, elapsedDays)` → consumed su; drains `population × elapsedDays` su per step; floors at 0.
|
|
20
|
+
- `tradeFoodSupply(fromGranary, toGranary, toPolity, amount_su)` → transferred su; limited by source grain, destination capacity. Integrates with Phase-83 trade routes.
|
|
21
|
+
- `raidGranary(granary, raidFraction_Q?)` → plundered su; defaults to `RAID_FRACTION_Q = q(0.40)`. Integrates with Phase-84 siege attacker victory.
|
|
22
|
+
- Added `./granary` subpath export to `package.json`.
|
|
23
|
+
- 47 new tests; 4,608 total. Coverage maintained above all thresholds.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## [0.1.31] — 2026-03-26
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **Phase 86 · Population Dynamics & Demographics** (`src/demography.ts`)
|
|
32
|
+
- Annual Q rates for birth and death (fraction of population per year) to preserve fixed-point precision.
|
|
33
|
+
- `BASELINE_BIRTH_RATE_ANNUAL_Q = q(0.035)` (≈ 3.5%/year); `BASELINE_DEATH_RATE_ANNUAL_Q = q(0.030)` (≈ 3.0%/year).
|
|
34
|
+
- `computeBirthRate(polity)` → Q: morale linearly scales rate between 50% and 150% of baseline.
|
|
35
|
+
- `computeDeathRate(polity, deathPressure_Q?, foodSupply_Q?)` → Q: baseline reduced by tech era (`TECH_ERA_DEATH_MUL`), plus instability bonus (up to `INSTABILITY_DEATH_ANNUAL_Q = q(0.015)`), optional external pressure, and famine bonus (`FAMINE_DEATH_ANNUAL_Q = q(0.030)`).
|
|
36
|
+
- `computeNetGrowthRate(polity, ...)` → signed number (may be negative).
|
|
37
|
+
- `stepPolityPopulation(polity, elapsedDays, deathPressure_Q?, foodSupply_Q?)` → `DemographicsStepResult`: mutates `polity.population`; formula `round(population × netAnnualRate_Q × days / (365 × SCALE.Q))`; clamps to ≥ 0.
|
|
38
|
+
- **Famine**: `FAMINE_THRESHOLD_Q = q(0.20)` — food below this activates extra mortality and migration push.
|
|
39
|
+
- `computeFamineMigrationPush(foodSupply_Q)` → Q [0, `FAMINE_MIGRATION_PUSH_Q = q(0.30)`]: linear from zero (at threshold) to peak (at food = 0); integrates with Phase-81 push pressure.
|
|
40
|
+
- `computeCarryingCapacity(polity)` — soft cap by tech era (Stone 50 k → Modern 200 M); `isOverCapacity(polity)`.
|
|
41
|
+
- `estimateAnnualBirths` / `estimateAnnualDeaths` — reporting utilities.
|
|
42
|
+
- Phase-56 (disease) and Phase-84 (siege) integrate via `deathPressure_Q`; Phase-81 (migration) integrates via `computeFamineMigrationPush`; Phase-78 (calendar) via caller-supplied seasonal multipliers.
|
|
43
|
+
- Added `./demography` subpath export to `package.json`.
|
|
44
|
+
- 51 new tests; 4,561 total. Coverage maintained above all thresholds.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
9
48
|
## [0.1.30] — 2026-03-26
|
|
10
49
|
|
|
11
50
|
### Added
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { Q } from "./units.js";
|
|
2
|
+
import type { Polity } from "./polity.js";
|
|
3
|
+
/** Baseline annual birth rate [Q = fraction of population per year]. ≈ 3.5%/year. */
|
|
4
|
+
export declare const BASELINE_BIRTH_RATE_ANNUAL_Q: Q;
|
|
5
|
+
/** Baseline annual death rate [Q = fraction of population per year]. ≈ 3.0%/year. */
|
|
6
|
+
export declare const BASELINE_DEATH_RATE_ANNUAL_Q: Q;
|
|
7
|
+
/**
|
|
8
|
+
* Morale floor multiplier on birth rate.
|
|
9
|
+
*
|
|
10
|
+
* Birth rate factor = `BIRTH_RATE_MORALE_FLOOR_Q + moraleQ`, yielding:
|
|
11
|
+
* moraleQ = 0 → factor = q(0.50) → birth rate × 0.50
|
|
12
|
+
* moraleQ = SCALE.Q → factor = q(1.50) → birth rate × 1.50
|
|
13
|
+
*/
|
|
14
|
+
export declare const BIRTH_RATE_MORALE_FLOOR_Q: Q;
|
|
15
|
+
/**
|
|
16
|
+
* Additional annual death rate at zero stability.
|
|
17
|
+
* Linearly scaled by `(SCALE.Q − stabilityQ) / SCALE.Q`.
|
|
18
|
+
* Full bonus at stability = 0; zero at stability = SCALE.Q.
|
|
19
|
+
*/
|
|
20
|
+
export declare const INSTABILITY_DEATH_ANNUAL_Q: Q;
|
|
21
|
+
/** Food supply fraction below which famine is active [0, SCALE.Q]. */
|
|
22
|
+
export declare const FAMINE_THRESHOLD_Q: Q;
|
|
23
|
+
/** Additional annual death rate during famine (+3%/year on top of baseline). */
|
|
24
|
+
export declare const FAMINE_DEATH_ANNUAL_Q: Q;
|
|
25
|
+
/**
|
|
26
|
+
* Peak famine-driven migration push pressure (at food = 0).
|
|
27
|
+
* Integrates with Phase-81 `computePushPressure` as an additive bonus.
|
|
28
|
+
*/
|
|
29
|
+
export declare const FAMINE_MIGRATION_PUSH_Q: Q;
|
|
30
|
+
/**
|
|
31
|
+
* Tech-era multiplier applied to the baseline death rate.
|
|
32
|
+
* Better technology → lower mortality from disease and malnutrition.
|
|
33
|
+
* Expressed as a Q fraction of SCALE.Q.
|
|
34
|
+
*/
|
|
35
|
+
export declare const TECH_ERA_DEATH_MUL: Record<string, number>;
|
|
36
|
+
/**
|
|
37
|
+
* Soft carrying capacity by tech era.
|
|
38
|
+
* `stepPolityPopulation` does not enforce it — host checks `isOverCapacity`
|
|
39
|
+
* and applies extra emigration pressure via Phase-81 if desired.
|
|
40
|
+
*/
|
|
41
|
+
export declare const CARRYING_CAPACITY_BY_ERA: Record<string, number>;
|
|
42
|
+
/** Outcome of a single `stepPolityPopulation` call. */
|
|
43
|
+
export interface DemographicsStepResult {
|
|
44
|
+
/** Signed population change (positive = growth, negative = decline). */
|
|
45
|
+
popDelta: number;
|
|
46
|
+
/** New population after applying delta (clamped to ≥ 0). */
|
|
47
|
+
newPopulation: number;
|
|
48
|
+
/** Effective annual birth rate used this step. */
|
|
49
|
+
effectiveBirthRate_Q: Q;
|
|
50
|
+
/** Effective annual death rate used this step (all pressures included). */
|
|
51
|
+
effectiveDeathRate_Q: Q;
|
|
52
|
+
/** Whether famine was active this step. */
|
|
53
|
+
famine: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Compute the effective annual birth rate for a polity [Q = fraction/year].
|
|
57
|
+
*
|
|
58
|
+
* Morale scales birth rate linearly between 50% and 150% of baseline:
|
|
59
|
+
* moraleQ = 0 → BASELINE × 0.50 (≈ 1.75%/year)
|
|
60
|
+
* moraleQ = SCALE.Q → BASELINE × 1.50 (≈ 5.25%/year)
|
|
61
|
+
*/
|
|
62
|
+
export declare function computeBirthRate(polity: Polity): Q;
|
|
63
|
+
/**
|
|
64
|
+
* Compute the effective annual death rate for a polity [Q = fraction/year].
|
|
65
|
+
*
|
|
66
|
+
* Factors (additive):
|
|
67
|
+
* 1. Baseline reduced by tech era (better medicine / nutrition).
|
|
68
|
+
* 2. Instability bonus: up to `INSTABILITY_DEATH_ANNUAL_Q` at stability = 0.
|
|
69
|
+
* 3. External death pressure (caller: Phase-56 epidemic or Phase-84 siege casualties).
|
|
70
|
+
* 4. Famine bonus: `FAMINE_DEATH_ANNUAL_Q` when `foodSupply_Q < FAMINE_THRESHOLD_Q`.
|
|
71
|
+
*
|
|
72
|
+
* @param deathPressure_Q Annual mortality fraction from external cause.
|
|
73
|
+
* @param foodSupply_Q Current food supply [0, SCALE.Q]; omit if unknown.
|
|
74
|
+
*/
|
|
75
|
+
export declare function computeDeathRate(polity: Polity, deathPressure_Q?: Q, foodSupply_Q?: Q): Q;
|
|
76
|
+
/**
|
|
77
|
+
* Compute the net annual growth rate (birth rate − death rate).
|
|
78
|
+
* Negative values indicate population decline.
|
|
79
|
+
*/
|
|
80
|
+
export declare function computeNetGrowthRate(polity: Polity, deathPressure_Q?: Q, foodSupply_Q?: Q): number;
|
|
81
|
+
/**
|
|
82
|
+
* Step polity population forward by `elapsedDays` simulated days.
|
|
83
|
+
*
|
|
84
|
+
* Mutates `polity.population` in place and returns step metadata.
|
|
85
|
+
*
|
|
86
|
+
* Delta formula (fixed-point, single rounding):
|
|
87
|
+
* `popDelta = round(population × netAnnualRate_Q × elapsedDays / (365 × SCALE.Q))`
|
|
88
|
+
*
|
|
89
|
+
* @param elapsedDays Number of simulated days to advance (typically 1–30).
|
|
90
|
+
* @param deathPressure_Q Annual mortality fraction from disease or siege casualties.
|
|
91
|
+
* @param foodSupply_Q Food supply level [0, SCALE.Q]; famine fires below
|
|
92
|
+
* `FAMINE_THRESHOLD_Q`.
|
|
93
|
+
*/
|
|
94
|
+
export declare function stepPolityPopulation(polity: Polity, elapsedDays: number, deathPressure_Q?: Q, foodSupply_Q?: Q): DemographicsStepResult;
|
|
95
|
+
/**
|
|
96
|
+
* Compute famine-driven migration push pressure [0, SCALE.Q].
|
|
97
|
+
*
|
|
98
|
+
* Zero at or above `FAMINE_THRESHOLD_Q`. Scales linearly from zero (at the
|
|
99
|
+
* threshold) to `FAMINE_MIGRATION_PUSH_Q` (at food = 0).
|
|
100
|
+
*
|
|
101
|
+
* Add the result to Phase-81 `computePushPressure` output.
|
|
102
|
+
*/
|
|
103
|
+
export declare function computeFamineMigrationPush(foodSupply_Q: Q): Q;
|
|
104
|
+
/**
|
|
105
|
+
* Soft carrying capacity for a polity based on tech era.
|
|
106
|
+
*
|
|
107
|
+
* `stepPolityPopulation` does not enforce this cap. The host should call
|
|
108
|
+
* `isOverCapacity` after each step and pass additional emigration pressure
|
|
109
|
+
* to Phase-81 when it returns `true`.
|
|
110
|
+
*/
|
|
111
|
+
export declare function computeCarryingCapacity(polity: Polity): number;
|
|
112
|
+
/** Return `true` if the polity's population exceeds its tech-era carrying capacity. */
|
|
113
|
+
export declare function isOverCapacity(polity: Polity): boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Estimate annual births from a birth rate and population.
|
|
116
|
+
* Useful for host display and scenario planning.
|
|
117
|
+
*/
|
|
118
|
+
export declare function estimateAnnualBirths(population: number, birthRate_Q: Q): number;
|
|
119
|
+
/**
|
|
120
|
+
* Estimate annual deaths from a death rate and population.
|
|
121
|
+
*/
|
|
122
|
+
export declare function estimateAnnualDeaths(population: number, deathRate_Q: Q): number;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// src/demography.ts — Phase 86: Population Dynamics & Demographics
|
|
2
|
+
//
|
|
3
|
+
// Models natural population growth, mortality, and famine at polity level.
|
|
4
|
+
// All rates are annual Q fractions (fraction of population per year) so that
|
|
5
|
+
// SCALE.Q = 10000 gives sufficient precision (0.01%/year resolution).
|
|
6
|
+
//
|
|
7
|
+
// Step formula:
|
|
8
|
+
// popDelta = round(population × netAnnualRate_Q × elapsedDays / (365 × SCALE.Q))
|
|
9
|
+
//
|
|
10
|
+
// For small polities (< ~10 000) single-day steps yield zero delta;
|
|
11
|
+
// call with 7- or 30-day intervals for visible change.
|
|
12
|
+
//
|
|
13
|
+
// Integration:
|
|
14
|
+
// - Phase 61 (Polity): reads/mutates polity.population; uses morale/stability/techEra.
|
|
15
|
+
// - Phase 56 (Disease): caller passes epidemic annual mortality as deathPressure_Q.
|
|
16
|
+
// - Phase 81 (Migration): computeFamineMigrationPush() is an additive push bonus.
|
|
17
|
+
// - Phase 78 (Calendar): caller may pass seasonal birth/death multipliers.
|
|
18
|
+
import { q, SCALE, clampQ, mulDiv } from "./units.js";
|
|
19
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
20
|
+
/** Baseline annual birth rate [Q = fraction of population per year]. ≈ 3.5%/year. */
|
|
21
|
+
export const BASELINE_BIRTH_RATE_ANNUAL_Q = q(0.035);
|
|
22
|
+
/** Baseline annual death rate [Q = fraction of population per year]. ≈ 3.0%/year. */
|
|
23
|
+
export const BASELINE_DEATH_RATE_ANNUAL_Q = q(0.030);
|
|
24
|
+
/**
|
|
25
|
+
* Morale floor multiplier on birth rate.
|
|
26
|
+
*
|
|
27
|
+
* Birth rate factor = `BIRTH_RATE_MORALE_FLOOR_Q + moraleQ`, yielding:
|
|
28
|
+
* moraleQ = 0 → factor = q(0.50) → birth rate × 0.50
|
|
29
|
+
* moraleQ = SCALE.Q → factor = q(1.50) → birth rate × 1.50
|
|
30
|
+
*/
|
|
31
|
+
export const BIRTH_RATE_MORALE_FLOOR_Q = q(0.50);
|
|
32
|
+
/**
|
|
33
|
+
* Additional annual death rate at zero stability.
|
|
34
|
+
* Linearly scaled by `(SCALE.Q − stabilityQ) / SCALE.Q`.
|
|
35
|
+
* Full bonus at stability = 0; zero at stability = SCALE.Q.
|
|
36
|
+
*/
|
|
37
|
+
export const INSTABILITY_DEATH_ANNUAL_Q = q(0.015);
|
|
38
|
+
/** Food supply fraction below which famine is active [0, SCALE.Q]. */
|
|
39
|
+
export const FAMINE_THRESHOLD_Q = q(0.20);
|
|
40
|
+
/** Additional annual death rate during famine (+3%/year on top of baseline). */
|
|
41
|
+
export const FAMINE_DEATH_ANNUAL_Q = q(0.030);
|
|
42
|
+
/**
|
|
43
|
+
* Peak famine-driven migration push pressure (at food = 0).
|
|
44
|
+
* Integrates with Phase-81 `computePushPressure` as an additive bonus.
|
|
45
|
+
*/
|
|
46
|
+
export const FAMINE_MIGRATION_PUSH_Q = q(0.30);
|
|
47
|
+
/**
|
|
48
|
+
* Tech-era multiplier applied to the baseline death rate.
|
|
49
|
+
* Better technology → lower mortality from disease and malnutrition.
|
|
50
|
+
* Expressed as a Q fraction of SCALE.Q.
|
|
51
|
+
*/
|
|
52
|
+
export const TECH_ERA_DEATH_MUL = {
|
|
53
|
+
"Stone": SCALE.Q, // no reduction
|
|
54
|
+
"Bronze": Math.round(SCALE.Q * 0.95), // −5%
|
|
55
|
+
"Iron": Math.round(SCALE.Q * 0.90), // −10%
|
|
56
|
+
"Medieval": Math.round(SCALE.Q * 0.85), // −15%
|
|
57
|
+
"Renaissance": Math.round(SCALE.Q * 0.80), // −20%
|
|
58
|
+
"Industrial": Math.round(SCALE.Q * 0.65), // −35%
|
|
59
|
+
"Modern": Math.round(SCALE.Q * 0.50), // −50%
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Soft carrying capacity by tech era.
|
|
63
|
+
* `stepPolityPopulation` does not enforce it — host checks `isOverCapacity`
|
|
64
|
+
* and applies extra emigration pressure via Phase-81 if desired.
|
|
65
|
+
*/
|
|
66
|
+
export const CARRYING_CAPACITY_BY_ERA = {
|
|
67
|
+
"Stone": 50_000,
|
|
68
|
+
"Bronze": 200_000,
|
|
69
|
+
"Iron": 500_000,
|
|
70
|
+
"Medieval": 2_000_000,
|
|
71
|
+
"Renaissance": 5_000_000,
|
|
72
|
+
"Industrial": 20_000_000,
|
|
73
|
+
"Modern": 200_000_000,
|
|
74
|
+
};
|
|
75
|
+
// ── Rate computation ──────────────────────────────────────────────────────────
|
|
76
|
+
/**
|
|
77
|
+
* Compute the effective annual birth rate for a polity [Q = fraction/year].
|
|
78
|
+
*
|
|
79
|
+
* Morale scales birth rate linearly between 50% and 150% of baseline:
|
|
80
|
+
* moraleQ = 0 → BASELINE × 0.50 (≈ 1.75%/year)
|
|
81
|
+
* moraleQ = SCALE.Q → BASELINE × 1.50 (≈ 5.25%/year)
|
|
82
|
+
*/
|
|
83
|
+
export function computeBirthRate(polity) {
|
|
84
|
+
// factor ∈ [q(0.50), q(1.50)] = [5000, 15000]
|
|
85
|
+
const factor = BIRTH_RATE_MORALE_FLOOR_Q + polity.moraleQ;
|
|
86
|
+
return clampQ(mulDiv(BASELINE_BIRTH_RATE_ANNUAL_Q, factor, SCALE.Q), 0, SCALE.Q);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Compute the effective annual death rate for a polity [Q = fraction/year].
|
|
90
|
+
*
|
|
91
|
+
* Factors (additive):
|
|
92
|
+
* 1. Baseline reduced by tech era (better medicine / nutrition).
|
|
93
|
+
* 2. Instability bonus: up to `INSTABILITY_DEATH_ANNUAL_Q` at stability = 0.
|
|
94
|
+
* 3. External death pressure (caller: Phase-56 epidemic or Phase-84 siege casualties).
|
|
95
|
+
* 4. Famine bonus: `FAMINE_DEATH_ANNUAL_Q` when `foodSupply_Q < FAMINE_THRESHOLD_Q`.
|
|
96
|
+
*
|
|
97
|
+
* @param deathPressure_Q Annual mortality fraction from external cause.
|
|
98
|
+
* @param foodSupply_Q Current food supply [0, SCALE.Q]; omit if unknown.
|
|
99
|
+
*/
|
|
100
|
+
export function computeDeathRate(polity, deathPressure_Q, foodSupply_Q) {
|
|
101
|
+
const techMul = TECH_ERA_DEATH_MUL[polity.techEra] ?? SCALE.Q;
|
|
102
|
+
const techBase = mulDiv(BASELINE_DEATH_RATE_ANNUAL_Q, techMul, SCALE.Q);
|
|
103
|
+
const instFrac = SCALE.Q - polity.stabilityQ; // [0, SCALE.Q]
|
|
104
|
+
const instDeath = mulDiv(INSTABILITY_DEATH_ANNUAL_Q, instFrac, SCALE.Q);
|
|
105
|
+
const famine = foodSupply_Q != null && foodSupply_Q < FAMINE_THRESHOLD_Q;
|
|
106
|
+
const famineBonus = famine ? FAMINE_DEATH_ANNUAL_Q : 0;
|
|
107
|
+
const pressure = deathPressure_Q ?? 0;
|
|
108
|
+
return clampQ(techBase + instDeath + famineBonus + pressure, 0, SCALE.Q);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Compute the net annual growth rate (birth rate − death rate).
|
|
112
|
+
* Negative values indicate population decline.
|
|
113
|
+
*/
|
|
114
|
+
export function computeNetGrowthRate(polity, deathPressure_Q, foodSupply_Q) {
|
|
115
|
+
return computeBirthRate(polity) - computeDeathRate(polity, deathPressure_Q, foodSupply_Q);
|
|
116
|
+
}
|
|
117
|
+
// ── Population step ───────────────────────────────────────────────────────────
|
|
118
|
+
/**
|
|
119
|
+
* Step polity population forward by `elapsedDays` simulated days.
|
|
120
|
+
*
|
|
121
|
+
* Mutates `polity.population` in place and returns step metadata.
|
|
122
|
+
*
|
|
123
|
+
* Delta formula (fixed-point, single rounding):
|
|
124
|
+
* `popDelta = round(population × netAnnualRate_Q × elapsedDays / (365 × SCALE.Q))`
|
|
125
|
+
*
|
|
126
|
+
* @param elapsedDays Number of simulated days to advance (typically 1–30).
|
|
127
|
+
* @param deathPressure_Q Annual mortality fraction from disease or siege casualties.
|
|
128
|
+
* @param foodSupply_Q Food supply level [0, SCALE.Q]; famine fires below
|
|
129
|
+
* `FAMINE_THRESHOLD_Q`.
|
|
130
|
+
*/
|
|
131
|
+
export function stepPolityPopulation(polity, elapsedDays, deathPressure_Q, foodSupply_Q) {
|
|
132
|
+
const birthRate = computeBirthRate(polity);
|
|
133
|
+
const deathRate = computeDeathRate(polity, deathPressure_Q, foodSupply_Q);
|
|
134
|
+
const netRate = birthRate - deathRate;
|
|
135
|
+
const famine = foodSupply_Q != null && foodSupply_Q < FAMINE_THRESHOLD_Q;
|
|
136
|
+
const rawDelta = Math.round(polity.population * netRate * elapsedDays / (365 * SCALE.Q));
|
|
137
|
+
const newPop = Math.max(0, polity.population + rawDelta);
|
|
138
|
+
polity.population = newPop;
|
|
139
|
+
return {
|
|
140
|
+
popDelta: rawDelta,
|
|
141
|
+
newPopulation: newPop,
|
|
142
|
+
effectiveBirthRate_Q: birthRate,
|
|
143
|
+
effectiveDeathRate_Q: deathRate,
|
|
144
|
+
famine,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
// ── Famine ────────────────────────────────────────────────────────────────────
|
|
148
|
+
/**
|
|
149
|
+
* Compute famine-driven migration push pressure [0, SCALE.Q].
|
|
150
|
+
*
|
|
151
|
+
* Zero at or above `FAMINE_THRESHOLD_Q`. Scales linearly from zero (at the
|
|
152
|
+
* threshold) to `FAMINE_MIGRATION_PUSH_Q` (at food = 0).
|
|
153
|
+
*
|
|
154
|
+
* Add the result to Phase-81 `computePushPressure` output.
|
|
155
|
+
*/
|
|
156
|
+
export function computeFamineMigrationPush(foodSupply_Q) {
|
|
157
|
+
if (foodSupply_Q >= FAMINE_THRESHOLD_Q)
|
|
158
|
+
return 0;
|
|
159
|
+
const deficit = FAMINE_THRESHOLD_Q - foodSupply_Q; // (0, FAMINE_THRESHOLD_Q]
|
|
160
|
+
return clampQ(mulDiv(FAMINE_MIGRATION_PUSH_Q, deficit, FAMINE_THRESHOLD_Q), 0, SCALE.Q);
|
|
161
|
+
}
|
|
162
|
+
// ── Carrying capacity ─────────────────────────────────────────────────────────
|
|
163
|
+
/**
|
|
164
|
+
* Soft carrying capacity for a polity based on tech era.
|
|
165
|
+
*
|
|
166
|
+
* `stepPolityPopulation` does not enforce this cap. The host should call
|
|
167
|
+
* `isOverCapacity` after each step and pass additional emigration pressure
|
|
168
|
+
* to Phase-81 when it returns `true`.
|
|
169
|
+
*/
|
|
170
|
+
export function computeCarryingCapacity(polity) {
|
|
171
|
+
return CARRYING_CAPACITY_BY_ERA[polity.techEra] ?? 50_000;
|
|
172
|
+
}
|
|
173
|
+
/** Return `true` if the polity's population exceeds its tech-era carrying capacity. */
|
|
174
|
+
export function isOverCapacity(polity) {
|
|
175
|
+
return polity.population > computeCarryingCapacity(polity);
|
|
176
|
+
}
|
|
177
|
+
// ── Reporting utilities ───────────────────────────────────────────────────────
|
|
178
|
+
/**
|
|
179
|
+
* Estimate annual births from a birth rate and population.
|
|
180
|
+
* Useful for host display and scenario planning.
|
|
181
|
+
*/
|
|
182
|
+
export function estimateAnnualBirths(population, birthRate_Q) {
|
|
183
|
+
return Math.round(population * birthRate_Q / SCALE.Q);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Estimate annual deaths from a death rate and population.
|
|
187
|
+
*/
|
|
188
|
+
export function estimateAnnualDeaths(population, deathRate_Q) {
|
|
189
|
+
return Math.round(population * deathRate_Q / SCALE.Q);
|
|
190
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { Q } from "./units.js";
|
|
2
|
+
import type { Polity } from "./polity.js";
|
|
3
|
+
/**
|
|
4
|
+
* Grain reserves for one polity.
|
|
5
|
+
*
|
|
6
|
+
* Capacity is derived (not stored): `population × GRANARY_CAPACITY_DAYS`.
|
|
7
|
+
* Attach one `GranaryState` per polity; store externally (e.g., `Map<string, GranaryState>`).
|
|
8
|
+
*/
|
|
9
|
+
export interface GranaryState {
|
|
10
|
+
polityId: string;
|
|
11
|
+
/** Current grain reserves in supply units (1 su = food for 1 person for 1 day). */
|
|
12
|
+
grain_su: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Granary holds this many person-days of food at full capacity.
|
|
16
|
+
* Default: 730 (≈ 2 years of food per capita).
|
|
17
|
+
*/
|
|
18
|
+
export declare const GRANARY_CAPACITY_DAYS = 730;
|
|
19
|
+
/**
|
|
20
|
+
* Each harvest at full yield contributes this many person-days per capita.
|
|
21
|
+
* With two harvests/year: 500 annual supply vs. 365 consumption → ~37% surplus headroom.
|
|
22
|
+
*/
|
|
23
|
+
export declare const HARVEST_BASE_SU_PER_CAPITA = 250;
|
|
24
|
+
/**
|
|
25
|
+
* Minimum harvest yield at zero stability [0, SCALE.Q].
|
|
26
|
+
* Stability linearly scales yield from this floor to `SCALE.Q` (full yield).
|
|
27
|
+
*/
|
|
28
|
+
export declare const HARVEST_YIELD_BASE_Q: Q;
|
|
29
|
+
/**
|
|
30
|
+
* Maximum additional yield from full stability [0, SCALE.Q].
|
|
31
|
+
* yieldFactor = HARVEST_YIELD_BASE_Q + mulDiv(HARVEST_STABILITY_BONUS_Q, stabilityQ, SCALE.Q).
|
|
32
|
+
*/
|
|
33
|
+
export declare const HARVEST_STABILITY_BONUS_Q: Q;
|
|
34
|
+
/**
|
|
35
|
+
* Fraction of the granary that a successful siege raid removes.
|
|
36
|
+
* Callers may pass a different fraction to `raidGranary`.
|
|
37
|
+
*/
|
|
38
|
+
export declare const RAID_FRACTION_Q: Q;
|
|
39
|
+
/**
|
|
40
|
+
* Create a new `GranaryState` for a polity.
|
|
41
|
+
* Initial reserves default to one year of consumption (stable starting point).
|
|
42
|
+
*/
|
|
43
|
+
export declare function createGranary(polity: Polity): GranaryState;
|
|
44
|
+
/**
|
|
45
|
+
* Maximum grain the polity can store [supply units].
|
|
46
|
+
* Scales with current population — a growing polity can store more.
|
|
47
|
+
*/
|
|
48
|
+
export declare function computeCapacity(polity: Polity): number;
|
|
49
|
+
/**
|
|
50
|
+
* Convert grain reserves to a [0, SCALE.Q] food supply fraction.
|
|
51
|
+
*
|
|
52
|
+
* This is the `foodSupply_Q` input for Phase-86 `stepPolityPopulation`:
|
|
53
|
+
* - q(1.0) = full granary (no famine)
|
|
54
|
+
* - below Phase-86 `FAMINE_THRESHOLD_Q = q(0.20)` → famine active
|
|
55
|
+
*
|
|
56
|
+
* Returns 0 when population is zero (prevents division by zero).
|
|
57
|
+
*/
|
|
58
|
+
export declare function computeFoodSupply_Q(polity: Polity, granary: GranaryState): Q;
|
|
59
|
+
/**
|
|
60
|
+
* Derive the harvest yield factor [0, SCALE.Q] for a polity.
|
|
61
|
+
*
|
|
62
|
+
* Formula: `HARVEST_YIELD_BASE_Q + mulDiv(HARVEST_STABILITY_BONUS_Q, stabilityQ, SCALE.Q)`
|
|
63
|
+
* then optionally multiplied by a Phase-78 seasonal factor.
|
|
64
|
+
*
|
|
65
|
+
* @param season_Q Seasonal multiplier [0, SCALE.Q] from Phase-78 Calendar.
|
|
66
|
+
* `q(1.0)` = summer peak; `q(0.50)` = winter harvest.
|
|
67
|
+
* Omit for an unseasoned annual harvest.
|
|
68
|
+
*/
|
|
69
|
+
export declare function deriveHarvestYieldFactor(polity: Polity, season_Q?: Q): Q;
|
|
70
|
+
/**
|
|
71
|
+
* Compute the grain added by one harvest [supply units].
|
|
72
|
+
*
|
|
73
|
+
* `yield_su = round(population × HARVEST_BASE_SU_PER_CAPITA × yieldFactor_Q / SCALE.Q)`
|
|
74
|
+
*
|
|
75
|
+
* @param yieldFactor_Q Override factor; defaults to `deriveHarvestYieldFactor(polity)`.
|
|
76
|
+
*/
|
|
77
|
+
export declare function computeHarvestYield(polity: Polity, yieldFactor_Q?: Q): number;
|
|
78
|
+
/**
|
|
79
|
+
* Add one harvest to the granary.
|
|
80
|
+
*
|
|
81
|
+
* Grain is clamped to `computeCapacity(polity)` — surplus is lost (no overflow).
|
|
82
|
+
* Returns the amount actually added (may be less than yield if near capacity).
|
|
83
|
+
*
|
|
84
|
+
* Call at the end of each harvest season (biannual: spring + autumn).
|
|
85
|
+
*/
|
|
86
|
+
export declare function triggerHarvest(polity: Polity, granary: GranaryState, yieldFactor_Q?: Q): number;
|
|
87
|
+
/**
|
|
88
|
+
* Drain daily grain consumption for `elapsedDays` days.
|
|
89
|
+
*
|
|
90
|
+
* Consumption = `polity.population × elapsedDays` supply units.
|
|
91
|
+
* Grain is clamped to 0 (no negative reserves).
|
|
92
|
+
*
|
|
93
|
+
* Returns the actual amount consumed (may be less than demand if reserves run low).
|
|
94
|
+
*/
|
|
95
|
+
export declare function stepGranaryConsumption(polity: Polity, granary: GranaryState, elapsedDays: number): number;
|
|
96
|
+
/**
|
|
97
|
+
* Transfer grain from one polity's granary to another.
|
|
98
|
+
*
|
|
99
|
+
* Actual transfer is limited by:
|
|
100
|
+
* - Grain available in the source granary.
|
|
101
|
+
* - Remaining capacity in the destination granary.
|
|
102
|
+
*
|
|
103
|
+
* Returns the amount actually transferred.
|
|
104
|
+
* Integrate with Phase-83 trade routes: host calls this when resolving a food route.
|
|
105
|
+
*/
|
|
106
|
+
export declare function tradeFoodSupply(fromGranary: GranaryState, toGranary: GranaryState, toPolity: Polity, amount_su: number): number;
|
|
107
|
+
/**
|
|
108
|
+
* Plunder a granary after a successful siege.
|
|
109
|
+
*
|
|
110
|
+
* Removes `raidFraction_Q` of current grain reserves.
|
|
111
|
+
* Returns the amount plundered.
|
|
112
|
+
*
|
|
113
|
+
* Integrates with Phase-84 siege: call on `outcome === "attacker_victory"`.
|
|
114
|
+
*
|
|
115
|
+
* @param raidFraction_Q Fraction of reserves plundered [0, SCALE.Q].
|
|
116
|
+
* Defaults to `RAID_FRACTION_Q = q(0.40)`.
|
|
117
|
+
*/
|
|
118
|
+
export declare function raidGranary(granary: GranaryState, raidFraction_Q?: Q): number;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// src/granary.ts — Phase 87: Granary & Food Supply
|
|
2
|
+
//
|
|
3
|
+
// Tracks grain reserves per polity. Grain is measured in "supply units" (su)
|
|
4
|
+
// where 1 su feeds one person for one day. The granary fills at each harvest
|
|
5
|
+
// and drains with daily consumption; when reserves fall below a fraction of
|
|
6
|
+
// capacity, Phase-86 famine mechanics activate.
|
|
7
|
+
//
|
|
8
|
+
// Design:
|
|
9
|
+
// - Pure data layer — no Entity fields, no kernel changes.
|
|
10
|
+
// - `GranaryState` stores only grain_su; capacity is derived from polity.population.
|
|
11
|
+
// - `computeFoodSupply_Q` produces the [0, SCALE.Q] value consumed by Phase-86
|
|
12
|
+
// `stepPolityPopulation(deathPressure_Q, foodSupply_Q)`.
|
|
13
|
+
// - Harvest yield is modulated by stability and an optional Phase-78 season multiplier.
|
|
14
|
+
// - `tradeFoodSupply` integrates with Phase-83 trade routes (caller-driven).
|
|
15
|
+
// - `raidGranary` integrates with Phase-84 siege warfare (plunder).
|
|
16
|
+
//
|
|
17
|
+
// Integration:
|
|
18
|
+
// Phase 61 (Polity): population drives capacity and harvest yield.
|
|
19
|
+
// Phase 78 (Calendar): season_Q passed to deriveHarvestYieldFactor.
|
|
20
|
+
// Phase 83 (Trade): tradeFoodSupply called when host resolves a food route.
|
|
21
|
+
// Phase 84 (Siege): raidGranary called on attacker victory.
|
|
22
|
+
// Phase 86 (Demography): computeFoodSupply_Q → foodSupply_Q parameter.
|
|
23
|
+
import { q, SCALE, clampQ, mulDiv } from "./units.js";
|
|
24
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
25
|
+
/**
|
|
26
|
+
* Granary holds this many person-days of food at full capacity.
|
|
27
|
+
* Default: 730 (≈ 2 years of food per capita).
|
|
28
|
+
*/
|
|
29
|
+
export const GRANARY_CAPACITY_DAYS = 730;
|
|
30
|
+
/**
|
|
31
|
+
* Each harvest at full yield contributes this many person-days per capita.
|
|
32
|
+
* With two harvests/year: 500 annual supply vs. 365 consumption → ~37% surplus headroom.
|
|
33
|
+
*/
|
|
34
|
+
export const HARVEST_BASE_SU_PER_CAPITA = 250;
|
|
35
|
+
/**
|
|
36
|
+
* Minimum harvest yield at zero stability [0, SCALE.Q].
|
|
37
|
+
* Stability linearly scales yield from this floor to `SCALE.Q` (full yield).
|
|
38
|
+
*/
|
|
39
|
+
export const HARVEST_YIELD_BASE_Q = q(0.70);
|
|
40
|
+
/**
|
|
41
|
+
* Maximum additional yield from full stability [0, SCALE.Q].
|
|
42
|
+
* yieldFactor = HARVEST_YIELD_BASE_Q + mulDiv(HARVEST_STABILITY_BONUS_Q, stabilityQ, SCALE.Q).
|
|
43
|
+
*/
|
|
44
|
+
export const HARVEST_STABILITY_BONUS_Q = q(0.30);
|
|
45
|
+
/**
|
|
46
|
+
* Fraction of the granary that a successful siege raid removes.
|
|
47
|
+
* Callers may pass a different fraction to `raidGranary`.
|
|
48
|
+
*/
|
|
49
|
+
export const RAID_FRACTION_Q = q(0.40);
|
|
50
|
+
// ── Factory ───────────────────────────────────────────────────────────────────
|
|
51
|
+
/**
|
|
52
|
+
* Create a new `GranaryState` for a polity.
|
|
53
|
+
* Initial reserves default to one year of consumption (stable starting point).
|
|
54
|
+
*/
|
|
55
|
+
export function createGranary(polity) {
|
|
56
|
+
return {
|
|
57
|
+
polityId: polity.id,
|
|
58
|
+
grain_su: polity.population * 365,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// ── Capacity & food supply ────────────────────────────────────────────────────
|
|
62
|
+
/**
|
|
63
|
+
* Maximum grain the polity can store [supply units].
|
|
64
|
+
* Scales with current population — a growing polity can store more.
|
|
65
|
+
*/
|
|
66
|
+
export function computeCapacity(polity) {
|
|
67
|
+
return polity.population * GRANARY_CAPACITY_DAYS;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Convert grain reserves to a [0, SCALE.Q] food supply fraction.
|
|
71
|
+
*
|
|
72
|
+
* This is the `foodSupply_Q` input for Phase-86 `stepPolityPopulation`:
|
|
73
|
+
* - q(1.0) = full granary (no famine)
|
|
74
|
+
* - below Phase-86 `FAMINE_THRESHOLD_Q = q(0.20)` → famine active
|
|
75
|
+
*
|
|
76
|
+
* Returns 0 when population is zero (prevents division by zero).
|
|
77
|
+
*/
|
|
78
|
+
export function computeFoodSupply_Q(polity, granary) {
|
|
79
|
+
const cap = computeCapacity(polity);
|
|
80
|
+
if (cap <= 0)
|
|
81
|
+
return 0;
|
|
82
|
+
return clampQ(Math.round(granary.grain_su * SCALE.Q / cap), 0, SCALE.Q);
|
|
83
|
+
}
|
|
84
|
+
// ── Harvest ───────────────────────────────────────────────────────────────────
|
|
85
|
+
/**
|
|
86
|
+
* Derive the harvest yield factor [0, SCALE.Q] for a polity.
|
|
87
|
+
*
|
|
88
|
+
* Formula: `HARVEST_YIELD_BASE_Q + mulDiv(HARVEST_STABILITY_BONUS_Q, stabilityQ, SCALE.Q)`
|
|
89
|
+
* then optionally multiplied by a Phase-78 seasonal factor.
|
|
90
|
+
*
|
|
91
|
+
* @param season_Q Seasonal multiplier [0, SCALE.Q] from Phase-78 Calendar.
|
|
92
|
+
* `q(1.0)` = summer peak; `q(0.50)` = winter harvest.
|
|
93
|
+
* Omit for an unseasoned annual harvest.
|
|
94
|
+
*/
|
|
95
|
+
export function deriveHarvestYieldFactor(polity, season_Q) {
|
|
96
|
+
const stabilityBonus = mulDiv(HARVEST_STABILITY_BONUS_Q, polity.stabilityQ, SCALE.Q);
|
|
97
|
+
const baseFactor = clampQ(HARVEST_YIELD_BASE_Q + stabilityBonus, 0, SCALE.Q);
|
|
98
|
+
if (season_Q == null)
|
|
99
|
+
return baseFactor;
|
|
100
|
+
return clampQ(mulDiv(baseFactor, season_Q, SCALE.Q), 0, SCALE.Q);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Compute the grain added by one harvest [supply units].
|
|
104
|
+
*
|
|
105
|
+
* `yield_su = round(population × HARVEST_BASE_SU_PER_CAPITA × yieldFactor_Q / SCALE.Q)`
|
|
106
|
+
*
|
|
107
|
+
* @param yieldFactor_Q Override factor; defaults to `deriveHarvestYieldFactor(polity)`.
|
|
108
|
+
*/
|
|
109
|
+
export function computeHarvestYield(polity, yieldFactor_Q) {
|
|
110
|
+
const factor = yieldFactor_Q ?? deriveHarvestYieldFactor(polity);
|
|
111
|
+
return Math.round(polity.population * HARVEST_BASE_SU_PER_CAPITA * factor / SCALE.Q);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Add one harvest to the granary.
|
|
115
|
+
*
|
|
116
|
+
* Grain is clamped to `computeCapacity(polity)` — surplus is lost (no overflow).
|
|
117
|
+
* Returns the amount actually added (may be less than yield if near capacity).
|
|
118
|
+
*
|
|
119
|
+
* Call at the end of each harvest season (biannual: spring + autumn).
|
|
120
|
+
*/
|
|
121
|
+
export function triggerHarvest(polity, granary, yieldFactor_Q) {
|
|
122
|
+
const cap = computeCapacity(polity);
|
|
123
|
+
const yield_ = computeHarvestYield(polity, yieldFactor_Q);
|
|
124
|
+
const added = Math.min(yield_, Math.max(0, cap - granary.grain_su));
|
|
125
|
+
granary.grain_su = Math.min(cap, granary.grain_su + yield_);
|
|
126
|
+
return added;
|
|
127
|
+
}
|
|
128
|
+
// ── Consumption ───────────────────────────────────────────────────────────────
|
|
129
|
+
/**
|
|
130
|
+
* Drain daily grain consumption for `elapsedDays` days.
|
|
131
|
+
*
|
|
132
|
+
* Consumption = `polity.population × elapsedDays` supply units.
|
|
133
|
+
* Grain is clamped to 0 (no negative reserves).
|
|
134
|
+
*
|
|
135
|
+
* Returns the actual amount consumed (may be less than demand if reserves run low).
|
|
136
|
+
*/
|
|
137
|
+
export function stepGranaryConsumption(polity, granary, elapsedDays) {
|
|
138
|
+
const demand = polity.population * elapsedDays;
|
|
139
|
+
const consumed = Math.min(demand, granary.grain_su);
|
|
140
|
+
granary.grain_su = Math.max(0, granary.grain_su - demand);
|
|
141
|
+
return consumed;
|
|
142
|
+
}
|
|
143
|
+
// ── Trade food ────────────────────────────────────────────────────────────────
|
|
144
|
+
/**
|
|
145
|
+
* Transfer grain from one polity's granary to another.
|
|
146
|
+
*
|
|
147
|
+
* Actual transfer is limited by:
|
|
148
|
+
* - Grain available in the source granary.
|
|
149
|
+
* - Remaining capacity in the destination granary.
|
|
150
|
+
*
|
|
151
|
+
* Returns the amount actually transferred.
|
|
152
|
+
* Integrate with Phase-83 trade routes: host calls this when resolving a food route.
|
|
153
|
+
*/
|
|
154
|
+
export function tradeFoodSupply(fromGranary, toGranary, toPolity, amount_su) {
|
|
155
|
+
const toCap = computeCapacity(toPolity);
|
|
156
|
+
const toSpace = Math.max(0, toCap - toGranary.grain_su);
|
|
157
|
+
const available = fromGranary.grain_su;
|
|
158
|
+
const transferred = Math.min(amount_su, available, toSpace);
|
|
159
|
+
fromGranary.grain_su -= transferred;
|
|
160
|
+
toGranary.grain_su += transferred;
|
|
161
|
+
return transferred;
|
|
162
|
+
}
|
|
163
|
+
// ── Siege raid ────────────────────────────────────────────────────────────────
|
|
164
|
+
/**
|
|
165
|
+
* Plunder a granary after a successful siege.
|
|
166
|
+
*
|
|
167
|
+
* Removes `raidFraction_Q` of current grain reserves.
|
|
168
|
+
* Returns the amount plundered.
|
|
169
|
+
*
|
|
170
|
+
* Integrates with Phase-84 siege: call on `outcome === "attacker_victory"`.
|
|
171
|
+
*
|
|
172
|
+
* @param raidFraction_Q Fraction of reserves plundered [0, SCALE.Q].
|
|
173
|
+
* Defaults to `RAID_FRACTION_Q = q(0.40)`.
|
|
174
|
+
*/
|
|
175
|
+
export function raidGranary(granary, raidFraction_Q) {
|
|
176
|
+
const fraction = raidFraction_Q ?? RAID_FRACTION_Q;
|
|
177
|
+
const plundered = Math.round(mulDiv(granary.grain_su, fraction, SCALE.Q));
|
|
178
|
+
granary.grain_su = Math.max(0, granary.grain_su - plundered);
|
|
179
|
+
return plundered;
|
|
180
|
+
}
|
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.32",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deterministic lockstep-friendly SI-units RPG/physics core (fixed-point TS)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -106,6 +106,14 @@
|
|
|
106
106
|
"./faith": {
|
|
107
107
|
"import": "./dist/src/faith.js",
|
|
108
108
|
"types": "./dist/src/faith.d.ts"
|
|
109
|
+
},
|
|
110
|
+
"./demography": {
|
|
111
|
+
"import": "./dist/src/demography.js",
|
|
112
|
+
"types": "./dist/src/demography.d.ts"
|
|
113
|
+
},
|
|
114
|
+
"./granary": {
|
|
115
|
+
"import": "./dist/src/granary.js",
|
|
116
|
+
"types": "./dist/src/granary.d.ts"
|
|
109
117
|
}
|
|
110
118
|
},
|
|
111
119
|
"files": [
|