@alpaca-software/40kdc-data 0.2.0 → 0.3.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.
Files changed (77) hide show
  1. package/dist/author-input.d.ts +20 -1
  2. package/dist/author-input.d.ts.map +1 -1
  3. package/dist/author-input.js +64 -8
  4. package/dist/author-input.js.map +1 -1
  5. package/dist/author-seed.d.ts +62 -0
  6. package/dist/author-seed.d.ts.map +1 -0
  7. package/dist/author-seed.js +194 -0
  8. package/dist/author-seed.js.map +1 -0
  9. package/dist/codegen-data.js +2 -0
  10. package/dist/codegen-data.js.map +1 -1
  11. package/dist/commands/translate.d.ts.map +1 -1
  12. package/dist/commands/translate.js +6 -68
  13. package/dist/commands/translate.js.map +1 -1
  14. package/dist/data/bundle.generated.js +1 -1
  15. package/dist/data/bundle.generated.js.map +1 -1
  16. package/dist/data/dataset.d.ts +16 -1
  17. package/dist/data/dataset.d.ts.map +1 -1
  18. package/dist/data/dataset.js +25 -0
  19. package/dist/data/dataset.js.map +1 -1
  20. package/dist/data/index.d.ts +4 -0
  21. package/dist/data/index.d.ts.map +1 -1
  22. package/dist/data/index.js +4 -0
  23. package/dist/data/index.js.map +1 -1
  24. package/dist/data/types.d.ts +5 -1
  25. package/dist/data/types.d.ts.map +1 -1
  26. package/dist/data/types.js +2 -0
  27. package/dist/data/types.js.map +1 -1
  28. package/dist/gen-conformance.js +180 -1
  29. package/dist/gen-conformance.js.map +1 -1
  30. package/dist/generated.d.ts +309 -154
  31. package/dist/generated.d.ts.map +1 -1
  32. package/dist/generated.js.map +1 -1
  33. package/dist/index.d.ts +3 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +7 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/migrate-terrain.d.ts +2 -0
  38. package/dist/migrate-terrain.d.ts.map +1 -0
  39. package/dist/migrate-terrain.js +297 -0
  40. package/dist/migrate-terrain.js.map +1 -0
  41. package/dist/runner.d.ts.map +1 -1
  42. package/dist/runner.js +42 -0
  43. package/dist/runner.js.map +1 -1
  44. package/dist/terrain/index.d.ts +11 -0
  45. package/dist/terrain/index.d.ts.map +1 -0
  46. package/dist/terrain/index.js +9 -0
  47. package/dist/terrain/index.js.map +1 -0
  48. package/dist/terrain/resolve.d.ts +122 -0
  49. package/dist/terrain/resolve.d.ts.map +1 -0
  50. package/dist/terrain/resolve.js +221 -0
  51. package/dist/terrain/resolve.js.map +1 -0
  52. package/dist/terrain/solve.d.ts +56 -0
  53. package/dist/terrain/solve.d.ts.map +1 -0
  54. package/dist/terrain/solve.js +80 -0
  55. package/dist/terrain/solve.js.map +1 -0
  56. package/dist/translate/condition.d.ts +26 -0
  57. package/dist/translate/condition.d.ts.map +1 -0
  58. package/dist/translate/condition.js +171 -0
  59. package/dist/translate/condition.js.map +1 -0
  60. package/dist/translate/index.d.ts +9 -0
  61. package/dist/translate/index.d.ts.map +1 -0
  62. package/dist/translate/index.js +9 -0
  63. package/dist/translate/index.js.map +1 -0
  64. package/dist/translate/scoring.d.ts +38 -0
  65. package/dist/translate/scoring.d.ts.map +1 -0
  66. package/dist/translate/scoring.js +80 -0
  67. package/dist/translate/scoring.js.map +1 -0
  68. package/dist/validate.d.ts.map +1 -1
  69. package/dist/validate.js +1 -0
  70. package/dist/validate.js.map +1 -1
  71. package/package.json +3 -1
  72. package/schemas/$defs/common.schema.json +43 -0
  73. package/schemas/core/secondary-card.schema.json +50 -28
  74. package/schemas/core/terrain-layout.schema.json +42 -56
  75. package/schemas/core/terrain-template.schema.json +105 -0
  76. package/schemas/enrichment/ability-dsl/condition.schema.json +5 -2
  77. package/schemas/enrichment/ability-dsl/effect.schema.json +2 -1
