hoi4-agent-tools 2.2.0 → 2.3.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 (62) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +30 -21
  3. package/dist/bin/stdio.js +0 -4
  4. package/dist/hoi4_agent_tools/core/clausewitz-evaluation.js +90 -0
  5. package/dist/hoi4_agent_tools/core/engine.js +5 -0
  6. package/dist/hoi4_agent_tools/core/index.js +57 -1
  7. package/dist/hoi4_agent_tools/core/source/ast-path.js +41 -0
  8. package/dist/hoi4_agent_tools/core/source/index.js +1 -0
  9. package/dist/hoi4_agent_tools/event/limits.js +1 -1
  10. package/dist/hoi4_agent_tools/mcp/server/create.js +3 -1
  11. package/dist/hoi4_agent_tools/mcp/tools/probability.js +413 -0
  12. package/dist/hoi4_agent_tools/probability/adapters.js +344 -0
  13. package/dist/hoi4_agent_tools/probability/compare.js +178 -0
  14. package/dist/hoi4_agent_tools/probability/evaluation.js +649 -0
  15. package/dist/hoi4_agent_tools/probability/index.js +13 -0
  16. package/dist/hoi4_agent_tools/probability/model.js +5 -0
  17. package/dist/hoi4_agent_tools/probability/rational.js +145 -0
  18. package/dist/hoi4_agent_tools/probability/render.js +307 -0
  19. package/dist/hoi4_agent_tools/probability/sequence.js +858 -0
  20. package/dist/hoi4_agent_tools/probability/service.js +1168 -0
  21. package/dist/hoi4_agent_tools/probability/simulation.js +545 -0
  22. package/dist/hoi4_agent_tools/probability/source-analysis.js +458 -0
  23. package/dist/hoi4_agent_tools/probability/sweep.js +265 -0
  24. package/dist/hoi4_agent_tools/probability/trigger-evaluator.js +280 -0
  25. package/dist/hoi4_agent_tools/probability/value-evaluator.js +357 -0
  26. package/dist/hoi4_agent_tools/schemas/probability.js +930 -0
  27. package/dist/hoi4_agent_tools/version.js +1 -1
  28. package/docs/README.md +1 -0
  29. package/docs/development.md +2 -0
  30. package/docs/probability.md +80 -0
  31. package/docs/research/probability-adapter-evidence.md +64 -0
  32. package/examples/clients/codex-config.toml +3 -0
  33. package/examples/clients/generic.json +8 -0
  34. package/examples/probability/adaptive-event-pool.json +92 -0
  35. package/examples/probability/adaptive-event-sequence-input.json +57 -0
  36. package/examples/probability/event-mtth-scenarios.json +57 -0
  37. package/examples/probability/event-mtth-simulate-input.json +37 -0
  38. package/examples/probability/focus-route-scenarios.json +61 -0
  39. package/examples/probability/focus-route-sweep-input.json +36 -0
  40. package/package.json +17 -8
  41. package/schemas/configuration.schema.json +747 -0
  42. package/schemas/continuous-focus-palette.schema.json +325 -0
  43. package/schemas/custom-weighted-pool.schema.json +298 -0
  44. package/schemas/event-feature-manifest.schema.json +63 -0
  45. package/schemas/focus-plan.schema.json +927 -0
  46. package/schemas/focus-planning-sidecar.schema.json +407 -0
  47. package/schemas/gui-animation-source.schema.json +176 -0
  48. package/schemas/gui-helper.schema.json +301 -0
  49. package/schemas/gui-scenario.schema.json +298 -0
  50. package/schemas/map-operation.schema.json +2284 -0
  51. package/schemas/operation-result.schema.json +327 -0
  52. package/schemas/probability-analysis-result.schema.json +2211 -0
  53. package/schemas/probability-compare-input.schema.json +664 -0
  54. package/schemas/probability-evaluate-input.schema.json +665 -0
  55. package/schemas/probability-inspect-input.schema.json +97 -0
  56. package/schemas/probability-render-input.schema.json +141 -0
  57. package/schemas/probability-scenario-set.schema.json +430 -0
  58. package/schemas/probability-sequence-input.schema.json +886 -0
  59. package/schemas/probability-simulate-input.schema.json +691 -0
  60. package/schemas/probability-sweep-input.schema.json +702 -0
  61. package/server.json +3 -3
  62. package/dist/hoi4_agent_tools/mcp/tools/chaosx.js +0 -530
