hoi4-agent-tools 3.3.0 → 3.4.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 +22 -0
- package/dist/hoi4_agent_tools/core/condition-evaluator.js +20 -8
- package/dist/hoi4_agent_tools/core/helper-expansion.js +3 -0
- package/dist/hoi4_agent_tools/core/index.js +1 -0
- package/dist/hoi4_agent_tools/core/job-worker-host.js +19 -1
- package/dist/hoi4_agent_tools/core/operation-result.js +4 -1
- package/dist/hoi4_agent_tools/event/comparison-selection.js +78 -0
- package/dist/hoi4_agent_tools/event/helper-expansion.js +48 -0
- package/dist/hoi4_agent_tools/event/operations.js +32 -10
- package/dist/hoi4_agent_tools/event/service.js +21 -2
- package/dist/hoi4_agent_tools/focus/compact.js +241 -27
- package/dist/hoi4_agent_tools/focus/layout.js +119 -8
- package/dist/hoi4_agent_tools/focus/render.js +62 -0
- package/dist/hoi4_agent_tools/focus/scenario.js +99 -0
- package/dist/hoi4_agent_tools/focus/tool-operations.js +30 -4
- package/dist/hoi4_agent_tools/gui/assets.js +54 -4
- package/dist/hoi4_agent_tools/gui/layout.js +935 -104
- package/dist/hoi4_agent_tools/gui/renderer.js +47 -17
- package/dist/hoi4_agent_tools/gui/shader.js +515 -0
- package/dist/hoi4_agent_tools/gui/source-graph.js +166 -6
- package/dist/hoi4_agent_tools/gui/studio.js +245 -19
- package/dist/hoi4_agent_tools/gui/tool-operations.js +9 -0
- package/dist/hoi4_agent_tools/gui/validators.js +41 -12
- package/dist/hoi4_agent_tools/map/operations.js +70 -1
- package/dist/hoi4_agent_tools/map/render.js +70 -1
- package/dist/hoi4_agent_tools/map/service.js +30 -6
- package/dist/hoi4_agent_tools/map/tool-operations.js +67 -1
- package/dist/hoi4_agent_tools/mcp/tools/event.js +10 -1
- package/dist/hoi4_agent_tools/mcp/tools/focus.js +14 -4
- package/dist/hoi4_agent_tools/mcp/tools/gui.js +5 -1
- package/dist/hoi4_agent_tools/mcp/tools/map.js +12 -2
- package/dist/hoi4_agent_tools/mcp/tools/probability.js +5 -0
- package/dist/hoi4_agent_tools/mechanic/interpreter.js +91 -30
- package/dist/hoi4_agent_tools/mechanic/source.js +19 -4
- package/dist/hoi4_agent_tools/probability/evaluation.js +3 -0
- package/dist/hoi4_agent_tools/probability/operations.js +8 -0
- package/dist/hoi4_agent_tools/probability/service.js +33 -12
- package/dist/hoi4_agent_tools/probability/source-analysis.js +1 -0
- package/dist/hoi4_agent_tools/schemas/event.js +8 -0
- package/dist/hoi4_agent_tools/schemas/focus-requests.js +40 -1
- package/dist/hoi4_agent_tools/schemas/gui-requests.js +8 -0
- package/dist/hoi4_agent_tools/schemas/map-requests.js +22 -1
- package/dist/hoi4_agent_tools/schemas/technology.js +7 -0
- package/dist/hoi4_agent_tools/technology/graph.js +25 -10
- package/dist/hoi4_agent_tools/technology/operations.js +29 -1
- package/dist/hoi4_agent_tools/technology/render.js +287 -31
- package/dist/hoi4_agent_tools/technology/service.js +56 -8
- package/dist/hoi4_agent_tools/technology/source-analysis.js +166 -3
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/events.md +4 -2
- package/docs/focus.md +10 -0
- package/docs/gui.md +50 -3
- package/docs/jobs.md +1 -0
- package/docs/map.md +8 -0
- package/docs/mechanics.md +2 -1
- package/docs/probability.md +3 -1
- package/docs/technology.md +11 -2
- 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/decision-inspect-input.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/helper-expansion-request.schema.json +1 -1
- package/schemas/helper-expansion-summary.schema.json +1 -1
- package/schemas/impact-inspect-input.schema.json +1 -1
- package/schemas/map-operation.schema.json +1 -1
- package/schemas/mechanic-test-input.schema.json +1 -1
- package/schemas/operation-result.schema.json +1 -1
- package/schemas/package-check-input.schema.json +1 -1
- package/schemas/package-manifest.schema.json +1 -1
- package/schemas/probability-analysis-result.schema.json +1 -1
- package/schemas/probability-compare-input.schema.json +1 -1
- package/schemas/probability-evaluate-input.schema.json +1 -1
- 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 +1 -1
- package/schemas/probability-sequence-input.schema.json +1 -1
- package/schemas/probability-simulate-input.schema.json +1 -1
- package/schemas/probability-sweep-input.schema.json +1 -1
- package/schemas/scenario-suite.schema.json +1 -1
- package/schemas/scenario-test-input.schema.json +1 -1
- package/schemas/shared-scenario.schema.json +1 -1
- package/server.json +2 -2
|
@@ -21,6 +21,9 @@ export function probabilityAnalysisData(result) {
|
|
|
21
21
|
cacheKey: result.metadata.cacheKey,
|
|
22
22
|
scenarios: result.scenarios.length,
|
|
23
23
|
candidates: result.scenarios.reduce((sum, scenario) => sum + scenario.candidates.length, 0),
|
|
24
|
+
eligibleCandidates: result.scenarios.reduce((sum, scenario) => sum + scenario.candidates.filter(({ eligibility }) => eligibility === 'true').length, 0),
|
|
25
|
+
excludedCandidates: result.scenarios.reduce((sum, scenario) => sum + scenario.candidates.filter(({ eligibility }) => eligibility === 'false').length, 0),
|
|
26
|
+
unresolvedCandidates: result.scenarios.reduce((sum, scenario) => sum + scenario.candidates.filter(({ eligibility }) => eligibility === 'unresolved').length, 0),
|
|
24
27
|
...(result.scenarios.some(({ scopePools }) => (scopePools?.length ?? 0) > 0)
|
|
25
28
|
? {
|
|
26
29
|
scopePools: result.scenarios.reduce((sum, scenario) => sum + (scenario.scopePools?.length ?? 0), 0),
|
|
@@ -87,6 +90,8 @@ export function normalizeProbabilityRenderRequest(input) {
|
|
|
87
90
|
}
|
|
88
91
|
export async function inspectProbabilities(analyzer, input, context) {
|
|
89
92
|
const inspected = await analyzer.inspect(runtime(context, input.refresh), input.adapter, input.source, input.candidatePool, input.customPoolManifest);
|
|
93
|
+
const sourceRequiredInputPaths = (inspected.surface?.requiredInputs ?? []).filter((path) => path !== 'focus.external_factors_complete' && path !== 'technology.external_factors_complete');
|
|
94
|
+
const adapterRequiredInputPaths = (inspected.surface?.requiredInputs ?? []).filter((path) => path === 'focus.external_factors_complete' || path === 'technology.external_factors_complete');
|
|
90
95
|
const result = emptyServiceResult(context.workspaceId, {
|
|
91
96
|
adapters: inspected.adapters.length,
|
|
92
97
|
...(inspected.surface === undefined
|
|
@@ -120,6 +125,9 @@ export async function inspectProbabilities(analyzer, input, context) {
|
|
|
120
125
|
})) ?? [],
|
|
121
126
|
candidateExamples: inspected.discovery?.exampleCandidateIds ?? [],
|
|
122
127
|
requiredInputs: inspected.surface?.requiredInputs.length ?? 0,
|
|
128
|
+
requiredInputPaths: sourceRequiredInputPaths.slice(0, 32),
|
|
129
|
+
requiredInputPathsTruncated: sourceRequiredInputPaths.length > 32,
|
|
130
|
+
adapterRequiredInputPaths,
|
|
123
131
|
unresolved: inspected.surface?.unsupported.length ?? 0,
|
|
124
132
|
});
|
|
125
133
|
result.code =
|
|
@@ -9,7 +9,7 @@ import { PACKAGE_VERSION } from '../version.js';
|
|
|
9
9
|
import { probabilityAnalysisResultSchema } from '../schemas/probability.js';
|
|
10
10
|
import { probabilityAdapter, probabilityAdapters } from './adapters.js';
|
|
11
11
|
import { compareProbabilityResults } from './compare.js';
|
|
12
|
-
import { evaluateExactCandidates, evaluateSurfaceScenarios } from './evaluation.js';
|
|
12
|
+
import { evaluateExactCandidates, evaluateSurfaceScenarios, } from './evaluation.js';
|
|
13
13
|
import { VERIFIED_GAME_CHECKSUM, VERIFIED_GAME_RAW_VERSION, VERIFIED_GAME_VERSION, } from './model.js';
|
|
14
14
|
import { renderProbabilityResult } from './render.js';
|
|
15
15
|
import { analyzeSequence, evaluateCustomPoolPoint } from './sequence.js';
|
|
@@ -17,6 +17,28 @@ import { simulateSurface } from './simulation.js';
|
|
|
17
17
|
import { Rational } from './rational.js';
|
|
18
18
|
import { discoverWeightedSurface, inventoryWeightedSurfaces, } from './source-analysis.js';
|
|
19
19
|
import { sweepSurface } from './sweep.js';
|
|
20
|
+
function missingScenarioInputs(unresolved) {
|
|
21
|
+
return [
|
|
22
|
+
...new Set(unresolved.flatMap(({ code, path, details }) => {
|
|
23
|
+
if (typeof details?.scenarioInput === 'string')
|
|
24
|
+
return [details.scenarioInput];
|
|
25
|
+
if (code === 'VALUE_UNRESOLVED' && path !== undefined && !/^(?:@|constant:)/u.test(path))
|
|
26
|
+
return [path];
|
|
27
|
+
if (path !== undefined &&
|
|
28
|
+
(code === 'FOCUS_EXTERNAL_FACTORS_UNDECLARED' ||
|
|
29
|
+
code === 'TECHNOLOGY_EXTERNAL_FACTORS_UNDECLARED'))
|
|
30
|
+
return [path];
|
|
31
|
+
return [];
|
|
32
|
+
})),
|
|
33
|
+
].sort(compareCodeUnits);
|
|
34
|
+
}
|
|
35
|
+
function blockingInspectionUnresolved(candidate) {
|
|
36
|
+
if (candidate.eligibility === 'false')
|
|
37
|
+
return [];
|
|
38
|
+
if (candidate.eligibility === 'unresolved')
|
|
39
|
+
return [...candidate.eligibilityUnresolved, ...candidate.externalUnresolved];
|
|
40
|
+
return [...candidate.valueUnresolved, ...candidate.externalUnresolved];
|
|
41
|
+
}
|
|
20
42
|
const states = new WeakMap();
|
|
21
43
|
const ANALYSIS_CACHE_ENTRIES = 16;
|
|
22
44
|
function resolveSourceBackedAdapter(snapshot, source, candidatePool, requested) {
|
|
@@ -1063,9 +1085,7 @@ export class ProbabilityAnalyzer {
|
|
|
1063
1085
|
}
|
|
1064
1086
|
const definitions = ClausewitzEvaluationDefinitions.build(snapshot);
|
|
1065
1087
|
const inspectedCandidates = evaluateExactCandidates(surface, { id: 'inspection', state: {} }, definitions);
|
|
1066
|
-
const requiredInputs =
|
|
1067
|
-
...new Set(inspectedCandidates.flatMap(({ unresolved }) => unresolved.flatMap(({ path }) => (path === undefined ? [] : [path])))),
|
|
1068
|
-
].sort();
|
|
1088
|
+
const requiredInputs = missingScenarioInputs(inspectedCandidates.flatMap(blockingInspectionUnresolved));
|
|
1069
1089
|
const report = {
|
|
1070
1090
|
schemaVersion: 'probability-inspection.v2',
|
|
1071
1091
|
workspaceId: context.workspaceId,
|
|
@@ -1079,9 +1099,8 @@ export class ProbabilityAnalyzer {
|
|
|
1079
1099
|
candidateCount: surface.candidates.length,
|
|
1080
1100
|
candidates: surface.candidates.map((candidate, index) => {
|
|
1081
1101
|
const evaluated = inspectedCandidates[index];
|
|
1082
|
-
const
|
|
1083
|
-
|
|
1084
|
-
].sort();
|
|
1102
|
+
const blockingUnresolved = blockingInspectionUnresolved(evaluated);
|
|
1103
|
+
const candidateRequiredInputs = missingScenarioInputs(blockingUnresolved);
|
|
1085
1104
|
return {
|
|
1086
1105
|
id: candidate.id,
|
|
1087
1106
|
sourceKind: candidate.sourceKind,
|
|
@@ -1092,11 +1111,13 @@ export class ProbabilityAnalyzer {
|
|
|
1092
1111
|
referencedProvenance: evaluated.provenance.filter(({ path, symbol }) => !candidate.provenance.some((source) => source.path === path && source.symbol === symbol)),
|
|
1093
1112
|
analysisSupport: candidateRequiredInputs.length > 0
|
|
1094
1113
|
? 'requires_scenario'
|
|
1095
|
-
:
|
|
1096
|
-
? '
|
|
1097
|
-
: surface.adapter.
|
|
1098
|
-
? '
|
|
1099
|
-
:
|
|
1114
|
+
: blockingUnresolved.length > 0
|
|
1115
|
+
? 'unresolved_source'
|
|
1116
|
+
: surface.adapter.selectionRule === 'score_only'
|
|
1117
|
+
? 'score_only'
|
|
1118
|
+
: surface.adapter.completePoolRequired && !surface.poolComplete
|
|
1119
|
+
? 'requires_complete_pool'
|
|
1120
|
+
: 'exact',
|
|
1100
1121
|
};
|
|
1101
1122
|
}),
|
|
1102
1123
|
requiredInputs,
|
|
@@ -421,6 +421,7 @@ export function inventoryWeightedSurfaces(snapshot, source, candidatePool = [],
|
|
|
421
421
|
? candidates.length
|
|
422
422
|
: candidates.filter(({ id }) => wanted.has(id)).length,
|
|
423
423
|
exampleCandidateIds: [...new Set(candidates.map(({ id }) => id))].slice(0, 10),
|
|
424
|
+
candidateCatalog: candidates.map(({ id, provenance }) => ({ id, provenance })),
|
|
424
425
|
sourcePaths: [
|
|
425
426
|
...new Set(candidates.flatMap(({ provenance }) => provenance.map(({ path }) => path))),
|
|
426
427
|
].sort(compareCodeUnits),
|
|
@@ -151,6 +151,12 @@ export function validateEventCompareRequest(value, context) {
|
|
|
151
151
|
path: ['after'],
|
|
152
152
|
message: 'after and proposedSources are mutually exclusive',
|
|
153
153
|
});
|
|
154
|
+
if (value.maxChainNodes !== undefined && value.selector === undefined)
|
|
155
|
+
context.addIssue({
|
|
156
|
+
code: 'custom',
|
|
157
|
+
path: ['maxChainNodes'],
|
|
158
|
+
message: 'maxChainNodes requires selector',
|
|
159
|
+
});
|
|
154
160
|
}
|
|
155
161
|
export const eventCompareRequestSchema = z
|
|
156
162
|
.object({
|
|
@@ -158,6 +164,8 @@ export const eventCompareRequestSchema = z
|
|
|
158
164
|
before: eventGraphReferenceSchema.optional(),
|
|
159
165
|
after: eventGraphReferenceSchema.optional(),
|
|
160
166
|
proposedSources: z.array(eventProposedSourceSchema).min(1).max(64).optional(),
|
|
167
|
+
selector: eventSelectorSchema.optional(),
|
|
168
|
+
maxChainNodes: z.number().int().min(1).max(5_000).optional(),
|
|
161
169
|
render: z.boolean().optional(),
|
|
162
170
|
maxRenderNodes: z.number().int().min(1).max(240).optional(),
|
|
163
171
|
refresh: z.boolean().optional(),
|
|
@@ -17,6 +17,10 @@ export const focusInspectRequestSchema = z
|
|
|
17
17
|
previous: compactFocusLayoutSchema.optional(),
|
|
18
18
|
laneSpacing: z.number().int().min(1).max(100).optional(),
|
|
19
19
|
nodeSpacing: z.number().int().min(1).max(100).optional(),
|
|
20
|
+
scenario: z
|
|
21
|
+
.object({ completedFocusIds: z.array(z.string().min(1).max(256)).max(10_000) })
|
|
22
|
+
.strict()
|
|
23
|
+
.optional(),
|
|
20
24
|
})
|
|
21
25
|
.strict()
|
|
22
26
|
.superRefine((value, context) => {
|
|
@@ -34,7 +38,7 @@ export const focusInspectRequestSchema = z
|
|
|
34
38
|
message: 'treeId is only valid in national mode',
|
|
35
39
|
});
|
|
36
40
|
if (mode === 'continuous') {
|
|
37
|
-
for (const field of ['previous', 'laneSpacing', 'nodeSpacing']) {
|
|
41
|
+
for (const field of ['previous', 'laneSpacing', 'nodeSpacing', 'scenario']) {
|
|
38
42
|
if (value[field] !== undefined)
|
|
39
43
|
context.addIssue({
|
|
40
44
|
code: 'custom',
|
|
@@ -49,6 +53,12 @@ export const focusInspectRequestSchema = z
|
|
|
49
53
|
path: ['previous'],
|
|
50
54
|
message: 'previous requires treeId so it applies to exactly one tree',
|
|
51
55
|
});
|
|
56
|
+
if (value.scenario !== undefined && value.treeId === undefined)
|
|
57
|
+
context.addIssue({
|
|
58
|
+
code: 'custom',
|
|
59
|
+
path: ['scenario'],
|
|
60
|
+
message: 'scenario requires treeId so it applies to exactly one tree',
|
|
61
|
+
});
|
|
52
62
|
});
|
|
53
63
|
export const focusRenderRequestSchema = z
|
|
54
64
|
.object({
|
|
@@ -64,6 +74,7 @@ export const focusRenderRequestSchema = z
|
|
|
64
74
|
.min(FOCUS_RENDER_MIN_OUTPUT_SCALE)
|
|
65
75
|
.max(FOCUS_RENDER_MAX_OUTPUT_SCALE)
|
|
66
76
|
.optional(),
|
|
77
|
+
cropFocusIds: z.array(z.string().min(1).max(256)).max(16).optional(),
|
|
67
78
|
columns: z.number().int().min(1).max(12).optional(),
|
|
68
79
|
padding: z.number().int().min(0).max(1000).optional(),
|
|
69
80
|
})
|
|
@@ -80,6 +91,7 @@ export const focusRenderRequestSchema = z
|
|
|
80
91
|
['horizontalSpacing', value.horizontalSpacing],
|
|
81
92
|
['verticalSpacing', value.verticalSpacing],
|
|
82
93
|
['reviewScale', value.reviewScale],
|
|
94
|
+
['cropFocusIds', value.cropFocusIds],
|
|
83
95
|
];
|
|
84
96
|
for (const [field, fieldValue] of invalid) {
|
|
85
97
|
if (fieldValue !== undefined)
|
|
@@ -103,6 +115,25 @@ export const focusRewriteRequestSchema = z
|
|
|
103
115
|
relativePath: workspaceRelativePathSchema,
|
|
104
116
|
treeId: z.string().min(1).max(256).optional(),
|
|
105
117
|
layoutMode: z.enum(['authored', 'compact']).default('authored'),
|
|
118
|
+
compactFocusIds: z.array(z.string().min(1).max(256)).min(1).max(10_000).optional(),
|
|
119
|
+
pinnedFocusIds: z.array(z.string().min(1).max(256)).max(10_000).optional(),
|
|
120
|
+
symmetryGroups: z
|
|
121
|
+
.array(z
|
|
122
|
+
.object({
|
|
123
|
+
centerFocusId: z.string().min(1).max(256),
|
|
124
|
+
pairs: z
|
|
125
|
+
.array(z
|
|
126
|
+
.object({
|
|
127
|
+
leftFocusId: z.string().min(1).max(256),
|
|
128
|
+
rightFocusId: z.string().min(1).max(256),
|
|
129
|
+
})
|
|
130
|
+
.strict())
|
|
131
|
+
.min(1)
|
|
132
|
+
.max(10_000),
|
|
133
|
+
})
|
|
134
|
+
.strict())
|
|
135
|
+
.max(64)
|
|
136
|
+
.optional(),
|
|
106
137
|
plan: compactFocusPlanSchema.optional(),
|
|
107
138
|
createIfMissing: z.boolean().default(false),
|
|
108
139
|
horizontalSpacing: z.number().int().min(80).max(1000).optional(),
|
|
@@ -117,6 +148,14 @@ export const focusRewriteRequestSchema = z
|
|
|
117
148
|
})
|
|
118
149
|
.strict()
|
|
119
150
|
.superRefine((value, context) => {
|
|
151
|
+
if (value.layoutMode !== 'compact')
|
|
152
|
+
for (const field of ['compactFocusIds', 'pinnedFocusIds', 'symmetryGroups'])
|
|
153
|
+
if (value[field] !== undefined)
|
|
154
|
+
context.addIssue({
|
|
155
|
+
code: 'custom',
|
|
156
|
+
path: [field],
|
|
157
|
+
message: `${field} requires compact layout mode`,
|
|
158
|
+
});
|
|
120
159
|
const mode = value.mode ?? 'national';
|
|
121
160
|
const schema = mode === 'continuous' ? continuousFocusPaletteSchema : focusTreePlanSchema;
|
|
122
161
|
if (value.plan !== undefined && !schema.safeParse(value.plan).success)
|
|
@@ -82,6 +82,14 @@ export const guiRenderRequestSchema = z
|
|
|
82
82
|
relatedScenarios: z.array(compactGuiScenarioSchema).max(32).optional(),
|
|
83
83
|
generatedScenarios: compactGeneratedScenarioOptionsSchema.optional(),
|
|
84
84
|
comparisonScenario: compactGuiScenarioSchema.optional(),
|
|
85
|
+
sourceBaseline: z
|
|
86
|
+
.object({
|
|
87
|
+
relativePath: workspaceRelativePathSchema,
|
|
88
|
+
source: z.string().max(SOURCE_MAX_BYTES),
|
|
89
|
+
})
|
|
90
|
+
.strict()
|
|
91
|
+
.optional()
|
|
92
|
+
.describe('Previous mod-owned .gui source for a matched source comparison using the requested scenario.'),
|
|
85
93
|
})
|
|
86
94
|
.strict()
|
|
87
95
|
.superRefine(({ scenario, states, resolutions, relatedScenarios, generatedScenarios }, context) => {
|
|
@@ -63,6 +63,7 @@ export const mapInspectRequestSchema = z
|
|
|
63
63
|
]))
|
|
64
64
|
.max(100)
|
|
65
65
|
.default([]),
|
|
66
|
+
lookupOnly: z.boolean().default(false),
|
|
66
67
|
includeOverview: z.boolean().default(true),
|
|
67
68
|
allocationRequests: z.array(mapAllocationRequestSchema).max(100).default([]),
|
|
68
69
|
})
|
|
@@ -73,8 +74,28 @@ export const mapRenderRequestSchema = z
|
|
|
73
74
|
layer: mapLayerSchema.optional(),
|
|
74
75
|
overlays: z.array(mapOverlaySchema).max(12).optional(),
|
|
75
76
|
scale: z.number().int().min(1).max(16).optional(),
|
|
77
|
+
tile: z
|
|
78
|
+
.object({
|
|
79
|
+
x: z.number().int().min(0),
|
|
80
|
+
y: z.number().int().min(0),
|
|
81
|
+
width: z.number().int().min(1).max(2_048),
|
|
82
|
+
height: z.number().int().min(1).max(2_048),
|
|
83
|
+
})
|
|
84
|
+
.strict()
|
|
85
|
+
.optional(),
|
|
86
|
+
area: z
|
|
87
|
+
.object({
|
|
88
|
+
provinceIds: z.array(z.number().int().nonnegative()).max(256).optional(),
|
|
89
|
+
stateIds: z.array(z.number().int().nonnegative()).max(64).optional(),
|
|
90
|
+
regionIds: z.array(z.number().int().nonnegative()).max(64).optional(),
|
|
91
|
+
padding: z.number().int().min(0).max(256).optional(),
|
|
92
|
+
})
|
|
93
|
+
.strict()
|
|
94
|
+
.refine(({ provinceIds, stateIds, regionIds }) => (provinceIds?.length ?? 0) + (stateIds?.length ?? 0) + (regionIds?.length ?? 0) > 0, 'Map area requires at least one province, state, or region ID')
|
|
95
|
+
.optional(),
|
|
76
96
|
})
|
|
77
|
-
.strict()
|
|
97
|
+
.strict()
|
|
98
|
+
.refine(({ tile, area }) => tile === undefined || area === undefined, 'Choose tile or area, not both');
|
|
78
99
|
export const mapRewriteRequestSchema = z
|
|
79
100
|
.object({
|
|
80
101
|
workspaceId: workspaceIdSchema,
|
|
@@ -132,6 +132,13 @@ export const technologyRenderRequestSchema = z
|
|
|
132
132
|
categoryId: technologyIdSchema.optional(),
|
|
133
133
|
targetId: technologyIdSchema.optional(),
|
|
134
134
|
maxNodes: z.number().int().min(1).max(2_000).optional(),
|
|
135
|
+
scenario: z
|
|
136
|
+
.object({
|
|
137
|
+
year: z.number().int().min(1).max(9999),
|
|
138
|
+
researchedTechnologyIds: z.array(technologyIdSchema).max(25_000),
|
|
139
|
+
})
|
|
140
|
+
.strict()
|
|
141
|
+
.optional(),
|
|
135
142
|
includeHtml: z.boolean().optional(),
|
|
136
143
|
refresh: z.boolean().optional(),
|
|
137
144
|
})
|
|
@@ -11,6 +11,7 @@ const MAX_EXTERNAL_REFERENCES = 1_000_000;
|
|
|
11
11
|
const MAX_ISSUES = 100_000;
|
|
12
12
|
const MAX_HELPER_DEPTH = 64;
|
|
13
13
|
const MAX_HELPER_PROJECTIONS = 500_000;
|
|
14
|
+
const MAX_FULL_HELPER_CALLS = 4_096;
|
|
14
15
|
const confidenceRank = {
|
|
15
16
|
confirmed: 0,
|
|
16
17
|
high: 1,
|
|
@@ -188,6 +189,20 @@ function activeYearMarkers(values) {
|
|
|
188
189
|
left.year - right.year ||
|
|
189
190
|
compareCodeUnits(left.name, right.name));
|
|
190
191
|
}
|
|
192
|
+
function activeBackgrounds(values) {
|
|
193
|
+
const groups = new Map();
|
|
194
|
+
for (const background of values) {
|
|
195
|
+
const key = `${background.folderId}\0${background.name}`;
|
|
196
|
+
const group = groups.get(key) ?? [];
|
|
197
|
+
group.push(background);
|
|
198
|
+
groups.set(key, group);
|
|
199
|
+
}
|
|
200
|
+
return [...groups.values()]
|
|
201
|
+
.map((group) => group.sort((left, right) => right.loadOrder - left.loadOrder ||
|
|
202
|
+
compareCodeUnits(left.sourcePath, right.sourcePath) ||
|
|
203
|
+
left.location.start.offset - right.location.start.offset)[0])
|
|
204
|
+
.sort((left, right) => compareCodeUnits(left.folderId, right.folderId) || compareCodeUnits(left.name, right.name));
|
|
205
|
+
}
|
|
191
206
|
function enrichPlacements(placements, edges, gridboxes, technologies, unlocks, itemLayouts, signal) {
|
|
192
207
|
const technologyById = new Map(technologies.map((technology) => [technology.id, technology]));
|
|
193
208
|
const unlockKindsByTechnology = new Map();
|
|
@@ -238,11 +253,8 @@ function enrichPlacements(placements, edges, gridboxes, technologies, unlocks, i
|
|
|
238
253
|
return placements.map((placement) => {
|
|
239
254
|
const roots = rootsFor(placement.technologyId, placement.folderId);
|
|
240
255
|
const branchRootId = roots.length === 1 ? roots[0] : undefined;
|
|
241
|
-
const expectedGridbox = branchRootId
|
|
242
|
-
const candidates = expectedGridbox === undefined
|
|
243
|
-
? []
|
|
244
|
-
: gridboxes.filter(({ name, folderId }) => name === expectedGridbox &&
|
|
245
|
-
(folderId === undefined || folderId === placement.folderId));
|
|
256
|
+
const expectedGridbox = `${branchRootId ?? placement.technologyId}_tree`;
|
|
257
|
+
const candidates = gridboxes.filter(({ name, folderId }) => name === expectedGridbox && (folderId === undefined || folderId === placement.folderId));
|
|
246
258
|
const gridbox = candidates[0];
|
|
247
259
|
const technology = technologyById.get(placement.technologyId);
|
|
248
260
|
const folderLayouts = layoutsByFolder.get(placement.folderId);
|
|
@@ -1155,6 +1167,7 @@ function* technologyGraphAnalysis(snapshot, options) {
|
|
|
1155
1167
|
const gridboxes = activeGridboxes(fragments.flatMap(({ gridboxes }) => gridboxes));
|
|
1156
1168
|
const itemLayouts = activeItemLayouts(fragments.flatMap(({ itemLayouts }) => itemLayouts));
|
|
1157
1169
|
const yearMarkers = activeYearMarkers(fragments.flatMap(({ yearMarkers }) => yearMarkers));
|
|
1170
|
+
const backgrounds = activeBackgrounds(fragments.flatMap(({ backgrounds }) => backgrounds ?? []));
|
|
1158
1171
|
const categories = activeCategories(fragments.flatMap(({ categories }) => categories));
|
|
1159
1172
|
const selectedDoctrines = activeDefinitions(fragments.flatMap(({ doctrineDefinitions }) => doctrineDefinitions));
|
|
1160
1173
|
const doctrineDefinitions = localiseDoctrines(selectedDoctrines.active, snapshot.index, assets);
|
|
@@ -1168,10 +1181,11 @@ function* technologyGraphAnalysis(snapshot, options) {
|
|
|
1168
1181
|
const placements = enrichPlacements(basePlacements, edges, gridboxes, technologies, unlocks, itemLayouts, options.signal);
|
|
1169
1182
|
const unresolved = fragments.flatMap(({ unresolved }) => unresolved);
|
|
1170
1183
|
const helperCalls = fragments.flatMap(({ helperCalls }) => helperCalls);
|
|
1171
|
-
const
|
|
1184
|
+
const analysisMode = options.analysisMode ?? (helperCalls.length > MAX_FULL_HELPER_CALLS ? 'focused' : 'full');
|
|
1185
|
+
const walk = analysisMode === 'focused'
|
|
1172
1186
|
? undefined
|
|
1173
1187
|
: yield technologyHelperWalk(helperCalls, snapshot.revision);
|
|
1174
|
-
const externalReferences = expandHelperReferences(fragments.flatMap(({ externalReferences }) => externalReferences), helperCalls, unresolved,
|
|
1188
|
+
const externalReferences = expandHelperReferences(fragments.flatMap(({ externalReferences }) => externalReferences), helperCalls, unresolved, analysisMode, walk, options.signal);
|
|
1175
1189
|
const issues = diagnose(technologies, folders, placements, gridboxes, edges, categories, doctrineDefinitions, unlocks, externalReferences, selectedTechnologies.duplicates.map((group) => group.map(({ iconSprite, effectSignatureFields, ...technology }) => ({
|
|
1176
1190
|
...technology,
|
|
1177
1191
|
icon: iconStatus(iconSprite, snapshot.index, assets),
|
|
@@ -1197,14 +1211,14 @@ function* technologyGraphAnalysis(snapshot, options) {
|
|
|
1197
1211
|
const graph = {
|
|
1198
1212
|
schemaVersion: TECHNOLOGY_GRAPH_SCHEMA_VERSION,
|
|
1199
1213
|
parserVersion: TECHNOLOGY_PARSER_VERSION,
|
|
1200
|
-
analysisMode
|
|
1214
|
+
analysisMode,
|
|
1201
1215
|
workspaceId: snapshot.workspaceId,
|
|
1202
1216
|
workspaceIdentity: options.workspaceIdentity,
|
|
1203
1217
|
revision,
|
|
1204
1218
|
complete: snapshot.complete &&
|
|
1205
1219
|
unresolved.every(({ kind }) => kind !== 'partial_source') &&
|
|
1206
1220
|
!unresolved.some(({ blockers }) => blockers.some(({ code }) => code === 'TECH_HELPER_DEPTH_BLOCKED' || code === 'TECH_HELPER_PROJECTION_LIMIT')) &&
|
|
1207
|
-
!(
|
|
1221
|
+
!(analysisMode === 'focused' && helperCalls.length > 0),
|
|
1208
1222
|
analysisBoundary: {
|
|
1209
1223
|
staticAnalysis: true,
|
|
1210
1224
|
language: 'l_english',
|
|
@@ -1215,7 +1229,7 @@ function* technologyGraphAnalysis(snapshot, options) {
|
|
|
1215
1229
|
'meta-generated or variable technology identifiers',
|
|
1216
1230
|
'exact AI research choice and runtime research time',
|
|
1217
1231
|
'dynamic scripted localisation without supplied runtime values',
|
|
1218
|
-
...(
|
|
1232
|
+
...(analysisMode === 'focused' && helperCalls.length > 0
|
|
1219
1233
|
? ['large-workspace scripted-effect helper projections are deferred']
|
|
1220
1234
|
: []),
|
|
1221
1235
|
],
|
|
@@ -1235,6 +1249,7 @@ function* technologyGraphAnalysis(snapshot, options) {
|
|
|
1235
1249
|
gridboxes,
|
|
1236
1250
|
itemLayouts,
|
|
1237
1251
|
yearMarkers,
|
|
1252
|
+
backgrounds,
|
|
1238
1253
|
edges,
|
|
1239
1254
|
categories,
|
|
1240
1255
|
doctrineDefinitions,
|
|
@@ -58,6 +58,34 @@ export function technologyValidation(graph) {
|
|
|
58
58
|
],
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
|
+
export function technologyRenderValidation(graph, render) {
|
|
62
|
+
const omitted = render.omittedNodeCount;
|
|
63
|
+
const unresolvedSprites = render.unresolvedIconSprites.length;
|
|
64
|
+
const sourcePlacementPassed = render.view !== 'folder' || render.sourceAccurate;
|
|
65
|
+
const analysisBoundaryPassed = graph.complete || (graph.analysisMode === 'focused' && graph.skippedSourceCount === 0);
|
|
66
|
+
const renderPassed = sourcePlacementPassed && omitted === 0 && unresolvedSprites === 0;
|
|
67
|
+
return {
|
|
68
|
+
passed: renderPassed && analysisBoundaryPassed,
|
|
69
|
+
checks: [
|
|
70
|
+
{
|
|
71
|
+
id: 'technology-render',
|
|
72
|
+
passed: renderPassed,
|
|
73
|
+
message: renderPassed
|
|
74
|
+
? 'The requested technology view rendered with complete node and sprite coverage'
|
|
75
|
+
: `${sourcePlacementPassed ? 0 : 1} source-placement failure(s), ${omitted} omitted node(s), and ${unresolvedSprites} unresolved sprite(s) affect the requested technology view`,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'technology-analysis-boundary',
|
|
79
|
+
passed: analysisBoundaryPassed,
|
|
80
|
+
message: graph.complete
|
|
81
|
+
? 'The supporting technology analysis is complete'
|
|
82
|
+
: analysisBoundaryPassed
|
|
83
|
+
? 'Whole-workspace helper projections were deferred; the requested render retains direct source evidence'
|
|
84
|
+
: `${graph.skippedSourceCount} technology source(s) were skipped; linked evidence records the partial analysis boundary`,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
61
89
|
export async function inspectTechnologies(viewer, input) {
|
|
62
90
|
const output = await viewer.analyze(input);
|
|
63
91
|
const result = emptyServiceResult(input.workspaceId, {
|
|
@@ -93,7 +121,7 @@ export async function renderTechnologies(viewer, input) {
|
|
|
93
121
|
result.code = output.graph.complete ? 'TECH_RENDERED' : 'TECH_RENDERED_PARTIAL';
|
|
94
122
|
setInlineFilesScanned(result, output.graph.filesScanned);
|
|
95
123
|
result.artifacts = output.artifacts.map(publicArtifactLink);
|
|
96
|
-
result.validation =
|
|
124
|
+
result.validation = technologyRenderValidation(output.graph, output.render);
|
|
97
125
|
return result;
|
|
98
126
|
}
|
|
99
127
|
export async function compareTechnologies(viewer, input) {
|