@@ -0,0 +1,297 @@
1
+ /**
2
+ * One-shot (and re-runnable) migration of shadowboxing (`~/bevy-deploy-helper`)
3
+ * 11e terrain layouts into the canonical centroid-anchored `terrain-layout`
4
+ * representation, written to `data/core/terrain-layouts.json`.
5
+ *
6
+ * Two representations are produced, both geometry-preserving and verified by
7
+ * re-resolving the output and diffing against ground truth (≤5e-4):
8
+ *
9
+ * - **Areas** → `template` (matched by source name) + the centroid as `position`
10
+ * + a Procrustes-recovered `rotation_degrees`/`mirror`. This exercises the
11
+ * template + orientation representation the schema is designed around.
12
+ * - **Features** (walls/corners/containers/floors) → an inline baked
13
+ * `footprint` polygon (centroid-relative) with `rotation_degrees: 0`,
14
+ * `mirror: "none"`, carrying the matched `template` id as provenance. Walls
15
+ * and corners are lines in the source with no vertex-identical catalog
16
+ * footprint, so baking is the faithful, exactly-verifiable choice. Multi-line
17
+ * features (corners) emit one piece per segment.
18
+ *
19
+ * Usage: `npx tsx src/migrate-terrain.ts` (run from `tools/`).
20
+ */
21
+ import { readFileSync, writeFileSync } from "node:fs";
22
+ import { homedir } from "node:os";
23
+ import { join } from "node:path";
24
+ import { resolveLayout, polygonCentroid, footprintVertices } from "./terrain/resolve.js";
25
+ const BOARD_HEIGHT = 44;
26
+ const TOL = 5e-4;
27
+ const SB = join(homedir(), "bevy-deploy-helper", "assets", "terrain-layouts", "gw-11e");
28
+ const REPO_ROOT = join(new URL("../..", import.meta.url).pathname);
29
+ const CATALOG_PATH = join(REPO_ROOT, "data", "core", "terrain-templates.json");
30
+ const OUT_PATH = join(REPO_ROOT, "data", "core", "terrain-layouts.json");
31
+ // ---- shadowboxing transform (reproduced from src/los/shapes.rs) -----------
32
+ // local_to_world: mirror → rotate(-θ) → translate(world_position); world is
33
+ // y-up (world_position flips JSON y). We then map back to the 40kdc board
34
+ // frame (top-left origin, y-down) — which equals shadowboxing's JSON frame.
35
+ const sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
36
+ const add = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
37
+ function sbMirror(v, m) {
38
+ if (m === "horizontal")
39
+ return { x: -v.x, y: v.y };
40
+ if (m === "vertical")
41
+ return { x: v.x, y: -v.y };
42
+ return v;
43
+ }
44
+ /** shadowboxing local(y-up) → world(y-up): mirror → Mat2::from_angle(-θ) → +pos */
45
+ function sbLocalToWorld(localYup, piece) {
46
+ const m = sbMirror(localYup, piece.mirror ?? "none");
47
+ const a = -((piece.rotation ?? 0) * Math.PI) / 180;
48
+ // glam Mat2::from_angle(a) = [[cos a, -sin a],[sin a, cos a]]
49
+ const c = Math.cos(a);
50
+ const s = Math.sin(a);
51
+ const rotated = { x: c * m.x - s * m.y, y: s * m.x + c * m.y };
52
+ const worldPos = { x: piece.position.x, y: BOARD_HEIGHT - piece.position.y };
53
+ return add(rotated, worldPos);
54
+ }
55
+ /** world(y-up) → 40kdc board frame (y-down). */
56
+ const toBoard = (world) => ({ x: world.x, y: BOARD_HEIGHT - world.y });
57
+ /** A shape's outline vertices in shadowboxing local y-up coordinates. */
58
+ function shapeLocalYupVerts(shape) {
59
+ switch (shape.type) {
60
+ case "rectangle": {
61
+ const ox = shape.x ?? 0;
62
+ const oy = shape.y ?? 0;
63
+ // JSON-local span x∈[ox,ox+w], y∈[oy,oy+h] (y-down) → y-up negate y.
64
+ return [
65
+ { x: ox, y: -oy },
66
+ { x: ox + shape.width, y: -oy },
67
+ { x: ox + shape.width, y: -(oy + shape.height) },
68
+ { x: ox, y: -(oy + shape.height) },
69
+ ];
70
+ }
71
+ case "polygon":
72
+ return shape.points.map((p) => ({ x: p.x, y: -p.y }));
73
+ case "line": {
74
+ const s = { x: shape.start.x, y: -shape.start.y };
75
+ const e = { x: shape.end.x, y: -shape.end.y };
76
+ const dx = e.x - s.x;
77
+ const dy = e.y - s.y;
78
+ const len = Math.hypot(dx, dy) || 1;
79
+ const dir = { x: dx / len, y: dy / len };
80
+ const perp = { x: -dir.y * (shape.thickness / 2), y: dir.x * (shape.thickness / 2) };
81
+ return [add(s, perp), add(e, perp), sub(e, perp), sub(s, perp)];
82
+ }
83
+ }
84
+ }
85
+ /** Ground-truth board-frame (y-down) outline of a source shape. */
86
+ function sbShapeBoardVerts(shape, piece) {
87
+ return shapeLocalYupVerts(shape).map((v) => toBoard(sbLocalToWorld(v, piece)));
88
+ }
89
+ // ---- area template matching + Procrustes orientation recovery -------------
90
+ const AREA_NAME_TO_TEMPLATE = {
91
+ "11e Large Area": "area-large",
92
+ "11e Trapezoid Area": "area-trapezoid",
93
+ "11e Medium Area": "area-medium",
94
+ "11e Long Line Area": "area-long-line",
95
+ "11e Short Line Area": "area-short-line",
96
+ };
97
+ const FEATURE_NAME_TO_TEMPLATE = {
98
+ "Wall Long": "wall-long",
99
+ "Wall Medium": "wall-medium",
100
+ "Wall Short": "wall-short",
101
+ "Wall XS": "wall-xs",
102
+ "Warzone Wall Lg": "warzone-wall-large",
103
+ "Warzone Wall Md": "warzone-wall-medium",
104
+ "Warzone Wall Sm": "warzone-wall-small",
105
+ "XS Corner": "corner-xs",
106
+ "Small Corner": "corner-small",
107
+ "Medium Corner": "corner-medium",
108
+ "Large Corner": "corner-large",
109
+ Canister: "canister",
110
+ Scaffold: "scaffold",
111
+ Pipe: "pipe",
112
+ "Floor 4x4": "floor-4x4",
113
+ "Floor 3x4": "floor-3x4",
114
+ "Floor 2.5x4": "floor-2p5x4",
115
+ "Floor Trapezoid": "floor-trapezoid",
116
+ };
117
+ const MIRRORS = ["none", "horizontal", "vertical"];
118
+ function applyMirror(v, m) {
119
+ if (m === "horizontal")
120
+ return { x: -v.x, y: v.y };
121
+ if (m === "vertical")
122
+ return { x: v.x, y: -v.y };
123
+ return v;
124
+ }
125
+ function rotateCw(v, deg) {
126
+ const r = (deg * Math.PI) / 180;
127
+ const c = Math.cos(r);
128
+ const s = Math.sin(r);
129
+ return { x: c * v.x - s * v.y, y: s * v.x + c * v.y };
130
+ }
131
+ /**
132
+ * Recover (rotation, mirror) such that the resolver, placing `template` at the
133
+ * ground-truth centroid, reproduces `gt`. `templateVerts` are the catalog
134
+ * footprint vertices in the SAME order as `gt` (the source shape is identical
135
+ * to the catalog shape for areas). Returns null if no mirror gives a clean fit.
136
+ */
137
+ function recoverOrientation(templateVerts, gt) {
138
+ const cV = polygonCentroid(templateVerts);
139
+ const cGt = polygonCentroid(gt);
140
+ let best = null;
141
+ for (const mirror of MIRRORS) {
142
+ const A = templateVerts.map((v) => applyMirror(sub(v, cV), mirror));
143
+ const B = gt.map((v) => sub(v, cGt));
144
+ let cross = 0;
145
+ let dot = 0;
146
+ for (let i = 0; i < A.length; i++) {
147
+ cross += A[i].x * B[i].y - A[i].y * B[i].x;
148
+ dot += A[i].x * B[i].x + A[i].y * B[i].y;
149
+ }
150
+ const theta = Math.atan2(cross, dot);
151
+ let residual = 0;
152
+ for (let i = 0; i < A.length; i++) {
153
+ const r = rotateCw(A[i], (theta * 180) / Math.PI);
154
+ residual = Math.max(residual, Math.hypot(r.x - B[i].x, r.y - B[i].y));
155
+ }
156
+ let deg = ((theta * 180) / Math.PI) % 360;
157
+ if (deg < 0)
158
+ deg += 360;
159
+ if (deg >= 360 - 1e-9)
160
+ deg = 0;
161
+ if (!best || residual < best.residual)
162
+ best = { rotation_degrees: deg, mirror, residual };
163
+ }
164
+ return best && best.residual <= TOL ? { rotation_degrees: best.rotation_degrees, mirror: best.mirror } : null;
165
+ }
166
+ // ---- per-shape baked footprint (centroid-relative) ------------------------
167
+ function bakedFootprint(gt) {
168
+ const c = polygonCentroid(gt);
169
+ const rounded = (n) => Math.round(n * 1e4) / 1e4;
170
+ return {
171
+ footprint: { type: "polygon", points: gt.map((v) => ({ x: rounded(v.x - c.x), y: rounded(v.y - c.y) })) },
172
+ position: { x: rounded(c.x), y: rounded(c.y) },
173
+ };
174
+ }
175
+ function migrateLayout(src, idPrefix, name, catalogById) {
176
+ const pieces = [];
177
+ const groundTruth = [];
178
+ for (const p of src.pieces) {
179
+ const isArea = (p.pieceType ?? "area") === "area";
180
+ if (isArea && p.shapes.length === 1) {
181
+ const tid = AREA_NAME_TO_TEMPLATE[p.name];
182
+ const tmpl = tid ? catalogById.get(tid) : undefined;
183
+ if (tmpl) {
184
+ const templateVerts = footprintVertices(tmpl.footprint);
185
+ const gt = sbShapeBoardVerts(p.shapes[0], p);
186
+ const ori = recoverOrientation(templateVerts, gt);
187
+ if (ori) {
188
+ const c = polygonCentroid(gt);
189
+ const round = (n) => Math.round(n * 1e4) / 1e4;
190
+ const piece = {
191
+ id: p.id,
192
+ name: p.name,
193
+ piece_type: "area",
194
+ template: tid,
195
+ position: { x: round(c.x), y: round(c.y) },
196
+ };
197
+ if (Math.abs(ori.rotation_degrees) > 1e-6)
198
+ piece.rotation_degrees = round(ori.rotation_degrees);
199
+ if (ori.mirror !== "none")
200
+ piece.mirror = ori.mirror;
201
+ if (p.linkGroup)
202
+ piece.link_group = p.linkGroup;
203
+ pieces.push(piece);
204
+ groundTruth.push({ pieceId: p.id, verts: gt });
205
+ continue;
206
+ }
207
+ }
208
+ }
209
+ // Feature (or unmatched area): bake each shape as its own inline piece.
210
+ const tid = FEATURE_NAME_TO_TEMPLATE[p.name] ?? AREA_NAME_TO_TEMPLATE[p.name];
211
+ p.shapes.forEach((shape, i) => {
212
+ const gt = sbShapeBoardVerts(shape, p);
213
+ const { footprint, position } = bakedFootprint(gt);
214
+ const pieceId = p.shapes.length > 1 ? `${p.id}-${i + 1}` : p.id;
215
+ const piece = {
216
+ id: pieceId,
217
+ name: p.name,
218
+ piece_type: isArea ? "area" : "feature",
219
+ footprint,
220
+ position,
221
+ };
222
+ if (tid)
223
+ piece.template = tid;
224
+ if (p.floor)
225
+ piece.floor = p.floor;
226
+ if (p.linkGroup)
227
+ piece.link_group = p.linkGroup;
228
+ pieces.push(piece);
229
+ groundTruth.push({ pieceId, verts: gt });
230
+ });
231
+ }
232
+ return {
233
+ layout: {
234
+ id: idPrefix,
235
+ name,
236
+ source: "gw-11e",
237
+ pieces,
238
+ game_version: { edition: "11th", dataslate: "pre-launch-provisional" },
239
+ },
240
+ groundTruth,
241
+ };
242
+ }
243
+ // ---- verify: re-resolve and diff against ground truth ---------------------
244
+ function multisetMatch(a, b) {
245
+ if (a.length !== b.length)
246
+ return false;
247
+ const used = new Array(b.length).fill(false);
248
+ for (const va of a) {
249
+ let found = -1;
250
+ for (let j = 0; j < b.length; j++) {
251
+ if (!used[j] && Math.abs(va.x - b[j].x) <= TOL && Math.abs(va.y - b[j].y) <= TOL) {
252
+ found = j;
253
+ break;
254
+ }
255
+ }
256
+ if (found < 0)
257
+ return false;
258
+ used[found] = true;
259
+ }
260
+ return true;
261
+ }
262
+ function main() {
263
+ const catalog = JSON.parse(readFileSync(CATALOG_PATH, "utf8"));
264
+ const catalogById = new Map(catalog.map((t) => [t.id, t]));
265
+ const sources = [
266
+ { file: "crucible-of-battle.json", id: "gw-11e-crucible", name: "Crucible of Battle" },
267
+ { file: "hammer-and-anvil.json", id: "gw-11e-hammer-anvil", name: "Hammer and Anvil" },
268
+ ];
269
+ const out = [];
270
+ let totalPieces = 0;
271
+ let failures = 0;
272
+ for (const s of sources) {
273
+ const src = JSON.parse(readFileSync(join(SB, s.file), "utf8"));
274
+ const { layout, groundTruth } = migrateLayout(src, s.id, s.name, catalogById);
275
+ // Verify by re-resolving the migrated layout against the catalog.
276
+ const resolved = resolveLayout(layout, catalog);
277
+ const byId = new Map(resolved.filter((r) => r.id).map((r) => [r.id, r]));
278
+ for (const { pieceId, verts } of groundTruth) {
279
+ totalPieces++;
280
+ const r = byId.get(pieceId);
281
+ if (!r || !multisetMatch(r.vertices, verts)) {
282
+ failures++;
283
+ console.error(` ✗ ${s.id}/${pieceId}: resolved geometry does not match source`);
284
+ }
285
+ }
286
+ console.log(`${s.id}: ${layout.pieces.length} pieces, ${groundTruth.length} verified`);
287
+ out.push(layout);
288
+ }
289
+ if (failures > 0) {
290
+ console.error(`\nMIGRATION FAILED: ${failures}/${totalPieces} pieces did not reproduce source geometry.`);
291
+ process.exit(1);
292
+ }
293
+ writeFileSync(OUT_PATH, `${JSON.stringify(out, null, 2)}\n`);
294
+ console.log(`\nAll ${totalPieces} pieces verified within ${TOL}. Wrote ${OUT_PATH}`);
295
+ }
296
+ main();
297
+ //# sourceMappingURL=migrate-terrain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-terrain.js","sourceRoot":"","sources":["../src/migrate-terrain.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One-shot (and re-runnable) migration of shadowboxing (`~/bevy-deploy-helper`)\n * 11e terrain layouts into the canonical centroid-anchored `terrain-layout`\n * representation, written to `data/core/terrain-layouts.json`.\n *\n * Two representations are produced, both geometry-preserving and verified by\n * re-resolving the output and diffing against ground truth (≤5e-4):\n *\n * - **Areas** → `template` (matched by source name) + the centroid as `position`\n * + a Procrustes-recovered `rotation_degrees`/`mirror`. This exercises the\n * template + orientation representation the schema is designed around.\n * - **Features** (walls/corners/containers/floors) → an inline baked\n * `footprint` polygon (centroid-relative) with `rotation_degrees: 0`,\n * `mirror: \"none\"`, carrying the matched `template` id as provenance. Walls\n * and corners are lines in the source with no vertex-identical catalog\n * footprint, so baking is the faithful, exactly-verifiable choice. Multi-line\n * features (corners) emit one piece per segment.\n *\n * Usage: `npx tsx src/migrate-terrain.ts` (run from `tools/`).\n */\nimport { readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport { resolveLayout, polygonCentroid, footprintVertices, type Vec2, type Mirror, type Footprint, type TerrainTemplate, type TerrainLayout } from \"./terrain/resolve.js\";\n\nconst BOARD_HEIGHT = 44;\nconst TOL = 5e-4;\nconst SB = join(homedir(), \"bevy-deploy-helper\", \"assets\", \"terrain-layouts\", \"gw-11e\");\nconst REPO_ROOT = join(new URL(\"../..\", import.meta.url).pathname);\nconst CATALOG_PATH = join(REPO_ROOT, \"data\", \"core\", \"terrain-templates.json\");\nconst OUT_PATH = join(REPO_ROOT, \"data\", \"core\", \"terrain-layouts.json\");\n\n// ---- shadowboxing source shapes (subset we migrate) -----------------------\n\ntype SbShape =\n | { type: \"rectangle\"; width: number; height: number; x?: number; y?: number }\n | { type: \"polygon\"; points: Vec2[] }\n | { type: \"line\"; start: Vec2; end: Vec2; thickness: number };\n\ninterface SbPiece {\n id: string;\n name: string;\n shapes: SbShape[];\n position: Vec2;\n rotation?: number;\n mirror?: Mirror;\n pieceType?: \"area\" | \"feature\";\n category?: string;\n height?: number;\n floor?: number;\n linkGroup?: string;\n}\n\ninterface SbLayout {\n id: string;\n name: string;\n source?: string;\n pieces: SbPiece[];\n}\n\n// ---- shadowboxing transform (reproduced from src/los/shapes.rs) -----------\n// local_to_world: mirror → rotate(-θ) → translate(world_position); world is\n// y-up (world_position flips JSON y). We then map back to the 40kdc board\n// frame (top-left origin, y-down) — which equals shadowboxing's JSON frame.\n\nconst sub = (a: Vec2, b: Vec2): Vec2 => ({ x: a.x - b.x, y: a.y - b.y });\nconst add = (a: Vec2, b: Vec2): Vec2 => ({ x: a.x + b.x, y: a.y + b.y });\n\nfunction sbMirror(v: Vec2, m: Mirror): Vec2 {\n if (m === \"horizontal\") return { x: -v.x, y: v.y };\n if (m === \"vertical\") return { x: v.x, y: -v.y };\n return v;\n}\n\n/** shadowboxing local(y-up) → world(y-up): mirror → Mat2::from_angle(-θ) → +pos */\nfunction sbLocalToWorld(localYup: Vec2, piece: SbPiece): Vec2 {\n const m = sbMirror(localYup, piece.mirror ?? \"none\");\n const a = -((piece.rotation ?? 0) * Math.PI) / 180;\n // glam Mat2::from_angle(a) = [[cos a, -sin a],[sin a, cos a]]\n const c = Math.cos(a);\n const s = Math.sin(a);\n const rotated = { x: c * m.x - s * m.y, y: s * m.x + c * m.y };\n const worldPos = { x: piece.position.x, y: BOARD_HEIGHT - piece.position.y };\n return add(rotated, worldPos);\n}\n\n/** world(y-up) → 40kdc board frame (y-down). */\nconst toBoard = (world: Vec2): Vec2 => ({ x: world.x, y: BOARD_HEIGHT - world.y });\n\n/** A shape's outline vertices in shadowboxing local y-up coordinates. */\nfunction shapeLocalYupVerts(shape: SbShape): Vec2[] {\n switch (shape.type) {\n case \"rectangle\": {\n const ox = shape.x ?? 0;\n const oy = shape.y ?? 0;\n // JSON-local span x∈[ox,ox+w], y∈[oy,oy+h] (y-down) → y-up negate y.\n return [\n { x: ox, y: -oy },\n { x: ox + shape.width, y: -oy },\n { x: ox + shape.width, y: -(oy + shape.height) },\n { x: ox, y: -(oy + shape.height) },\n ];\n }\n case \"polygon\":\n return shape.points.map((p) => ({ x: p.x, y: -p.y }));\n case \"line\": {\n const s = { x: shape.start.x, y: -shape.start.y };\n const e = { x: shape.end.x, y: -shape.end.y };\n const dx = e.x - s.x;\n const dy = e.y - s.y;\n const len = Math.hypot(dx, dy) || 1;\n const dir = { x: dx / len, y: dy / len };\n const perp = { x: -dir.y * (shape.thickness / 2), y: dir.x * (shape.thickness / 2) };\n return [add(s, perp), add(e, perp), sub(e, perp), sub(s, perp)];\n }\n }\n}\n\n/** Ground-truth board-frame (y-down) outline of a source shape. */\nfunction sbShapeBoardVerts(shape: SbShape, piece: SbPiece): Vec2[] {\n return shapeLocalYupVerts(shape).map((v) => toBoard(sbLocalToWorld(v, piece)));\n}\n\n// ---- area template matching + Procrustes orientation recovery -------------\n\nconst AREA_NAME_TO_TEMPLATE: Record<string, string> = {\n \"11e Large Area\": \"area-large\",\n \"11e Trapezoid Area\": \"area-trapezoid\",\n \"11e Medium Area\": \"area-medium\",\n \"11e Long Line Area\": \"area-long-line\",\n \"11e Short Line Area\": \"area-short-line\",\n};\n\nconst FEATURE_NAME_TO_TEMPLATE: Record<string, string> = {\n \"Wall Long\": \"wall-long\",\n \"Wall Medium\": \"wall-medium\",\n \"Wall Short\": \"wall-short\",\n \"Wall XS\": \"wall-xs\",\n \"Warzone Wall Lg\": \"warzone-wall-large\",\n \"Warzone Wall Md\": \"warzone-wall-medium\",\n \"Warzone Wall Sm\": \"warzone-wall-small\",\n \"XS Corner\": \"corner-xs\",\n \"Small Corner\": \"corner-small\",\n \"Medium Corner\": \"corner-medium\",\n \"Large Corner\": \"corner-large\",\n Canister: \"canister\",\n Scaffold: \"scaffold\",\n Pipe: \"pipe\",\n \"Floor 4x4\": \"floor-4x4\",\n \"Floor 3x4\": \"floor-3x4\",\n \"Floor 2.5x4\": \"floor-2p5x4\",\n \"Floor Trapezoid\": \"floor-trapezoid\",\n};\n\nconst MIRRORS: Mirror[] = [\"none\", \"horizontal\", \"vertical\"];\n\nfunction applyMirror(v: Vec2, m: Mirror): Vec2 {\n if (m === \"horizontal\") return { x: -v.x, y: v.y };\n if (m === \"vertical\") return { x: v.x, y: -v.y };\n return v;\n}\nfunction rotateCw(v: Vec2, deg: number): Vec2 {\n const r = (deg * Math.PI) / 180;\n const c = Math.cos(r);\n const s = Math.sin(r);\n return { x: c * v.x - s * v.y, y: s * v.x + c * v.y };\n}\n\n/**\n * Recover (rotation, mirror) such that the resolver, placing `template` at the\n * ground-truth centroid, reproduces `gt`. `templateVerts` are the catalog\n * footprint vertices in the SAME order as `gt` (the source shape is identical\n * to the catalog shape for areas). Returns null if no mirror gives a clean fit.\n */\nfunction recoverOrientation(\n templateVerts: Vec2[],\n gt: Vec2[],\n): { rotation_degrees: number; mirror: Mirror } | null {\n const cV = polygonCentroid(templateVerts);\n const cGt = polygonCentroid(gt);\n let best: { rotation_degrees: number; mirror: Mirror; residual: number } | null = null;\n for (const mirror of MIRRORS) {\n const A = templateVerts.map((v) => applyMirror(sub(v, cV), mirror));\n const B = gt.map((v) => sub(v, cGt));\n let cross = 0;\n let dot = 0;\n for (let i = 0; i < A.length; i++) {\n cross += A[i].x * B[i].y - A[i].y * B[i].x;\n dot += A[i].x * B[i].x + A[i].y * B[i].y;\n }\n const theta = Math.atan2(cross, dot);\n let residual = 0;\n for (let i = 0; i < A.length; i++) {\n const r = rotateCw(A[i], (theta * 180) / Math.PI);\n residual = Math.max(residual, Math.hypot(r.x - B[i].x, r.y - B[i].y));\n }\n let deg = ((theta * 180) / Math.PI) % 360;\n if (deg < 0) deg += 360;\n if (deg >= 360 - 1e-9) deg = 0;\n if (!best || residual < best.residual) best = { rotation_degrees: deg, mirror, residual };\n }\n return best && best.residual <= TOL ? { rotation_degrees: best.rotation_degrees, mirror: best.mirror } : null;\n}\n\n// ---- per-shape baked footprint (centroid-relative) ------------------------\n\nfunction bakedFootprint(gt: Vec2[]): { footprint: Footprint; position: Vec2 } {\n const c = polygonCentroid(gt);\n const rounded = (n: number) => Math.round(n * 1e4) / 1e4;\n return {\n footprint: { type: \"polygon\", points: gt.map((v) => ({ x: rounded(v.x - c.x), y: rounded(v.y - c.y) })) },\n position: { x: rounded(c.x), y: rounded(c.y) },\n };\n}\n\n// ---- migrate one layout ---------------------------------------------------\n\ntype OutPiece = Record<string, unknown>;\n\nfunction migrateLayout(src: SbLayout, idPrefix: string, name: string, catalogById: Map<string, TerrainTemplate>): {\n layout: OutPiece;\n groundTruth: { pieceId: string; verts: Vec2[] }[];\n} {\n const pieces: OutPiece[] = [];\n const groundTruth: { pieceId: string; verts: Vec2[] }[] = [];\n\n for (const p of src.pieces) {\n const isArea = (p.pieceType ?? \"area\") === \"area\";\n if (isArea && p.shapes.length === 1) {\n const tid = AREA_NAME_TO_TEMPLATE[p.name];\n const tmpl = tid ? catalogById.get(tid) : undefined;\n if (tmpl) {\n const templateVerts = footprintVertices(tmpl.footprint);\n const gt = sbShapeBoardVerts(p.shapes[0], p);\n const ori = recoverOrientation(templateVerts, gt);\n if (ori) {\n const c = polygonCentroid(gt);\n const round = (n: number) => Math.round(n * 1e4) / 1e4;\n const piece: OutPiece = {\n id: p.id,\n name: p.name,\n piece_type: \"area\",\n template: tid,\n position: { x: round(c.x), y: round(c.y) },\n };\n if (Math.abs(ori.rotation_degrees) > 1e-6) piece.rotation_degrees = round(ori.rotation_degrees);\n if (ori.mirror !== \"none\") piece.mirror = ori.mirror;\n if (p.linkGroup) piece.link_group = p.linkGroup;\n pieces.push(piece);\n groundTruth.push({ pieceId: p.id, verts: gt });\n continue;\n }\n }\n }\n // Feature (or unmatched area): bake each shape as its own inline piece.\n const tid = FEATURE_NAME_TO_TEMPLATE[p.name] ?? AREA_NAME_TO_TEMPLATE[p.name];\n p.shapes.forEach((shape, i) => {\n const gt = sbShapeBoardVerts(shape, p);\n const { footprint, position } = bakedFootprint(gt);\n const pieceId = p.shapes.length > 1 ? `${p.id}-${i + 1}` : p.id;\n const piece: OutPiece = {\n id: pieceId,\n name: p.name,\n piece_type: isArea ? \"area\" : \"feature\",\n footprint,\n position,\n };\n if (tid) piece.template = tid;\n if (p.floor) piece.floor = p.floor;\n if (p.linkGroup) piece.link_group = p.linkGroup;\n pieces.push(piece);\n groundTruth.push({ pieceId, verts: gt });\n });\n }\n\n return {\n layout: {\n id: idPrefix,\n name,\n source: \"gw-11e\",\n pieces,\n game_version: { edition: \"11th\", dataslate: \"pre-launch-provisional\" },\n },\n groundTruth,\n };\n}\n\n// ---- verify: re-resolve and diff against ground truth ---------------------\n\nfunction multisetMatch(a: Vec2[], b: Vec2[]): boolean {\n if (a.length !== b.length) return false;\n const used = new Array<boolean>(b.length).fill(false);\n for (const va of a) {\n let found = -1;\n for (let j = 0; j < b.length; j++) {\n if (!used[j] && Math.abs(va.x - b[j].x) <= TOL && Math.abs(va.y - b[j].y) <= TOL) {\n found = j;\n break;\n }\n }\n if (found < 0) return false;\n used[found] = true;\n }\n return true;\n}\n\nfunction main(): void {\n const catalog = JSON.parse(readFileSync(CATALOG_PATH, \"utf8\")) as TerrainTemplate[];\n const catalogById = new Map(catalog.map((t) => [t.id, t]));\n\n const sources: { file: string; id: string; name: string }[] = [\n { file: \"crucible-of-battle.json\", id: \"gw-11e-crucible\", name: \"Crucible of Battle\" },\n { file: \"hammer-and-anvil.json\", id: \"gw-11e-hammer-anvil\", name: \"Hammer and Anvil\" },\n ];\n\n const out: OutPiece[] = [];\n let totalPieces = 0;\n let failures = 0;\n\n for (const s of sources) {\n const src = JSON.parse(readFileSync(join(SB, s.file), \"utf8\")) as SbLayout;\n const { layout, groundTruth } = migrateLayout(src, s.id, s.name, catalogById);\n\n // Verify by re-resolving the migrated layout against the catalog.\n const resolved = resolveLayout(layout as unknown as TerrainLayout, catalog);\n const byId = new Map(resolved.filter((r) => r.id).map((r) => [r.id as string, r]));\n for (const { pieceId, verts } of groundTruth) {\n totalPieces++;\n const r = byId.get(pieceId);\n if (!r || !multisetMatch(r.vertices, verts)) {\n failures++;\n console.error(` ✗ ${s.id}/${pieceId}: resolved geometry does not match source`);\n }\n }\n console.log(`${s.id}: ${(layout.pieces as unknown[]).length} pieces, ${groundTruth.length} verified`);\n out.push(layout);\n }\n\n if (failures > 0) {\n console.error(`\\nMIGRATION FAILED: ${failures}/${totalPieces} pieces did not reproduce source geometry.`);\n process.exit(1);\n }\n writeFileSync(OUT_PATH, `${JSON.stringify(out, null, 2)}\\n`);\n console.log(`\\nAll ${totalPieces} pieces verified within ${TOL}. Wrote ${OUT_PATH}`);\n}\n\nmain();\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":";AAsBA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAO5C,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;AA8C3B,MAAM,MAAM,cAAc,GACtB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC5B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,SAAS,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAElE,QAAA,MAAM,WAAW,oJASP,CAAC;AACX,KAAK,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAgB9C,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,GAAG,CAAC;CACjB;AAED,wBAAgB,iBAAiB,IAAI,WAAW,CAE/C;AAsWD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,cAAc,CA8BhG;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc9E"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":";AAsBA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAU5C,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;AA8C3B,MAAM,MAAM,cAAc,GACtB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC5B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,SAAS,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAElE,QAAA,MAAM,WAAW,oJASP,CAAC;AACX,KAAK,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAgB9C,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,GAAG,CAAC;CACjB;AAED,wBAAgB,iBAAiB,IAAI,WAAW,CAE/C;AA0YD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,cAAc,CAkChG;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc9E"}
package/dist/runner.js CHANGED
@@ -25,6 +25,8 @@ import { exportRoster } from "./export/index.js";
25
25
  import { importRoster, tryImportRoster } from "./import/import-roster.js";