@@ -0,0 +1,357 @@
1
+ import { assignments, astPathFor, nodeLocation, } from '../core/source/index.js';
2
+ import { Rational } from './rational.js';
3
+ import { evaluateTriggerBlock } from './trigger-evaluator.js';
4
+ function appendReference(evaluated, reference) {
5
+ return {
6
+ ...evaluated,
7
+ referencedProvenance: [reference, ...(evaluated.referencedProvenance ?? [])],
8
+ };
9
+ }
10
+ function interval(value) {
11
+ return { min: value, max: value };
12
+ }
13
+ function nodeProvenance(candidate, node, symbol) {
14
+ const source = candidate.provenance[0];
15
+ if (source === undefined || candidate.document === undefined)
16
+ return source;
17
+ const astPath = astPathFor(candidate.document, node);
18
+ return {
19
+ ...source,
20
+ location: nodeLocation(candidate.document, node, symbol),
21
+ ...(astPath === undefined ? {} : { astPath }),
22
+ ...(symbol === undefined ? {} : { symbol }),
23
+ };
24
+ }
25
+ function conditionSignature(block) {
26
+ const encode = (value) => assignments(value).map((assignment) => [
27
+ assignment.key.value,
28
+ assignment.operator.text,
29
+ assignment.value.type === 'scalar' ? assignment.value.value : encode(assignment.value),
30
+ ]);
31
+ const encoded = encode(block);
32
+ return encoded.length === 0 ? 'always' : JSON.stringify(encoded);
33
+ }
34
+ function multiplyIntervals(left, right) {
35
+ const products = [
36
+ left.min.multiply(right.min),
37
+ left.min.multiply(right.max),
38
+ left.max.multiply(right.min),
39
+ left.max.multiply(right.max),
40
+ ].sort((a, b) => a.compare(b));
41
+ return { min: products[0], max: products.at(-1) };
42
+ }
43
+ function addIntervals(left, right) {
44
+ return { min: left.min.add(right.min), max: left.max.add(right.max) };
45
+ }
46
+ function scenarioNumber(scenario, expression) {
47
+ if (Object.hasOwn(scenario.state, expression))
48
+ return scenario.state[expression];
49
+ if (Object.hasOwn(scenario.state, `variable.${expression}`))
50
+ return scenario.state[`variable.${expression}`];
51
+ if (expression.startsWith('var:') &&
52
+ Object.hasOwn(scenario.state, `variable.${expression.slice(4)}`))
53
+ return scenario.state[`variable.${expression.slice(4)}`];
54
+ return undefined;
55
+ }
56
+ function expressionValue(expression, candidate, scenario, definitions, mtthStack) {
57
+ const numeric = Rational.parse(expression);
58
+ if (numeric !== undefined && Number.isFinite(numeric.toNumber()))
59
+ return { value: numeric, interval: interval(numeric), trace: [], unresolved: [] };
60
+ if (numeric !== undefined || /^(?:[+-]?(?:inf(?:inity)?|nan))$/iu.test(expression.trim()))
61
+ return {
62
+ trace: [],
63
+ unresolved: [
64
+ {
65
+ code: 'VALUE_NON_FINITE',
66
+ message: `Numeric expression ${expression} is outside finite analyzer range`,
67
+ path: expression,
68
+ candidateId: candidate.id,
69
+ ...(candidate.provenance[0] === undefined ? {} : { provenance: candidate.provenance[0] }),
70
+ },
71
+ ],
72
+ };
73
+ if (expression.startsWith('@')) {
74
+ const definition = definitions.localConstants
75
+ .get(candidate.provenance[0]?.path ?? '')
76
+ ?.get(expression);
77
+ if (definition !== undefined)
78
+ return appendReference(expressionValue(definition.value, candidate, scenario, definitions, mtthStack), {
79
+ path: definition.file.displayPath,
80
+ rootKind: definition.file.rootKind,
81
+ loadOrder: definition.file.loadOrder,
82
+ sourceHash: definition.file.sha256,
83
+ location: nodeLocation(definition.document, definition.node, expression),
84
+ ...(astPathFor(definition.document, definition.node) === undefined
85
+ ? {}
86
+ : { astPath: astPathFor(definition.document, definition.node) }),
87
+ symbol: expression,
88
+ });
89
+ const inline = candidate.document === undefined
90
+ ? undefined
91
+ : assignments(candidate.document.root, expression).find(({ value }) => value.type === 'scalar');
92
+ if (inline?.value.type === 'scalar')
93
+ return appendReference(expressionValue(inline.value.value, candidate, scenario, definitions, mtthStack), {
94
+ path: candidate.provenance[0]?.path ?? 'proposed:inline-probability-source.txt',
95
+ rootKind: candidate.provenance[0]?.rootKind ?? 'fixture',
96
+ loadOrder: candidate.provenance[0]?.loadOrder ?? 0,
97
+ sourceHash: candidate.provenance[0]?.sourceHash ?? '',
98
+ location: nodeLocation(candidate.document, inline, expression),
99
+ ...(astPathFor(candidate.document, inline) === undefined
100
+ ? {}
101
+ : { astPath: astPathFor(candidate.document, inline) }),
102
+ symbol: expression,
103
+ });
104
+ }
105
+ if (expression.startsWith('constant:')) {
106
+ const id = expression.slice('constant:'.length);
107
+ const definition = definitions.scriptConstants.get(id);
108
+ if (definition !== undefined)
109
+ return appendReference(expressionValue(definition.value, candidate, scenario, definitions, mtthStack), {
110
+ path: definition.file.displayPath,
111
+ rootKind: definition.file.rootKind,
112
+ loadOrder: definition.file.loadOrder,
113
+ sourceHash: definition.file.sha256,
114
+ location: nodeLocation(definition.document, definition.node, id),
115
+ ...(astPathFor(definition.document, definition.node) === undefined
116
+ ? {}
117
+ : { astPath: astPathFor(definition.document, definition.node) }),
118
+ symbol: id,
119
+ });
120
+ }
121
+ if (expression.startsWith('mtth:')) {
122
+ const id = expression.slice('mtth:'.length);
123
+ const definition = definitions.mtthVariables.get(id);
124
+ if (definition !== undefined && !mtthStack.includes(id)) {
125
+ const definitionCandidate = {
126
+ ...candidate,
127
+ document: definition.document,
128
+ provenance: [
129
+ {
130
+ path: definition.file.displayPath,
131
+ rootKind: definition.file.rootKind,
132
+ loadOrder: definition.file.loadOrder,
133
+ sourceHash: definition.file.sha256,
134
+ location: nodeLocation(definition.document, definition.node, id),
135
+ ...(astPathFor(definition.document, definition.node) === undefined
136
+ ? {}
137
+ : { astPath: astPathFor(definition.document, definition.node) }),
138
+ symbol: id,
139
+ helperChain: [...mtthStack, id],
140
+ },
141
+ ...candidate.provenance,
142
+ ],
143
+ };
144
+ return appendReference(evaluateValueBlock(definition.value, definitionCandidate, scenario, definitions, Rational.one, [...mtthStack, id]), definitionCandidate.provenance[0]);
145
+ }
146
+ if (mtthStack.includes(id))
147
+ return {
148
+ trace: [],
149
+ unresolved: [
150
+ {
151
+ code: 'MTTH_RECURSION',
152
+ message: `MTTH variable recursion detected at ${id}`,
153
+ candidateId: candidate.id,
154
+ ...(candidate.provenance[0] === undefined
155
+ ? {}
156
+ : { provenance: candidate.provenance[0] }),
157
+ },
158
+ ],
159
+ };
160
+ }
161
+ const declared = scenarioNumber(scenario, expression);
162
+ if (typeof declared === 'number' || typeof declared === 'string') {
163
+ const parsed = Rational.parse(declared);
164
+ if (parsed !== undefined)
165
+ return { value: parsed, interval: interval(parsed), trace: [], unresolved: [] };
166
+ }
167
+ return {
168
+ trace: [],
169
+ unresolved: [
170
+ {
171
+ code: 'VALUE_UNRESOLVED',
172
+ message: `Scenario or source definitions do not resolve numeric expression ${expression}`,
173
+ path: expression,
174
+ candidateId: candidate.id,
175
+ ...(candidate.provenance[0] === undefined ? {} : { provenance: candidate.provenance[0] }),
176
+ },
177
+ ],
178
+ };
179
+ }
180
+ function conditionBlock(block) {
181
+ const operationKeys = new Set(['base', 'add', 'factor', 'days', 'months', 'years']);
182
+ return {
183
+ ...block,
184
+ entries: block.entries.filter((entry) => entry.type !== 'assignment' || !operationKeys.has(entry.key.value)),
185
+ };
186
+ }
187
+ function applyOperation(current, operation, operand) {
188
+ if (operation === 'base')
189
+ return operand;
190
+ return operation === 'add' ? addIntervals(current, operand) : multiplyIntervals(current, operand);
191
+ }
192
+ export function evaluateValueBlock(block, candidate, scenario, definitions, initial, mtthStack = []) {
193
+ let current = interval(initial);
194
+ const trace = [];
195
+ const unresolved = [];
196
+ const referencedProvenance = [];
197
+ if (block === undefined)
198
+ return { value: initial, interval: current, trace, unresolved };
199
+ for (const assignment of assignments(block)) {
200
+ if (assignment.value.type === 'scalar') {
201
+ let operation;
202
+ let unit = Rational.one;
203
+ if (assignment.key.value === 'base')
204
+ operation = 'base';
205
+ else if (assignment.key.value === 'add')
206
+ operation = 'add';
207
+ else if (assignment.key.value === 'factor')
208
+ operation = 'factor';
209
+ else if (assignment.key.value === 'days')
210
+ operation = 'add';
211
+ else if (assignment.key.value === 'months') {
212
+ operation = 'add';
213
+ unit = new Rational(30n);
214
+ }
215
+ else if (assignment.key.value === 'years') {
216
+ operation = 'add';
217
+ unit = new Rational(365n);
218
+ }
219
+ if (operation === undefined)
220
+ continue;
221
+ const operand = expressionValue(assignment.value.value, candidate, scenario, definitions, mtthStack);
222
+ unresolved.push(...operand.unresolved);
223
+ referencedProvenance.push(...(operand.referencedProvenance ?? []));
224
+ if (operand.interval === undefined)
225
+ continue;
226
+ const scaled = multiplyIntervals(operand.interval, interval(unit));
227
+ const before = current;
228
+ current = applyOperation(current, operation, scaled);
229
+ if (!Number.isFinite(current.min.toNumber()) || !Number.isFinite(current.max.toNumber())) {
230
+ unresolved.push({
231
+ code: 'VALUE_NON_FINITE',
232
+ message: `${candidate.id} modifier chain exceeds finite analyzer range`,
233
+ candidateId: candidate.id,
234
+ ...(nodeProvenance(candidate, assignment, assignment.key.value) === undefined
235
+ ? {}
236
+ : { provenance: nodeProvenance(candidate, assignment, assignment.key.value) }),
237
+ });
238
+ return { trace, unresolved, referencedProvenance };
239
+ }
240
+ trace.push({
241
+ operation,
242
+ expression: assignment.value.value,
243
+ applied: 'true',
244
+ ...(scaled.min.compare(scaled.max) === 0 ? { value: scaled.min.toJSON() } : {}),
245
+ ...(before.min.compare(before.max) === 0 ? { before: before.min.toJSON() } : {}),
246
+ ...(current.min.compare(current.max) === 0 ? { after: current.min.toJSON() } : {}),
247
+ ...(nodeProvenance(candidate, assignment, assignment.key.value) === undefined
248
+ ? {}
249
+ : { provenance: nodeProvenance(candidate, assignment, assignment.key.value) }),
250
+ });
251
+ continue;
252
+ }
253
+ if (assignment.key.value !== 'modifier')
254
+ continue;
255
+ const modifier = assignment.value;
256
+ const condition = evaluateTriggerBlock(conditionBlock(modifier), scenario, candidate, definitions);
257
+ const conditionExpression = conditionSignature(conditionBlock(modifier));
258
+ unresolved.push(...condition.unresolved);
259
+ referencedProvenance.push(...(condition.helperProvenance ?? []));
260
+ for (const modifierAssignment of assignments(modifier)) {
261
+ if (modifierAssignment.value.type !== 'scalar' ||
262
+ !['base', 'add', 'factor'].includes(modifierAssignment.key.value))
263
+ continue;
264
+ const operation = modifierAssignment.key.value;
265
+ const expression = modifierAssignment.value.value;
266
+ const operand = expressionValue(expression, candidate, scenario, definitions, mtthStack);
267
+ unresolved.push(...operand.unresolved);
268
+ referencedProvenance.push(...(operand.referencedProvenance ?? []));
269
+ if (operand.interval === undefined || condition.state === 'false') {
270
+ trace.push({
271
+ operation,
272
+ expression,
273
+ applied: condition.state,
274
+ conditionExpression,
275
+ ...(nodeProvenance(candidate, modifierAssignment, modifierAssignment.key.value) ===
276
+ undefined
277
+ ? {}
278
+ : {
279
+ provenance: nodeProvenance(candidate, modifierAssignment, modifierAssignment.key.value),
280
+ }),
281
+ });
282
+ continue;
283
+ }
284
+ const before = current;
285
+ const applied = applyOperation(current, operation, operand.interval);
286
+ current =
287
+ condition.state === 'true'
288
+ ? applied
289
+ : {
290
+ min: current.min.min(applied.min),
291
+ max: current.max.max(applied.max),
292
+ };
293
+ if (!Number.isFinite(current.min.toNumber()) || !Number.isFinite(current.max.toNumber())) {
294
+ unresolved.push({
295
+ code: 'VALUE_NON_FINITE',
296
+ message: `${candidate.id} modifier chain exceeds finite analyzer range`,
297
+ candidateId: candidate.id,
298
+ ...(nodeProvenance(candidate, modifierAssignment, modifierAssignment.key.value) ===
299
+ undefined
300
+ ? {}
301
+ : {
302
+ provenance: nodeProvenance(candidate, modifierAssignment, modifierAssignment.key.value),
303
+ }),
304
+ });
305
+ return { trace, unresolved, referencedProvenance };
306
+ }
307
+ trace.push({
308
+ operation,
309
+ expression,
310
+ applied: condition.state,
311
+ conditionExpression,
312
+ ...(condition.helperProvenance?.length
313
+ ? {
314
+ note: `Condition helper chain: ${condition.helperProvenance
315
+ .map(({ symbol }) => symbol)
316
+ .filter(Boolean)
317
+ .join(' -> ')}`,
318
+ }
319
+ : {}),
320
+ ...(operand.interval.min.compare(operand.interval.max) === 0
321
+ ? { value: operand.interval.min.toJSON() }
322
+ : {}),
323
+ ...(before.min.compare(before.max) === 0 ? { before: before.min.toJSON() } : {}),
324
+ ...(current.min.compare(current.max) === 0 ? { after: current.min.toJSON() } : {}),
325
+ ...(nodeProvenance(candidate, modifierAssignment, modifierAssignment.key.value) ===
326
+ undefined
327
+ ? {}
328
+ : {
329
+ provenance: nodeProvenance(candidate, modifierAssignment, modifierAssignment.key.value),
330
+ }),
331
+ });
332
+ }
333
+ }
334
+ return {
335
+ ...(current.min.compare(current.max) === 0 ? { value: current.min } : {}),
336
+ interval: current,
337
+ trace,
338
+ unresolved,
339
+ ...(referencedProvenance.length === 0 ? {} : { referencedProvenance }),
340
+ };
341
+ }
342
+ export function evaluateCandidateValue(candidate, scenario, definitions) {
343
+ const initialExpression = candidate.valueExpression ?? candidate.defaultValue;
344
+ const initial = expressionValue(initialExpression, candidate, scenario, definitions, []);
345
+ if (initial.interval === undefined)
346
+ return initial;
347
+ const evaluated = evaluateValueBlock(candidate.weightBlock, candidate, scenario, definitions, initial.interval.min);
348
+ return {
349
+ ...evaluated,
350
+ trace: [...initial.trace, ...evaluated.trace],
351
+ unresolved: [...initial.unresolved, ...evaluated.unresolved],
352
+ referencedProvenance: [
353
+ ...(initial.referencedProvenance ?? []),
354
+ ...(evaluated.referencedProvenance ?? []),
355
+ ],
356
+ };
357
+ }