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,189 @@
1
+ import { ServiceError } from '../core/result.js';
2
+ import { borderQualityDiagnostics, borderTargetsForLayer, checkBorderTargets, measureBorderNaturalness, } from './border-quality.js';
3
+ import { borderQualityInputFromIndex } from './border-quality-input.js';
4
+ function targetsFor(layer, overrides) {
5
+ const targets = { ...borderTargetsForLayer(layer) };
6
+ if (overrides?.minEnrichment !== undefined)
7
+ targets.minEnrichment = overrides.minEnrichment;
8
+ if (overrides?.maxStraightness !== undefined)
9
+ targets.maxStraightness = overrides.maxStraightness;
10
+ if (overrides?.minNaturalShare !== undefined)
11
+ targets.minNaturalShare = overrides.minNaturalShare;
12
+ return targets;
13
+ }
14
+ function requests(plan) {
15
+ const result = [];
16
+ for (const report of plan.territory ?? []) {
17
+ const { mode, layer, targets } = report.borderQuality;
18
+ if (mode === 'off')
19
+ continue;
20
+ result.push({
21
+ operationId: report.operationId,
22
+ kind: report.kind,
23
+ mode,
24
+ layers: layer === 'country' ? ['country', 'state'] : ['state'],
25
+ overrides: targets,
26
+ enforced: [layer],
27
+ });
28
+ }
29
+ const failed = new Set(plan.blockers.map(({ operationId }) => operationId));
30
+ for (const operation of plan.operations) {
31
+ if (operation.kind !== 'create_world' || failed.has(operation.id))
32
+ continue;
33
+ const mode = operation.data.borderQuality;
34
+ if (mode !== 'report' && mode !== 'enforce')
35
+ continue;
36
+ result.push({
37
+ operationId: operation.id,
38
+ kind: 'create_world',
39
+ mode,
40
+ layers: ['province', 'state', 'region', 'country'],
41
+ overrides: operation.data.borderTargets,
42
+ enforced: ['province', 'state', 'region', 'country'],
43
+ });
44
+ }
45
+ return result;
46
+ }
47
+ function describeFailures(check, layer) {
48
+ return `The ${layer} borders look artificial: ${check.failures
49
+ .map(({ metric, value, target }) => metric === 'enrichment'
50
+ ? `enrichment ${String(value)} is below ${String(target)}`
51
+ : metric === 'straightness'
52
+ ? `${String(value)} of samples are locally straight (limit ${String(target)})`
53
+ : `natural share ${String(value)} is below ${String(target)}`)
54
+ .join('; ')}`;
55
+ }
56
+ /** Measure the final index for the operations that asked for border quality. */
57
+ export async function evaluateTerritoryQuality(plan, signal) {
58
+ const asked = requests(plan);
59
+ if (asked.length === 0)
60
+ return undefined;
61
+ const wanted = new Set(asked.flatMap(({ layers }) => layers));
62
+ let measured;
63
+ let skipped;
64
+ try {
65
+ const { input, skipped: missing } = borderQualityInputFromIndex(plan.finalIndex);
66
+ skipped = missing;
67
+ measured = await measureBorderNaturalness({ ...input, layers: input.layers.filter(({ name }) => wanted.has(name)) }, signal === undefined ? {} : { signal });
68
+ }
69
+ catch (error) {
70
+ if (signal?.aborted === true)
71
+ throw error;
72
+ const reason = error instanceof ServiceError ? error.code : 'MAP_BORDER_QUALITY_UNAVAILABLE';
73
+ const message = error instanceof Error ? error.message : String(error);
74
+ const blockers = [];
75
+ const diagnostics = [];
76
+ for (const request of asked) {
77
+ const text = `Border quality could not be measured (${reason}): ${message}`;
78
+ if (request.mode === 'enforce')
79
+ blockers.push({
80
+ code: 'MAP_BORDER_QUALITY_UNAVAILABLE',
81
+ message: text,
82
+ operationId: request.operationId,
83
+ });
84
+ diagnostics.push({
85
+ code: 'MAP_BORDER_QUALITY_UNAVAILABLE',
86
+ severity: request.mode === 'enforce' ? 'blocker' : 'warning',
87
+ category: 'map',
88
+ message: text,
89
+ operationId: request.operationId,
90
+ });
91
+ }
92
+ return { diagnostics, blockers, operations: [], skippedInputs: [] };
93
+ }
94
+ const diagnostics = [];
95
+ const blockers = [];
96
+ const operations = [];
97
+ const layerReports = new Map(measured.report.layers.map((layer) => [layer.layer, layer]));
98
+ const inputsMissing = borderQualityDiagnostics({ ...measured.report, layers: [] }).filter(({ code }) => code === 'MAP_BORDER_INPUT_MISSING');
99
+ for (const request of asked) {
100
+ const verdicts = [];
101
+ for (const layer of request.layers) {
102
+ const report = layerReports.get(layer);
103
+ if (report === undefined)
104
+ continue;
105
+ const targets = targetsFor(layer, request.overrides);
106
+ const check = checkBorderTargets(report, targets);
107
+ verdicts.push({ layer, mode: request.mode, targets, check, report });
108
+ const judged = report.status === 'ok';
109
+ const enforcedHere = request.mode === 'enforce' && request.enforced.includes(layer);
110
+ if (enforcedHere && judged && !check.passed) {
111
+ const message = `${describeFailures(check, layer)}. Natural share ${String(report.natural.share)}; ${report.worstRuns[0] === undefined ? 'no long artificial run' : `the longest artificial run is ${report.worstRuns[0].edges} pixels between ${report.worstRuns[0].sides[0]} and ${report.worstRuns[0].sides[1]} near (${report.worstRuns[0].from.x}, ${report.worstRuns[0].from.y})`}. Move seeds or blocked/forced land, or lower the targets with borderTargets, or use borderQuality "report".`;
112
+ const code = request.kind === 'create_world'
113
+ ? 'MAP_WORLD_BORDER_ARTIFICIAL'
114
+ : 'MAP_TERRITORY_BORDER_ARTIFICIAL';
115
+ blockers.push({
116
+ code,
117
+ message,
118
+ operationId: request.operationId,
119
+ details: { layer, failures: check.failures, worstRun: report.worstRuns[0] ?? null },
120
+ });
121
+ diagnostics.push({
122
+ code,
123
+ severity: 'blocker',
124
+ category: 'map',
125
+ message,
126
+ operationId: request.operationId,
127
+ details: { layer, failures: check.failures, worstRun: report.worstRuns[0] ?? null },
128
+ });
129
+ continue;
130
+ }
131
+ if (!judged) {
132
+ diagnostics.push({
133
+ code: report.status === 'not-meaningful'
134
+ ? 'MAP_BORDER_NOT_MEANINGFUL'
135
+ : 'MAP_BORDER_NOT_JUDGED',
136
+ severity: 'info',
137
+ category: 'map',
138
+ operationId: request.operationId,
139
+ message: `The ${layer} border naturalness was not judged (${report.status}): ${report.status === 'not-meaningful'
140
+ ? 'the map has too few rivers, slopes or shores to align to'
141
+ : 'there are too few borders'}`,
142
+ details: { layer, status: report.status, baseRate: report.barrier.baseRate },
143
+ });
144
+ continue;
145
+ }
146
+ diagnostics.push(...borderQualityDiagnostics({ ...measured.report, layers: [report], targets: { [layer]: targets } }, targets)
147
+ .filter(({ code }) => code === 'MAP_BORDER_ARTIFICIAL_REVIEW')
148
+ .map((diagnostic) => ({ ...diagnostic, operationId: request.operationId })));
149
+ }
150
+ operations.push({ operationId: request.operationId, kind: request.kind, layers: verdicts });
151
+ diagnostics.push({
152
+ code: 'MAP_BORDER_QUALITY_MEASURED',
153
+ severity: 'info',
154
+ category: 'map',
155
+ operationId: request.operationId,
156
+ message: `Border naturalness of the proposed map: ${verdicts
157
+ .map(({ layer, report, check }) => `${layer} enrichment ${report.barrier.enrichment ?? 'n/a'}, natural share ${report.natural.share ?? 'n/a'}${report.straightness.fraction === null ? '' : `, straight ${report.straightness.fraction}`} (${check.passed ? 'meets' : 'misses'} the targets)`)
158
+ .join('; ')}`,
159
+ details: {
160
+ layers: verdicts.map(({ layer, report, check, targets }) => ({
161
+ layer,
162
+ status: report.status,
163
+ enrichment: report.barrier.enrichment,
164
+ alignment: report.barrier.alignment,
165
+ baseRate: report.barrier.baseRate,
166
+ naturalShare: report.natural.share,
167
+ straightFraction: report.straightness.fraction,
168
+ passed: check.passed,
169
+ targets,
170
+ })),
171
+ },
172
+ });
173
+ }
174
+ diagnostics.push(...inputsMissing.slice(0, 1));
175
+ return { diagnostics, blockers, operations, report: measured.report, skippedInputs: skipped };
176
+ }
177
+ /** The complete territory report written as `map-territory-report.json`. */
178
+ export function territoryArtifactContent(reports, quality) {
179
+ const byOperation = new Map((quality?.operations ?? []).map((entry) => [entry.operationId, entry]));
180
+ return {
181
+ schemaVersion: 1,
182
+ reports: reports.map((report) => {
183
+ const found = byOperation.get(report.operationId);
184
+ return found === undefined ? report : { ...report, quality: found };
185
+ }),
186
+ ...(quality?.report === undefined ? {} : { borderQuality: quality.report }),
187
+ skippedInputs: quality?.skippedInputs ?? [],
188
+ };
189
+ }