hoi4-agent-tools 4.3.0 → 4.5.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 (119) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/dist/hoi4_agent_tools/core/bounded-source.js +31 -9
  3. package/dist/hoi4_agent_tools/core/configuration.js +11 -0
  4. package/dist/hoi4_agent_tools/core/managed-outputs.js +26 -6
  5. package/dist/hoi4_agent_tools/core/operation-context.js +17 -1
  6. package/dist/hoi4_agent_tools/core/source/encoding.js +56 -2
  7. package/dist/hoi4_agent_tools/core/source/parser.js +16 -1
  8. package/dist/hoi4_agent_tools/core/temporary-directories.js +7 -2
  9. package/dist/hoi4_agent_tools/core/transaction-limits.js +18 -4
  10. package/dist/hoi4_agent_tools/core/transactions.js +23 -4
  11. package/dist/hoi4_agent_tools/core/workspace-json.js +54 -0
  12. package/dist/hoi4_agent_tools/core/workspace.js +42 -6
  13. package/dist/hoi4_agent_tools/focus/drift.js +34 -0
  14. package/dist/hoi4_agent_tools/focus/service.js +9 -3
  15. package/dist/hoi4_agent_tools/focus/tool-operations.js +2 -0
  16. package/dist/hoi4_agent_tools/map/appearance-validation.js +1110 -0
  17. package/dist/hoi4_agent_tools/map/bmp.js +9 -1
  18. package/dist/hoi4_agent_tools/map/border-quality-input.js +209 -0
  19. package/dist/hoi4_agent_tools/map/border-quality.js +1180 -0
  20. package/dist/hoi4_agent_tools/map/colormap.js +128 -0
  21. package/dist/hoi4_agent_tools/map/geography/geometry.js +284 -0
  22. package/dist/hoi4_agent_tools/map/geography/index.js +6 -0
  23. package/dist/hoi4_agent_tools/map/geography/model.js +196 -0
  24. package/dist/hoi4_agent_tools/map/geography/preparation.js +120 -0
  25. package/dist/hoi4_agent_tools/map/geography/preview.js +647 -0
  26. package/dist/hoi4_agent_tools/map/geography/raster.js +253 -0
  27. package/dist/hoi4_agent_tools/map/geography/resolve.js +1467 -0
  28. package/dist/hoi4_agent_tools/map/model.js +17 -3
  29. package/dist/hoi4_agent_tools/map/operations.js +284 -14
  30. package/dist/hoi4_agent_tools/map/partition-fields.js +398 -0
  31. package/dist/hoi4_agent_tools/map/partition-geography.js +113 -0
  32. package/dist/hoi4_agent_tools/map/partition-graph.js +165 -0
  33. package/dist/hoi4_agent_tools/map/partition-groups.js +164 -0
  34. package/dist/hoi4_agent_tools/map/partition-provinces.js +727 -0
  35. package/dist/hoi4_agent_tools/map/partition-tuning.js +101 -0
  36. package/dist/hoi4_agent_tools/map/partition-watershed.js +252 -0
  37. package/dist/hoi4_agent_tools/map/partition.js +163 -0
  38. package/dist/hoi4_agent_tools/map/reference-import.js +333 -0
  39. package/dist/hoi4_agent_tools/map/reference-preparation.js +142 -6
  40. package/dist/hoi4_agent_tools/map/reference-sheets.js +231 -0
  41. package/dist/hoi4_agent_tools/map/reference-world.js +104 -13
  42. package/dist/hoi4_agent_tools/map/relief/checks.js +281 -0
  43. package/dist/hoi4_agent_tools/map/relief/fields.js +15 -0
  44. package/dist/hoi4_agent_tools/map/relief/full-resolution.js +133 -0
  45. package/dist/hoi4_agent_tools/map/relief/grid.js +190 -0
  46. package/dist/hoi4_agent_tools/map/relief/hydrology.js +1315 -0
  47. package/dist/hoi4_agent_tools/map/relief/index.js +27 -0
  48. package/dist/hoi4_agent_tools/map/relief/land.js +110 -0
  49. package/dist/hoi4_agent_tools/map/relief/macro-relief.js +535 -0
  50. package/dist/hoi4_agent_tools/map/relief/math.js +93 -0
  51. package/dist/hoi4_agent_tools/map/relief/river-raster.js +680 -0
  52. package/dist/hoi4_agent_tools/map/relief/terrain-synthesis.js +304 -0
  53. package/dist/hoi4_agent_tools/map/render.js +36 -0
  54. package/dist/hoi4_agent_tools/map/service.js +206 -16
  55. package/dist/hoi4_agent_tools/map/tool-operations.js +249 -3
  56. package/dist/hoi4_agent_tools/map/validation.js +9 -0
  57. package/dist/hoi4_agent_tools/map/water.js +227 -0
  58. package/dist/hoi4_agent_tools/map/world-context.js +47 -1
  59. package/dist/hoi4_agent_tools/map/world-edge.js +335 -0
  60. package/dist/hoi4_agent_tools/map/world-noise.js +34 -0
  61. package/dist/hoi4_agent_tools/map/world-outputs.js +22 -10
  62. package/dist/hoi4_agent_tools/map/world-partition.js +119 -0
  63. package/dist/hoi4_agent_tools/map/world-provinces.js +131 -56
  64. package/dist/hoi4_agent_tools/map/world-realms.js +152 -0
  65. package/dist/hoi4_agent_tools/map/world.js +562 -163
  66. package/dist/hoi4_agent_tools/mcp/tools/map.js +69 -1
  67. package/dist/hoi4_agent_tools/reference/operations.js +3 -2
  68. package/dist/hoi4_agent_tools/schemas/geography.js +325 -0
  69. package/dist/hoi4_agent_tools/schemas/map-requests.js +20 -2
  70. package/dist/hoi4_agent_tools/schemas/map.js +280 -15
  71. package/dist/hoi4_agent_tools/schemas/reference-sheets.js +92 -0
  72. package/dist/hoi4_agent_tools/version.js +1 -1
  73. package/docs/configuration.md +17 -14
  74. package/docs/focus.md +6 -0
  75. package/docs/map.md +292 -15
  76. package/docs/tools.md +4 -4
  77. package/docs/troubleshooting.md +11 -11
  78. package/npm-shrinkwrap.json +109 -91
  79. package/package.json +3 -3
  80. package/schemas/configuration.schema.json +65 -58
  81. package/schemas/continuous-focus-palette.schema.json +1 -1
  82. package/schemas/custom-weighted-pool.schema.json +1 -1
  83. package/schemas/decision-inspect-input.schema.json +1 -1
  84. package/schemas/error-log-input.schema.json +1 -1
  85. package/schemas/event-feature-manifest.schema.json +1 -1
  86. package/schemas/focus-plan.schema.json +1 -1
  87. package/schemas/focus-planning-sidecar.schema.json +1 -1
  88. package/schemas/geography-model.schema.json +1148 -0
  89. package/schemas/gui-animation-source.schema.json +1 -1
  90. package/schemas/gui-helper.schema.json +5 -11
  91. package/schemas/gui-scenario.schema.json +1 -1
  92. package/schemas/helper-expansion-request.schema.json +1 -1
  93. package/schemas/helper-expansion-summary.schema.json +1 -1
  94. package/schemas/impact-inspect-input.schema.json +1 -1
  95. package/schemas/map-operation.schema.json +2325 -292
  96. package/schemas/mechanic-test-input.schema.json +1 -1
  97. package/schemas/mod-index-input.schema.json +1 -1
  98. package/schemas/operation-result.schema.json +1 -1
  99. package/schemas/package-check-input.schema.json +1 -1
  100. package/schemas/package-manifest.schema.json +1 -1
  101. package/schemas/probability-analysis-result.schema.json +25 -7
  102. package/schemas/probability-compare-input.schema.json +1 -1
  103. package/schemas/probability-evaluate-input.schema.json +1 -1
  104. package/schemas/probability-inspect-input.schema.json +1 -1
  105. package/schemas/probability-render-input.schema.json +1 -1
  106. package/schemas/probability-scenario-set.schema.json +1 -1
  107. package/schemas/probability-sequence-input.schema.json +1 -1
  108. package/schemas/probability-simulate-input.schema.json +1 -1
  109. package/schemas/probability-sweep-input.schema.json +1 -1
  110. package/schemas/reference-context-input.schema.json +1 -1
  111. package/schemas/reference-read-input.schema.json +1 -1
  112. package/schemas/reference-search-input.schema.json +1 -1
  113. package/schemas/reference-sheets.schema.json +267 -0
  114. package/schemas/scenario-suite.schema.json +1 -1
  115. package/schemas/scenario-test-input.schema.json +1 -1
  116. package/schemas/script-validate-input.schema.json +1 -1
  117. package/schemas/shared-scenario.schema.json +1 -1
  118. package/schemas/source-lookup-input.schema.json +1 -1
  119. package/server.json +2 -2
