hoi4-agent-tools 2.7.0 → 2.8.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.
- package/CHANGELOG.md +11 -0
- package/README.md +1 -1
- package/dist/hoi4_agent_tools/mcp/server/create.js +1 -1
- package/dist/hoi4_agent_tools/mcp/server/progress.js +57 -0
- package/dist/hoi4_agent_tools/mcp/tools/gui.js +15 -15
- package/dist/hoi4_agent_tools/mcp/tools/probability.js +9 -0
- package/dist/hoi4_agent_tools/probability/adapters.js +5 -0
- package/dist/hoi4_agent_tools/probability/evaluation.js +6 -1
- package/dist/hoi4_agent_tools/probability/scenario-state.js +165 -0
- package/dist/hoi4_agent_tools/probability/scope-pools.js +199 -0
- package/dist/hoi4_agent_tools/probability/simulation.js +4 -3
- package/dist/hoi4_agent_tools/probability/sweep.js +6 -2
- package/dist/hoi4_agent_tools/probability/trigger-evaluator.js +35 -28
- package/dist/hoi4_agent_tools/probability/value-evaluator.js +4 -0
- package/dist/hoi4_agent_tools/schemas/probability.js +90 -0
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/gui.md +2 -0
- package/docs/probability.md +46 -0
- package/docs/research/probability-adapter-evidence.md +2 -1
- package/examples/probability/scoped-dynamic-pools-evaluate-input.json +50 -0
- package/package.json +1 -1
- package/schemas/configuration.schema.json +1 -1
- package/schemas/continuous-focus-palette.schema.json +1 -1
- package/schemas/custom-weighted-pool.schema.json +1 -1
- package/schemas/event-feature-manifest.schema.json +1 -1
- package/schemas/focus-plan.schema.json +1 -1
- package/schemas/focus-planning-sidecar.schema.json +1 -1
- package/schemas/gui-animation-source.schema.json +1 -1
- package/schemas/gui-helper.schema.json +1 -1
- package/schemas/gui-scenario.schema.json +1 -1
- package/schemas/map-operation.schema.json +1 -1
- package/schemas/operation-result.schema.json +1 -1
- package/schemas/probability-analysis-result.schema.json +175 -66
- package/schemas/probability-compare-input.schema.json +251 -66
- package/schemas/probability-evaluate-input.schema.json +252 -67
- package/schemas/probability-inspect-input.schema.json +1 -1
- package/schemas/probability-render-input.schema.json +1 -1
- package/schemas/probability-scenario-set.schema.json +231 -46
- package/schemas/probability-sequence-input.schema.json +273 -88
- package/schemas/probability-simulate-input.schema.json +252 -67
- package/schemas/probability-sweep-input.schema.json +254 -69
- package/server.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ServiceError } from '../core/result.js';
|
|
2
2
|
import { conditionBreakpointValues, evaluateSurfaceScenarios } from './evaluation.js';
|
|
3
|
+
import { scenarioPathValue, withScenarioPathValue } from './scenario-state.js';
|
|
3
4
|
const MAX_SWEEP_POINTS = 250_000;
|
|
4
5
|
function declaredValues(surface, scenario, path, steps) {
|
|
5
6
|
const uncertain = scenario.uncertainInputs?.find((input) => input.path === path);
|
|
@@ -15,7 +16,7 @@ function declaredValues(surface, scenario, path, steps) {
|
|
|
15
16
|
if (values.length > 0)
|
|
16
17
|
return [...new Set(values)].sort((left, right) => left - right);
|
|
17
18
|
}
|
|
18
|
-
const current = scenario
|
|
19
|
+
const current = scenarioPathValue(scenario, path);
|
|
19
20
|
if (typeof current === 'number')
|
|
20
21
|
return [current];
|
|
21
22
|
throw new ServiceError('PROBABILITY_SWEEP_RANGE_REQUIRED', 'Every sweep path requires a scenario range, numeric alternatives, or numeric state value', { scenarioId: scenario.id, path });
|
|
@@ -26,7 +27,10 @@ function values(range, steps) {
|
|
|
26
27
|
return Array.from({ length: steps }, (_, index) => range.min + ((range.max - range.min) * index) / (steps - 1));
|
|
27
28
|
}
|
|
28
29
|
function point(surface, scenario, definitions, path, value, assigned) {
|
|
29
|
-
const
|
|
30
|
+
const base = { ...scenario, uncertainInputs: [] };
|
|
31
|
+
const evaluated = evaluateSurfaceScenarios(surface, [
|
|
32
|
+
Object.entries(assigned).reduce((updated, [assignedPath, assignedValue]) => withScenarioPathValue(updated, assignedPath, assignedValue), base),
|
|
33
|
+
], definitions)[0];
|
|
30
34
|
return {
|
|
31
35
|
scenarioId: scenario.id,
|
|
32
36
|
path,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { assignments, astPathFor, firstScalar, nodeLocation, } from '../core/source/index.js';
|
|
2
|
+
import { resolveScopeContext, rootScopeContext, rootStateValue, scopeIdentity, scopeStateValue, } from './scenario-state.js';
|
|
2
3
|
function combineAnd(values) {
|
|
3
4
|
if (values.includes('false'))
|
|
4
5
|
return 'false';
|
|
@@ -51,19 +52,13 @@ function unresolved(assignment, candidate, reason) {
|
|
|
51
52
|
],
|
|
52
53
|
};
|
|
53
54
|
}
|
|
54
|
-
function
|
|
55
|
-
if (Object.hasOwn(scenario.state, key))
|
|
56
|
-
return scenario.state[key];
|
|
57
|
-
if (Object.hasOwn(scenario.state, `trigger.${key}`))
|
|
58
|
-
return scenario.state[`trigger.${key}`];
|
|
59
|
-
return undefined;
|
|
60
|
-
}
|
|
61
|
-
function evaluateCheckVariable(block, scenario, candidate, assignment) {
|
|
55
|
+
function evaluateCheckVariable(block, scenario, candidate, assignment, scopeContext) {
|
|
62
56
|
const variable = firstScalar(block, 'var')?.value;
|
|
63
57
|
const target = firstScalar(block, 'value')?.value;
|
|
64
58
|
if (variable === undefined || target === undefined)
|
|
65
59
|
return unresolved(assignment, candidate, 'check_variable requires declared var and value');
|
|
66
|
-
const left =
|
|
60
|
+
const left = scopeStateValue(scenario, `variable.${variable}`, scopeContext) ??
|
|
61
|
+
scopeStateValue(scenario, variable, scopeContext);
|
|
67
62
|
const leftNumber = numeric(left);
|
|
68
63
|
const rightNumber = numeric(target);
|
|
69
64
|
if (leftNumber === undefined || rightNumber === undefined)
|
|
@@ -82,13 +77,13 @@ function evaluateCheckVariable(block, scenario, candidate, assignment) {
|
|
|
82
77
|
: leftNumber === rightNumber;
|
|
83
78
|
return { state: result ? 'true' : 'false', unresolved: [] };
|
|
84
79
|
}
|
|
85
|
-
function evaluateAssignmentCore(assignment, scenario, candidate, definitions, helperStack) {
|
|
80
|
+
function evaluateAssignmentCore(assignment, scenario, candidate, definitions, helperStack, scopeContext) {
|
|
86
81
|
const key = assignment.key.value;
|
|
87
82
|
if (assignment.value.type === 'block') {
|
|
88
83
|
if (key === 'AND')
|
|
89
|
-
return evaluateTriggerBlock(assignment.value, scenario, candidate, definitions, helperStack);
|
|
84
|
+
return evaluateTriggerBlock(assignment.value, scenario, candidate, definitions, helperStack, scopeContext);
|
|
90
85
|
if (key === 'OR') {
|
|
91
|
-
const children = assignments(assignment.value).map((child) => evaluateAssignment(child, scenario, candidate, definitions, helperStack));
|
|
86
|
+
const children = assignments(assignment.value).map((child) => evaluateAssignment(child, scenario, candidate, definitions, helperStack, scopeContext));
|
|
92
87
|
return {
|
|
93
88
|
state: combineOr(children.map(({ state }) => state)),
|
|
94
89
|
unresolved: children.flatMap(({ unresolved: items }) => items),
|
|
@@ -96,7 +91,7 @@ function evaluateAssignmentCore(assignment, scenario, candidate, definitions, he
|
|
|
96
91
|
};
|
|
97
92
|
}
|
|
98
93
|
if (key === 'NOT' || key === 'NOR' || key === 'NAND') {
|
|
99
|
-
const children = assignments(assignment.value).map((child) => evaluateAssignment(child, scenario, candidate, definitions, helperStack));
|
|
94
|
+
const children = assignments(assignment.value).map((child) => evaluateAssignment(child, scenario, candidate, definitions, helperStack, scopeContext));
|
|
100
95
|
const base = key === 'NAND'
|
|
101
96
|
? combineAnd(children.map(({ state }) => state))
|
|
102
97
|
: combineOr(children.map(({ state }) => state));
|
|
@@ -107,18 +102,25 @@ function evaluateAssignmentCore(assignment, scenario, candidate, definitions, he
|
|
|
107
102
|
};
|
|
108
103
|
}
|
|
109
104
|
if (key === 'check_variable')
|
|
110
|
-
return evaluateCheckVariable(assignment.value, scenario, candidate, assignment);
|
|
105
|
+
return evaluateCheckVariable(assignment.value, scenario, candidate, assignment, scopeContext);
|
|
111
106
|
if (key === 'is_in_array' || key === 'is_variable_in_array') {
|
|
112
107
|
const arrayId = firstScalar(assignment.value, 'array')?.value;
|
|
113
108
|
const declaredValue = firstScalar(assignment.value, 'value')?.value;
|
|
114
109
|
if (arrayId === undefined || declaredValue === undefined)
|
|
115
110
|
return unresolved(assignment, candidate, `${key} requires array and value`);
|
|
116
|
-
const array =
|
|
111
|
+
const array = scopeStateValue(scenario, `array.${arrayId}`, scopeContext) ??
|
|
112
|
+
rootStateValue(scenario, `array.${arrayId}`);
|
|
117
113
|
if (!Array.isArray(array))
|
|
118
114
|
return unresolved(assignment, candidate, `Scenario does not declare array ${arrayId}`);
|
|
119
|
-
const compared =
|
|
115
|
+
const compared = ['THIS', 'ROOT', 'PREV'].includes(declaredValue.toUpperCase()) ||
|
|
116
|
+
resolveScopeContext(scenario, declaredValue, scopeContext) !== undefined
|
|
117
|
+
? scopeIdentity(resolveScopeContext(scenario, declaredValue, scopeContext) ?? scopeContext)
|
|
118
|
+
: (scopeStateValue(scenario, declaredValue, scopeContext) ?? declaredValue);
|
|
120
119
|
return { state: array.includes(compared) ? 'true' : 'false', unresolved: [] };
|
|
121
120
|
}
|
|
121
|
+
const resolvedScope = resolveScopeContext(scenario, key, scopeContext);
|
|
122
|
+
if (resolvedScope !== undefined)
|
|
123
|
+
return evaluateTriggerBlock(assignment.value, scenario, candidate, definitions, helperStack, resolvedScope);
|
|
122
124
|
const helper = definitions.scriptedTriggers.get(key);
|
|
123
125
|
if (helper !== undefined) {
|
|
124
126
|
if (helperStack.includes(key))
|
|
@@ -141,13 +143,13 @@ function evaluateAssignmentCore(assignment, scenario, candidate, definitions, he
|
|
|
141
143
|
...candidate,
|
|
142
144
|
document: helper.document,
|
|
143
145
|
provenance: [helperProvenance, ...candidate.provenance],
|
|
144
|
-
}, definitions, [...helperStack, key]);
|
|
146
|
+
}, definitions, [...helperStack, key], scopeContext);
|
|
145
147
|
return {
|
|
146
148
|
...evaluated,
|
|
147
149
|
helperProvenance: [helperProvenance, ...(evaluated.helperProvenance ?? [])],
|
|
148
150
|
};
|
|
149
151
|
}
|
|
150
|
-
const declaredScope = bool(
|
|
152
|
+
const declaredScope = bool(rootStateValue(scenario, `scope.${key}`));
|
|
151
153
|
if (declaredScope !== undefined)
|
|
152
154
|
return { state: declaredScope ? 'true' : 'false', unresolved: [] };
|
|
153
155
|
return unresolved(assignment, candidate, `Scoped or compound trigger ${key} is not declared by the scenario`);
|
|
@@ -160,19 +162,24 @@ function evaluateAssignmentCore(assignment, scenario, candidate, definitions, he
|
|
|
160
162
|
: { state: value ? 'true' : 'false', unresolved: [] };
|
|
161
163
|
}
|
|
162
164
|
if (key === 'has_country_flag' || key === 'has_global_flag' || key === 'has_state_flag') {
|
|
163
|
-
|
|
165
|
+
const flags = scopeContext.expression === 'ROOT' ? scenario.flags : scopeContext.binding.flags;
|
|
166
|
+
return { state: flags?.includes(right) === true ? 'true' : 'false', unresolved: [] };
|
|
164
167
|
}
|
|
165
168
|
if (key === 'has_event_target') {
|
|
169
|
+
const eventTargets = scopeContext.binding.eventTargets ?? scenario.eventTargets;
|
|
166
170
|
return {
|
|
167
|
-
state: Object.hasOwn(
|
|
171
|
+
state: Object.hasOwn(eventTargets ?? {}, right) ? 'true' : 'false',
|
|
168
172
|
unresolved: [],
|
|
169
173
|
};
|
|
170
174
|
}
|
|
171
175
|
if (key === 'tag' || key === 'original_tag') {
|
|
172
|
-
|
|
176
|
+
const actor = scopeContext.expression === 'ROOT'
|
|
177
|
+
? scenario.actor
|
|
178
|
+
: (scopeContext.binding.actor ?? scopeContext.binding.id);
|
|
179
|
+
if (actor === undefined)
|
|
173
180
|
return unresolved(assignment, candidate, `Scenario does not declare actor for ${key}`);
|
|
174
181
|
return {
|
|
175
|
-
state: compare(
|
|
182
|
+
state: compare(actor, right, assignment.operator.text) ? 'true' : 'false',
|
|
176
183
|
unresolved: [],
|
|
177
184
|
};
|
|
178
185
|
}
|
|
@@ -208,13 +215,13 @@ function evaluateAssignmentCore(assignment, scenario, candidate, definitions, he
|
|
|
208
215
|
...candidate,
|
|
209
216
|
document: helper.document,
|
|
210
217
|
provenance: [helperProvenance, ...candidate.provenance],
|
|
211
|
-
}, definitions, [...helperStack, key]);
|
|
218
|
+
}, definitions, [...helperStack, key], scopeContext);
|
|
212
219
|
return {
|
|
213
220
|
...evaluated,
|
|
214
221
|
helperProvenance: [helperProvenance, ...(evaluated.helperProvenance ?? [])],
|
|
215
222
|
};
|
|
216
223
|
}
|
|
217
|
-
const declared =
|
|
224
|
+
const declared = scopeStateValue(scenario, key, scopeContext);
|
|
218
225
|
if (declared === undefined)
|
|
219
226
|
return unresolved(assignment, candidate, `Scenario does not declare trigger ${key}`);
|
|
220
227
|
const expectedBoolean = bool(right);
|
|
@@ -235,8 +242,8 @@ function evaluateAssignmentCore(assignment, scenario, candidate, definitions, he
|
|
|
235
242
|
};
|
|
236
243
|
return unresolved(assignment, candidate, `Trigger ${key} cannot compare the declared scenario value`);
|
|
237
244
|
}
|
|
238
|
-
function evaluateAssignment(assignment, scenario, candidate, definitions, helperStack) {
|
|
239
|
-
const evaluated = evaluateAssignmentCore(assignment, scenario, candidate, definitions, helperStack);
|
|
245
|
+
function evaluateAssignment(assignment, scenario, candidate, definitions, helperStack, scopeContext) {
|
|
246
|
+
const evaluated = evaluateAssignmentCore(assignment, scenario, candidate, definitions, helperStack, scopeContext);
|
|
240
247
|
const source = candidate.provenance[0];
|
|
241
248
|
const provenance = candidate.document === undefined || source === undefined
|
|
242
249
|
? source
|
|
@@ -264,13 +271,13 @@ function evaluateAssignment(assignment, scenario, candidate, definitions, helper
|
|
|
264
271
|
],
|
|
265
272
|
};
|
|
266
273
|
}
|
|
267
|
-
export function evaluateTriggerBlock(block, scenario, candidate, definitions, helperStack = []) {
|
|
274
|
+
export function evaluateTriggerBlock(block, scenario, candidate, definitions, helperStack = [], scopeContext = rootScopeContext(scenario)) {
|
|
268
275
|
const override = scenario.candidateOverrides?.[candidate.id];
|
|
269
276
|
if (override !== undefined)
|
|
270
277
|
return { state: override ? 'true' : 'false', unresolved: [] };
|
|
271
278
|
if (block === undefined)
|
|
272
279
|
return { state: 'true', unresolved: [] };
|
|
273
|
-
const children = assignments(block).map((assignment) => evaluateAssignment(assignment, scenario, candidate, definitions, helperStack));
|
|
280
|
+
const children = assignments(block).map((assignment) => evaluateAssignment(assignment, scenario, candidate, definitions, helperStack, scopeContext));
|
|
274
281
|
return {
|
|
275
282
|
state: combineAnd(children.map(({ state }) => state)),
|
|
276
283
|
unresolved: children.flatMap(({ unresolved: items }) => items),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { assignments, astPathFor, nodeLocation, } from '../core/source/index.js';
|
|
2
2
|
import { Rational } from './rational.js';
|
|
3
|
+
import { scopedExpressionValue } from './scenario-state.js';
|
|
3
4
|
import { evaluateTriggerBlock } from './trigger-evaluator.js';
|
|
4
5
|
function appendReference(evaluated, reference) {
|
|
5
6
|
return {
|
|
@@ -51,6 +52,9 @@ function scenarioNumber(scenario, expression) {
|
|
|
51
52
|
if (expression.startsWith('var:') &&
|
|
52
53
|
Object.hasOwn(scenario.state, `variable.${expression.slice(4)}`))
|
|
53
54
|
return scenario.state[`variable.${expression.slice(4)}`];
|
|
55
|
+
const scoped = scopedExpressionValue(scenario, expression);
|
|
56
|
+
if (scoped !== undefined)
|
|
57
|
+
return scoped;
|
|
54
58
|
return undefined;
|
|
55
59
|
}
|
|
56
60
|
function expressionValue(expression, candidate, scenario, definitions, mtthStack) {
|
|
@@ -82,6 +82,62 @@ const uncertainInputSchema = z
|
|
|
82
82
|
message: 'Range min must not exceed max',
|
|
83
83
|
});
|
|
84
84
|
});
|
|
85
|
+
const probabilityScopeBindingSchema = z
|
|
86
|
+
.object({
|
|
87
|
+
id: z.string().min(1).max(512),
|
|
88
|
+
type: z
|
|
89
|
+
.enum([
|
|
90
|
+
'country',
|
|
91
|
+
'state',
|
|
92
|
+
'character',
|
|
93
|
+
'unit_leader',
|
|
94
|
+
'operative',
|
|
95
|
+
'strategic_region',
|
|
96
|
+
'province',
|
|
97
|
+
'unknown',
|
|
98
|
+
])
|
|
99
|
+
.optional(),
|
|
100
|
+
actor: z.string().max(256).optional(),
|
|
101
|
+
state: z.record(z.string().min(1).max(1024), scenarioValueSchema).default({}),
|
|
102
|
+
flags: z.array(z.string().min(1).max(512)).max(100_000).optional(),
|
|
103
|
+
eventTargets: z.record(z.string().max(512), z.string().max(512)).optional(),
|
|
104
|
+
weight: z.union([z.number().nonnegative(), z.string().min(1).max(1024)]).optional(),
|
|
105
|
+
})
|
|
106
|
+
.strict();
|
|
107
|
+
const probabilityScopePoolSchema = z
|
|
108
|
+
.object({
|
|
109
|
+
id: z.string().min(1).max(512),
|
|
110
|
+
bindAs: z.string().min(1).max(512).default('THIS'),
|
|
111
|
+
selection: z
|
|
112
|
+
.enum(['enumeration', 'uniform', 'proportional_categorical'])
|
|
113
|
+
.default('enumeration'),
|
|
114
|
+
complete: z.boolean().default(false),
|
|
115
|
+
filter: z
|
|
116
|
+
.object({
|
|
117
|
+
inlineClausewitz: z.string().min(1).max(1_048_576).optional(),
|
|
118
|
+
scriptedTrigger: z.string().min(1).max(512).optional(),
|
|
119
|
+
})
|
|
120
|
+
.strict()
|
|
121
|
+
.superRefine((filter, context) => {
|
|
122
|
+
if ((filter.inlineClausewitz === undefined) === (filter.scriptedTrigger === undefined))
|
|
123
|
+
context.addIssue({
|
|
124
|
+
code: 'custom',
|
|
125
|
+
message: 'Scope-pool filter requires exactly one inlineClausewitz or scriptedTrigger',
|
|
126
|
+
});
|
|
127
|
+
})
|
|
128
|
+
.optional(),
|
|
129
|
+
candidates: z.array(probabilityScopeBindingSchema).min(1).max(100_000),
|
|
130
|
+
})
|
|
131
|
+
.strict()
|
|
132
|
+
.superRefine((pool, context) => {
|
|
133
|
+
const ids = pool.candidates.map(({ id }) => id);
|
|
134
|
+
if (new Set(ids).size !== ids.length)
|
|
135
|
+
context.addIssue({
|
|
136
|
+
code: 'custom',
|
|
137
|
+
path: ['candidates'],
|
|
138
|
+
message: 'Scope-pool candidate IDs must be unique',
|
|
139
|
+
});
|
|
140
|
+
});
|
|
85
141
|
export const probabilityScenarioSchema = z
|
|
86
142
|
.object({
|
|
87
143
|
id: z.string().min(1).max(512),
|
|
@@ -92,6 +148,8 @@ export const probabilityScenarioSchema = z
|
|
|
92
148
|
state: z.record(z.string().min(1).max(1024), scenarioValueSchema),
|
|
93
149
|
flags: z.array(z.string().min(1).max(512)).max(100_000).optional(),
|
|
94
150
|
eventTargets: z.record(z.string().max(512), z.string().max(512)).optional(),
|
|
151
|
+
scopes: z.record(z.string().min(1).max(512), probabilityScopeBindingSchema).optional(),
|
|
152
|
+
scopePools: z.array(probabilityScopePoolSchema).max(100_000).optional(),
|
|
95
153
|
candidateOverrides: z.record(z.string().max(512), z.boolean()).optional(),
|
|
96
154
|
uncertainInputs: z.array(uncertainInputSchema).max(10_000).optional(),
|
|
97
155
|
correlations: z
|
|
@@ -118,6 +176,13 @@ export const probabilityScenarioSchema = z
|
|
|
118
176
|
})
|
|
119
177
|
.strict()
|
|
120
178
|
.superRefine((scenario, context) => {
|
|
179
|
+
const poolIds = scenario.scopePools?.map(({ id }) => id) ?? [];
|
|
180
|
+
if (new Set(poolIds).size !== poolIds.length)
|
|
181
|
+
context.addIssue({
|
|
182
|
+
code: 'custom',
|
|
183
|
+
path: ['scopePools'],
|
|
184
|
+
message: 'Scope-pool IDs must be unique within a scenario',
|
|
185
|
+
});
|
|
121
186
|
const paths = scenario.uncertainInputs?.map(({ path }) => path) ?? [];
|
|
122
187
|
if (new Set(paths).size !== paths.length)
|
|
123
188
|
context.addIssue({
|
|
@@ -596,6 +661,30 @@ const candidateAnalysisSchema = z
|
|
|
596
661
|
unresolved: z.array(probabilityUnresolvedSchema),
|
|
597
662
|
})
|
|
598
663
|
.strict();
|
|
664
|
+
const scopePoolCandidateAnalysisSchema = z
|
|
665
|
+
.object({
|
|
666
|
+
id: z.string(),
|
|
667
|
+
eligibility: z.enum(['true', 'false', 'unresolved']),
|
|
668
|
+
weight: rationalJsonSchema.optional(),
|
|
669
|
+
conditionalProbability: z.number().min(0).max(1).optional(),
|
|
670
|
+
exactConditionalProbability: rationalJsonSchema.optional(),
|
|
671
|
+
trace: z.array(valueTraceSchema),
|
|
672
|
+
unresolved: z.array(probabilityUnresolvedSchema),
|
|
673
|
+
})
|
|
674
|
+
.strict();
|
|
675
|
+
const scopePoolAnalysisSchema = z
|
|
676
|
+
.object({
|
|
677
|
+
id: z.string(),
|
|
678
|
+
bindAs: z.string(),
|
|
679
|
+
selection: z.enum(['enumeration', 'uniform', 'proportional_categorical']),
|
|
680
|
+
poolComplete: z.boolean(),
|
|
681
|
+
candidates: z.array(scopePoolCandidateAnalysisSchema),
|
|
682
|
+
eligibleCandidateIds: z.array(z.string()),
|
|
683
|
+
unresolvedCandidateIds: z.array(z.string()),
|
|
684
|
+
poolTotal: rationalJsonSchema.optional(),
|
|
685
|
+
unresolved: z.array(probabilityUnresolvedSchema),
|
|
686
|
+
})
|
|
687
|
+
.strict();
|
|
599
688
|
const scenarioAnalysisSchema = z
|
|
600
689
|
.object({
|
|
601
690
|
id: z.string(),
|
|
@@ -623,6 +712,7 @@ const scenarioAnalysisSchema = z
|
|
|
623
712
|
})
|
|
624
713
|
.strict())
|
|
625
714
|
.optional(),
|
|
715
|
+
scopePools: z.array(scopePoolAnalysisSchema).optional(),
|
|
626
716
|
summary: z
|
|
627
717
|
.object({
|
|
628
718
|
topOutcomes: z.array(z.string()),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/sdk/types.js';
|
|
2
2
|
export const PACKAGE_NAME = 'hoi4-agent-tools';
|
|
3
|
-
export const PACKAGE_VERSION = '2.
|
|
3
|
+
export const PACKAGE_VERSION = '2.8.1';
|
|
4
4
|
export const MCP_PROTOCOL_VERSION = LATEST_PROTOCOL_VERSION;
|
|
5
5
|
export const CONFIG_VERSION = 1;
|
|
6
6
|
export const TRANSACTION_VERSION = 1;
|
package/docs/gui.md
CHANGED
|
@@ -58,6 +58,8 @@ Broad inspection indexes localisation actually referenced by GUI source and retu
|
|
|
58
58
|
|
|
59
59
|
GUI rewrites keep complete validation evidence in a linked JSON resource and retain the highest-priority findings in the transaction result. Large surrounding interface inventories therefore do not turn a small scalar patch into a transaction structure-limit error.
|
|
60
60
|
|
|
61
|
+
Large inspections, renders, and rewrites send periodic MCP progress heartbeats while a service stage is still running. Clients that request progress can reset their idle request timer from these notifications; there is no 180-second GUI operation limit in the server, and cancellation still follows the request signal.
|
|
62
|
+
|
|
61
63
|
## Offline preview limits
|
|
62
64
|
|
|
63
65
|
The renderer does not run the game engine. Each render includes a fidelity report that separates fields it models from fields it approximates, ignores, cannot resolve, or does not support. Treat that report as part of the review.
|
package/docs/probability.md
CHANGED
|
@@ -42,6 +42,50 @@ A scenario contains only state the caller is willing to declare. Missing values
|
|
|
42
42
|
}
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
Use `scopes` to bind exact Clausewitz scope expressions. A binding carries its own actor, state, flags, and event targets, so a `FROM` trigger is evaluated against `FROM` rather than the root country. Chained names such as `FROM.FROM`, plus `ROOT`, `THIS`, `PREV`, `scope:name`, and `event_target:name`, are resolved without replacing their inner conditions with a single boolean. Scoped numeric expressions such as `FROM.variable_name` and uncertain paths such as `scopes.FROM.variable.variable_name` use the same binding.
|
|
46
|
+
|
|
47
|
+
Use `scopePools` when source logic builds a destination, target, opposition, donor, or other runtime candidate set. Each pool supplies a candidate catalog, the scope alias to bind, and either an inline trigger filter or a named scripted trigger. The result enumerates every eligible, excluded, and unresolved candidate. `uniform` and `proportional_categorical` pools also return exact shares when `complete` is true; an incomplete catalog is still enumerated but is not normalized.
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"id": "relief_routes",
|
|
52
|
+
"state": {},
|
|
53
|
+
"scopes": {
|
|
54
|
+
"FROM": {
|
|
55
|
+
"id": "origin",
|
|
56
|
+
"type": "country",
|
|
57
|
+
"actor": "FRA",
|
|
58
|
+
"state": { "variable.relief_pressure": 7 }
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"scopePools": [
|
|
62
|
+
{
|
|
63
|
+
"id": "relief_donor",
|
|
64
|
+
"bindAs": "FROM",
|
|
65
|
+
"selection": "proportional_categorical",
|
|
66
|
+
"complete": true,
|
|
67
|
+
"filter": {
|
|
68
|
+
"inlineClausewitz": "FROM = { check_variable = { var = relief_ready value = 1 compare = greater_than_or_equals } }"
|
|
69
|
+
},
|
|
70
|
+
"candidates": [
|
|
71
|
+
{
|
|
72
|
+
"id": "GER",
|
|
73
|
+
"actor": "GER",
|
|
74
|
+
"state": { "variable.relief_ready": 1, "variable.donor_weight": 1 },
|
|
75
|
+
"weight": "FROM.donor_weight"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": "ITA",
|
|
79
|
+
"actor": "ITA",
|
|
80
|
+
"state": { "variable.relief_ready": 1, "variable.donor_weight": 3 },
|
|
81
|
+
"weight": "FROM.donor_weight"
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
45
89
|
Focus, technology, and doctrine probabilities require a complete candidate pool. Their engine rule is an independent uniform score race, not weight divided by total weight. Focus scenarios use `focus.external_factors_complete: true` only after the caller has supplied every relevant prerequisite and strategy factor. Technology and doctrine scenarios use `technology.external_factors_complete: true` only after cost, date, bonus, strategy, and candidate effects are accounted for.
|
|
46
90
|
|
|
47
91
|
Decision and mission adapters intentionally return scores and ranks without inventing normalized probabilities. Event-option and `random_list` adapters normalize only their complete local pools. Direct random remains an independent percentage. MTTH horizon chance uses the versioned game timing model and returns a bound when an inactive-to-active polling phase is unknown.
|
|
@@ -61,6 +105,8 @@ Authoritative JSON keeps these fields separate:
|
|
|
61
105
|
|
|
62
106
|
Every candidate includes source provenance with a stable AST path, an ordered modifier trace, support level, and unresolved analysis. `external` support means the parsed candidate is valid but its surrounding game factors were not fully declared. The tool response stays compact; complete matrices, traces, simulations, comparisons, and visuals are linked MCP resources.
|
|
63
107
|
|
|
108
|
+
Every evaluated scope pool is stored with the scenario in authoritative JSON. Pool rows include eligibility, resolved weight, exact conditional share when supported, trigger traces, eligible IDs, unresolved IDs, and unresolved evidence. Compact MCP responses report pool and pool-candidate counts without copying the full catalog into the prompt.
|
|
109
|
+
|
|
64
110
|
Nested `random_list` entries report both their conditional share inside the immediate list and their full path probability through every enclosing list. Dynamic parent paths remain explicit unresolved evidence.
|
|
65
111
|
|
|
66
112
|
Deterministic simulation uses constant-memory Latin hypercube sampling by default, with seeded pseudo-random sampling available when requested. Numeric distributions can use a Gaussian copula correlation matrix. Discrete or categorical correlation requests are sampled independently and reported as unresolved instead of being approximated silently. Simulation reports Wilson intervals, effective sample count, global input importance, and HOI4 daily-hazard MTTH samples. Timing quantiles use a deterministic bounded reservoir and include their confidence basis and retained sample count.
|
|
@@ -55,7 +55,8 @@ When a game root is configured, adapter execution verifies `launcher-settings.js
|
|
|
55
55
|
The analyzer reports, but does not guess or execute:
|
|
56
56
|
|
|
57
57
|
- arbitrary effects used to calculate later weights;
|
|
58
|
-
-
|
|
58
|
+
- scope chains, event targets, game rules, DLC state, dynamic identifiers, meta effects, or scripted-localisation results that are neither present in source nor declared through structured scenario bindings;
|
|
59
|
+
- runtime scope pools whose candidate catalog is not declared; declared catalogs are filtered and enumerated exactly, while normalized shares additionally require explicit completeness;
|
|
59
60
|
- parameterized scripted triggers whose arguments cannot be resolved from the selected source;
|
|
60
61
|
- recursive MTTH variables or helper cycles;
|
|
61
62
|
- incomplete focus, technology, doctrine, event-option, or random-list candidate pools;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adapter": "event_option_ai_chance",
|
|
3
|
+
"source": {
|
|
4
|
+
"inlineClausewitz": "country_event = { id = scoped.1 option = { name = scoped.1.a ai_chance = { base = 1 modifier = { factor = 3 FROM = { tag = FRA } } } } option = { name = scoped.1.b ai_chance = { base = 1 } } }"
|
|
5
|
+
},
|
|
6
|
+
"scenarioSet": {
|
|
7
|
+
"schemaVersion": "1.0",
|
|
8
|
+
"id": "scoped_dynamic_pools",
|
|
9
|
+
"scenarios": [
|
|
10
|
+
{
|
|
11
|
+
"id": "france_origin",
|
|
12
|
+
"state": {},
|
|
13
|
+
"scopes": {
|
|
14
|
+
"FROM": {
|
|
15
|
+
"id": "origin",
|
|
16
|
+
"type": "country",
|
|
17
|
+
"actor": "FRA",
|
|
18
|
+
"state": {}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"scopePools": [
|
|
22
|
+
{
|
|
23
|
+
"id": "destination",
|
|
24
|
+
"bindAs": "FROM",
|
|
25
|
+
"selection": "uniform",
|
|
26
|
+
"complete": true,
|
|
27
|
+
"filter": {
|
|
28
|
+
"inlineClausewitz": "FROM = { is_ai = yes }"
|
|
29
|
+
},
|
|
30
|
+
"candidates": [
|
|
31
|
+
{
|
|
32
|
+
"id": "GER",
|
|
33
|
+
"type": "country",
|
|
34
|
+
"actor": "GER",
|
|
35
|
+
"state": { "is_ai": true }
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "ITA",
|
|
39
|
+
"type": "country",
|
|
40
|
+
"actor": "ITA",
|
|
41
|
+
"state": { "is_ai": true }
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"outputs": ["json", "unresolved"]
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hoi4-agent-tools",
|
|
3
3
|
"mcpName": "io.github.klimPaskov/hoi4-agent-tools",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.8.1",
|
|
5
5
|
"description": "Analyze HOI4 events, tech trees, AI weights, and MTTH; build focus trees, GUIs, and maps.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "Apache-2.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/configuration.schema.json",
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/continuous-focus-palette.schema.json",
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/custom-weighted-pool.schema.json",
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/event-feature-manifest.schema.json",
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/focus-plan.schema.json",
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/focus-planning-sidecar.schema.json",
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/gui-animation-source.schema.json",
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/gui-helper.schema.json",
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/gui-scenario.schema.json",
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/map-operation.schema.json",
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
4
|
"oneOf": [
|
|
5
5
|
{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/operation-result.schema.json",
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|