hoi4-agent-tools 4.5.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/hoi4_agent_tools/map/border-quality.js +25 -7
  3. package/dist/hoi4_agent_tools/map/derived.js +16 -3
  4. package/dist/hoi4_agent_tools/map/gameplay/crossing-validation.js +268 -0
  5. package/dist/hoi4_agent_tools/map/gameplay/crossings.js +961 -0
  6. package/dist/hoi4_agent_tools/map/gameplay/development.js +168 -0
  7. package/dist/hoi4_agent_tools/map/gameplay/economy.js +75 -0
  8. package/dist/hoi4_agent_tools/map/gameplay/facts.js +264 -0
  9. package/dist/hoi4_agent_tools/map/gameplay/inland-sea.js +66 -0
  10. package/dist/hoi4_agent_tools/map/gameplay/metrics.js +132 -0
  11. package/dist/hoi4_agent_tools/map/gameplay/names.js +419 -0
  12. package/dist/hoi4_agent_tools/map/gameplay/ports.js +244 -0
  13. package/dist/hoi4_agent_tools/map/gameplay/prepare.js +27 -0
  14. package/dist/hoi4_agent_tools/map/gameplay/rail-cost.js +77 -0
  15. package/dist/hoi4_agent_tools/map/gameplay/sites.js +61 -0
  16. package/dist/hoi4_agent_tools/map/geography/resolve.js +15 -4
  17. package/dist/hoi4_agent_tools/map/operations.js +109 -32
  18. package/dist/hoi4_agent_tools/map/relief/hydrology.js +316 -192
  19. package/dist/hoi4_agent_tools/map/relief/terrain-synthesis.js +43 -0
  20. package/dist/hoi4_agent_tools/map/service.js +78 -16
  21. package/dist/hoi4_agent_tools/map/state-restructure.js +378 -0
  22. package/dist/hoi4_agent_tools/map/territory-graph.js +238 -0
  23. package/dist/hoi4_agent_tools/map/territory-operations.js +944 -0
  24. package/dist/hoi4_agent_tools/map/territory-quality.js +189 -0
  25. package/dist/hoi4_agent_tools/map/territory.js +1108 -0
  26. package/dist/hoi4_agent_tools/map/tool-operations.js +78 -0
  27. package/dist/hoi4_agent_tools/map/validation.js +4 -0
  28. package/dist/hoi4_agent_tools/map/world.js +301 -107
  29. package/dist/hoi4_agent_tools/schemas/geography.js +12 -1
  30. package/dist/hoi4_agent_tools/schemas/map.js +206 -21
  31. package/dist/hoi4_agent_tools/version.js +1 -1
  32. package/docs/map.md +98 -4
  33. package/npm-shrinkwrap.json +2 -2
  34. package/package.json +1 -1
  35. package/schemas/configuration.schema.json +1 -1
  36. package/schemas/continuous-focus-palette.schema.json +1 -1
  37. package/schemas/custom-weighted-pool.schema.json +1 -1
  38. package/schemas/decision-inspect-input.schema.json +1 -1
  39. package/schemas/error-log-input.schema.json +1 -1
  40. package/schemas/event-feature-manifest.schema.json +1 -1
  41. package/schemas/focus-plan.schema.json +1 -1
  42. package/schemas/focus-planning-sidecar.schema.json +1 -1
  43. package/schemas/geography-model.schema.json +13 -1
  44. package/schemas/gui-animation-source.schema.json +1 -1
  45. package/schemas/gui-helper.schema.json +1 -1
  46. package/schemas/gui-scenario.schema.json +1 -1
  47. package/schemas/helper-expansion-request.schema.json +1 -1
  48. package/schemas/helper-expansion-summary.schema.json +1 -1
  49. package/schemas/impact-inspect-input.schema.json +1 -1
  50. package/schemas/map-operation.schema.json +1355 -736
  51. package/schemas/mechanic-test-input.schema.json +1 -1
  52. package/schemas/mod-index-input.schema.json +1 -1
  53. package/schemas/operation-result.schema.json +1 -1
  54. package/schemas/package-check-input.schema.json +1 -1
  55. package/schemas/package-manifest.schema.json +1 -1
  56. package/schemas/probability-analysis-result.schema.json +1 -1
  57. package/schemas/probability-compare-input.schema.json +1 -1
  58. package/schemas/probability-evaluate-input.schema.json +1 -1
  59. package/schemas/probability-inspect-input.schema.json +1 -1
  60. package/schemas/probability-render-input.schema.json +1 -1
  61. package/schemas/probability-scenario-set.schema.json +1 -1
  62. package/schemas/probability-sequence-input.schema.json +1 -1
  63. package/schemas/probability-simulate-input.schema.json +1 -1
  64. package/schemas/probability-sweep-input.schema.json +1 -1
  65. package/schemas/reference-context-input.schema.json +1 -1
  66. package/schemas/reference-read-input.schema.json +1 -1
  67. package/schemas/reference-search-input.schema.json +1 -1
  68. package/schemas/reference-sheets.schema.json +1 -1
  69. package/schemas/scenario-suite.schema.json +1 -1
  70. package/schemas/scenario-test-input.schema.json +1 -1
  71. package/schemas/script-validate-input.schema.json +1 -1
  72. package/schemas/shared-scenario.schema.json +1 -1
  73. package/schemas/source-lookup-input.schema.json +1 -1
  74. package/server.json +2 -2
@@ -0,0 +1,238 @@
1
+ import { ServiceError } from '../core/result.js';
2
+ import { BORDER_BARRIER_MASK, BORDER_FEATURE_BITS, BORDER_PROVINCE_KIND, } from './border-quality.js';
3
+ import { buildProvinceGraph } from './partition-graph.js';
4
+ /**
5
+ * The graph territory assignment grows countries over (ADR 0060). Provinces come from a border
6
+ * edge table of the border metric (ADR 0059), so the natural lines are exactly the ones the metric rewards:
7
+ * every shared border carries the strength of the features it lies on and the number of its pixel
8
+ * edges that count as natural. States (or any other grouping of provinces) are aggregated into
9
+ * atoms with the same two numbers, and the algorithm in `territory.ts` runs on atoms only.
10
+ */
11
+ /** Weight of a natural feature when it forms part of a border, 0 (none) to 1 (impassable line). */
12
+ export const TERRITORY_FEATURE_STRENGTH = {
13
+ river: 1,
14
+ extra: 1,
15
+ slope: 0.8,
16
+ terrainChange: 0.45,
17
+ lakeShore: 0.35,
18
+ };
19
+ /** Credit of a border edge towards natural border length: barrier features full, terrain half. */
20
+ const BARRIER_CREDIT = 1;
21
+ const TERRAIN_CREDIT = 0.5;
22
+ /** Strength of one border edge from its feature bits (the strongest feature wins). */
23
+ export function borderEdgeStrength(bits) {
24
+ let strength = 0;
25
+ if ((bits & BORDER_FEATURE_BITS.river) !== 0)
26
+ strength = Math.max(strength, TERRITORY_FEATURE_STRENGTH.river);
27
+ if ((bits & BORDER_FEATURE_BITS.extra) !== 0)
28
+ strength = Math.max(strength, TERRITORY_FEATURE_STRENGTH.extra);
29
+ if ((bits & BORDER_FEATURE_BITS.slope) !== 0)
30
+ strength = Math.max(strength, TERRITORY_FEATURE_STRENGTH.slope);
31
+ if ((bits & BORDER_FEATURE_BITS.terrainChange) !== 0)
32
+ strength = Math.max(strength, TERRITORY_FEATURE_STRENGTH.terrainChange);
33
+ if ((bits & BORDER_FEATURE_BITS.lakeShore) !== 0)
34
+ strength = Math.max(strength, TERRITORY_FEATURE_STRENGTH.lakeShore);
35
+ return strength;
36
+ }
37
+ export function borderEdgeCredit(bits) {
38
+ if ((bits & BORDER_BARRIER_MASK) !== 0)
39
+ return BARRIER_CREDIT;
40
+ if ((bits & BORDER_FEATURE_BITS.terrainChange) !== 0)
41
+ return TERRAIN_CREDIT;
42
+ return 0;
43
+ }
44
+ function graphKind(kind) {
45
+ return kind === BORDER_PROVINCE_KIND.land ? 1 : kind === BORDER_PROVINCE_KIND.lake ? 2 : 0;
46
+ }
47
+ /**
48
+ * Build the province graph of a map from the border edge table of the border metric (ADR 0059). The table holds
49
+ * every land border pixel edge with its natural features, so the mean strength and the natural
50
+ * length of each shared border are exact; borders with water have none.
51
+ */
52
+ export function buildTerritoryProvinceGraph(table) {
53
+ const { width, height, provinceIds, provinceKinds } = table;
54
+ const count = provinceKinds.length;
55
+ if (count > 65_535)
56
+ throw new ServiceError('MAP_TERRITORY_TOO_MANY_PROVINCES', 'Territory assignment supports at most 65,535 provinces', { provinces: count });
57
+ const size = new Uint32Array(count);
58
+ const sumX = new Float64Array(count);
59
+ const sumY = new Float64Array(count);
60
+ let negative = false;
61
+ for (let y = 0; y < height; y += 1) {
62
+ const row = y * width;
63
+ for (let x = 0; x < width; x += 1) {
64
+ const id = provinceIds[row + x];
65
+ if (id < 0 || id >= count) {
66
+ negative = true;
67
+ continue;
68
+ }
69
+ size[id] += 1;
70
+ sumX[id] += x;
71
+ sumY[id] += y;
72
+ }
73
+ }
74
+ let pixel = provinceIds;
75
+ if (negative) {
76
+ pixel = new Int32Array(provinceIds.length);
77
+ for (let p = 0; p < pixel.length; p += 1) {
78
+ const id = provinceIds[p];
79
+ pixel[p] = id < 0 || id >= count ? 0 : id;
80
+ }
81
+ }
82
+ const graph = buildProvinceGraph({
83
+ width,
84
+ height,
85
+ pixel,
86
+ kind: Uint8Array.from(provinceKinds, graphKind),
87
+ size,
88
+ sumX,
89
+ sumY,
90
+ });
91
+ const credit = new Float32Array(graph.to.length);
92
+ const strength = new Float64Array(graph.to.length);
93
+ const slotOf = (from, to) => {
94
+ let low = graph.offsets[from];
95
+ let high = graph.offsets[from + 1] - 1;
96
+ while (low <= high) {
97
+ const middle = (low + high) >> 1;
98
+ const value = graph.to[middle];
99
+ if (value === to)
100
+ return middle;
101
+ if (value < to)
102
+ low = middle + 1;
103
+ else
104
+ high = middle - 1;
105
+ }
106
+ return -1;
107
+ };
108
+ for (let index = 0; index < table.edgeCount; index += 1) {
109
+ const a = table.provinceA[index];
110
+ const b = table.provinceB[index];
111
+ const bits = table.features[index];
112
+ const forward = slotOf(a, b);
113
+ const backward = slotOf(b, a);
114
+ if (forward < 0 || backward < 0)
115
+ continue;
116
+ const edgeCredit = borderEdgeCredit(bits);
117
+ const edgeStrength = borderEdgeStrength(bits);
118
+ credit[forward] += edgeCredit;
119
+ credit[backward] += edgeCredit;
120
+ strength[forward] += edgeStrength;
121
+ strength[backward] += edgeStrength;
122
+ }
123
+ for (let slot = 0; slot < strength.length; slot += 1)
124
+ graph.barrier[slot] = strength[slot] / Math.max(1, graph.length[slot]);
125
+ return { graph, credit, table, sumX, sumY, size };
126
+ }
127
+ /**
128
+ * The province graph of a partition (ADR 0058) as a territory province graph: its mean barrier
129
+ * strength per border gives the natural length (a border at full strength counts as natural).
130
+ * Used when the border edge table does not exist yet, as during world generation.
131
+ */
132
+ export function territoryGraphFromPartition(graph, sumX, sumY) {
133
+ const credit = new Float32Array(graph.to.length);
134
+ for (let slot = 0; slot < credit.length; slot += 1)
135
+ credit[slot] = graph.length[slot] * Math.min(1, graph.barrier[slot] * 1.25);
136
+ return {
137
+ graph,
138
+ credit,
139
+ sumX: Float64Array.from({ length: graph.count }, (_, i) => sumX[i]),
140
+ sumY: Float64Array.from({ length: graph.count }, (_, i) => sumY[i]),
141
+ size: graph.size,
142
+ };
143
+ }
144
+ /**
145
+ * Aggregate provinces into atoms. `groups[i]` lists the provinces of atom `i`; every province must
146
+ * be land and appear once. `region` is optional and gives the group id of each atom.
147
+ */
148
+ export function buildTerritoryAtoms(base, groups, region) {
149
+ const provinceCount = base.graph.count;
150
+ const atomOfProvince = new Int32Array(provinceCount).fill(-1);
151
+ const members = [];
152
+ for (const [atom, group] of groups.entries()) {
153
+ const sorted = [...group].sort((x, y) => x - y);
154
+ for (const province of sorted) {
155
+ if (province < 0 ||
156
+ province >= provinceCount ||
157
+ base.graph.kind[province] !== 1 ||
158
+ atomOfProvince[province] >= 0)
159
+ throw new ServiceError('MAP_TERRITORY_ATOM_INVALID', `Province ${province} cannot belong to atom ${atom}: it is not land or belongs to another atom`, { province, atom });
160
+ atomOfProvince[province] = atom;
161
+ }
162
+ members.push(sorted);
163
+ }
164
+ const count = members.length;
165
+ const size = new Uint32Array(count);
166
+ const sumX = new Float64Array(count);
167
+ const sumY = new Float64Array(count);
168
+ const rows = Array.from({ length: count }, () => new Map());
169
+ const { graph } = base;
170
+ for (let atom = 0; atom < count; atom += 1) {
171
+ const row = rows[atom];
172
+ for (const province of members[atom]) {
173
+ size[atom] += base.size[province];
174
+ sumX[atom] += base.sumX[province];
175
+ sumY[atom] += base.sumY[province];
176
+ for (let slot = graph.offsets[province]; slot < graph.offsets[province + 1]; slot += 1) {
177
+ const other = atomOfProvince[graph.to[slot]];
178
+ if (other < 0 || other === atom)
179
+ continue;
180
+ const length = graph.length[slot];
181
+ const entry = row.get(other);
182
+ if (entry === undefined)
183
+ row.set(other, [length, graph.barrier[slot] * length, base.credit[slot]]);
184
+ else {
185
+ entry[0] += length;
186
+ entry[1] += graph.barrier[slot] * length;
187
+ entry[2] += base.credit[slot];
188
+ }
189
+ }
190
+ }
191
+ }
192
+ const offsets = new Int32Array(count + 1);
193
+ for (let atom = 0; atom < count; atom += 1)
194
+ offsets[atom + 1] = offsets[atom] + rows[atom].size;
195
+ const to = new Int32Array(offsets[count]);
196
+ const length = new Uint32Array(offsets[count]);
197
+ const barrier = new Float32Array(offsets[count]);
198
+ const credit = new Float32Array(offsets[count]);
199
+ for (let atom = 0; atom < count; atom += 1) {
200
+ let slot = offsets[atom];
201
+ for (const other of [...rows[atom].keys()].sort((x, y) => x - y)) {
202
+ const [pairLength, pairStrength, pairCredit] = rows[atom].get(other);
203
+ to[slot] = other;
204
+ length[slot] = pairLength;
205
+ barrier[slot] = pairStrength / Math.max(1, pairLength);
206
+ credit[slot] = pairCredit;
207
+ slot += 1;
208
+ }
209
+ }
210
+ const centerX = new Float32Array(count);
211
+ const centerY = new Float32Array(count);
212
+ for (let atom = 0; atom < count; atom += 1) {
213
+ const n = Math.max(1, size[atom]);
214
+ centerX[atom] = sumX[atom] / n;
215
+ centerY[atom] = sumY[atom] / n;
216
+ }
217
+ const atomGraph = {
218
+ count,
219
+ kind: new Uint8Array(count).fill(1),
220
+ offsets,
221
+ to,
222
+ length,
223
+ barrier,
224
+ centerX,
225
+ centerY,
226
+ width: graph.width,
227
+ height: graph.height,
228
+ size,
229
+ };
230
+ return {
231
+ count,
232
+ graph: atomGraph,
233
+ credit,
234
+ members,
235
+ atomOfProvince,
236
+ region: region ?? new Int32Array(count).fill(-1),
237
+ };
238
+ }