@@ -0,0 +1,253 @@
1
+ import { pointSegment } from './geometry.js';
2
+ export function stripRows(strip) {
3
+ return strip.y1 - strip.y0;
4
+ }
5
+ /** Split the grid into strips of at most `rows` rows, top to bottom. */
6
+ export function rowStrips(grid, rows = 64) {
7
+ const strips = [];
8
+ for (let y0 = 0; y0 < grid.height; y0 += rows)
9
+ strips.push({ y0, y1: Math.min(grid.height, y0 + rows) });
10
+ return strips;
11
+ }
12
+ /** The pixel containing a frame point, clamped to the grid. */
13
+ export function pointToPixel(grid, x, y) {
14
+ return {
15
+ x: Math.max(0, Math.min(grid.width - 1, Math.floor(x * grid.width))),
16
+ y: Math.max(0, Math.min(grid.height - 1, Math.floor(y * grid.height))),
17
+ };
18
+ }
19
+ /** Area covered by a shape in pixels (analytic, not counted). */
20
+ export function areaPixels(area, grid) {
21
+ return area.areaFraction * grid.width * grid.height;
22
+ }
23
+ /**
24
+ * Visit the horizontal spans `[x0, x1)` of row `y` covered by the area, for every row of the
25
+ * strip. Even-odd filling over all outer rings and holes; spans of one row are ordered and do not
26
+ * overlap.
27
+ */
28
+ export function scanAreaStrip(area, grid, strip, visit) {
29
+ const rows = stripRows(strip);
30
+ if (rows <= 0)
31
+ return;
32
+ const lo = Math.max(strip.y0, Math.ceil(area.bounds.y0 * grid.height - 0.5));
33
+ const hi = Math.min(strip.y1, Math.ceil(area.bounds.y1 * grid.height - 0.5) + 1);
34
+ if (hi <= lo)
35
+ return;
36
+ const crossings = Array.from({ length: hi - lo }, () => []);
37
+ for (const ring of [...area.outers, ...area.holes]) {
38
+ const n = ring.xs.length;
39
+ for (let index = 0, previous = n - 1; index < n; previous = index++) {
40
+ const ay = ring.ys[previous], by = ring.ys[index];
41
+ if (ay === by)
42
+ continue;
43
+ const ymin = Math.min(ay, by), ymax = Math.max(ay, by);
44
+ const first = Math.max(lo, Math.ceil(ymin * grid.height - 0.5));
45
+ const last = Math.min(hi, Math.ceil(ymax * grid.height - 0.5));
46
+ if (last <= first)
47
+ continue;
48
+ const ax = ring.xs[previous], bx = ring.xs[index];
49
+ for (let row = first; row < last; row++) {
50
+ const yc = (row + 0.5) / grid.height;
51
+ crossings[row - lo].push((ax + ((yc - ay) * (bx - ax)) / (by - ay)) * grid.width);
52
+ }
53
+ }
54
+ }
55
+ for (let offset = 0; offset < crossings.length; offset++) {
56
+ const list = crossings[offset];
57
+ if (list.length < 2)
58
+ continue;
59
+ list.sort((a, b) => a - b);
60
+ for (let index = 0; index + 1 < list.length; index += 2) {
61
+ const x0 = Math.max(0, Math.ceil(list[index] - 0.5));
62
+ const x1 = Math.min(grid.width, Math.ceil(list[index + 1] - 0.5));
63
+ if (x1 > x0)
64
+ visit(lo + offset, x0, x1);
65
+ }
66
+ }
67
+ }
68
+ /** Write `value` into a strip-sized buffer (`width * rows` cells) for every pixel the area covers. */
69
+ export function fillAreaStrip(area, grid, strip, target, value) {
70
+ scanAreaStrip(area, grid, strip, (y, x0, x1) => {
71
+ const base = (y - strip.y0) * grid.width;
72
+ target.fill(value, base + x0, base + x1);
73
+ });
74
+ }
75
+ /** Number of pixels of the area inside a strip; summed over strips this is the exact pixel area. */
76
+ export function countAreaStrip(area, grid, strip) {
77
+ let count = 0;
78
+ scanAreaStrip(area, grid, strip, (_y, x0, x1) => {
79
+ count += x1 - x0;
80
+ });
81
+ return count;
82
+ }
83
+ /** Number of segments in a path, counting the closing edge of a closed path. */
84
+ export function segmentCount(line) {
85
+ return line.closed ? line.xs.length : line.xs.length - 1;
86
+ }
87
+ /** The point at arc fraction `t` (0..1) with interpolated half-width and crest height. */
88
+ export function sampleAlong(line, frame, t) {
89
+ const clamped = Math.max(0, Math.min(1, t));
90
+ const target = clamped * line.length;
91
+ const segments = segmentCount(line);
92
+ let low = 0, high = segments - 1;
93
+ while (low < high) {
94
+ const middle = (low + high) >> 1;
95
+ if (line.arc[middle + 1] < target)
96
+ low = middle + 1;
97
+ else
98
+ high = middle;
99
+ }
100
+ const next = (low + 1) % line.xs.length;
101
+ const start = line.arc[low], end = line.arc[low + 1];
102
+ const local = end > start ? Math.max(0, Math.min(1, (target - start) / (end - start))) : 0;
103
+ const dx = (line.xs[next] - line.xs[low]) * frame.width;
104
+ const dy = (line.ys[next] - line.ys[low]) * frame.height;
105
+ const length = Math.sqrt(dx * dx + dy * dy);
106
+ return {
107
+ x: line.xs[low] + (line.xs[next] - line.xs[low]) * local,
108
+ y: line.ys[low] + (line.ys[next] - line.ys[low]) * local,
109
+ tangentX: length === 0 ? 1 : dx / length,
110
+ tangentY: length === 0 ? 0 : dy / length,
111
+ halfWidth: line.halfWidth[low] + (line.halfWidth[next] - line.halfWidth[low]) * local,
112
+ peak: line.peak[low] + (line.peak[next] - line.peak[low]) * local,
113
+ };
114
+ }
115
+ /**
116
+ * The point of a path nearest to a frame point, measured in map-width units so a unit step east and
117
+ * a unit step south are the same distance. The first of equally near segments wins.
118
+ */
119
+ export function nearestOnPolyline(line, frame, x, y) {
120
+ const aspect = frame.height / frame.width;
121
+ const segments = segmentCount(line);
122
+ let best = { distance: Infinity, t: 0, segment: 0 };
123
+ for (let segment = 0; segment < segments; segment++) {
124
+ const next = (segment + 1) % line.xs.length;
125
+ const found = pointSegment(x, y * aspect, line.xs[segment], line.ys[segment] * aspect, line.xs[next], line.ys[next] * aspect);
126
+ if (found.distance < best.distance)
127
+ best = { ...found, segment };
128
+ }
129
+ const next = (best.segment + 1) % line.xs.length;
130
+ const start = line.arc[best.segment], end = line.arc[best.segment + 1];
131
+ return {
132
+ x: line.xs[best.segment] + (line.xs[next] - line.xs[best.segment]) * best.t,
133
+ y: line.ys[best.segment] + (line.ys[next] - line.ys[best.segment]) * best.t,
134
+ at: line.length === 0 ? 0 : (start + (end - start) * best.t) / line.length,
135
+ distance: best.distance,
136
+ segment: best.segment,
137
+ halfWidth: line.halfWidth[best.segment] +
138
+ (line.halfWidth[next] - line.halfWidth[best.segment]) * best.t,
139
+ };
140
+ }
141
+ /**
142
+ * Visit every pixel a path touches, in path order, once per run (8-connected Bresenham between the
143
+ * pixels of consecutive vertices). `t` is the arc fraction at the visited pixel.
144
+ */
145
+ export function forEachPolylinePixel(line, grid, strip, visit) {
146
+ const segments = segmentCount(line);
147
+ let lastKey = -1;
148
+ for (let segment = 0; segment < segments; segment++) {
149
+ const next = (segment + 1) % line.xs.length;
150
+ const a = pointToPixel(grid, line.xs[segment], line.ys[segment]);
151
+ const b = pointToPixel(grid, line.xs[next], line.ys[next]);
152
+ const dx = Math.abs(b.x - a.x), dy = Math.abs(b.y - a.y);
153
+ const steps = Math.max(dx, dy);
154
+ const startArc = line.arc[segment], endArc = line.arc[segment + 1];
155
+ for (let step = 0; step <= steps; step++) {
156
+ const f = steps === 0 ? 0 : step / steps;
157
+ // Rounding at .5 goes up, deterministically.
158
+ const x = Math.floor(a.x + (b.x - a.x) * f + 0.5);
159
+ const y = Math.floor(a.y + (b.y - a.y) * f + 0.5);
160
+ const key = y * grid.width + x;
161
+ if (key === lastKey)
162
+ continue;
163
+ lastKey = key;
164
+ if (y < strip.y0 || y >= strip.y1)
165
+ continue;
166
+ visit(x, y, line.length === 0 ? 0 : (startArc + (endArc - startArc) * f) / line.length);
167
+ }
168
+ }
169
+ }
170
+ export function createBandStrip(grid, strip) {
171
+ const cells = grid.width * stripRows(strip);
172
+ return {
173
+ strip,
174
+ width: grid.width,
175
+ distance: new Float32Array(cells).fill(Infinity),
176
+ t: new Float32Array(cells),
177
+ side: new Int8Array(cells),
178
+ };
179
+ }
180
+ /** Reset a band so the buffer can be reused for the next path or strip. */
181
+ export function clearBandStrip(band) {
182
+ band.distance.fill(Infinity);
183
+ band.t.fill(0);
184
+ band.side.fill(0);
185
+ }
186
+ /**
187
+ * For every pixel of the strip within `maxDistancePx` of the path, store the distance to the
188
+ * nearest segment, the arc fraction there and the side. The first of equally near segments wins.
189
+ */
190
+ export function rasterizeBandStrip(line, grid, strip, maxDistancePx, band) {
191
+ const segments = segmentCount(line);
192
+ for (let segment = 0; segment < segments; segment++) {
193
+ const next = (segment + 1) % line.xs.length;
194
+ const ax = line.xs[segment] * grid.width, ay = line.ys[segment] * grid.height, bx = line.xs[next] * grid.width, by = line.ys[next] * grid.height;
195
+ const sx = bx - ax, sy = by - ay;
196
+ const lengthSquared = sx * sx + sy * sy;
197
+ const length = Math.sqrt(lengthSquared);
198
+ const tx = length === 0 ? 1 : sx / length, ty = length === 0 ? 0 : sy / length;
199
+ const minX = Math.max(0, Math.floor(Math.min(ax, bx) - maxDistancePx - 1));
200
+ const maxX = Math.min(grid.width - 1, Math.ceil(Math.max(ax, bx) + maxDistancePx));
201
+ const minY = Math.max(strip.y0, Math.floor(Math.min(ay, by) - maxDistancePx - 1));
202
+ const maxY = Math.min(strip.y1 - 1, Math.ceil(Math.max(ay, by) + maxDistancePx));
203
+ const startArc = line.arc[segment], endArc = line.arc[segment + 1];
204
+ for (let y = minY; y <= maxY; y++) {
205
+ const py = y + 0.5;
206
+ for (let x = minX; x <= maxX; x++) {
207
+ const px = x + 0.5;
208
+ let along = lengthSquared === 0 ? 0 : ((px - ax) * sx + (py - ay) * sy) / lengthSquared;
209
+ if (along < 0)
210
+ along = 0;
211
+ else if (along > 1)
212
+ along = 1;
213
+ const qx = ax + along * sx, qy = ay + along * sy;
214
+ const ox = px - qx, oy = py - qy;
215
+ const distance = Math.sqrt(ox * ox + oy * oy);
216
+ if (distance > maxDistancePx)
217
+ continue;
218
+ const cell = (y - strip.y0) * grid.width + x;
219
+ if (distance >= band.distance[cell])
220
+ continue;
221
+ band.distance[cell] = distance;
222
+ band.t[cell] =
223
+ line.length === 0 ? 0 : (startArc + (endArc - startArc) * along) / line.length;
224
+ const cross = tx * oy - ty * ox;
225
+ band.side[cell] = cross > 1e-9 ? 1 : cross < -1e-9 ? -1 : 0;
226
+ }
227
+ }
228
+ }
229
+ }
230
+ /** Visit every pixel whose centre lies within `radiusFraction` (of the map width) of the point. */
231
+ export function forEachDiscPixel(center, radiusFraction, grid, strip, visit) {
232
+ const cx = center.x * grid.width, cy = center.y * grid.height;
233
+ const radius = radiusFraction * grid.width;
234
+ const minX = Math.max(0, Math.floor(cx - radius - 1));
235
+ const maxX = Math.min(grid.width - 1, Math.ceil(cx + radius));
236
+ const minY = Math.max(strip.y0, Math.floor(cy - radius - 1));
237
+ const maxY = Math.min(strip.y1 - 1, Math.ceil(cy + radius));
238
+ for (let y = minY; y <= maxY; y++)
239
+ for (let x = minX; x <= maxX; x++) {
240
+ const dx = x + 0.5 - cx, dy = y + 0.5 - cy;
241
+ const distance = Math.sqrt(dx * dx + dy * dy);
242
+ if (distance <= radius)
243
+ visit(x, y, distance);
244
+ }
245
+ }
246
+ /** A coarse grid for validation and previews: at most `maxWidth` pixels wide, same aspect ratio. */
247
+ export function analysisGrid(frame, maxWidth = 512) {
248
+ const scale = Math.min(1, maxWidth / frame.width);
249
+ return {
250
+ width: Math.max(1, Math.round(frame.width * scale)),
251
+ height: Math.max(1, Math.round(frame.height * scale)),
252
+ };
253
+ }