@its-not-rocket-science/ananke 0.1.0 → 0.1.1

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.
@@ -0,0 +1,233 @@
1
+ # Ananke — Performance & Scalability Report
2
+
3
+ Generated by `npm run run:benchmark` (Item #9).
4
+ Re-run this tool after breaking changes to detect regressions.
5
+
6
+ ---
7
+
8
+ ## Reference hardware
9
+
10
+ | Field | Value |
11
+ |-------|-------|
12
+ | CPU | Intel i7-12700 (reference) |
13
+ | Runtime | Node.js 22 LTS |
14
+ | OS | Windows 10 Pro 10.0.19045 |
15
+ | TypeScript | 5.5 compiled to ES2022 modules |
16
+
17
+ ---
18
+
19
+ ## Benchmark results
20
+
21
+ | Scenario | Entities | Ticks | Median tick | p99 tick | Throughput |
22
+ |----------|----------|-------|-------------|----------|------------|
23
+ | Melee skirmish (open ground) | 10 | 5 000 | 0.190 ms | 0.905 ms | **5.3k ticks/s** |
24
+ | Mixed ranged/melee | 100 | 2 000 | 4.680 ms | 9.758 ms | **214 ticks/s** |
25
+ | Formation combat | 500 | 500 | 31.1 ms | 55.4 ms | **32 ticks/s** |
26
+ | Battle with weather + disease | 1 000 | 200 | 64.5 ms | 126 ms | **16 ticks/s** |
27
+
28
+ At the default simulation rate (20 Hz / 50 ms per tick):
29
+
30
+ | Entity count | Tick budget used (median) | Headroom |
31
+ |---|---|---|
32
+ | 10 | 0.4% | 99.6% |
33
+ | 100 | 9.4% | 90.6% |
34
+ | 500 | 62% | 38% |
35
+ | 1 000 | 129% | **over budget** |
36
+
37
+ At 1 000 entities and 20 Hz, median tick cost (64.5 ms) exceeds the 50 ms budget.
38
+ Reduce update rate to 10 Hz or apply the mitigations below.
39
+
40
+ ---
41
+
42
+ ## Tick-budget breakdown at 500 entities
43
+
44
+ | Phase | Median cost | % of tick |
45
+ |-------|-------------|-----------|
46
+ | `buildWorldIndex` + `buildSpatialIndex` + `buildAICommands` | 0.29 ms | 1% |
47
+ | `stepWorld` (kernel) | 30.6 ms | 98% |
48
+ | **Total** | **31.1 ms** | **100%** |
49
+
50
+ **Finding:** kernel physics computation dominates at all entity counts.
51
+ AI command generation is negligible (< 1%) and does not warrant optimisation.
52
+
53
+ ---
54
+
55
+ ## Spatial index comparison at 500 entities
56
+
57
+ | Cell size | Median tick | Throughput |
58
+ |-----------|-------------|------------|
59
+ | 4 m (optimal for melee) | 35.7 ms | 28 ticks/s |
60
+ | 10 km (naïve O(n²)) | 30.0 ms | 33 ticks/s |
61
+ | Ratio | 1.19× **slower with index** | — |
62
+
63
+ **Finding:** For a dense close-formation scenario (all entities within ~100 m),
64
+ the `SpatialIndex` cell-map overhead cancels the pair-count reduction.
65
+ Benefit becomes measurable only in sparse large-area engagements where the
66
+ majority of entity pairs never interact.
67
+
68
+ ---
69
+
70
+ ## Memory footprint
71
+
72
+ Heap delta measured after JIT warmup (figures are approximate due to GC timing):
73
+
74
+ | Scenario | Heap Δ / entity |
75
+ |---|---|
76
+ | 10 entities, melee | ~256 KB |
77
+
78
+ Note: JavaScript heap includes GC overhead and JIT artefacts.
79
+ Per-entity allocation in the kernel itself is dominated by the `Entity` object
80
+ (~15 fields, most numeric) — estimated < 2 KB cold per entity.
81
+
82
+ ---
83
+
84
+ ## Tuning guide
85
+
86
+ ### Use smaller entity counts for real-time play
87
+
88
+ | Use case | Recommended entity cap | Rationale |
89
+ |---|---|---|
90
+ | 1v1 duel | 2–10 | Full resolution, well under budget |
91
+ | Squad skirmish | 10–50 | 20 Hz easily achievable |
92
+ | Battle scene | 50–200 | 20 Hz achievable with mitigations |
93
+ | Army simulation | 200–500 | Reduce to 5–10 Hz |
94
+ | Strategic/campaign | 500–1 000 | Run in downtime mode (1–5 Hz) |
95
+
96
+ ### Mitigations for high entity counts
97
+
98
+ 1. **Reduce tick rate**: At 500 entities, 5 Hz (200 ms tick) gives ample headroom.
99
+ Physics remain accurate — the simulation is tick-rate agnostic.
100
+
101
+ 2. **Skip AI on distant entities**: Entities beyond engagement range can skip
102
+ `buildAICommands` entirely; they take no action but still receive kernel effects.
103
+
104
+ 3. **Stagger AI updates**: Update AI for ¼ of entities per tick (round-robin).
105
+ Full coverage every 4 ticks at 20 Hz = 200 ms AI latency — imperceptible.
106
+
107
+ 4. **SpatialIndex cell size**: For dense close-formation combat (≤ 500 m² area),
108
+ naïve O(n²) pair scanning matches or beats a 4 m spatial grid.
109
+ Use 30–50 m cells for ranged-heavy scenarios (engagement range ≥ 30 m).
110
+
111
+ 5. **Skip `ctx.weather`** when weather is not relevant: saves ~2–5% per tick.
112
+
113
+ 6. **Keep `spreadDisease` out of combat ticks**: `spreadDisease` is O(n²) in pairs.
114
+ Call it once per in-game minute in downtime/campaign simulations, not each tick.
115
+
116
+ ---
117
+
118
+ ## Regenerating this report
119
+
120
+ ```
121
+ npm run build && npm run run:benchmark
122
+ ```
123
+
124
+ Results will vary by hardware. The reference numbers above were measured on
125
+ an Intel i7-12700 running Node 22 LTS under Windows 10.
126
+
127
+ ---
128
+
129
+ ## Operational Guide
130
+
131
+ > Run `npm run benchmark:guide` to regenerate this table from a live measurement on your
132
+ > hardware. The numbers below are from the reference configuration (Intel i7-12700,
133
+ > Node 22 LTS, Windows 10).
134
+
135
+ ### Quick-reference: entity cap by tick rate
136
+
137
+ This table answers "how many entities can I run at tick rate X?" without running benchmarks.
138
+
139
+ | Entities | 20 Hz (50 ms) | 10 Hz (100 ms) | 5 Hz (200 ms) | 1 Hz (1 000 ms) |
140
+ |----------|:-------------:|:--------------:|:-------------:|:---------------:|
141
+ | 10 | ✅ (0.4%) | ✅ | ✅ | ✅ |
142
+ | 50 | ✅ (~5%) | ✅ | ✅ | ✅ |
143
+ | 100 | ✅ (9%) | ✅ | ✅ | ✅ |
144
+ | 200 | ✅ (~25%) | ✅ | ✅ | ✅ |
145
+ | 300 | ✅ (~40%) | ✅ | ✅ | ✅ |
146
+ | 500 | ⚠️ (62%) | ✅ | ✅ | ✅ |
147
+ | 750 | ⚠️ (~95%) | ✅ | ✅ | ✅ |
148
+ | 1 000 | ❌ (129%) | ⚠️ (65%) | ✅ | ✅ |
149
+ | 2 000 | ❌ | ❌ | ⚠️ (~65%) | ✅ |
150
+ | 5 000 | ❌ | ❌ | ❌ | ⚠️ (~65%) |
151
+
152
+ **Legend:** ✅ = comfortably within budget ⚠️ = p99 may spike above budget under load ❌ = over budget
153
+
154
+ ### Recommended tick rate by scenario class
155
+
156
+ | Scenario class | Typical entity count | Recommended tick rate | Notes |
157
+ |----------------|---------------------|-----------------------|-------|
158
+ | 1v1 duel / tactical combat | 2–20 | **20 Hz** | Full physics resolution; headroom > 90% |
159
+ | Squad skirmish | 20–100 | **20 Hz** | Safe on any modern CPU |
160
+ | Battle scene (real-time) | 100–500 | **20 Hz** (or 10 Hz if p99 spikes) | Monitor p99; enable AI staggering |
161
+ | Large battle / RTS simulation | 500–1 000 | **10 Hz** | Reduces tick budget to ~65% at 1k entities |
162
+ | Campaign / world-simulation | 1 000–5 000 | **1 Hz** | One tick per in-game second; plenty of headroom |
163
+ | Downtime / recovery simulation | any | **0.01–0.1 Hz** | Sleeping, disease, aging — not real-time |
164
+
165
+ ### Supported real-time envelope
166
+
167
+ What Ananke guarantees on the reference hardware at 20 Hz (50 ms tick budget):
168
+
169
+ | Guarantee | Value |
170
+ |-----------|-------|
171
+ | **Maximum entity count within median budget** | 500 entities |
172
+ | **Maximum entity count within p99 budget** | ~350 entities |
173
+ | **Median tick at 500 entities** | 31 ms (62% of budget) |
174
+ | **p99 tick at 500 entities** | 55 ms (110% — occasional overrun) |
175
+ | **Median tick at 100 entities** | 4.7 ms (9% of budget) |
176
+ | **Minimum entity count for budget to matter** | > 200 entities |
177
+
178
+ For reliable real-time at 20 Hz, stay under **300 entities** to keep p99 inside the 50 ms budget.
179
+ At 500 entities, median is fine but occasional p99 spikes exceed the budget by ~10%.
180
+
181
+ ### Subsystem feature-toggle guidance
182
+
183
+ Each optional subsystem adds per-tick cost when entities carry the corresponding
184
+ `@subsystem` field. Costs below are rough estimates at 500 entities.
185
+
186
+ | Subsystem | `Entity` field | Tick cost (500 entities) | Toggle guidance |
187
+ |-----------|----------------|--------------------------|-----------------|
188
+ | AI command generation | `ai?` | < 1% (negligible) | Always safe; stagger updates to save CPU |
189
+ | Ablative armour | `armourState?` | < 1% | Negligible; enable freely |
190
+ | Weather modifiers | `ctx.weather` | ~2–5% | Omit when weather is irrelevant |
191
+ | Disease spread | `activeDiseases?` / `spreadDisease()` | O(n²) in pairs | **Call once per in-game minute**, not each tick |
192
+ | Thermoregulation | `physiology?` | < 2% | Negligible at combat scale |
193
+ | Sleep deprivation | `sleep?` | < 1% | Downtime only; safe in combat ticks |
194
+ | Aging | `age?` | < 1% | Downtime only; negligible in combat ticks |
195
+ | Mounted combat | `mount?` | < 1% | Negligible |
196
+ | Extended senses | `extendedSenses?` | < 1% | Negligible |
197
+ | Capability / magic | `capabilitySources?` | 1–3% depending on active effects | Profile if many entities have active auras |
198
+
199
+ **Rule of thumb:** subsystems add < 1–3% each at combat scale. The dominant cost at all entity
200
+ counts is the kernel physics computation (`stepWorld`), not subsystems. Removing subsystems
201
+ rarely buys more than 5–10% at high entity counts.
202
+
203
+ ### Spatial-index guidance
204
+
205
+ | Scenario | Recommended setting | Rationale |
206
+ |----------|---------------------|-----------|
207
+ | Dense close-formation (≤ 500 m² area, melee-only) | **No spatial index** (cell size = arena) | Cell-map overhead cancels pair-count reduction |
208
+ | Mixed melee + ranged (engagement range ≥ 10 m) | **4–10 m cell size** | Filters most non-interacting pairs |
209
+ | Sparse open-field (engagement range ≥ 30 m) | **30–50 m cell size** | Large benefit; most pairs never interact |
210
+ | Large-scale simulation (> 500 entities, spread over km) | **50–200 m cell size** | Essential; naïve O(n²) is prohibitive |
211
+
212
+ For dense formations (all entities within ~100 m), the overhead of building and querying the
213
+ spatial index exceeds its benefit. At 500 entities in a 100 m² arena, naïve scanning is
214
+ 1.19× faster than a 4 m cell grid (see benchmark data above).
215
+
216
+ ### Choosing a tick rate
217
+
218
+ Use this decision tree:
219
+
220
+ 1. Is the simulation **interactive / real-time**?
221
+ - **Yes** → start at 20 Hz; profile at your target entity count; step down to 10 Hz if p99 > 50 ms.
222
+ - **No (campaign / downtime)** → use 1 Hz or lower; entity count is rarely the bottleneck.
223
+
224
+ 2. Do you have **> 300 entities**?
225
+ - **No** → 20 Hz is safe; no further tuning needed.
226
+ - **Yes** → enable AI staggering (update ¼ of entities per tick); consider 10 Hz if still over budget.
227
+
228
+ 3. Are entities **spread across > 1 km**?
229
+ - **No** → omit spatial index or use large cells.
230
+ - **Yes** → use a 50–200 m spatial grid.
231
+
232
+ 4. Do you use `spreadDisease`?
233
+ - **Yes** → call it once per in-game minute, not each tick (it is O(n²)).