26
26
  import { createValidator } from "./schema-loader.js";
27
27
  import { attributeStages, crunch } from "./cruncher/index.js";
28
+ import { describeScoringCard } from "./translate/index.js";
29
+ import { resolveLayout, TerrainResolveError } from "./terrain/index.js";
28
30
  // -----------------------------------------------------------------------------
29
31
  // Constants — spec version and implementation identity.
30
32
  // -----------------------------------------------------------------------------
@@ -425,6 +427,42 @@ function handleAttribution(state, args) {
425
427
  return err("CRUNCH_ERROR", { detail: e.message });
426
428
  }
427
429
  }
430
+ function handleTranslateScoring(state, args) {
431
+ if (typeof args !== "object" || args === null) {
432
+ return err("INVALID_INPUT", { detail: "translate_scoring args must be an object" });
433
+ }
434
+ const a = args;
435
+ if (typeof a.cardId !== "string") {
436
+ return err("INVALID_INPUT", { detail: "translate_scoring.cardId must be a string" });
437
+ }
438
+ const card = getDataset(state).secondaryCards.get(a.cardId);
439
+ if (!card)
440
+ return err("UNKNOWN_ENTITY", { kind: "secondary-card", id: a.cardId });
441
+ return ok({ awards: describeScoringCard(card) });
442
+ }
443
+ function handleResolveTerrain(args) {
444
+ if (typeof args !== "object" || args === null) {
445
+ return err("INVALID_INPUT", { detail: "resolve_terrain args must be an object" });
446
+ }
447
+ const a = args;
448
+ if (typeof a.layout !== "object" || a.layout === null) {
449
+ return err("INVALID_INPUT", { detail: "resolve_terrain.layout must be an object" });
450
+ }
451
+ const templates = a.templates ?? [];
452
+ if (!Array.isArray(templates)) {
453
+ return err("INVALID_INPUT", { detail: "resolve_terrain.templates must be an array" });
454
+ }
455
+ try {
456
+ const pieces = resolveLayout(a.layout, templates);
457
+ return ok({ pieces });
458
+ }
459
+ catch (e) {
460
+ if (e instanceof TerrainResolveError) {
461
+ return err("INVALID_INPUT", { detail: e.message });
462
+ }
463
+ return err("INTERNAL_ERROR", { detail: e.message });
464
+ }
465
+ }
428
466
  // -----------------------------------------------------------------------------
429
467
  // Dispatcher and per-line entry point.
430
468
  // -----------------------------------------------------------------------------
@@ -457,6 +495,10 @@ export function dispatch(state, req) {
457
495
  return handleCrunch(state, req.args);
458
496
  case "attribution":
459
497
  return handleAttribution(state, req.args);
498
+ case "translate_scoring":
499
+ return handleTranslateScoring(state, req.args);
500
+ case "resolve_terrain":
501
+ return handleResolveTerrain(req.args);
460
502
  case "shutdown":
461
503
  return ok(null);
462
504
  default: