@its-not-rocket-science/ananke 0.1.42 → 0.1.44
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 +48 -0
- package/dist/src/containment.d.ts +103 -0
- package/dist/src/containment.js +180 -0
- package/dist/src/mercenaries.d.ts +131 -0
- package/dist/src/mercenaries.js +187 -0
- package/package.json +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,54 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [0.1.44] — 2026-03-27
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Phase 99 · Mercenaries & Hired Forces** (`src/mercenaries.ts`)
|
|
14
|
+
- `MercenaryBand { bandId, name, size, quality_Q, dailyWagePerSoldier_cu }` — immutable descriptor.
|
|
15
|
+
- `MercenaryContract { contractId, polityId, bandId, daysActive, loyalty_Q, arrears_cu }` — mutable live state stored externally.
|
|
16
|
+
- `MercenaryStepResult { wagePaid_cu, arrearsAdded_cu, loyaltyDelta, deserted }` — step outcome.
|
|
17
|
+
- `DESERT_LOYALTY_THRESHOLD_Q = q(0.25)` — below this, desertion roll fires.
|
|
18
|
+
- `LOYALTY_DECAY_PER_DAY_UNPAID = 80` — loyalty drops 0.8%/day when wages owed.
|
|
19
|
+
- `LOYALTY_GROWTH_PER_DAY_PAID = 20` — loyalty grows 0.2%/day when fully paid.
|
|
20
|
+
- `MAX_MERC_STRENGTH_BONUS_Q = q(0.30)` — caps advisory strength contribution.
|
|
21
|
+
- `computeMercenaryWage(band, elapsedDays)` — `size × dailyWage × days`.
|
|
22
|
+
- `computeMercenaryStrengthContribution(band, contract)` → Q — `size × quality × loyalty / SCALE.Q²`; capped at q(0.30); add to Phase-93 battle strength.
|
|
23
|
+
- `stepMercenaryContract(contract, band, polity, elapsedDays, worldSeed, tick)` — pays wages from treasury, accrues arrears, grows/decays loyalty, rolls desertion via `eventSeed` (deterministic).
|
|
24
|
+
- `applyVictoryLoyaltyBonus(contract)` — q(0.10) boost after campaign victory.
|
|
25
|
+
- `hireMercenaries(contractId, polityId, band, initialLoyalty_Q?)` — factory; default loyalty q(0.70).
|
|
26
|
+
- `isMercenaryReliable(contract)` / `hasMercenaryArrears(contract)` — predicates.
|
|
27
|
+
- Three sample bands: `BAND_LIGHT_CAVALRY` (400 soldiers, q(0.65), 3 cu/day), `BAND_HEAVY_INFANTRY` (600, q(0.85), 5 cu/day), `BAND_SIEGE_ENGINEERS` (200, q(0.75), 8 cu/day).
|
|
28
|
+
- Added `./mercenaries` subpath export to `package.json`.
|
|
29
|
+
- 44 new tests; 5,173 total. Coverage: 100% statements/branches/functions/lines on `mercenaries.ts`.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## [0.1.43] — 2026-03-26
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- **Phase 98 · Plague Containment & Quarantine** (`src/containment.ts`)
|
|
38
|
+
- `QuarantinePolicy`: `"none" | "voluntary" | "enforced" | "total_lockdown"`.
|
|
39
|
+
- `ContainmentState { polityId, policy, daysActive, complianceDecay_Q }` — per-polity mutable tracker stored externally.
|
|
40
|
+
- Compliance decay models population resistance to prolonged enforcement: voluntary decays 2/day, enforced 8/day, total_lockdown 18/day (out of SCALE.Q=10000). `changeQuarantinePolicy` resets decay.
|
|
41
|
+
- `QUARANTINE_TRANSMISSION_REDUCTION_Q`: voluntary q(0.20) → enforced q(0.55) → total_lockdown q(0.85) — base transmission cut fed to Phase-88 `spreadEpidemic`.
|
|
42
|
+
- `QUARANTINE_HEALTH_BONUS_Q`: voluntary q(0.05) → total_lockdown q(0.25) — stacks with Phase-88 `deriveHealthCapacity` as additive `healthCapacity_Q` bonus.
|
|
43
|
+
- `QUARANTINE_UNREST_Q`: q(0.02) → q(0.28); grows further as compliance decays.
|
|
44
|
+
- `QUARANTINE_DAILY_COST_PER_1000`: 1 → 5 → 15 cu/1000 pop/day.
|
|
45
|
+
- `computeEffectiveTransmissionReduction(state)` — base reduction × compliance factor.
|
|
46
|
+
- `computeContainmentHealthBonus(state)` — health bonus scaled by compliance.
|
|
47
|
+
- `computeContainmentUnrest(state)` — base unrest + decay-driven bonus.
|
|
48
|
+
- `computeContainmentCost_cu(polity, state, elapsedDays)` — treasury drain.
|
|
49
|
+
- `stepContainment(state, elapsedDays)` — increments daysActive; accrues complianceDecay_Q.
|
|
50
|
+
- `applyQuarantineToContact(contactIntensity_Q, state)` — scales Phase-88 contact parameter by effective reduction; returns reduced value for `computeSpreadToPolity`.
|
|
51
|
+
- `isQuarantineActive(state)` / `isTotalLockdown(state)` — convenience predicates.
|
|
52
|
+
- Added `./containment` subpath export to `package.json`.
|
|
53
|
+
- 47 new tests; 5,129 total. Coverage: 100% statements/branches/functions/lines on `containment.ts`.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
9
57
|
## [0.1.42] — 2026-03-26
|
|
10
58
|
|
|
11
59
|
### Added
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { Q } from "./units.js";
|
|
2
|
+
import type { Polity } from "./polity.js";
|
|
3
|
+
/** Polity-level quarantine policy tier. */
|
|
4
|
+
export type QuarantinePolicy = "none" | "voluntary" | "enforced" | "total_lockdown";
|
|
5
|
+
/**
|
|
6
|
+
* Per-polity containment tracking state.
|
|
7
|
+
* Attach one per polity; store externally (e.g. `Map<string, ContainmentState>`).
|
|
8
|
+
*/
|
|
9
|
+
export interface ContainmentState {
|
|
10
|
+
polityId: string;
|
|
11
|
+
policy: QuarantinePolicy;
|
|
12
|
+
/** Days the current policy has been continuously active. */
|
|
13
|
+
daysActive: number;
|
|
14
|
+
/**
|
|
15
|
+
* Accumulated non-compliance [0, SCALE.Q].
|
|
16
|
+
* Rises each day a strict policy is maintained; resets when policy changes.
|
|
17
|
+
* Reduces effective transmission reduction as populations resist enforcement.
|
|
18
|
+
*/
|
|
19
|
+
complianceDecay_Q: Q;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Base transmission reduction fraction per policy tier [0, SCALE.Q].
|
|
23
|
+
* Applied to `contactIntensity_Q` in Phase-88 spread calculations.
|
|
24
|
+
* Actual effect is scaled down by `(SCALE.Q − complianceDecay_Q) / SCALE.Q`.
|
|
25
|
+
*/
|
|
26
|
+
export declare const QUARANTINE_TRANSMISSION_REDUCTION_Q: Record<QuarantinePolicy, Q>;
|
|
27
|
+
/**
|
|
28
|
+
* Health capacity bonus per policy tier [0, SCALE.Q].
|
|
29
|
+
* Stack with Phase-88 `deriveHealthCapacity` as additive bonus to `healthCapacity_Q`.
|
|
30
|
+
* Reflects improved isolation, triage, and care coordination.
|
|
31
|
+
*/
|
|
32
|
+
export declare const QUARANTINE_HEALTH_BONUS_Q: Record<QuarantinePolicy, Q>;
|
|
33
|
+
/** Unrest pressure per policy tier [0, SCALE.Q]. Pass to Phase-90 `computeUnrestLevel`. */
|
|
34
|
+
export declare const QUARANTINE_UNREST_Q: Record<QuarantinePolicy, Q>;
|
|
35
|
+
/**
|
|
36
|
+
* Daily treasury cost per 1,000 population by policy tier [cu].
|
|
37
|
+
* Scale: `computeContainmentCost_cu = cost × population / 1000 × elapsedDays`.
|
|
38
|
+
*/
|
|
39
|
+
export declare const QUARANTINE_DAILY_COST_PER_1000: Record<QuarantinePolicy, number>;
|
|
40
|
+
/**
|
|
41
|
+
* Compliance decay accrued per day by policy tier [out of SCALE.Q].
|
|
42
|
+
* Voluntary: minimal decay (people accept guidance).
|
|
43
|
+
* Total lockdown: fast erosion (coercion breeds resistance).
|
|
44
|
+
*/
|
|
45
|
+
export declare const COMPLIANCE_DECAY_PER_DAY: Record<QuarantinePolicy, number>;
|
|
46
|
+
/** Create a `ContainmentState` with no active quarantine policy. */
|
|
47
|
+
export declare function createContainmentState(polityId: string): ContainmentState;
|
|
48
|
+
/**
|
|
49
|
+
* Change the active quarantine policy.
|
|
50
|
+
*
|
|
51
|
+
* Resets `daysActive` and `complianceDecay_Q` — a policy change resets the
|
|
52
|
+
* population's compliance posture (initial goodwill or fear of the new measure).
|
|
53
|
+
*/
|
|
54
|
+
export declare function changeQuarantinePolicy(state: ContainmentState, newPolicy: QuarantinePolicy): void;
|
|
55
|
+
/**
|
|
56
|
+
* Compute the effective transmission reduction fraction [0, SCALE.Q],
|
|
57
|
+
* factoring in accumulated compliance decay.
|
|
58
|
+
*
|
|
59
|
+
* `effective = baseReduction × (SCALE.Q − complianceDecay_Q) / SCALE.Q`
|
|
60
|
+
*/
|
|
61
|
+
export declare function computeEffectiveTransmissionReduction(state: ContainmentState): Q;
|
|
62
|
+
/**
|
|
63
|
+
* Compute the health capacity bonus from active quarantine [0, SCALE.Q].
|
|
64
|
+
* Add to the output of Phase-88 `deriveHealthCapacity(polity)`.
|
|
65
|
+
* The bonus also decays with compliance.
|
|
66
|
+
*/
|
|
67
|
+
export declare function computeContainmentHealthBonus(state: ContainmentState): Q;
|
|
68
|
+
/**
|
|
69
|
+
* Compute the unrest pressure from the current quarantine policy [0, SCALE.Q].
|
|
70
|
+
*
|
|
71
|
+
* Unrest grows as compliance erodes — a partially-enforced lockdown is more
|
|
72
|
+
* resented than a fresh voluntary advisory.
|
|
73
|
+
* `unrest = baseUnrest + decayFraction × baseUnrest / SCALE.Q`
|
|
74
|
+
*/
|
|
75
|
+
export declare function computeContainmentUnrest(state: ContainmentState): Q;
|
|
76
|
+
/**
|
|
77
|
+
* Compute the daily treasury cost of the active quarantine policy.
|
|
78
|
+
*
|
|
79
|
+
* `cost = DAILY_COST_PER_1000 × population / 1000 × elapsedDays`
|
|
80
|
+
*/
|
|
81
|
+
export declare function computeContainmentCost_cu(polity: Polity, state: ContainmentState, elapsedDays: number): number;
|
|
82
|
+
/**
|
|
83
|
+
* Advance containment state by `elapsedDays`.
|
|
84
|
+
*
|
|
85
|
+
* - Increments `daysActive`.
|
|
86
|
+
* - Accrues `complianceDecay_Q` at `COMPLIANCE_DECAY_PER_DAY[policy]`; clamped to SCALE.Q.
|
|
87
|
+
* Policy "none" does not decay (nothing to comply with).
|
|
88
|
+
*/
|
|
89
|
+
export declare function stepContainment(state: ContainmentState, elapsedDays: number): void;
|
|
90
|
+
/**
|
|
91
|
+
* Scale down a Phase-88 `contactIntensity_Q` by the effective quarantine reduction.
|
|
92
|
+
*
|
|
93
|
+
* Pass the returned value to `spreadEpidemic` or `computeSpreadToPolity`:
|
|
94
|
+
* ```ts
|
|
95
|
+
* const adjContact = applyQuarantineToContact(tradeIntensity_Q, containmentState);
|
|
96
|
+
* computeSpreadToPolity(source, profile, adjContact);
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export declare function applyQuarantineToContact(contactIntensity_Q: Q, state: ContainmentState): Q;
|
|
100
|
+
/** Return `true` when any active containment policy is in effect. */
|
|
101
|
+
export declare function isQuarantineActive(state: ContainmentState): boolean;
|
|
102
|
+
/** Return `true` when the current policy is the most restrictive tier. */
|
|
103
|
+
export declare function isTotalLockdown(state: ContainmentState): boolean;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// src/containment.ts — Phase 98: Plague Containment & Quarantine
|
|
2
|
+
//
|
|
3
|
+
// Active polity-level response to epidemic outbreaks. Sits above Phase-88
|
|
4
|
+
// (Epidemic) as the policy layer, just as Phase-97 (Famine) sits above Phase-87.
|
|
5
|
+
//
|
|
6
|
+
// Design:
|
|
7
|
+
// - Pure data layer — no Entity fields, no kernel changes.
|
|
8
|
+
// - `ContainmentState` tracks active policy, days enforced, and compliance decay.
|
|
9
|
+
// Store one per polity externally (e.g. `Map<string, ContainmentState>`).
|
|
10
|
+
// - `computeEffectiveTransmissionReduction` factors in compliance decay — strict
|
|
11
|
+
// lockdowns erode over time as populations resist enforcement.
|
|
12
|
+
// - `applyQuarantineToContact` scales the `contactIntensity_Q` parameter passed
|
|
13
|
+
// to Phase-88 `spreadEpidemic` / `computeSpreadToPolity`.
|
|
14
|
+
// - `computeContainmentHealthBonus` stacks with Phase-88 `deriveHealthCapacity`
|
|
15
|
+
// as the `healthCapacity_Q` bonus passed to `stepEpidemic`.
|
|
16
|
+
// - Cost, unrest, and health bonus are all advisory; callers apply them.
|
|
17
|
+
//
|
|
18
|
+
// Integration:
|
|
19
|
+
// Phase 88 (Epidemic): applyQuarantineToContact → contactIntensity_Q;
|
|
20
|
+
// computeContainmentHealthBonus → healthCapacity_Q bonus.
|
|
21
|
+
// Phase 90 (Unrest): computeContainmentUnrest → unrestPressure_Q.
|
|
22
|
+
// Phase 92 (Taxation): computeContainmentCost_cu → daily treasury drain.
|
|
23
|
+
// Phase 97 (Famine): simultaneous famine+plague compounds; host stacks pressures.
|
|
24
|
+
// Phase 96 (Climate): plague_season epidemicGrowthBonus may prompt policy change.
|
|
25
|
+
import { q, SCALE, clampQ, mulDiv } from "./units.js";
|
|
26
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
27
|
+
/**
|
|
28
|
+
* Base transmission reduction fraction per policy tier [0, SCALE.Q].
|
|
29
|
+
* Applied to `contactIntensity_Q` in Phase-88 spread calculations.
|
|
30
|
+
* Actual effect is scaled down by `(SCALE.Q − complianceDecay_Q) / SCALE.Q`.
|
|
31
|
+
*/
|
|
32
|
+
export const QUARANTINE_TRANSMISSION_REDUCTION_Q = {
|
|
33
|
+
none: 0,
|
|
34
|
+
voluntary: q(0.20),
|
|
35
|
+
enforced: q(0.55),
|
|
36
|
+
total_lockdown: q(0.85),
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Health capacity bonus per policy tier [0, SCALE.Q].
|
|
40
|
+
* Stack with Phase-88 `deriveHealthCapacity` as additive bonus to `healthCapacity_Q`.
|
|
41
|
+
* Reflects improved isolation, triage, and care coordination.
|
|
42
|
+
*/
|
|
43
|
+
export const QUARANTINE_HEALTH_BONUS_Q = {
|
|
44
|
+
none: 0,
|
|
45
|
+
voluntary: q(0.05),
|
|
46
|
+
enforced: q(0.15),
|
|
47
|
+
total_lockdown: q(0.25),
|
|
48
|
+
};
|
|
49
|
+
/** Unrest pressure per policy tier [0, SCALE.Q]. Pass to Phase-90 `computeUnrestLevel`. */
|
|
50
|
+
export const QUARANTINE_UNREST_Q = {
|
|
51
|
+
none: 0,
|
|
52
|
+
voluntary: q(0.02),
|
|
53
|
+
enforced: q(0.12),
|
|
54
|
+
total_lockdown: q(0.28),
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Daily treasury cost per 1,000 population by policy tier [cu].
|
|
58
|
+
* Scale: `computeContainmentCost_cu = cost × population / 1000 × elapsedDays`.
|
|
59
|
+
*/
|
|
60
|
+
export const QUARANTINE_DAILY_COST_PER_1000 = {
|
|
61
|
+
none: 0,
|
|
62
|
+
voluntary: 1, // public messaging, basic clinics
|
|
63
|
+
enforced: 5, // enforcement personnel, field hospitals
|
|
64
|
+
total_lockdown: 15, // military deployment, supply distribution
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Compliance decay accrued per day by policy tier [out of SCALE.Q].
|
|
68
|
+
* Voluntary: minimal decay (people accept guidance).
|
|
69
|
+
* Total lockdown: fast erosion (coercion breeds resistance).
|
|
70
|
+
*/
|
|
71
|
+
export const COMPLIANCE_DECAY_PER_DAY = {
|
|
72
|
+
none: 0,
|
|
73
|
+
voluntary: 2,
|
|
74
|
+
enforced: 8,
|
|
75
|
+
total_lockdown: 18,
|
|
76
|
+
};
|
|
77
|
+
// ── Factory ───────────────────────────────────────────────────────────────────
|
|
78
|
+
/** Create a `ContainmentState` with no active quarantine policy. */
|
|
79
|
+
export function createContainmentState(polityId) {
|
|
80
|
+
return {
|
|
81
|
+
polityId,
|
|
82
|
+
policy: "none",
|
|
83
|
+
daysActive: 0,
|
|
84
|
+
complianceDecay_Q: 0,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// ── Policy management ─────────────────────────────────────────────────────────
|
|
88
|
+
/**
|
|
89
|
+
* Change the active quarantine policy.
|
|
90
|
+
*
|
|
91
|
+
* Resets `daysActive` and `complianceDecay_Q` — a policy change resets the
|
|
92
|
+
* population's compliance posture (initial goodwill or fear of the new measure).
|
|
93
|
+
*/
|
|
94
|
+
export function changeQuarantinePolicy(state, newPolicy) {
|
|
95
|
+
state.policy = newPolicy;
|
|
96
|
+
state.daysActive = 0;
|
|
97
|
+
state.complianceDecay_Q = 0;
|
|
98
|
+
}
|
|
99
|
+
// ── Effectiveness computation ─────────────────────────────────────────────────
|
|
100
|
+
/**
|
|
101
|
+
* Compute the effective transmission reduction fraction [0, SCALE.Q],
|
|
102
|
+
* factoring in accumulated compliance decay.
|
|
103
|
+
*
|
|
104
|
+
* `effective = baseReduction × (SCALE.Q − complianceDecay_Q) / SCALE.Q`
|
|
105
|
+
*/
|
|
106
|
+
export function computeEffectiveTransmissionReduction(state) {
|
|
107
|
+
const base = QUARANTINE_TRANSMISSION_REDUCTION_Q[state.policy];
|
|
108
|
+
const compliance = SCALE.Q - state.complianceDecay_Q;
|
|
109
|
+
return clampQ(mulDiv(base, compliance, SCALE.Q), 0, SCALE.Q);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Compute the health capacity bonus from active quarantine [0, SCALE.Q].
|
|
113
|
+
* Add to the output of Phase-88 `deriveHealthCapacity(polity)`.
|
|
114
|
+
* The bonus also decays with compliance.
|
|
115
|
+
*/
|
|
116
|
+
export function computeContainmentHealthBonus(state) {
|
|
117
|
+
const base = QUARANTINE_HEALTH_BONUS_Q[state.policy];
|
|
118
|
+
const compliance = SCALE.Q - state.complianceDecay_Q;
|
|
119
|
+
return clampQ(mulDiv(base, compliance, SCALE.Q), 0, SCALE.Q);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Compute the unrest pressure from the current quarantine policy [0, SCALE.Q].
|
|
123
|
+
*
|
|
124
|
+
* Unrest grows as compliance erodes — a partially-enforced lockdown is more
|
|
125
|
+
* resented than a fresh voluntary advisory.
|
|
126
|
+
* `unrest = baseUnrest + decayFraction × baseUnrest / SCALE.Q`
|
|
127
|
+
*/
|
|
128
|
+
export function computeContainmentUnrest(state) {
|
|
129
|
+
const base = QUARANTINE_UNREST_Q[state.policy];
|
|
130
|
+
if (base === 0)
|
|
131
|
+
return 0;
|
|
132
|
+
const decayBonus = mulDiv(base, state.complianceDecay_Q, SCALE.Q);
|
|
133
|
+
return clampQ(base + decayBonus, 0, SCALE.Q);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Compute the daily treasury cost of the active quarantine policy.
|
|
137
|
+
*
|
|
138
|
+
* `cost = DAILY_COST_PER_1000 × population / 1000 × elapsedDays`
|
|
139
|
+
*/
|
|
140
|
+
export function computeContainmentCost_cu(polity, state, elapsedDays) {
|
|
141
|
+
const costPer1k = QUARANTINE_DAILY_COST_PER_1000[state.policy];
|
|
142
|
+
return Math.round(costPer1k * polity.population / 1000 * elapsedDays);
|
|
143
|
+
}
|
|
144
|
+
// ── State step ────────────────────────────────────────────────────────────────
|
|
145
|
+
/**
|
|
146
|
+
* Advance containment state by `elapsedDays`.
|
|
147
|
+
*
|
|
148
|
+
* - Increments `daysActive`.
|
|
149
|
+
* - Accrues `complianceDecay_Q` at `COMPLIANCE_DECAY_PER_DAY[policy]`; clamped to SCALE.Q.
|
|
150
|
+
* Policy "none" does not decay (nothing to comply with).
|
|
151
|
+
*/
|
|
152
|
+
export function stepContainment(state, elapsedDays) {
|
|
153
|
+
state.daysActive += elapsedDays;
|
|
154
|
+
const decay = COMPLIANCE_DECAY_PER_DAY[state.policy] * elapsedDays;
|
|
155
|
+
state.complianceDecay_Q = clampQ(state.complianceDecay_Q + decay, 0, SCALE.Q);
|
|
156
|
+
}
|
|
157
|
+
// ── Integration helpers ───────────────────────────────────────────────────────
|
|
158
|
+
/**
|
|
159
|
+
* Scale down a Phase-88 `contactIntensity_Q` by the effective quarantine reduction.
|
|
160
|
+
*
|
|
161
|
+
* Pass the returned value to `spreadEpidemic` or `computeSpreadToPolity`:
|
|
162
|
+
* ```ts
|
|
163
|
+
* const adjContact = applyQuarantineToContact(tradeIntensity_Q, containmentState);
|
|
164
|
+
* computeSpreadToPolity(source, profile, adjContact);
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
export function applyQuarantineToContact(contactIntensity_Q, state) {
|
|
168
|
+
const reduction = computeEffectiveTransmissionReduction(state);
|
|
169
|
+
const reduced = mulDiv(contactIntensity_Q, reduction, SCALE.Q);
|
|
170
|
+
return clampQ(contactIntensity_Q - reduced, 0, SCALE.Q);
|
|
171
|
+
}
|
|
172
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
173
|
+
/** Return `true` when any active containment policy is in effect. */
|
|
174
|
+
export function isQuarantineActive(state) {
|
|
175
|
+
return state.policy !== "none";
|
|
176
|
+
}
|
|
177
|
+
/** Return `true` when the current policy is the most restrictive tier. */
|
|
178
|
+
export function isTotalLockdown(state) {
|
|
179
|
+
return state.policy === "total_lockdown";
|
|
180
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { Q } from "./units.js";
|
|
2
|
+
import type { Polity } from "./polity.js";
|
|
3
|
+
/**
|
|
4
|
+
* Immutable descriptor for a mercenary band.
|
|
5
|
+
* Create via `createMercenaryBand`; share across multiple contracts if needed.
|
|
6
|
+
*/
|
|
7
|
+
export interface MercenaryBand {
|
|
8
|
+
bandId: string;
|
|
9
|
+
name: string;
|
|
10
|
+
/** Number of soldiers in the band. */
|
|
11
|
+
size: number;
|
|
12
|
+
/** Combat effectiveness [0, SCALE.Q]. q(0.50) = average militia; q(0.90) = elite. */
|
|
13
|
+
quality_Q: Q;
|
|
14
|
+
/** Base daily wage per soldier in cost-units. */
|
|
15
|
+
dailyWagePerSoldier_cu: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Live contract state for one hired band.
|
|
19
|
+
* Store externally (e.g. `Map<string, MercenaryContract>`); pass to step each tick.
|
|
20
|
+
*/
|
|
21
|
+
export interface MercenaryContract {
|
|
22
|
+
contractId: string;
|
|
23
|
+
polityId: string;
|
|
24
|
+
bandId: string;
|
|
25
|
+
/** Days the contract has been active. */
|
|
26
|
+
daysActive: number;
|
|
27
|
+
/**
|
|
28
|
+
* Current loyalty of the band to this polity [0, SCALE.Q].
|
|
29
|
+
* Grows when paid; decays when in arrears; below DESERT_THRESHOLD → desertion risk.
|
|
30
|
+
*/
|
|
31
|
+
loyalty_Q: Q;
|
|
32
|
+
/**
|
|
33
|
+
* Unpaid wages accumulated [cu].
|
|
34
|
+
* Grows when treasury is insufficient; cleared when back-paid.
|
|
35
|
+
*/
|
|
36
|
+
arrears_cu: number;
|
|
37
|
+
}
|
|
38
|
+
/** Outcome of a single `stepMercenaryContract` call. */
|
|
39
|
+
export interface MercenaryStepResult {
|
|
40
|
+
/** Amount actually paid from treasury this step. */
|
|
41
|
+
wagePaid_cu: number;
|
|
42
|
+
/** Arrears added this step (0 if fully paid). */
|
|
43
|
+
arrearsAdded_cu: number;
|
|
44
|
+
/** Loyalty change this step (negative = decay, positive = growth). */
|
|
45
|
+
loyaltyDelta: number;
|
|
46
|
+
/** Whether the band deserted this step. */
|
|
47
|
+
deserted: boolean;
|
|
48
|
+
}
|
|
49
|
+
/** Loyalty below this → desertion roll fires. */
|
|
50
|
+
export declare const DESERT_LOYALTY_THRESHOLD_Q: Q;
|
|
51
|
+
/** Loyalty decay per day when wages are in arrears [out of SCALE.Q]. */
|
|
52
|
+
export declare const LOYALTY_DECAY_PER_DAY_UNPAID: number;
|
|
53
|
+
/** Loyalty growth per day when wages are paid in full [out of SCALE.Q]. */
|
|
54
|
+
export declare const LOYALTY_GROWTH_PER_DAY_PAID: number;
|
|
55
|
+
/**
|
|
56
|
+
* Loyalty bonus on campaign victory — reward for shared triumph.
|
|
57
|
+
* Caller applies via `applyVictoryLoyaltyBonus`.
|
|
58
|
+
*/
|
|
59
|
+
export declare const LOYALTY_VICTORY_BONUS_Q: Q;
|
|
60
|
+
/**
|
|
61
|
+
* Maximum military strength contribution from any single mercenary contract [Q].
|
|
62
|
+
* Prevents a single large band from trivially dominating a polity's army.
|
|
63
|
+
*/
|
|
64
|
+
export declare const MAX_MERC_STRENGTH_BONUS_Q: Q;
|
|
65
|
+
/**
|
|
66
|
+
* Daily desertion probability roll threshold when loyalty is at zero [out of SCALE.Q].
|
|
67
|
+
* At loyalty = DESERT_THRESHOLD: ~25% chance/day; scales linearly to 0 at threshold.
|
|
68
|
+
*/
|
|
69
|
+
export declare const DESERT_ROLL_MAX: number;
|
|
70
|
+
/** Create a `MercenaryBand` descriptor. */
|
|
71
|
+
export declare function createMercenaryBand(bandId: string, name: string, size: number, quality_Q: Q, dailyWagePerSoldier_cu: number): MercenaryBand;
|
|
72
|
+
/**
|
|
73
|
+
* Hire a mercenary band, creating a contract with initial loyalty.
|
|
74
|
+
*
|
|
75
|
+
* Does NOT deduct an advance payment — caller may pay via `computeMercenaryWage`
|
|
76
|
+
* before the first step if an upfront retainer is desired.
|
|
77
|
+
*
|
|
78
|
+
* @param initialLoyalty_Q Starting loyalty. Defaults to q(0.70) (neutral-positive hire).
|
|
79
|
+
*/
|
|
80
|
+
export declare function hireMercenaries(contractId: string, polityId: string, band: MercenaryBand, initialLoyalty_Q?: Q): MercenaryContract;
|
|
81
|
+
/**
|
|
82
|
+
* Compute total wages due for `elapsedDays` days.
|
|
83
|
+
*
|
|
84
|
+
* `wage = band.size × band.dailyWagePerSoldier_cu × elapsedDays`
|
|
85
|
+
*/
|
|
86
|
+
export declare function computeMercenaryWage(band: MercenaryBand, elapsedDays: number): number;
|
|
87
|
+
/**
|
|
88
|
+
* Compute the military strength contribution of a hired band [0, SCALE.Q].
|
|
89
|
+
*
|
|
90
|
+
* Formula: `round(size × quality_Q × loyalty_Q / SCALE.Q²)`, clamped to
|
|
91
|
+
* `MAX_MERC_STRENGTH_BONUS_Q`.
|
|
92
|
+
*
|
|
93
|
+
* Add the result to Phase-93 `computeBattleStrength` output.
|
|
94
|
+
* At full quality and full loyalty: ~q(0.05) per 500 soldiers; caps at q(0.30).
|
|
95
|
+
*/
|
|
96
|
+
export declare function computeMercenaryStrengthContribution(band: MercenaryBand, contract: MercenaryContract): Q;
|
|
97
|
+
/**
|
|
98
|
+
* Apply a loyalty bonus after a campaign victory.
|
|
99
|
+
* Clamps result to SCALE.Q.
|
|
100
|
+
*/
|
|
101
|
+
export declare function applyVictoryLoyaltyBonus(contract: MercenaryContract): void;
|
|
102
|
+
/**
|
|
103
|
+
* Advance a mercenary contract by `elapsedDays`.
|
|
104
|
+
*
|
|
105
|
+
* Each step:
|
|
106
|
+
* 1. Compute wages due = `computeMercenaryWage(band, elapsedDays)`.
|
|
107
|
+
* 2. Pay as much as `polity.treasury_cu` allows; add remainder to `arrears_cu`.
|
|
108
|
+
* 3. If fully paid: grow loyalty, clear any arrears previously owed.
|
|
109
|
+
* If in arrears: decay loyalty by `LOYALTY_DECAY_PER_DAY_UNPAID × elapsedDays`.
|
|
110
|
+
* 4. If `loyalty_Q < DESERT_LOYALTY_THRESHOLD_Q`: roll for desertion via `eventSeed`.
|
|
111
|
+
* Desertion probability scales linearly from `DESERT_ROLL_MAX` at loyalty 0
|
|
112
|
+
* to 0 at `DESERT_LOYALTY_THRESHOLD_Q`.
|
|
113
|
+
* 5. If deserted: set `loyalty_Q = 0` (signal to caller to remove contract).
|
|
114
|
+
*
|
|
115
|
+
* Mutates `polity.treasury_cu`, `contract.loyalty_Q`, `contract.arrears_cu`,
|
|
116
|
+
* and `contract.daysActive`.
|
|
117
|
+
*
|
|
118
|
+
* @param worldSeed World-level seed for deterministic desertion roll.
|
|
119
|
+
* @param tick Current simulation tick (day).
|
|
120
|
+
*/
|
|
121
|
+
export declare function stepMercenaryContract(contract: MercenaryContract, band: MercenaryBand, polity: Polity, elapsedDays: number, worldSeed: number, tick: number): MercenaryStepResult;
|
|
122
|
+
/** Return `true` when the band is loyal enough to remain in service reliably. */
|
|
123
|
+
export declare function isMercenaryReliable(contract: MercenaryContract): boolean;
|
|
124
|
+
/** Return `true` when the contract has active arrears. */
|
|
125
|
+
export declare function hasMercenaryArrears(contract: MercenaryContract): boolean;
|
|
126
|
+
/** A typical 400-man light cavalry band — mobile, moderate quality. */
|
|
127
|
+
export declare const BAND_LIGHT_CAVALRY: MercenaryBand;
|
|
128
|
+
/** A 600-man heavy infantry cohort — expensive, high quality. */
|
|
129
|
+
export declare const BAND_HEAVY_INFANTRY: MercenaryBand;
|
|
130
|
+
/** A 200-man specialist siege engineers unit. */
|
|
131
|
+
export declare const BAND_SIEGE_ENGINEERS: MercenaryBand;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// src/mercenaries.ts — Phase 99: Mercenaries & Hired Forces
|
|
2
|
+
//
|
|
3
|
+
// Professional soldiers hired from the treasury. Mercenaries augment polity
|
|
4
|
+
// armies with high-quality fighters but demand regular wages; unpaid bands
|
|
5
|
+
// lose loyalty and eventually desert.
|
|
6
|
+
//
|
|
7
|
+
// Design:
|
|
8
|
+
// - Pure data layer — no Entity fields, no kernel changes.
|
|
9
|
+
// - `MercenaryBand` is an immutable descriptor (could represent an NPC faction
|
|
10
|
+
// or a persistent world actor).
|
|
11
|
+
// - `MercenaryContract` is the mutable live state; host stores one per hired band.
|
|
12
|
+
// - Loyalty drives both effectiveness and desertion risk.
|
|
13
|
+
// - `stepMercenaryContract` pays wages, accrues arrears, decays/grows loyalty,
|
|
14
|
+
// and rolls desertion via `eventSeed` for full determinism.
|
|
15
|
+
// - `computeMercenaryStrengthContribution` returns an advisory [0, SCALE.Q]
|
|
16
|
+
// bonus stacked on top of Phase-93 `computeBattleStrength`.
|
|
17
|
+
//
|
|
18
|
+
// Integration:
|
|
19
|
+
// Phase 92 (Taxation): daily wage drains `polity.treasury_cu`.
|
|
20
|
+
// Phase 93 (Military Campaign): strength contribution stacks with battle strength.
|
|
21
|
+
// Phase 90 (Unrest): deserted band may leave unrest pressure (caller-driven).
|
|
22
|
+
// Phase 61 (Polity): polity.treasury_cu mutated by wage payment.
|
|
23
|
+
import { eventSeed } from "./sim/seeds.js";
|
|
24
|
+
import { q, SCALE, clampQ, mulDiv } from "./units.js";
|
|
25
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
26
|
+
/** Loyalty below this → desertion roll fires. */
|
|
27
|
+
export const DESERT_LOYALTY_THRESHOLD_Q = q(0.25);
|
|
28
|
+
/** Loyalty decay per day when wages are in arrears [out of SCALE.Q]. */
|
|
29
|
+
export const LOYALTY_DECAY_PER_DAY_UNPAID = 80; // q(0.008)/day
|
|
30
|
+
/** Loyalty growth per day when wages are paid in full [out of SCALE.Q]. */
|
|
31
|
+
export const LOYALTY_GROWTH_PER_DAY_PAID = 20; // q(0.002)/day
|
|
32
|
+
/**
|
|
33
|
+
* Loyalty bonus on campaign victory — reward for shared triumph.
|
|
34
|
+
* Caller applies via `applyVictoryLoyaltyBonus`.
|
|
35
|
+
*/
|
|
36
|
+
export const LOYALTY_VICTORY_BONUS_Q = q(0.10);
|
|
37
|
+
/**
|
|
38
|
+
* Maximum military strength contribution from any single mercenary contract [Q].
|
|
39
|
+
* Prevents a single large band from trivially dominating a polity's army.
|
|
40
|
+
*/
|
|
41
|
+
export const MAX_MERC_STRENGTH_BONUS_Q = q(0.30);
|
|
42
|
+
/**
|
|
43
|
+
* Daily desertion probability roll threshold when loyalty is at zero [out of SCALE.Q].
|
|
44
|
+
* At loyalty = DESERT_THRESHOLD: ~25% chance/day; scales linearly to 0 at threshold.
|
|
45
|
+
*/
|
|
46
|
+
export const DESERT_ROLL_MAX = 2500; // 2500/10000 = 25% when loyalty = 0
|
|
47
|
+
// ── Factory ───────────────────────────────────────────────────────────────────
|
|
48
|
+
/** Create a `MercenaryBand` descriptor. */
|
|
49
|
+
export function createMercenaryBand(bandId, name, size, quality_Q, dailyWagePerSoldier_cu) {
|
|
50
|
+
return {
|
|
51
|
+
bandId,
|
|
52
|
+
name,
|
|
53
|
+
size: Math.max(1, size),
|
|
54
|
+
quality_Q: clampQ(quality_Q, 0, SCALE.Q),
|
|
55
|
+
dailyWagePerSoldier_cu: Math.max(0, dailyWagePerSoldier_cu),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Hire a mercenary band, creating a contract with initial loyalty.
|
|
60
|
+
*
|
|
61
|
+
* Does NOT deduct an advance payment — caller may pay via `computeMercenaryWage`
|
|
62
|
+
* before the first step if an upfront retainer is desired.
|
|
63
|
+
*
|
|
64
|
+
* @param initialLoyalty_Q Starting loyalty. Defaults to q(0.70) (neutral-positive hire).
|
|
65
|
+
*/
|
|
66
|
+
export function hireMercenaries(contractId, polityId, band, initialLoyalty_Q = q(0.70)) {
|
|
67
|
+
return {
|
|
68
|
+
contractId,
|
|
69
|
+
polityId,
|
|
70
|
+
bandId: band.bandId,
|
|
71
|
+
daysActive: 0,
|
|
72
|
+
loyalty_Q: clampQ(initialLoyalty_Q, 0, SCALE.Q),
|
|
73
|
+
arrears_cu: 0,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// ── Cost computation ──────────────────────────────────────────────────────────
|
|
77
|
+
/**
|
|
78
|
+
* Compute total wages due for `elapsedDays` days.
|
|
79
|
+
*
|
|
80
|
+
* `wage = band.size × band.dailyWagePerSoldier_cu × elapsedDays`
|
|
81
|
+
*/
|
|
82
|
+
export function computeMercenaryWage(band, elapsedDays) {
|
|
83
|
+
return band.size * band.dailyWagePerSoldier_cu * elapsedDays;
|
|
84
|
+
}
|
|
85
|
+
// ── Strength contribution ─────────────────────────────────────────────────────
|
|
86
|
+
/**
|
|
87
|
+
* Compute the military strength contribution of a hired band [0, SCALE.Q].
|
|
88
|
+
*
|
|
89
|
+
* Formula: `round(size × quality_Q × loyalty_Q / SCALE.Q²)`, clamped to
|
|
90
|
+
* `MAX_MERC_STRENGTH_BONUS_Q`.
|
|
91
|
+
*
|
|
92
|
+
* Add the result to Phase-93 `computeBattleStrength` output.
|
|
93
|
+
* At full quality and full loyalty: ~q(0.05) per 500 soldiers; caps at q(0.30).
|
|
94
|
+
*/
|
|
95
|
+
export function computeMercenaryStrengthContribution(band, contract) {
|
|
96
|
+
const step1 = mulDiv(band.size, band.quality_Q, SCALE.Q); // size × quality / SCALE
|
|
97
|
+
const step2 = mulDiv(step1, contract.loyalty_Q, SCALE.Q); // × loyalty / SCALE
|
|
98
|
+
return clampQ(step2, 0, MAX_MERC_STRENGTH_BONUS_Q);
|
|
99
|
+
}
|
|
100
|
+
// ── Loyalty management ────────────────────────────────────────────────────────
|
|
101
|
+
/**
|
|
102
|
+
* Apply a loyalty bonus after a campaign victory.
|
|
103
|
+
* Clamps result to SCALE.Q.
|
|
104
|
+
*/
|
|
105
|
+
export function applyVictoryLoyaltyBonus(contract) {
|
|
106
|
+
contract.loyalty_Q = clampQ(contract.loyalty_Q + LOYALTY_VICTORY_BONUS_Q, 0, SCALE.Q);
|
|
107
|
+
}
|
|
108
|
+
// ── Contract step ─────────────────────────────────────────────────────────────
|
|
109
|
+
/**
|
|
110
|
+
* Advance a mercenary contract by `elapsedDays`.
|
|
111
|
+
*
|
|
112
|
+
* Each step:
|
|
113
|
+
* 1. Compute wages due = `computeMercenaryWage(band, elapsedDays)`.
|
|
114
|
+
* 2. Pay as much as `polity.treasury_cu` allows; add remainder to `arrears_cu`.
|
|
115
|
+
* 3. If fully paid: grow loyalty, clear any arrears previously owed.
|
|
116
|
+
* If in arrears: decay loyalty by `LOYALTY_DECAY_PER_DAY_UNPAID × elapsedDays`.
|
|
117
|
+
* 4. If `loyalty_Q < DESERT_LOYALTY_THRESHOLD_Q`: roll for desertion via `eventSeed`.
|
|
118
|
+
* Desertion probability scales linearly from `DESERT_ROLL_MAX` at loyalty 0
|
|
119
|
+
* to 0 at `DESERT_LOYALTY_THRESHOLD_Q`.
|
|
120
|
+
* 5. If deserted: set `loyalty_Q = 0` (signal to caller to remove contract).
|
|
121
|
+
*
|
|
122
|
+
* Mutates `polity.treasury_cu`, `contract.loyalty_Q`, `contract.arrears_cu`,
|
|
123
|
+
* and `contract.daysActive`.
|
|
124
|
+
*
|
|
125
|
+
* @param worldSeed World-level seed for deterministic desertion roll.
|
|
126
|
+
* @param tick Current simulation tick (day).
|
|
127
|
+
*/
|
|
128
|
+
export function stepMercenaryContract(contract, band, polity, elapsedDays, worldSeed, tick) {
|
|
129
|
+
contract.daysActive += elapsedDays;
|
|
130
|
+
// ── 1. Wages due ──────────────────────────────────────────────────────────
|
|
131
|
+
const wageDue = computeMercenaryWage(band, elapsedDays);
|
|
132
|
+
// ── 2. Payment ────────────────────────────────────────────────────────────
|
|
133
|
+
const totalOwed = wageDue + contract.arrears_cu;
|
|
134
|
+
const paid = Math.min(totalOwed, polity.treasury_cu);
|
|
135
|
+
polity.treasury_cu -= paid;
|
|
136
|
+
const remaining = totalOwed - paid;
|
|
137
|
+
const wagePaid_cu = Math.min(wageDue, paid);
|
|
138
|
+
const arrearsAdded = wageDue - wagePaid_cu;
|
|
139
|
+
contract.arrears_cu = remaining;
|
|
140
|
+
// ── 3. Loyalty update ─────────────────────────────────────────────────────
|
|
141
|
+
let loyaltyDelta;
|
|
142
|
+
if (remaining === 0) {
|
|
143
|
+
// Fully paid — loyalty grows
|
|
144
|
+
loyaltyDelta = LOYALTY_GROWTH_PER_DAY_PAID * elapsedDays;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
// In arrears — loyalty decays
|
|
148
|
+
loyaltyDelta = -(LOYALTY_DECAY_PER_DAY_UNPAID * elapsedDays);
|
|
149
|
+
}
|
|
150
|
+
contract.loyalty_Q = clampQ(contract.loyalty_Q + loyaltyDelta, 0, SCALE.Q);
|
|
151
|
+
// ── 4. Desertion check ────────────────────────────────────────────────────
|
|
152
|
+
let deserted = false;
|
|
153
|
+
if (contract.loyalty_Q < DESERT_LOYALTY_THRESHOLD_Q) {
|
|
154
|
+
// Probability scales: 0 at threshold, DESERT_ROLL_MAX at loyalty=0
|
|
155
|
+
const loyaltyFrac = contract.loyalty_Q; // 0 = worst, DESERT_THRESHOLD = boundary
|
|
156
|
+
const deserProbScale = SCALE.Q - Math.round(loyaltyFrac * SCALE.Q / DESERT_LOYALTY_THRESHOLD_Q);
|
|
157
|
+
const deserProb = Math.round(deserProbScale * DESERT_ROLL_MAX / SCALE.Q);
|
|
158
|
+
const seed = eventSeed(worldSeed, tick, 0, 0, contract.contractId.length + band.size);
|
|
159
|
+
const roll = seed % SCALE.Q;
|
|
160
|
+
if (roll < deserProb) {
|
|
161
|
+
deserted = true;
|
|
162
|
+
contract.loyalty_Q = 0;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
wagePaid_cu,
|
|
167
|
+
arrearsAdded_cu: arrearsAdded,
|
|
168
|
+
loyaltyDelta,
|
|
169
|
+
deserted,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
173
|
+
/** Return `true` when the band is loyal enough to remain in service reliably. */
|
|
174
|
+
export function isMercenaryReliable(contract) {
|
|
175
|
+
return contract.loyalty_Q >= DESERT_LOYALTY_THRESHOLD_Q;
|
|
176
|
+
}
|
|
177
|
+
/** Return `true` when the contract has active arrears. */
|
|
178
|
+
export function hasMercenaryArrears(contract) {
|
|
179
|
+
return contract.arrears_cu > 0;
|
|
180
|
+
}
|
|
181
|
+
// ── Sample bands ─────────────────────────────────────────────────────────────
|
|
182
|
+
/** A typical 400-man light cavalry band — mobile, moderate quality. */
|
|
183
|
+
export const BAND_LIGHT_CAVALRY = createMercenaryBand("light_cavalry", "Free Riders", 400, q(0.65), 3);
|
|
184
|
+
/** A 600-man heavy infantry cohort — expensive, high quality. */
|
|
185
|
+
export const BAND_HEAVY_INFANTRY = createMercenaryBand("heavy_infantry", "Iron Shields", 600, q(0.85), 5);
|
|
186
|
+
/** A 200-man specialist siege engineers unit. */
|
|
187
|
+
export const BAND_SIEGE_ENGINEERS = createMercenaryBand("siege_engineers", "The Sappers", 200, q(0.75), 8);
|
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.44",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deterministic lockstep-friendly SI-units RPG/physics core (fixed-point TS)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -154,6 +154,14 @@
|
|
|
154
154
|
"./famine": {
|
|
155
155
|
"import": "./dist/src/famine.js",
|
|
156
156
|
"types": "./dist/src/famine.d.ts"
|
|
157
|
+
},
|
|
158
|
+
"./containment": {
|
|
159
|
+
"import": "./dist/src/containment.js",
|
|
160
|
+
"types": "./dist/src/containment.d.ts"
|
|
161
|
+
},
|
|
162
|
+
"./mercenaries": {
|
|
163
|
+
"import": "./dist/src/mercenaries.js",
|
|
164
|
+
"types": "./dist/src/mercenaries.d.ts"
|
|
157
165
|
}
|
|
158
166
|
},
|
|
159
167
|
"files": [
|