hoi4-agent-tools 2.6.0 → 2.8.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.
- package/CHANGELOG.md +14 -0
- package/README.md +1 -1
- package/dist/hoi4_agent_tools/gui/assets.js +15 -3
- package/dist/hoi4_agent_tools/gui/layout.js +270 -75
- package/dist/hoi4_agent_tools/gui/renderer.js +185 -22
- package/dist/hoi4_agent_tools/gui/source-graph.js +40 -11
- package/dist/hoi4_agent_tools/gui/studio.js +35 -7
- 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 +5 -1
- 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
|
@@ -86,10 +86,23 @@ function renderText(element, toolText) {
|
|
|
86
86
|
(glyphLine === undefined || glyphLine.source === 'deterministic-fallback'
|
|
87
87
|
? text.lineHeight * 0.8
|
|
88
88
|
: glyphLine.baseline);
|
|
89
|
+
const horizontalScale = glyphLine !== undefined && glyphLine.width > 0 && width > 0 ? width / glyphLine.width : 1;
|
|
90
|
+
const inlineIconMarkup = (text.inlineIcons ?? [])
|
|
91
|
+
.filter((icon) => icon.lineIndex === index)
|
|
92
|
+
.map((icon) => {
|
|
93
|
+
const iconRect = {
|
|
94
|
+
x: originX + icon.offsetX * horizontalScale,
|
|
95
|
+
y: lineTop + (text.lineHeight - icon.height) / 2,
|
|
96
|
+
width: icon.width * horizontalScale,
|
|
97
|
+
height: icon.height,
|
|
98
|
+
};
|
|
99
|
+
if (icon.sprite?.supported === true && icon.sprite.dataUri !== undefined)
|
|
100
|
+
return `<image data-inline-icon="${escapeXml(icon.token)}" ${rectAttributes(iconRect)} href="${icon.sprite.dataUri}" preserveAspectRatio="xMidYMid meet"/>`;
|
|
101
|
+
return `<g data-inline-icon-missing="${escapeXml(icon.token)}"><rect ${rectAttributes(iconRect)} fill="#331f3f" stroke="#ff42d0" stroke-width="1"/><path d="M ${finite(iconRect.x)} ${finite(iconRect.y)} L ${finite(iconRect.x + iconRect.width)} ${finite(iconRect.y + iconRect.height)} M ${finite(iconRect.x + iconRect.width)} ${finite(iconRect.y)} L ${finite(iconRect.x)} ${finite(iconRect.y + iconRect.height)}" stroke="#ff42d0"/></g>`;
|
|
102
|
+
})
|
|
103
|
+
.join('');
|
|
89
104
|
const colourRuns = text.colourRuns?.[index] ?? [];
|
|
90
105
|
if (colourRuns.length > 0) {
|
|
91
|
-
const runWidth = colourRuns.reduce((maximum, run) => Math.max(maximum, run.offsetX + run.width), 0);
|
|
92
|
-
const horizontalScale = runWidth > 0 && width > 0 ? width / runWidth : 1;
|
|
93
106
|
return `<g data-hoi4-colour-runs="true">${colourRuns
|
|
94
107
|
.map((run) => toolText.render(run.text, {
|
|
95
108
|
x: originX + run.offsetX * horizontalScale,
|
|
@@ -100,23 +113,22 @@ function renderText(element, toolText) {
|
|
|
100
113
|
strokeWidth: 0.6,
|
|
101
114
|
...(run.width > 0 ? { targetWidth: run.width * horizontalScale } : {}),
|
|
102
115
|
}))
|
|
103
|
-
.join('')}</g>`;
|
|
116
|
+
.join('')}${inlineIconMarkup}</g>`;
|
|
104
117
|
}
|
|
105
118
|
if (glyphLine?.source === 'fontkit-path') {
|
|
106
|
-
const horizontalScale = glyphLine.width > 0 && width > 0 ? width / glyphLine.width : 1;
|
|
107
119
|
return `<g data-font-sha256="${glyphLine.sourceHash}">${glyphLine.glyphs
|
|
108
120
|
.filter((glyph) => glyph.kind === 'outline')
|
|
109
121
|
.map((glyph) => `<use href="#${outlineDefinitionId(glyph.key)}" transform="translate(${finite(originX + glyph.x * horizontalScale)} ${finite(baseline + glyph.y)}) scale(${finite(glyph.scale * horizontalScale)} ${finite(-glyph.scale)})"/>`)
|
|
110
|
-
.join('')}</g>`;
|
|
122
|
+
.join('')}${inlineIconMarkup}</g>`;
|
|
111
123
|
}
|
|
112
124
|
if (glyphLine?.source === 'bmfont-atlas') {
|
|
113
|
-
const horizontalScale = glyphLine.width > 0 && width > 0 ? width / glyphLine.width : 1;
|
|
114
125
|
return `<g data-font-sha256="${glyphLine.sourceHash}">${glyphLine.glyphs
|
|
115
126
|
.filter((glyph) => glyph.kind === 'bitmap')
|
|
116
127
|
.map((glyph) => `<use href="#${bitmapDefinitionId(glyph.key)}" transform="translate(${finite(originX + glyph.x * horizontalScale)} ${finite(baseline + glyph.y - glyphLine.baseline)}) scale(${finite(horizontalScale)} 1)"/>`)
|
|
117
|
-
.join('')}</g>`;
|
|
128
|
+
.join('')}${inlineIconMarkup}</g>`;
|
|
118
129
|
}
|
|
119
|
-
|
|
130
|
+
const visibleLine = line.replace(/[\uE000-\uF8FF]/gu, ' ');
|
|
131
|
+
return `<g>${toolText.render(visibleLine, {
|
|
120
132
|
x: originX,
|
|
121
133
|
y: baseline,
|
|
122
134
|
fontSize: text.fontSize,
|
|
@@ -124,9 +136,153 @@ function renderText(element, toolText) {
|
|
|
124
136
|
stroke: '#12151a',
|
|
125
137
|
strokeWidth: 0.6,
|
|
126
138
|
...(width > 0 ? { targetWidth: width } : {}),
|
|
127
|
-
})
|
|
139
|
+
})}${inlineIconMarkup}</g>`;
|
|
128
140
|
});
|
|
129
|
-
|
|
141
|
+
const textClipId = `gui-text-clip-${sha256Bytes(element.id).slice(0, 20)}`;
|
|
142
|
+
const clipDefinition = text.fixedSize
|
|
143
|
+
? `<defs><clipPath id="${textClipId}"><rect ${rectAttributes(rect)}/></clipPath></defs>`
|
|
144
|
+
: '';
|
|
145
|
+
const clip = text.fixedSize ? ` clip-path="url(#${textClipId})"` : '';
|
|
146
|
+
return `${clipDefinition}<g data-source-id="${escapeXml(element.sourceId)}" fill="#f5f2e8" stroke="#12151a" stroke-width="0.6" paint-order="stroke"${clip}>${lines.join('')}</g>`;
|
|
147
|
+
}
|
|
148
|
+
function renderSpriteImage(sprite, rect) {
|
|
149
|
+
if (!sprite.supported || sprite.dataUri === undefined)
|
|
150
|
+
return '';
|
|
151
|
+
return `<image ${rectAttributes(rect)} href="${sprite.dataUri}" preserveAspectRatio="none"/>`;
|
|
152
|
+
}
|
|
153
|
+
function renderSpriteSlice(sprite, source, destination) {
|
|
154
|
+
if (!sprite.supported ||
|
|
155
|
+
sprite.dataUri === undefined ||
|
|
156
|
+
source.width <= 0 ||
|
|
157
|
+
source.height <= 0 ||
|
|
158
|
+
destination.width <= 0 ||
|
|
159
|
+
destination.height <= 0)
|
|
160
|
+
return '';
|
|
161
|
+
return `<svg ${rectAttributes(destination)} viewBox="${finite(source.x)} ${finite(source.y)} ${finite(source.width)} ${finite(source.height)}" preserveAspectRatio="none" overflow="hidden"><image x="0" y="0" width="${finite(sprite.width)}" height="${finite(sprite.height)}" href="${sprite.dataUri}" preserveAspectRatio="none"/></svg>`;
|
|
162
|
+
}
|
|
163
|
+
function renderTiledSpriteSlice(element, sprite, source, destination) {
|
|
164
|
+
if (!sprite.supported ||
|
|
165
|
+
sprite.dataUri === undefined ||
|
|
166
|
+
source.width <= 0 ||
|
|
167
|
+
source.height <= 0 ||
|
|
168
|
+
destination.width <= 0 ||
|
|
169
|
+
destination.height <= 0)
|
|
170
|
+
return '';
|
|
171
|
+
const tileWidth = Math.max(1, source.width * element.scale);
|
|
172
|
+
const tileHeight = Math.max(1, source.height * element.scale);
|
|
173
|
+
const id = `gui-tile-${sha256Bytes(`${element.id}:${source.x}:${source.y}:${source.width}:${source.height}`).slice(0, 20)}`;
|
|
174
|
+
return `<defs><pattern id="${id}" patternUnits="userSpaceOnUse" x="${finite(destination.x)}" y="${finite(destination.y)}" width="${finite(tileWidth)}" height="${finite(tileHeight)}"><svg width="${finite(tileWidth)}" height="${finite(tileHeight)}" viewBox="${finite(source.x)} ${finite(source.y)} ${finite(source.width)} ${finite(source.height)}" preserveAspectRatio="none" overflow="hidden"><image x="0" y="0" width="${finite(sprite.width)}" height="${finite(sprite.height)}" href="${sprite.dataUri}" preserveAspectRatio="none"/></svg></pattern></defs><rect ${rectAttributes(destination)} fill="url(#${id})"/>`;
|
|
175
|
+
}
|
|
176
|
+
function renderCorneredTile(element) {
|
|
177
|
+
const sprite = element.sprite;
|
|
178
|
+
const rect = element.unclippedRect;
|
|
179
|
+
const border = element.spriteBorderSize;
|
|
180
|
+
if (sprite?.supported !== true ||
|
|
181
|
+
sprite.dataUri === undefined ||
|
|
182
|
+
border === undefined ||
|
|
183
|
+
border.width <= 0 ||
|
|
184
|
+
border.height <= 0)
|
|
185
|
+
return sprite === undefined ? '' : renderSpriteImage(sprite, rect);
|
|
186
|
+
const sourceBorderX = Math.min(border.width, sprite.width / 2);
|
|
187
|
+
const sourceBorderY = Math.min(border.height, sprite.height / 2);
|
|
188
|
+
const destinationBorderX = Math.min(sourceBorderX * element.scale, rect.width / 2);
|
|
189
|
+
const destinationBorderY = Math.min(sourceBorderY * element.scale, rect.height / 2);
|
|
190
|
+
const sourceWidths = [sourceBorderX, sprite.width - sourceBorderX * 2, sourceBorderX];
|
|
191
|
+
const sourceHeights = [sourceBorderY, sprite.height - sourceBorderY * 2, sourceBorderY];
|
|
192
|
+
const destinationWidths = [
|
|
193
|
+
destinationBorderX,
|
|
194
|
+
rect.width - destinationBorderX * 2,
|
|
195
|
+
destinationBorderX,
|
|
196
|
+
];
|
|
197
|
+
const destinationHeights = [
|
|
198
|
+
destinationBorderY,
|
|
199
|
+
rect.height - destinationBorderY * 2,
|
|
200
|
+
destinationBorderY,
|
|
201
|
+
];
|
|
202
|
+
const sourceXs = [0, sourceBorderX, sprite.width - sourceBorderX];
|
|
203
|
+
const sourceYs = [0, sourceBorderY, sprite.height - sourceBorderY];
|
|
204
|
+
const destinationXs = [
|
|
205
|
+
rect.x,
|
|
206
|
+
rect.x + destinationBorderX,
|
|
207
|
+
rect.x + rect.width - destinationBorderX,
|
|
208
|
+
];
|
|
209
|
+
const destinationYs = [
|
|
210
|
+
rect.y,
|
|
211
|
+
rect.y + destinationBorderY,
|
|
212
|
+
rect.y + rect.height - destinationBorderY,
|
|
213
|
+
];
|
|
214
|
+
const parts = [];
|
|
215
|
+
for (let row = 0; row < 3; row += 1) {
|
|
216
|
+
for (let column = 0; column < 3; column += 1) {
|
|
217
|
+
const source = {
|
|
218
|
+
x: sourceXs[column] ?? 0,
|
|
219
|
+
y: sourceYs[row] ?? 0,
|
|
220
|
+
width: sourceWidths[column] ?? 0,
|
|
221
|
+
height: sourceHeights[row] ?? 0,
|
|
222
|
+
};
|
|
223
|
+
const destination = {
|
|
224
|
+
x: destinationXs[column] ?? rect.x,
|
|
225
|
+
y: destinationYs[row] ?? rect.y,
|
|
226
|
+
width: destinationWidths[column] ?? 0,
|
|
227
|
+
height: destinationHeights[row] ?? 0,
|
|
228
|
+
};
|
|
229
|
+
parts.push(row === 1 && column === 1 && element.spriteTilingCenter === true
|
|
230
|
+
? renderTiledSpriteSlice(element, sprite, source, destination)
|
|
231
|
+
: renderSpriteSlice(sprite, source, destination));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return parts.join('');
|
|
235
|
+
}
|
|
236
|
+
function renderProgressbar(element) {
|
|
237
|
+
const rect = element.unclippedRect;
|
|
238
|
+
const sprite = element.sprite;
|
|
239
|
+
const secondary = element.secondarySprite;
|
|
240
|
+
const ratio = Math.max(0, Math.min(1, element.progressRatio ?? 1));
|
|
241
|
+
const background = secondary === undefined ? '' : renderSpriteImage(secondary, rect);
|
|
242
|
+
if (sprite?.supported !== true || sprite.dataUri === undefined)
|
|
243
|
+
return background;
|
|
244
|
+
if (ratio <= 0)
|
|
245
|
+
return background;
|
|
246
|
+
const horizontal = element.progressHorizontal !== false;
|
|
247
|
+
const filled = horizontal
|
|
248
|
+
? { x: rect.x, y: rect.y, width: rect.width * ratio, height: rect.height }
|
|
249
|
+
: {
|
|
250
|
+
x: rect.x,
|
|
251
|
+
y: rect.y + rect.height * (1 - ratio),
|
|
252
|
+
width: rect.width,
|
|
253
|
+
height: rect.height * ratio,
|
|
254
|
+
};
|
|
255
|
+
const source = horizontal
|
|
256
|
+
? { x: 0, y: 0, width: sprite.width * ratio, height: sprite.height }
|
|
257
|
+
: {
|
|
258
|
+
x: 0,
|
|
259
|
+
y: sprite.height * (1 - ratio),
|
|
260
|
+
width: sprite.width,
|
|
261
|
+
height: sprite.height * ratio,
|
|
262
|
+
};
|
|
263
|
+
return `${background}${renderSpriteSlice(sprite, source, filled)}`;
|
|
264
|
+
}
|
|
265
|
+
function renderMaskedShield(element) {
|
|
266
|
+
const rect = element.unclippedRect;
|
|
267
|
+
const sprite = element.sprite;
|
|
268
|
+
const mask = element.secondarySprite;
|
|
269
|
+
if (sprite?.supported !== true || sprite.dataUri === undefined)
|
|
270
|
+
return '';
|
|
271
|
+
if (mask?.supported !== true || mask.dataUri === undefined)
|
|
272
|
+
return renderSpriteImage(sprite, rect);
|
|
273
|
+
const id = `gui-mask-${sha256Bytes(element.id).slice(0, 20)}`;
|
|
274
|
+
return `<defs><mask id="${id}" maskUnits="userSpaceOnUse" ${rectAttributes(rect)}><image ${rectAttributes(rect)} href="${mask.dataUri}" preserveAspectRatio="none"/></mask></defs><image ${rectAttributes(rect)} href="${sprite.dataUri}" preserveAspectRatio="none" mask="url(#${id})"/>`;
|
|
275
|
+
}
|
|
276
|
+
function renderElementSprite(element) {
|
|
277
|
+
if (element.sprite?.supported !== true || element.sprite.dataUri === undefined)
|
|
278
|
+
return '';
|
|
279
|
+
if (element.spriteRenderMode === 'cornered-tile')
|
|
280
|
+
return renderCorneredTile(element);
|
|
281
|
+
if (element.spriteRenderMode === 'progressbar')
|
|
282
|
+
return renderProgressbar(element);
|
|
283
|
+
if (element.spriteRenderMode === 'masked-shield')
|
|
284
|
+
return renderMaskedShield(element);
|
|
285
|
+
return renderSpriteImage(element.sprite, element.unclippedRect);
|
|
130
286
|
}
|
|
131
287
|
function renderBaseElement(element, clipId, toolText) {
|
|
132
288
|
if (!element.visible)
|
|
@@ -135,25 +291,15 @@ function renderBaseElement(element, clipId, toolText) {
|
|
|
135
291
|
const clip = clipId === undefined ? '' : ` clip-path="url(#${clipId})"`;
|
|
136
292
|
const content = [];
|
|
137
293
|
if (element.sprite?.supported === true && element.sprite.dataUri !== undefined) {
|
|
138
|
-
content.push(
|
|
294
|
+
content.push(renderElementSprite(element));
|
|
139
295
|
}
|
|
140
296
|
else if (element.sprite !== undefined) {
|
|
141
297
|
content.push(`<rect ${rectAttributes(rect)} fill="#331f3f" stroke="#ff42d0" stroke-width="1"/><path d="M ${finite(rect.x)} ${finite(rect.y)} L ${finite(rect.x + rect.width)} ${finite(rect.y + rect.height)} M ${finite(rect.x + rect.width)} ${finite(rect.y)} L ${finite(rect.x)} ${finite(rect.y + rect.height)}" stroke="#ff42d0"/>`);
|
|
142
298
|
}
|
|
143
|
-
if (element.progressRatio !== undefined) {
|
|
299
|
+
if (element.progressRatio !== undefined && element.spriteRenderMode !== 'progressbar') {
|
|
144
300
|
content.push(`<rect x="${finite(rect.x)}" y="${finite(rect.y)}" width="${finite(rect.width * element.progressRatio)}" height="${finite(rect.height)}" fill="#5ecf8d" fill-opacity="0.65"/>`);
|
|
145
301
|
}
|
|
146
302
|
content.push(renderText(element, toolText));
|
|
147
|
-
if (element.state === 'hover')
|
|
148
|
-
content.push(`<rect ${rectAttributes(rect)} fill="#fff" opacity="0.08"/>`);
|
|
149
|
-
if (element.state === 'selected' || element.state === 'active')
|
|
150
|
-
content.push(`<rect ${rectAttributes(rect)} fill="none" stroke="#ffd166" stroke-width="2"/>`);
|
|
151
|
-
if (element.state === 'locked' || element.state === 'disabled')
|
|
152
|
-
content.push(`<rect ${rectAttributes(rect)} fill="#111820" opacity="0.5"/>`);
|
|
153
|
-
if (element.state === 'warning')
|
|
154
|
-
content.push(`<rect ${rectAttributes(rect)} fill="none" stroke="#ff5d5d" stroke-width="3"/>`);
|
|
155
|
-
if (element.state === 'completed')
|
|
156
|
-
content.push(`<rect ${rectAttributes(rect)} fill="none" stroke="#5ecf8d" stroke-width="2"/>`);
|
|
157
303
|
return `<g id="${escapeXml(element.id)}" data-source="${escapeXml(element.sourcePath)}" data-source-id="${escapeXml(element.sourceId)}"${clip}>${content.join('')}</g>`;
|
|
158
304
|
}
|
|
159
305
|
function renderOverlay(element, variant, toolText) {
|
|
@@ -299,6 +445,23 @@ function sceneLayoutEvidence(scene) {
|
|
|
299
445
|
...(element.sprite.reason === undefined ? {} : { reason: element.sprite.reason }),
|
|
300
446
|
},
|
|
301
447
|
}),
|
|
448
|
+
...(element.secondarySprite === undefined
|
|
449
|
+
? {}
|
|
450
|
+
: {
|
|
451
|
+
secondarySprite: {
|
|
452
|
+
spriteName: element.secondarySprite.spriteName,
|
|
453
|
+
texturePath: element.secondarySprite.texturePath,
|
|
454
|
+
frame: element.secondarySprite.frame,
|
|
455
|
+
frameCount: element.secondarySprite.frameCount,
|
|
456
|
+
width: element.secondarySprite.width,
|
|
457
|
+
height: element.secondarySprite.height,
|
|
458
|
+
format: element.secondarySprite.format,
|
|
459
|
+
supported: element.secondarySprite.supported,
|
|
460
|
+
...(element.secondarySprite.reason === undefined
|
|
461
|
+
? {}
|
|
462
|
+
: { reason: element.secondarySprite.reason }),
|
|
463
|
+
},
|
|
464
|
+
}),
|
|
302
465
|
...(element.text === undefined
|
|
303
466
|
? {}
|
|
304
467
|
: {
|
|
@@ -54,6 +54,8 @@ const modelledElementAttributes = new Set([
|
|
|
54
54
|
'font',
|
|
55
55
|
'fontSize',
|
|
56
56
|
'maxWidth',
|
|
57
|
+
'maxHeight',
|
|
58
|
+
'fixedsize',
|
|
57
59
|
'format',
|
|
58
60
|
'buttonText',
|
|
59
61
|
'buttonFont',
|
|
@@ -65,11 +67,10 @@ const modelledElementAttributes = new Set([
|
|
|
65
67
|
'minValue',
|
|
66
68
|
'maxValue',
|
|
67
69
|
'startValue',
|
|
70
|
+
'centerposition',
|
|
68
71
|
]);
|
|
69
72
|
const ignoredElementAttributes = new Set([
|
|
70
73
|
'margin',
|
|
71
|
-
'maxHeight',
|
|
72
|
-
'fixedsize',
|
|
73
74
|
'pdx_tooltip',
|
|
74
75
|
'pdx_tooltip_delayed',
|
|
75
76
|
'hint_tag',
|
|
@@ -84,7 +85,6 @@ const ignoredElementAttributes = new Set([
|
|
|
84
85
|
'smooth_scrolling',
|
|
85
86
|
'borderSize',
|
|
86
87
|
'stepSize',
|
|
87
|
-
'centerposition',
|
|
88
88
|
'fullScreen',
|
|
89
89
|
'moveable',
|
|
90
90
|
'show_position',
|
|
@@ -194,6 +194,14 @@ function sizeFrom(block) {
|
|
|
194
194
|
const height = numberScalar(size, 'height', 'y');
|
|
195
195
|
return width === undefined || height === undefined ? undefined : { width, height };
|
|
196
196
|
}
|
|
197
|
+
function nestedSizeFrom(block, key) {
|
|
198
|
+
const nested = assignments(block).find((assignment) => assignment.key.value.toLowerCase() === key.toLowerCase() && assignment.value.type === 'block')?.value;
|
|
199
|
+
if (nested?.type !== 'block')
|
|
200
|
+
return undefined;
|
|
201
|
+
const width = numberScalar(nested, 'width', 'x');
|
|
202
|
+
const height = numberScalar(nested, 'height', 'y');
|
|
203
|
+
return width === undefined || height === undefined ? undefined : { width, height };
|
|
204
|
+
}
|
|
197
205
|
function raw(document, node) {
|
|
198
206
|
return document.text.slice(node.start, node.end);
|
|
199
207
|
}
|
|
@@ -335,7 +343,7 @@ function indexSpritesAndFonts(document, file, fileNodeId, nodes, edges, sprites,
|
|
|
335
343
|
const name = firstScalarInsensitive(child, 'name');
|
|
336
344
|
if (name !== undefined) {
|
|
337
345
|
const id = deterministicId('gui_sprite', { path: file.displayPath, name });
|
|
338
|
-
const texturePath = firstScalarInsensitive(child, 'texturefile', 'textureFile');
|
|
346
|
+
const texturePath = firstScalarInsensitive(child, 'texturefile', 'textureFile', 'textureFile1');
|
|
339
347
|
const texturePath2 = firstScalarInsensitive(child, 'textureFile2');
|
|
340
348
|
const staticFallback = firstScalarInsensitive(child, 'static_fallback', 'staticFallback');
|
|
341
349
|
const animationRateFps = numberScalar(child, 'animation_rate_fps');
|
|
@@ -344,6 +352,10 @@ function indexSpritesAndFonts(document, file, fileNodeId, nodes, edges, sprites,
|
|
|
344
352
|
const pauseOnLoop = numberScalar(child, 'pause_on_loop');
|
|
345
353
|
const effectFile = firstScalarInsensitive(child, 'effectFile');
|
|
346
354
|
const declaredSize = sizeFrom(child);
|
|
355
|
+
const borderSize = nestedSizeFrom(child, 'borderSize');
|
|
356
|
+
const tilingCenter = boolScalar(child, 'tilingCenter');
|
|
357
|
+
const horizontal = boolScalar(child, 'horizontal');
|
|
358
|
+
const steps = numberScalar(child, 'steps');
|
|
347
359
|
const definition = {
|
|
348
360
|
id,
|
|
349
361
|
name,
|
|
@@ -361,6 +373,10 @@ function indexSpritesAndFonts(document, file, fileNodeId, nodes, edges, sprites,
|
|
|
361
373
|
...(effectFile === undefined ? {} : { effectFile }),
|
|
362
374
|
...(staticFallback === undefined ? {} : { staticFallback }),
|
|
363
375
|
...(declaredSize === undefined ? {} : { declaredSize }),
|
|
376
|
+
...(borderSize === undefined ? {} : { borderSize }),
|
|
377
|
+
...(tilingCenter === undefined ? {} : { tilingCenter }),
|
|
378
|
+
...(horizontal === undefined ? {} : { horizontal }),
|
|
379
|
+
...(steps === undefined ? {} : { steps: Math.trunc(steps) }),
|
|
364
380
|
rawSource: raw(document, assignment),
|
|
365
381
|
};
|
|
366
382
|
addDomainEntry(sprites, definition, 'sprite');
|
|
@@ -374,6 +390,19 @@ function indexSpritesAndFonts(document, file, fileNodeId, nodes, edges, sprites,
|
|
|
374
390
|
spriteType: definition.spriteType,
|
|
375
391
|
frameCount: definition.frameCount,
|
|
376
392
|
frameAnimated: definition.frameAnimated,
|
|
393
|
+
...(definition.borderSize === undefined
|
|
394
|
+
? {}
|
|
395
|
+
: {
|
|
396
|
+
borderSize: {
|
|
397
|
+
width: definition.borderSize.width,
|
|
398
|
+
height: definition.borderSize.height,
|
|
399
|
+
},
|
|
400
|
+
}),
|
|
401
|
+
...(definition.tilingCenter === undefined
|
|
402
|
+
? {}
|
|
403
|
+
: { tilingCenter: definition.tilingCenter }),
|
|
404
|
+
...(definition.horizontal === undefined ? {} : { horizontal: definition.horizontal }),
|
|
405
|
+
...(definition.steps === undefined ? {} : { steps: definition.steps }),
|
|
377
406
|
...(definition.animationRateFps === undefined
|
|
378
407
|
? {}
|
|
379
408
|
: { animationRateFps: definition.animationRateFps }),
|
|
@@ -607,9 +636,9 @@ function indexAnimationSourceManifest(file, fileNodeId, nodes, edges, manifests,
|
|
|
607
636
|
addEdge(edges, 'contains', fileNodeId, id, true, {}, location);
|
|
608
637
|
}
|
|
609
638
|
function linkAnimationSources(nodes, edges, manifests, sprites, textureNodes, frameNodes) {
|
|
610
|
-
const spritesByName = new Map(sprites.map((sprite) => [sprite.name, sprite]));
|
|
639
|
+
const spritesByName = new Map(sprites.map((sprite) => [sprite.name.toLocaleLowerCase('en-US'), sprite]));
|
|
611
640
|
for (const manifest of manifests) {
|
|
612
|
-
const sprite = spritesByName.get(manifest.sprite);
|
|
641
|
+
const sprite = spritesByName.get(manifest.sprite.toLocaleLowerCase('en-US'));
|
|
613
642
|
addEdge(edges, 'animation_provenance', sprite?.id ?? `sprite:${manifest.sprite}`, manifest.id, sprite !== undefined, { sprite: manifest.sprite }, manifest.location);
|
|
614
643
|
const sheet = textureNodes.get(normalizeAssetPath(manifest.sheet.path));
|
|
615
644
|
addEdge(edges, 'animation_sheet', manifest.id, sheet ?? `texture:${normalizeAssetPath(manifest.sheet.path)}`, sheet !== undefined, { texturePath: manifest.sheet.path }, manifest.location);
|
|
@@ -688,8 +717,8 @@ function indexDecisionEntries(document, file, nodes, pendingDecisionEdges) {
|
|
|
688
717
|
walk(document.root);
|
|
689
718
|
}
|
|
690
719
|
function linkGraph(nodes, edges, elements, sprites, fonts, scriptedGuis, scriptedLocalisation, localisation, textureNodes, fontAssetNodes, pendingDecisionEdges) {
|
|
691
|
-
const spriteByName = new Map(sprites.map((sprite) => [sprite.name, sprite]));
|
|
692
|
-
const fontByName = new Map(fonts.map((font) => [font.name, font]));
|
|
720
|
+
const spriteByName = new Map(sprites.map((sprite) => [sprite.name.toLocaleLowerCase('en-US'), sprite]));
|
|
721
|
+
const fontByName = new Map(fonts.map((font) => [font.name.toLocaleLowerCase('en-US'), font]));
|
|
693
722
|
const elementByName = new Map();
|
|
694
723
|
for (const element of [...elements].sort((a, b) => a.definitionOrder - b.definitionOrder)) {
|
|
695
724
|
if (!elementByName.has(element.name))
|
|
@@ -719,7 +748,7 @@ function linkGraph(nodes, edges, elements, sprites, fonts, scriptedGuis, scripte
|
|
|
719
748
|
`${sprite.name}_fallback`,
|
|
720
749
|
].filter((value) => value !== undefined);
|
|
721
750
|
const fallback = candidates
|
|
722
|
-
.map((name) => spriteByName.get(name))
|
|
751
|
+
.map((name) => spriteByName.get(name.toLocaleLowerCase('en-US')))
|
|
723
752
|
.find((value) => value !== undefined);
|
|
724
753
|
if (fallback !== undefined)
|
|
725
754
|
addEdge(edges, 'static_fallback', sprite.id, fallback.id, true, {}, sprite.location);
|
|
@@ -739,12 +768,12 @@ function linkGraph(nodes, edges, elements, sprites, fonts, scriptedGuis, scripte
|
|
|
739
768
|
for (const element of elements) {
|
|
740
769
|
const spriteName = [element.attributes.spriteType, element.attributes.quadTextureSprite].find((value) => typeof value === 'string' && value.trim().length > 0);
|
|
741
770
|
if (spriteName !== undefined) {
|
|
742
|
-
const sprite = spriteByName.get(spriteName);
|
|
771
|
+
const sprite = spriteByName.get(spriteName.toLocaleLowerCase('en-US'));
|
|
743
772
|
addEdge(edges, 'uses_sprite', element.id, sprite?.id ?? `sprite:${spriteName}`, sprite !== undefined, { spriteName }, element.location);
|
|
744
773
|
}
|
|
745
774
|
const fontName = [element.attributes.font, element.attributes.buttonFont].find((value) => typeof value === 'string' && value.trim().length > 0);
|
|
746
775
|
if (fontName !== undefined) {
|
|
747
|
-
const font = fontByName.get(fontName);
|
|
776
|
+
const font = fontByName.get(fontName.toLocaleLowerCase('en-US'));
|
|
748
777
|
addEdge(edges, 'uses_font', element.id, font?.id ?? `font:${fontName}`, font !== undefined, { fontName }, element.location);
|
|
749
778
|
}
|
|
750
779
|
for (const key of localisationAttributeNames) {
|
|
@@ -195,7 +195,10 @@ function guiLocalisationPatterns(workspace, languages = ['l_english']) {
|
|
|
195
195
|
].sort((left, right) => compareCodeUnits(left, right));
|
|
196
196
|
}
|
|
197
197
|
function normalizedAssetReference(value) {
|
|
198
|
-
const normalized = value
|
|
198
|
+
const normalized = value
|
|
199
|
+
.replaceAll('\\', '/')
|
|
200
|
+
.replace(/^\/+|^\.\//u, '')
|
|
201
|
+
.replace(/\/+/gu, '/');
|
|
199
202
|
return isSafeAnimationSourcePath(normalized) ? normalized : undefined;
|
|
200
203
|
}
|
|
201
204
|
function referenceVariants(value) {
|
|
@@ -264,29 +267,54 @@ function collectNamedAttributes(value, keys, output) {
|
|
|
264
267
|
collectNamedAttributes(entry, keys, output);
|
|
265
268
|
}
|
|
266
269
|
}
|
|
270
|
+
function localisationIconTokens(value) {
|
|
271
|
+
return [...value.matchAll(/£([A-Za-z0-9_.:+-]+)/gu)]
|
|
272
|
+
.map((match) => match[1])
|
|
273
|
+
.filter((token) => token !== undefined && token.length > 0);
|
|
274
|
+
}
|
|
275
|
+
function localisationIconSpriteCandidates(token) {
|
|
276
|
+
const normalized = token.startsWith('GFX_') ? token : `GFX_${token}`;
|
|
277
|
+
return [...new Set([normalized, `${normalized}_texticon`, `${normalized}_text_icon`])];
|
|
278
|
+
}
|
|
267
279
|
function referencedAssetPatternsForWindow(graph, windowName) {
|
|
268
280
|
const spriteNames = new Set();
|
|
269
281
|
const fontNames = new Set();
|
|
282
|
+
const textValues = new Set();
|
|
270
283
|
for (const element of selectedElements(graph, windowName)) {
|
|
271
284
|
collectNamedAttributes(element.attributes, new Set(['spriteType', 'quadTextureSprite']), spriteNames);
|
|
272
285
|
collectNamedAttributes(element.attributes, new Set(['font', 'buttonFont']), fontNames);
|
|
286
|
+
collectNamedAttributes(element.attributes, new Set(['text', 'buttonText']), textValues);
|
|
287
|
+
}
|
|
288
|
+
for (const localisation of graph.localisation) {
|
|
289
|
+
if (textValues.has(localisation.key))
|
|
290
|
+
textValues.add(localisation.value);
|
|
273
291
|
}
|
|
274
|
-
const
|
|
292
|
+
for (const value of textValues) {
|
|
293
|
+
for (const token of localisationIconTokens(value)) {
|
|
294
|
+
for (const candidate of localisationIconSpriteCandidates(token))
|
|
295
|
+
spriteNames.add(candidate);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const spritesByName = new Map(graph.sprites.map((sprite) => [sprite.name.toLocaleLowerCase('en-US'), sprite]));
|
|
275
299
|
const selectedSprites = new Map();
|
|
276
300
|
const pendingSprites = [...spriteNames];
|
|
277
301
|
while (pendingSprites.length > 0) {
|
|
278
302
|
const name = pendingSprites.pop();
|
|
279
|
-
if (name === undefined
|
|
303
|
+
if (name === undefined)
|
|
304
|
+
continue;
|
|
305
|
+
const key = name.toLocaleLowerCase('en-US');
|
|
306
|
+
if (selectedSprites.has(key))
|
|
280
307
|
continue;
|
|
281
|
-
const sprite = spritesByName.get(
|
|
308
|
+
const sprite = spritesByName.get(key);
|
|
282
309
|
if (sprite === undefined)
|
|
283
310
|
continue;
|
|
284
|
-
selectedSprites.set(
|
|
311
|
+
selectedSprites.set(key, sprite);
|
|
285
312
|
if (sprite.staticFallback !== undefined)
|
|
286
313
|
pendingSprites.push(sprite.staticFallback);
|
|
287
314
|
}
|
|
288
|
-
const selectedManifests = graph.animationSources.filter(({ sprite }) => selectedSprites.has(sprite));
|
|
289
|
-
const
|
|
315
|
+
const selectedManifests = graph.animationSources.filter(({ sprite }) => selectedSprites.has(sprite.toLocaleLowerCase('en-US')));
|
|
316
|
+
const selectedFontNames = new Set([...fontNames].map((name) => name.toLocaleLowerCase('en-US')));
|
|
317
|
+
const selectedFonts = graph.fonts.filter(({ name }) => selectedFontNames.has(name.toLocaleLowerCase('en-US')));
|
|
290
318
|
const references = [
|
|
291
319
|
...[...selectedSprites.values()].flatMap(({ texturePath, texturePath2 }) => [texturePath, texturePath2].filter((value) => value !== undefined)),
|
|
292
320
|
...selectedFonts.flatMap(({ assetPaths }) => assetPaths),
|
|
@@ -120,6 +120,8 @@ const analysisDataSchema = z
|
|
|
120
120
|
cacheKey: sha256Schema,
|
|
121
121
|
scenarios: nonNegativeIntegerSchema,
|
|
122
122
|
candidates: nonNegativeIntegerSchema,
|
|
123
|
+
scopePools: nonNegativeIntegerSchema.optional(),
|
|
124
|
+
scopePoolCandidates: nonNegativeIntegerSchema.optional(),
|
|
123
125
|
unresolved: nonNegativeIntegerSchema,
|
|
124
126
|
diagnostics: nonNegativeIntegerSchema,
|
|
125
127
|
sweepPoints: nonNegativeIntegerSchema.optional(),
|
|
@@ -194,6 +196,13 @@ function analysisData(result) {
|
|
|
194
196
|
cacheKey: result.metadata.cacheKey,
|
|
195
197
|
scenarios: result.scenarios.length,
|
|
196
198
|
candidates: result.scenarios.reduce((sum, scenario) => sum + scenario.candidates.length, 0),
|
|
199
|
+
...(result.scenarios.some(({ scopePools }) => (scopePools?.length ?? 0) > 0)
|
|
200
|
+
? {
|
|
201
|
+
scopePools: result.scenarios.reduce((sum, scenario) => sum + (scenario.scopePools?.length ?? 0), 0),
|
|
202
|
+
scopePoolCandidates: result.scenarios.reduce((sum, scenario) => sum +
|
|
203
|
+
(scenario.scopePools?.reduce((poolSum, pool) => poolSum + pool.candidates.length, 0) ?? 0), 0),
|
|
204
|
+
}
|
|
205
|
+
: {}),
|
|
197
206
|
unresolved: result.unresolved.length,
|
|
198
207
|
diagnostics: result.diagnostics.length,
|
|
199
208
|
...(result.sweep === undefined ? {} : { sweepPoints: result.sweep.points.length }),
|
|
@@ -326,6 +326,11 @@ const descriptors = Object.fromEntries(Object.entries(sources).map(([id, source]
|
|
|
326
326
|
'Apply declared external factors only after source-local operations.',
|
|
327
327
|
],
|
|
328
328
|
poolNormalizationRules: normalizationRule(source.selectionRule),
|
|
329
|
+
supportedExpressions: [
|
|
330
|
+
...source.supportedExpressions,
|
|
331
|
+
'scenario-bound ROOT/THIS/PREV/FROM scope chains, saved scopes, and event targets',
|
|
332
|
+
'declared dynamic scope-pool filters with uniform or proportional selection',
|
|
333
|
+
],
|
|
329
334
|
timingConversion: source.selectionRule === 'median_daily_hazard'
|
|
330
335
|
? [
|
|
331
336
|
'Treat the effective duration as the median of verified daily checks while active.',
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { compareCodeUnits } from '../core/canonical.js';
|
|
2
2
|
import { assignments } from '../core/source/index.js';
|
|
3
3
|
import { Rational, sumRationals, uniformRaceProbabilities } from './rational.js';
|
|
4
|
+
import { withScenarioPathValue } from './scenario-state.js';
|
|
5
|
+
import { evaluateScopePools } from './scope-pools.js';
|
|
4
6
|
import { evaluateTriggerBlock } from './trigger-evaluator.js';
|
|
5
7
|
import { evaluateCandidateValue } from './value-evaluator.js';
|
|
6
8
|
const BRANCH_LIMIT = 4_096;
|
|
@@ -305,7 +307,7 @@ function scenarioBranches(surface, scenario) {
|
|
|
305
307
|
});
|
|
306
308
|
break;
|
|
307
309
|
}
|
|
308
|
-
branches = branches.flatMap((branch) => values.map((value) => (
|
|
310
|
+
branches = branches.flatMap((branch) => values.map((value) => withScenarioPathValue(branch, input.path, value)));
|
|
309
311
|
if (values.length > 1)
|
|
310
312
|
bounded = true;
|
|
311
313
|
}
|
|
@@ -599,10 +601,12 @@ export function evaluateSurfaceScenarios(surface, scenarios, definitions, horizo
|
|
|
599
601
|
return result;
|
|
600
602
|
});
|
|
601
603
|
rankCandidates(candidates);
|
|
604
|
+
const scopePools = evaluateScopePools(scenario, definitions);
|
|
602
605
|
const allUnresolved = [
|
|
603
606
|
...surface.unsupported,
|
|
604
607
|
...expanded.unresolved,
|
|
605
608
|
...candidates.flatMap(({ unresolved }) => unresolved),
|
|
609
|
+
...scopePools.flatMap(({ unresolved }) => unresolved),
|
|
606
610
|
];
|
|
607
611
|
const timing = horizonDays === undefined
|
|
608
612
|
? { unresolved: [] }
|
|
@@ -632,6 +636,7 @@ export function evaluateSurfaceScenarios(surface, scenarios, definitions, horizo
|
|
|
632
636
|
? 'score_only'
|
|
633
637
|
: 'exact',
|
|
634
638
|
candidates,
|
|
639
|
+
...(scopePools.length === 0 ? {} : { scopePools }),
|
|
635
640
|
...(exactValues.length === candidates.length &&
|
|
636
641
|
surface.adapter.selectionRule === 'proportional_categorical'
|
|
637
642
|
? { poolTotal: sumRationals(exactValues).toJSON() }
|