@alpaca-software/40kdc-data 0.4.15 → 0.4.18
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/dist/author-batch.d.ts.map +1 -1
- package/dist/author-batch.js +3 -2
- package/dist/author-batch.js.map +1 -1
- package/dist/commands/translate.d.ts +3 -2
- package/dist/commands/translate.d.ts.map +1 -1
- package/dist/commands/translate.js +6 -154
- package/dist/commands/translate.js.map +1 -1
- package/dist/data/bundle.generated.js +1 -1
- package/dist/data/bundle.generated.js.map +1 -1
- package/dist/data/entities.d.ts +7 -0
- package/dist/data/entities.d.ts.map +1 -1
- package/dist/data/entities.js +10 -0
- package/dist/data/entities.js.map +1 -1
- package/dist/gen-conformance.js +57 -1
- package/dist/gen-conformance.js.map +1 -1
- package/dist/generated.d.ts +1 -1
- package/dist/generated.d.ts.map +1 -1
- package/dist/generated.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +16 -1
- package/dist/runner.js.map +1 -1
- package/dist/terrain/index.d.ts +2 -2
- package/dist/terrain/index.d.ts.map +1 -1
- package/dist/terrain/index.js +1 -1
- package/dist/terrain/index.js.map +1 -1
- package/dist/terrain/solve.d.ts +71 -0
- package/dist/terrain/solve.d.ts.map +1 -1
- package/dist/terrain/solve.js +165 -0
- package/dist/terrain/solve.js.map +1 -1
- package/dist/translate/condition.d.ts.map +1 -1
- package/dist/translate/condition.js +11 -0
- package/dist/translate/condition.js.map +1 -1
- package/dist/translate/effect.d.ts +72 -0
- package/dist/translate/effect.d.ts.map +1 -0
- package/dist/translate/effect.js +241 -0
- package/dist/translate/effect.js.map +1 -0
- package/dist/translate/index.d.ts +7 -4
- package/dist/translate/index.d.ts.map +1 -1
- package/dist/translate/index.js +7 -4
- package/dist/translate/index.js.map +1 -1
- package/package.json +2 -2
- package/schemas/enrichment/ability-dsl/condition.schema.json +2 -2
package/dist/terrain/solve.js
CHANGED
|
@@ -177,4 +177,169 @@ export function solveCentroidTriangulated(input) {
|
|
|
177
177
|
const rotation = (((theta * 180) / Math.PI) % 360 + 360) % 360;
|
|
178
178
|
return { x, y, rotation };
|
|
179
179
|
}
|
|
180
|
+
/** How far apart attached corners may be (measurement noise) before the solve refuses. */
|
|
181
|
+
const ATTACH_TOLERANCE = 0.1;
|
|
182
|
+
const cross = (p, q) => p.x * q.y - p.y * q.x;
|
|
183
|
+
const angle = (v) => Math.atan2(v.y, v.x);
|
|
184
|
+
/** The board point a pair of perpendicular lock lines pins. */
|
|
185
|
+
function lockPoint(lines, board) {
|
|
186
|
+
const coord = (l) => {
|
|
187
|
+
switch (l.edge) {
|
|
188
|
+
case "left":
|
|
189
|
+
return { axis: "x", value: l.distance };
|
|
190
|
+
case "right":
|
|
191
|
+
return { axis: "x", value: board.width - l.distance };
|
|
192
|
+
case "top":
|
|
193
|
+
return { axis: "y", value: l.distance };
|
|
194
|
+
case "bottom":
|
|
195
|
+
return { axis: "y", value: board.height - l.distance };
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const a = coord(lines[0]);
|
|
199
|
+
const b = coord(lines[1]);
|
|
200
|
+
if (a.axis === b.axis) {
|
|
201
|
+
throw new TerrainSolveError("the two lock lines must pin different axes (one of left/right, one of top/bottom)");
|
|
202
|
+
}
|
|
203
|
+
return { x: a.axis === "x" ? a.value : b.value, y: a.axis === "y" ? a.value : b.value };
|
|
204
|
+
}
|
|
205
|
+
function prepAttach(piece, board) {
|
|
206
|
+
const offsets = orientedOffsets(piece.footprint, 0, piece.mirror);
|
|
207
|
+
const lockOffset = offsets[piece.lockVertex];
|
|
208
|
+
if (!lockOffset)
|
|
209
|
+
throw new TerrainSolveError(`lock vertex index ${piece.lockVertex} out of range`);
|
|
210
|
+
const n = offsets.length;
|
|
211
|
+
if (piece.attach.index < 0 || piece.attach.index >= n) {
|
|
212
|
+
throw new TerrainSolveError(`attach ${piece.attach.kind} index ${piece.attach.index} out of range`);
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
P: lockPoint(piece.lines, board),
|
|
216
|
+
lockOffset,
|
|
217
|
+
offsets,
|
|
218
|
+
hint: ((piece.rotationHint ?? 0) * Math.PI) / 180,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Vertex ↔ vertex (corners coincide; the joint pivots): each attach vertex
|
|
223
|
+
* traces a circle about its lock point with the rigid lock→attach radius, so
|
|
224
|
+
* the shared corner is a circle–circle intersection — two candidate points.
|
|
225
|
+
*/
|
|
226
|
+
function vertexCandidates(a, b, attachA, attachB) {
|
|
227
|
+
const relA = { x: a.offsets[attachA].x - a.lockOffset.x, y: a.offsets[attachA].y - a.lockOffset.y };
|
|
228
|
+
const relB = { x: b.offsets[attachB].x - b.lockOffset.x, y: b.offsets[attachB].y - b.lockOffset.y };
|
|
229
|
+
const r1 = Math.hypot(relA.x, relA.y);
|
|
230
|
+
const r2 = Math.hypot(relB.x, relB.y);
|
|
231
|
+
if (r1 < 1e-9 || r2 < 1e-9) {
|
|
232
|
+
throw new TerrainSolveError("the attach vertex must differ from the lock vertex");
|
|
233
|
+
}
|
|
234
|
+
const D = { x: b.P.x - a.P.x, y: b.P.y - a.P.y };
|
|
235
|
+
const d = Math.hypot(D.x, D.y);
|
|
236
|
+
if (d < 1e-9)
|
|
237
|
+
throw new TerrainSolveError("the two lock points coincide — nothing to attach across");
|
|
238
|
+
const miss = Math.max(d - (r1 + r2), Math.abs(r1 - r2) - d);
|
|
239
|
+
if (miss > ATTACH_TOLERANCE) {
|
|
240
|
+
throw new TerrainSolveError(`attached corners cannot meet: the lock points are ${round2(d)}″ apart but the corner radii are ${round2(r1)}″ and ${round2(r2)}″`);
|
|
241
|
+
}
|
|
242
|
+
// Circle–circle intersection; a tiny miss (measurement noise) clamps to tangent.
|
|
243
|
+
const along = (d * d + r1 * r1 - r2 * r2) / (2 * d);
|
|
244
|
+
const h = Math.sqrt(Math.max(0, r1 * r1 - along * along));
|
|
245
|
+
const u = { x: D.x / d, y: D.y / d };
|
|
246
|
+
const foot = { x: a.P.x + along * u.x, y: a.P.y + along * u.y };
|
|
247
|
+
const points = h < 1e-9
|
|
248
|
+
? [foot]
|
|
249
|
+
: [
|
|
250
|
+
{ x: foot.x - h * u.y, y: foot.y + h * u.x },
|
|
251
|
+
{ x: foot.x + h * u.y, y: foot.y - h * u.x },
|
|
252
|
+
];
|
|
253
|
+
return points.map((X) => ({
|
|
254
|
+
thetaA: angle({ x: X.x - a.P.x, y: X.y - a.P.y }) - angle(relA),
|
|
255
|
+
thetaB: angle({ x: X.x - b.P.x, y: X.y - b.P.y }) - angle(relB),
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Edge ↔ edge (edges flush; the contact slides): the signed perpendicular
|
|
260
|
+
* offset of a lock vertex from its own edge line is rotation-invariant, so the
|
|
261
|
+
* shared line is a common tangent of the two circles those offsets define.
|
|
262
|
+
* With line direction `t̂(ψ)`: `cross(t̂, P_a − P_b) = σ_a − ε·σ_b` for the two
|
|
263
|
+
* relative edge senses ε — up to four candidate orientations.
|
|
264
|
+
*/
|
|
265
|
+
function edgeCandidates(a, b, edgeA, edgeB) {
|
|
266
|
+
const edgeGeom = (p, e) => {
|
|
267
|
+
const v0 = p.offsets[e];
|
|
268
|
+
const v1 = p.offsets[(e + 1) % p.offsets.length];
|
|
269
|
+
const a0 = { x: v0.x - p.lockOffset.x, y: v0.y - p.lockOffset.y };
|
|
270
|
+
const u = { x: v1.x - v0.x, y: v1.y - v0.y };
|
|
271
|
+
const len = Math.hypot(u.x, u.y);
|
|
272
|
+
if (len < 1e-9)
|
|
273
|
+
throw new TerrainSolveError(`edge ${e} is degenerate (zero length)`);
|
|
274
|
+
const uHat = { x: u.x / len, y: u.y / len };
|
|
275
|
+
// Signed distance of the lock vertex from the (lock-relative) edge line.
|
|
276
|
+
return { uAng: angle(uHat), sigma: cross(uHat, { x: -a0.x, y: -a0.y }) };
|
|
277
|
+
};
|
|
278
|
+
const ga = edgeGeom(a, edgeA);
|
|
279
|
+
const gb = edgeGeom(b, edgeB);
|
|
280
|
+
const D = { x: a.P.x - b.P.x, y: a.P.y - b.P.y };
|
|
281
|
+
const d = Math.hypot(D.x, D.y);
|
|
282
|
+
if (d < 1e-9)
|
|
283
|
+
throw new TerrainSolveError("the two lock points coincide — nothing to attach across");
|
|
284
|
+
const beta = angle(D);
|
|
285
|
+
const out = [];
|
|
286
|
+
for (const eps of [1, -1]) {
|
|
287
|
+
const k = ga.sigma - eps * gb.sigma;
|
|
288
|
+
const s = k / d;
|
|
289
|
+
if (s > 1 + 1e-9 || s < -1 - 1e-9)
|
|
290
|
+
continue;
|
|
291
|
+
const asin = Math.asin(Math.max(-1, Math.min(1, s)));
|
|
292
|
+
for (const psi of [beta - asin, beta - (Math.PI - asin)]) {
|
|
293
|
+
out.push({
|
|
294
|
+
thetaA: psi - ga.uAng,
|
|
295
|
+
thetaB: psi - gb.uAng + (eps < 0 ? Math.PI : 0),
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (out.length === 0) {
|
|
300
|
+
throw new TerrainSolveError(`attached edges cannot lie on a common line: the lock points are ${round2(d)}″ apart but the edge offsets are ${round2(ga.sigma)}″ and ${round2(gb.sigma)}″`);
|
|
301
|
+
}
|
|
302
|
+
return out;
|
|
303
|
+
}
|
|
304
|
+
const round2 = (n) => Math.round(n * 100) / 100;
|
|
305
|
+
function attachPose(p, theta) {
|
|
306
|
+
const cos = Math.cos(theta);
|
|
307
|
+
const sin = Math.sin(theta);
|
|
308
|
+
return {
|
|
309
|
+
x: p.P.x - (cos * p.lockOffset.x - sin * p.lockOffset.y),
|
|
310
|
+
y: p.P.y - (sin * p.lockOffset.x + cos * p.lockOffset.y),
|
|
311
|
+
rotation: ((((theta * 180) / Math.PI) % 360) + 360) % 360,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Back-solve the centroid AND rotation of TWO pieces at once from the cluster
|
|
316
|
+
* card pattern: each piece's card prints exactly two dimension lines, both to
|
|
317
|
+
* one vertex — pinning that vertex's board position but not the rotation. The
|
|
318
|
+
* rotations come from the pieces' attachment to each other: matched vertices
|
|
319
|
+
* coincide (the pair pivots), or matched edges lie on one line (the contact
|
|
320
|
+
* slides — each piece's position along the line is fixed by its lock vertex).
|
|
321
|
+
*
|
|
322
|
+
* Closed form: with each lock vertex pinned, one rotation per piece is the
|
|
323
|
+
* only unknown. Vertex mode intersects the two circles the attach vertices
|
|
324
|
+
* sweep; edge mode finds the common line via the rotation-invariant signed
|
|
325
|
+
* offset of each lock vertex from its own edge. Both yield a small candidate
|
|
326
|
+
* set; the pair nearest the pieces' current rotations is chosen — rough both
|
|
327
|
+
* pieces in first.
|
|
328
|
+
*/
|
|
329
|
+
export function solveCentroidAttached(input) {
|
|
330
|
+
const { a, b } = input;
|
|
331
|
+
if (a.attach.kind !== b.attach.kind) {
|
|
332
|
+
throw new TerrainSolveError("attachment kinds must match: vertex pairs pivot, edge pairs slide — no mixing");
|
|
333
|
+
}
|
|
334
|
+
const pa = prepAttach(a, input.board);
|
|
335
|
+
const pb = prepAttach(b, input.board);
|
|
336
|
+
const candidates = a.attach.kind === "vertex"
|
|
337
|
+
? vertexCandidates(pa, pb, a.attach.index, b.attach.index)
|
|
338
|
+
: edgeCandidates(pa, pb, a.attach.index, b.attach.index);
|
|
339
|
+
const best = candidates.reduce((bestSoFar, c) => angularGap(c.thetaA, pa.hint) + angularGap(c.thetaB, pb.hint) <
|
|
340
|
+
angularGap(bestSoFar.thetaA, pa.hint) + angularGap(bestSoFar.thetaB, pb.hint)
|
|
341
|
+
? c
|
|
342
|
+
: bestSoFar);
|
|
343
|
+
return { a: attachPose(pa, best.thetaA), b: attachPose(pb, best.thetaB) };
|
|
344
|
+
}
|
|
180
345
|
//# sourceMappingURL=solve.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"solve.js","sourceRoot":"","sources":["../../src/terrain/solve.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Card-measurement centroid solver — the inverse of the resolver's placement.\n *\n * Reference cards locate a terrain area by dimension lines: \"this feature of the\n * area is D inches from a board edge\". The feature referenced varies per card\n * and per piece, which is exactly why a single canonical anchor (the centroid)\n * is hard to read off a card directly. This solver lets a user transcribe the\n * card verbatim — pick the template, set the orientation shown, then enter one\n * horizontal and one vertical dimension line against whatever feature the card\n * happens to draw — and back-solves the centroid `position` the schema stores.\n *\n * Because the centroid is rotation- and mirror-invariant, orientation is fixed\n * first; each dimension line then pins one axis of the centroid in closed form.\n */\nimport {\n orientedOffsets,\n type BoardEdge,\n type FeatureRef,\n type Footprint,\n type Mirror,\n type Vec2,\n} from \"./resolve.js\";\n\n// The edge/feature vocabulary lives in resolve.ts (shared with keystones);\n// re-exported here so existing solver imports keep working.\nexport type { BoardEdge, FeatureRef } from \"./resolve.js\";\n\n/** One card dimension line: `distance` inches from `edge` to `feature`. */\nexport interface DimensionLine {\n edge: BoardEdge;\n distance: number;\n feature: FeatureRef;\n}\n\nexport interface SolveInput {\n footprint: Footprint;\n rotation: number;\n mirror: Mirror;\n /** Board extents in inches (40kdc standard is 60 × 44). */\n board: { width: number; height: number };\n /** Two perpendicular dimension lines: exactly one must pin x, one must pin y. */\n lines: [DimensionLine, DimensionLine];\n}\n\nexport class TerrainSolveError extends Error {}\n\n/** The signed offset (from the centroid) the given feature resolves to, on its axis. */\nfunction featureOffset(offsets: Vec2[], feature: FeatureRef, axis: \"x\" | \"y\"): number {\n if (feature.kind === \"vertex\") {\n const o = offsets[feature.index];\n if (!o) throw new TerrainSolveError(`vertex index ${feature.index} out of range`);\n return axis === \"x\" ? o.x : o.y;\n }\n const xs = offsets.map((o) => o.x);\n const ys = offsets.map((o) => o.y);\n switch (feature.side) {\n case \"min-x\":\n return Math.min(...xs);\n case \"max-x\":\n return Math.max(...xs);\n case \"min-y\":\n return Math.min(...ys);\n case \"max-y\":\n return Math.max(...ys);\n }\n}\n\nfunction axisOfEdge(edge: BoardEdge): \"x\" | \"y\" {\n return edge === \"left\" || edge === \"right\" ? \"x\" : \"y\";\n}\n\n/** Solve one axis of the centroid from a single dimension line. */\nfunction solveAxis(line: DimensionLine, offsets: Vec2[], board: { width: number; height: number }): { axis: \"x\" | \"y\"; value: number } {\n const axis = axisOfEdge(line.edge);\n const o = featureOffset(offsets, line.feature, axis);\n // edge → centroid: near-side edges measure from 0; far-side from the extent.\n let value: number;\n switch (line.edge) {\n case \"left\":\n value = line.distance - o;\n break;\n case \"right\":\n value = board.width - line.distance - o;\n break;\n case \"top\":\n value = line.distance - o;\n break;\n case \"bottom\":\n value = board.height - line.distance - o;\n break;\n }\n return { axis, value };\n}\n\n/**\n * Back-solve the centroid `position` from a template, its orientation, and two\n * perpendicular card dimension lines. Closed form — one x-line and one y-line\n * pin the two unknowns directly.\n */\nexport function solveCentroid(input: SolveInput): Vec2 {\n const offsets = orientedOffsets(input.footprint, input.rotation, input.mirror);\n const a = solveAxis(input.lines[0], offsets, input.board);\n const b = solveAxis(input.lines[1], offsets, input.board);\n if (a.axis === b.axis) {\n throw new TerrainSolveError(\n \"the two dimension lines must pin different axes (one of left/right, one of top/bottom)\",\n );\n }\n const x = a.axis === \"x\" ? a.value : b.value;\n const y = a.axis === \"y\" ? a.value : b.value;\n return { x, y };\n}\n\n/**\n * One triangulation measurement: `distance` inches from board `edge` to a\n * specific footprint vertex (corner). Faces are intentionally excluded — an\n * arbitrarily-rotated piece has no axis-aligned face to measure to.\n */\nexport interface TriangulationLine {\n edge: BoardEdge;\n distance: number;\n vertex: number;\n}\n\nexport interface TriangulateInput {\n footprint: Footprint;\n mirror: Mirror;\n board: { width: number; height: number };\n /**\n * Three corner measurements. At least two must share an axis (left/right or\n * top/bottom) to fix the angle, and at least one must pin the other axis.\n */\n lines: [TriangulationLine, TriangulationLine, TriangulationLine];\n /** Current rotation in degrees, used to choose between the two angle roots. */\n rotationHint?: number;\n}\n\nconst TWO_PI = Math.PI * 2;\n/** Smallest absolute angular separation between two radian angles. */\nfunction angularGap(a: number, b: number): number {\n const d = (((a - b) % TWO_PI) + TWO_PI) % TWO_PI;\n return Math.min(d, TWO_PI - d);\n}\n\n/**\n * Back-solve a piece's centroid AND rotation from three card measurements to\n * specific footprint corners — the inverse needed for pieces at non-90° angles,\n * where the card pins three corner-to-edge distances rather than one per axis.\n *\n * Closed form: with the (unknown) rotation θ, each corner `v` resolves to\n * `centroid + R(θ)·v`. Subtracting two same-axis measurements cancels the\n * centroid and leaves `A·cosθ + B·sinθ = C`, solved as `θ = atan2(B,A) ±\n * acos(C/√(A²+B²))`; the root nearest `rotationHint` is chosen. One measurement\n * on each axis then pins the centroid.\n */\nexport function solveCentroidTriangulated(\n input: TriangulateInput,\n): { x: number; y: number; rotation: number } {\n // Mirror-applied, pre-rotation offsets (θ is the unknown we're solving for).\n const offsets = orientedOffsets(input.footprint, 0, input.mirror);\n const items = input.lines.map((l) => {\n const o = offsets[l.vertex];\n if (!o) throw new TerrainSolveError(`vertex index ${l.vertex} out of range`);\n const axis = axisOfEdge(l.edge);\n let target: number;\n switch (l.edge) {\n case \"left\":\n target = l.distance;\n break;\n case \"right\":\n target = input.board.width - l.distance;\n break;\n case \"top\":\n target = l.distance;\n break;\n case \"bottom\":\n target = input.board.height - l.distance;\n break;\n }\n return { axis, target, o };\n });\n const xs = items.filter((i) => i.axis === \"x\");\n const ys = items.filter((i) => i.axis === \"y\");\n\n let pivot: typeof items;\n let pivotAxis: \"x\" | \"y\";\n if (xs.length >= 2 && ys.length >= 1) {\n pivot = xs;\n pivotAxis = \"x\";\n } else if (ys.length >= 2 && xs.length >= 1) {\n pivot = ys;\n pivotAxis = \"y\";\n } else {\n throw new TerrainSolveError(\n \"triangulation needs two measurements from one pair of edges (left/right or top/bottom) and one from the other\",\n );\n }\n\n // Best-conditioned pair on the pivot axis (corners that are furthest apart).\n let a = pivot[0];\n let b = pivot[1];\n let spread = -1;\n for (let i = 0; i < pivot.length; i++) {\n for (let j = i + 1; j < pivot.length; j++) {\n const d = Math.hypot(pivot[i].o.x - pivot[j].o.x, pivot[i].o.y - pivot[j].o.y);\n if (d > spread) {\n spread = d;\n a = pivot[i];\n b = pivot[j];\n }\n }\n }\n\n // Subtract the two same-axis equations → A·cosθ + B·sinθ = C.\n // x-axis vertex eq: cx + (cosθ·o.x − sinθ·o.y) = target\n // y-axis vertex eq: cy + (sinθ·o.x + cosθ·o.y) = target\n const dx = a.o.x - b.o.x;\n const dy = a.o.y - b.o.y;\n const C = a.target - b.target;\n const A = pivotAxis === \"x\" ? dx : dy;\n const B = pivotAxis === \"x\" ? -dy : dx;\n const R = Math.hypot(A, B);\n if (R < 1e-9) {\n throw new TerrainSolveError(\"the two same-edge measurements must reference different corners\");\n }\n const ratio = C / R;\n if (ratio > 1 + 1e-6 || ratio < -1 - 1e-6) {\n throw new TerrainSolveError(\"measurements are inconsistent — no orientation fits\");\n }\n const phi = Math.atan2(B, A);\n const base = Math.acos(Math.max(-1, Math.min(1, ratio)));\n const hint = ((input.rotationHint ?? 0) * Math.PI) / 180;\n const theta = [phi + base, phi - base].reduce((best, c) =>\n angularGap(c, hint) < angularGap(best, hint) ? c : best,\n );\n\n const cos = Math.cos(theta);\n const sin = Math.sin(theta);\n const xLine = xs[0];\n const yLine = ys[0];\n const x = xLine.target - (cos * xLine.o.x - sin * xLine.o.y);\n const y = yLine.target - (sin * yLine.o.x + cos * yLine.o.y);\n const rotation = (((theta * 180) / Math.PI) % 360 + 360) % 360;\n return { x, y, rotation };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"solve.js","sourceRoot":"","sources":["../../src/terrain/solve.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Card-measurement centroid solver — the inverse of the resolver's placement.\n *\n * Reference cards locate a terrain area by dimension lines: \"this feature of the\n * area is D inches from a board edge\". The feature referenced varies per card\n * and per piece, which is exactly why a single canonical anchor (the centroid)\n * is hard to read off a card directly. This solver lets a user transcribe the\n * card verbatim — pick the template, set the orientation shown, then enter one\n * horizontal and one vertical dimension line against whatever feature the card\n * happens to draw — and back-solves the centroid `position` the schema stores.\n *\n * Because the centroid is rotation- and mirror-invariant, orientation is fixed\n * first; each dimension line then pins one axis of the centroid in closed form.\n */\nimport {\n orientedOffsets,\n type BoardEdge,\n type FeatureRef,\n type Footprint,\n type Mirror,\n type Vec2,\n} from \"./resolve.js\";\n\n// The edge/feature vocabulary lives in resolve.ts (shared with keystones);\n// re-exported here so existing solver imports keep working.\nexport type { BoardEdge, FeatureRef } from \"./resolve.js\";\n\n/** One card dimension line: `distance` inches from `edge` to `feature`. */\nexport interface DimensionLine {\n edge: BoardEdge;\n distance: number;\n feature: FeatureRef;\n}\n\nexport interface SolveInput {\n footprint: Footprint;\n rotation: number;\n mirror: Mirror;\n /** Board extents in inches (40kdc standard is 60 × 44). */\n board: { width: number; height: number };\n /** Two perpendicular dimension lines: exactly one must pin x, one must pin y. */\n lines: [DimensionLine, DimensionLine];\n}\n\nexport class TerrainSolveError extends Error {}\n\n/** The signed offset (from the centroid) the given feature resolves to, on its axis. */\nfunction featureOffset(offsets: Vec2[], feature: FeatureRef, axis: \"x\" | \"y\"): number {\n if (feature.kind === \"vertex\") {\n const o = offsets[feature.index];\n if (!o) throw new TerrainSolveError(`vertex index ${feature.index} out of range`);\n return axis === \"x\" ? o.x : o.y;\n }\n const xs = offsets.map((o) => o.x);\n const ys = offsets.map((o) => o.y);\n switch (feature.side) {\n case \"min-x\":\n return Math.min(...xs);\n case \"max-x\":\n return Math.max(...xs);\n case \"min-y\":\n return Math.min(...ys);\n case \"max-y\":\n return Math.max(...ys);\n }\n}\n\nfunction axisOfEdge(edge: BoardEdge): \"x\" | \"y\" {\n return edge === \"left\" || edge === \"right\" ? \"x\" : \"y\";\n}\n\n/** Solve one axis of the centroid from a single dimension line. */\nfunction solveAxis(line: DimensionLine, offsets: Vec2[], board: { width: number; height: number }): { axis: \"x\" | \"y\"; value: number } {\n const axis = axisOfEdge(line.edge);\n const o = featureOffset(offsets, line.feature, axis);\n // edge → centroid: near-side edges measure from 0; far-side from the extent.\n let value: number;\n switch (line.edge) {\n case \"left\":\n value = line.distance - o;\n break;\n case \"right\":\n value = board.width - line.distance - o;\n break;\n case \"top\":\n value = line.distance - o;\n break;\n case \"bottom\":\n value = board.height - line.distance - o;\n break;\n }\n return { axis, value };\n}\n\n/**\n * Back-solve the centroid `position` from a template, its orientation, and two\n * perpendicular card dimension lines. Closed form — one x-line and one y-line\n * pin the two unknowns directly.\n */\nexport function solveCentroid(input: SolveInput): Vec2 {\n const offsets = orientedOffsets(input.footprint, input.rotation, input.mirror);\n const a = solveAxis(input.lines[0], offsets, input.board);\n const b = solveAxis(input.lines[1], offsets, input.board);\n if (a.axis === b.axis) {\n throw new TerrainSolveError(\n \"the two dimension lines must pin different axes (one of left/right, one of top/bottom)\",\n );\n }\n const x = a.axis === \"x\" ? a.value : b.value;\n const y = a.axis === \"y\" ? a.value : b.value;\n return { x, y };\n}\n\n/**\n * One triangulation measurement: `distance` inches from board `edge` to a\n * specific footprint vertex (corner). Faces are intentionally excluded — an\n * arbitrarily-rotated piece has no axis-aligned face to measure to.\n */\nexport interface TriangulationLine {\n edge: BoardEdge;\n distance: number;\n vertex: number;\n}\n\nexport interface TriangulateInput {\n footprint: Footprint;\n mirror: Mirror;\n board: { width: number; height: number };\n /**\n * Three corner measurements. At least two must share an axis (left/right or\n * top/bottom) to fix the angle, and at least one must pin the other axis.\n */\n lines: [TriangulationLine, TriangulationLine, TriangulationLine];\n /** Current rotation in degrees, used to choose between the two angle roots. */\n rotationHint?: number;\n}\n\nconst TWO_PI = Math.PI * 2;\n/** Smallest absolute angular separation between two radian angles. */\nfunction angularGap(a: number, b: number): number {\n const d = (((a - b) % TWO_PI) + TWO_PI) % TWO_PI;\n return Math.min(d, TWO_PI - d);\n}\n\n/**\n * Back-solve a piece's centroid AND rotation from three card measurements to\n * specific footprint corners — the inverse needed for pieces at non-90° angles,\n * where the card pins three corner-to-edge distances rather than one per axis.\n *\n * Closed form: with the (unknown) rotation θ, each corner `v` resolves to\n * `centroid + R(θ)·v`. Subtracting two same-axis measurements cancels the\n * centroid and leaves `A·cosθ + B·sinθ = C`, solved as `θ = atan2(B,A) ±\n * acos(C/√(A²+B²))`; the root nearest `rotationHint` is chosen. One measurement\n * on each axis then pins the centroid.\n */\nexport function solveCentroidTriangulated(\n input: TriangulateInput,\n): { x: number; y: number; rotation: number } {\n // Mirror-applied, pre-rotation offsets (θ is the unknown we're solving for).\n const offsets = orientedOffsets(input.footprint, 0, input.mirror);\n const items = input.lines.map((l) => {\n const o = offsets[l.vertex];\n if (!o) throw new TerrainSolveError(`vertex index ${l.vertex} out of range`);\n const axis = axisOfEdge(l.edge);\n let target: number;\n switch (l.edge) {\n case \"left\":\n target = l.distance;\n break;\n case \"right\":\n target = input.board.width - l.distance;\n break;\n case \"top\":\n target = l.distance;\n break;\n case \"bottom\":\n target = input.board.height - l.distance;\n break;\n }\n return { axis, target, o };\n });\n const xs = items.filter((i) => i.axis === \"x\");\n const ys = items.filter((i) => i.axis === \"y\");\n\n let pivot: typeof items;\n let pivotAxis: \"x\" | \"y\";\n if (xs.length >= 2 && ys.length >= 1) {\n pivot = xs;\n pivotAxis = \"x\";\n } else if (ys.length >= 2 && xs.length >= 1) {\n pivot = ys;\n pivotAxis = \"y\";\n } else {\n throw new TerrainSolveError(\n \"triangulation needs two measurements from one pair of edges (left/right or top/bottom) and one from the other\",\n );\n }\n\n // Best-conditioned pair on the pivot axis (corners that are furthest apart).\n let a = pivot[0];\n let b = pivot[1];\n let spread = -1;\n for (let i = 0; i < pivot.length; i++) {\n for (let j = i + 1; j < pivot.length; j++) {\n const d = Math.hypot(pivot[i].o.x - pivot[j].o.x, pivot[i].o.y - pivot[j].o.y);\n if (d > spread) {\n spread = d;\n a = pivot[i];\n b = pivot[j];\n }\n }\n }\n\n // Subtract the two same-axis equations → A·cosθ + B·sinθ = C.\n // x-axis vertex eq: cx + (cosθ·o.x − sinθ·o.y) = target\n // y-axis vertex eq: cy + (sinθ·o.x + cosθ·o.y) = target\n const dx = a.o.x - b.o.x;\n const dy = a.o.y - b.o.y;\n const C = a.target - b.target;\n const A = pivotAxis === \"x\" ? dx : dy;\n const B = pivotAxis === \"x\" ? -dy : dx;\n const R = Math.hypot(A, B);\n if (R < 1e-9) {\n throw new TerrainSolveError(\"the two same-edge measurements must reference different corners\");\n }\n const ratio = C / R;\n if (ratio > 1 + 1e-6 || ratio < -1 - 1e-6) {\n throw new TerrainSolveError(\"measurements are inconsistent — no orientation fits\");\n }\n const phi = Math.atan2(B, A);\n const base = Math.acos(Math.max(-1, Math.min(1, ratio)));\n const hint = ((input.rotationHint ?? 0) * Math.PI) / 180;\n const theta = [phi + base, phi - base].reduce((best, c) =>\n angularGap(c, hint) < angularGap(best, hint) ? c : best,\n );\n\n const cos = Math.cos(theta);\n const sin = Math.sin(theta);\n const xLine = xs[0];\n const yLine = ys[0];\n const x = xLine.target - (cos * xLine.o.x - sin * xLine.o.y);\n const y = yLine.target - (sin * yLine.o.x + cos * yLine.o.y);\n const rotation = (((theta * 180) / Math.PI) % 360 + 360) % 360;\n return { x, y, rotation };\n}\n\n/**\n * One card dimension line in an attachment solve: `distance` inches from board\n * `edge` to the piece's lock vertex. No feature ref — both of a piece's lines\n * reach the same locked vertex (the card pattern this solver exists for).\n */\nexport interface AttachLine {\n edge: BoardEdge;\n distance: number;\n}\n\n/**\n * One piece of a two-body attachment solve. The card pins exactly ONE vertex\n * (the keystone anchor) with two perpendicular dimension lines; the piece is\n * otherwise free to rotate about that point. The `attach` feature is what\n * connects it to the other piece and removes that rotational freedom.\n */\nexport interface AttachPiece {\n footprint: Footprint;\n mirror: Mirror;\n /** The vertex pinned by `lines` — the piece's keystone anchor vertex. */\n lockVertex: number;\n /** Two perpendicular card lines pinning the lock vertex: one must pin x, one y. */\n lines: [AttachLine, AttachLine];\n /**\n * The attached feature: a specific vertex, or the footprint edge running\n * from vertex `index` to vertex `index + 1` (wrapping).\n */\n attach: { kind: \"vertex\" | \"edge\"; index: number };\n /** Current rotation in degrees, used to pick among the candidate roots. */\n rotationHint?: number;\n}\n\nexport interface AttachInput {\n /** Board extents in inches (40kdc standard is 60 × 44). */\n board: { width: number; height: number };\n /** The piece being placed. */\n a: AttachPiece;\n /** The piece it attaches to. */\n b: AttachPiece;\n}\n\n/** How far apart attached corners may be (measurement noise) before the solve refuses. */\nconst ATTACH_TOLERANCE = 0.1;\n\nconst cross = (p: Vec2, q: Vec2): number => p.x * q.y - p.y * q.x;\nconst angle = (v: Vec2): number => Math.atan2(v.y, v.x);\n\n/** The board point a pair of perpendicular lock lines pins. */\nfunction lockPoint(lines: [AttachLine, AttachLine], board: { width: number; height: number }): Vec2 {\n const coord = (l: AttachLine): { axis: \"x\" | \"y\"; value: number } => {\n switch (l.edge) {\n case \"left\":\n return { axis: \"x\", value: l.distance };\n case \"right\":\n return { axis: \"x\", value: board.width - l.distance };\n case \"top\":\n return { axis: \"y\", value: l.distance };\n case \"bottom\":\n return { axis: \"y\", value: board.height - l.distance };\n }\n };\n const a = coord(lines[0]);\n const b = coord(lines[1]);\n if (a.axis === b.axis) {\n throw new TerrainSolveError(\n \"the two lock lines must pin different axes (one of left/right, one of top/bottom)\",\n );\n }\n return { x: a.axis === \"x\" ? a.value : b.value, y: a.axis === \"y\" ? a.value : b.value };\n}\n\ninterface AttachPrepared {\n /** Fixed board position of the lock vertex. */\n P: Vec2;\n /** Mirror-applied, pre-rotation offset of the lock vertex from the centroid. */\n lockOffset: Vec2;\n /** Mirror-applied, pre-rotation offsets of every vertex from the centroid. */\n offsets: Vec2[];\n hint: number;\n}\n\nfunction prepAttach(piece: AttachPiece, board: { width: number; height: number }): AttachPrepared {\n const offsets = orientedOffsets(piece.footprint, 0, piece.mirror);\n const lockOffset = offsets[piece.lockVertex];\n if (!lockOffset) throw new TerrainSolveError(`lock vertex index ${piece.lockVertex} out of range`);\n const n = offsets.length;\n if (piece.attach.index < 0 || piece.attach.index >= n) {\n throw new TerrainSolveError(`attach ${piece.attach.kind} index ${piece.attach.index} out of range`);\n }\n return {\n P: lockPoint(piece.lines, board),\n lockOffset,\n offsets,\n hint: ((piece.rotationHint ?? 0) * Math.PI) / 180,\n };\n}\n\n/** A candidate rotation pair (radians) for the two pieces. */\ntype AttachCandidate = { thetaA: number; thetaB: number };\n\n/**\n * Vertex ↔ vertex (corners coincide; the joint pivots): each attach vertex\n * traces a circle about its lock point with the rigid lock→attach radius, so\n * the shared corner is a circle–circle intersection — two candidate points.\n */\nfunction vertexCandidates(\n a: AttachPrepared,\n b: AttachPrepared,\n attachA: number,\n attachB: number,\n): AttachCandidate[] {\n const relA = { x: a.offsets[attachA].x - a.lockOffset.x, y: a.offsets[attachA].y - a.lockOffset.y };\n const relB = { x: b.offsets[attachB].x - b.lockOffset.x, y: b.offsets[attachB].y - b.lockOffset.y };\n const r1 = Math.hypot(relA.x, relA.y);\n const r2 = Math.hypot(relB.x, relB.y);\n if (r1 < 1e-9 || r2 < 1e-9) {\n throw new TerrainSolveError(\"the attach vertex must differ from the lock vertex\");\n }\n const D = { x: b.P.x - a.P.x, y: b.P.y - a.P.y };\n const d = Math.hypot(D.x, D.y);\n if (d < 1e-9) throw new TerrainSolveError(\"the two lock points coincide — nothing to attach across\");\n const miss = Math.max(d - (r1 + r2), Math.abs(r1 - r2) - d);\n if (miss > ATTACH_TOLERANCE) {\n throw new TerrainSolveError(\n `attached corners cannot meet: the lock points are ${round2(d)}″ apart but the corner radii are ${round2(r1)}″ and ${round2(r2)}″`,\n );\n }\n // Circle–circle intersection; a tiny miss (measurement noise) clamps to tangent.\n const along = (d * d + r1 * r1 - r2 * r2) / (2 * d);\n const h = Math.sqrt(Math.max(0, r1 * r1 - along * along));\n const u = { x: D.x / d, y: D.y / d };\n const foot = { x: a.P.x + along * u.x, y: a.P.y + along * u.y };\n const points: Vec2[] =\n h < 1e-9\n ? [foot]\n : [\n { x: foot.x - h * u.y, y: foot.y + h * u.x },\n { x: foot.x + h * u.y, y: foot.y - h * u.x },\n ];\n return points.map((X) => ({\n thetaA: angle({ x: X.x - a.P.x, y: X.y - a.P.y }) - angle(relA),\n thetaB: angle({ x: X.x - b.P.x, y: X.y - b.P.y }) - angle(relB),\n }));\n}\n\n/**\n * Edge ↔ edge (edges flush; the contact slides): the signed perpendicular\n * offset of a lock vertex from its own edge line is rotation-invariant, so the\n * shared line is a common tangent of the two circles those offsets define.\n * With line direction `t̂(ψ)`: `cross(t̂, P_a − P_b) = σ_a − ε·σ_b` for the two\n * relative edge senses ε — up to four candidate orientations.\n */\nfunction edgeCandidates(\n a: AttachPrepared,\n b: AttachPrepared,\n edgeA: number,\n edgeB: number,\n): AttachCandidate[] {\n const edgeGeom = (p: AttachPrepared, e: number): { uAng: number; sigma: number } => {\n const v0 = p.offsets[e];\n const v1 = p.offsets[(e + 1) % p.offsets.length];\n const a0 = { x: v0.x - p.lockOffset.x, y: v0.y - p.lockOffset.y };\n const u = { x: v1.x - v0.x, y: v1.y - v0.y };\n const len = Math.hypot(u.x, u.y);\n if (len < 1e-9) throw new TerrainSolveError(`edge ${e} is degenerate (zero length)`);\n const uHat = { x: u.x / len, y: u.y / len };\n // Signed distance of the lock vertex from the (lock-relative) edge line.\n return { uAng: angle(uHat), sigma: cross(uHat, { x: -a0.x, y: -a0.y }) };\n };\n const ga = edgeGeom(a, edgeA);\n const gb = edgeGeom(b, edgeB);\n const D = { x: a.P.x - b.P.x, y: a.P.y - b.P.y };\n const d = Math.hypot(D.x, D.y);\n if (d < 1e-9) throw new TerrainSolveError(\"the two lock points coincide — nothing to attach across\");\n const beta = angle(D);\n const out: AttachCandidate[] = [];\n for (const eps of [1, -1]) {\n const k = ga.sigma - eps * gb.sigma;\n const s = k / d;\n if (s > 1 + 1e-9 || s < -1 - 1e-9) continue;\n const asin = Math.asin(Math.max(-1, Math.min(1, s)));\n for (const psi of [beta - asin, beta - (Math.PI - asin)]) {\n out.push({\n thetaA: psi - ga.uAng,\n thetaB: psi - gb.uAng + (eps < 0 ? Math.PI : 0),\n });\n }\n }\n if (out.length === 0) {\n throw new TerrainSolveError(\n `attached edges cannot lie on a common line: the lock points are ${round2(d)}″ apart but the edge offsets are ${round2(ga.sigma)}″ and ${round2(gb.sigma)}″`,\n );\n }\n return out;\n}\n\nconst round2 = (n: number): number => Math.round(n * 100) / 100;\n\nfunction attachPose(p: AttachPrepared, theta: number): { x: number; y: number; rotation: number } {\n const cos = Math.cos(theta);\n const sin = Math.sin(theta);\n return {\n x: p.P.x - (cos * p.lockOffset.x - sin * p.lockOffset.y),\n y: p.P.y - (sin * p.lockOffset.x + cos * p.lockOffset.y),\n rotation: ((((theta * 180) / Math.PI) % 360) + 360) % 360,\n };\n}\n\n/**\n * Back-solve the centroid AND rotation of TWO pieces at once from the cluster\n * card pattern: each piece's card prints exactly two dimension lines, both to\n * one vertex — pinning that vertex's board position but not the rotation. The\n * rotations come from the pieces' attachment to each other: matched vertices\n * coincide (the pair pivots), or matched edges lie on one line (the contact\n * slides — each piece's position along the line is fixed by its lock vertex).\n *\n * Closed form: with each lock vertex pinned, one rotation per piece is the\n * only unknown. Vertex mode intersects the two circles the attach vertices\n * sweep; edge mode finds the common line via the rotation-invariant signed\n * offset of each lock vertex from its own edge. Both yield a small candidate\n * set; the pair nearest the pieces' current rotations is chosen — rough both\n * pieces in first.\n */\nexport function solveCentroidAttached(input: AttachInput): {\n a: { x: number; y: number; rotation: number };\n b: { x: number; y: number; rotation: number };\n} {\n const { a, b } = input;\n if (a.attach.kind !== b.attach.kind) {\n throw new TerrainSolveError(\n \"attachment kinds must match: vertex pairs pivot, edge pairs slide — no mixing\",\n );\n }\n const pa = prepAttach(a, input.board);\n const pb = prepAttach(b, input.board);\n const candidates =\n a.attach.kind === \"vertex\"\n ? vertexCandidates(pa, pb, a.attach.index, b.attach.index)\n : edgeCandidates(pa, pb, a.attach.index, b.attach.index);\n const best = candidates.reduce((bestSoFar, c) =>\n angularGap(c.thetaA, pa.hint) + angularGap(c.thetaB, pb.hint) <\n angularGap(bestSoFar.thetaA, pa.hint) + angularGap(bestSoFar.thetaB, pb.hint)\n ? c\n : bestSoFar,\n );\n return { a: attachPose(pa, best.thetaA), b: attachPose(pb, best.thetaB) };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"condition.d.ts","sourceRoot":"","sources":["../../src/translate/condition.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,KAAK,GAAG,IAAI,GAAG,KAAK,CAAC;IAChC,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,kFAAkF;AAClF,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzC;
|
|
1
|
+
{"version":3,"file":"condition.d.ts","sourceRoot":"","sources":["../../src/translate/condition.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,KAAK,GAAG,IAAI,GAAG,KAAK,CAAC;IAChC,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,kFAAkF;AAClF,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzC;AAYD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM,CAoKtD"}
|
|
@@ -12,6 +12,8 @@ export function dekebab(s) {
|
|
|
12
12
|
return s.replace(/-/g, " ");
|
|
13
13
|
}
|
|
14
14
|
function str(v) {
|
|
15
|
+
if (v == null)
|
|
16
|
+
return "?";
|
|
15
17
|
return typeof v === "string" ? v : String(v);
|
|
16
18
|
}
|
|
17
19
|
/** `2` + `objective` → `2+ objectives`. Nouns here are all regular plurals. */
|
|
@@ -64,6 +66,15 @@ export function describeCondition(c) {
|
|
|
64
66
|
return `${negate}the unit is battle-shocked`;
|
|
65
67
|
case "has-lost-wounds":
|
|
66
68
|
return `${negate}the model has lost wounds`;
|
|
69
|
+
case "was-hit-by-attack": {
|
|
70
|
+
const subject = p.subject === "target" ? "the target" : "the unit";
|
|
71
|
+
const atk = p.attack_type ? `${str(p.attack_type)} ` : "";
|
|
72
|
+
const weapon = p.weapon_name ? ` by ${str(p.weapon_name)}` : "";
|
|
73
|
+
const n = Number(p.count_min ?? 1);
|
|
74
|
+
if (n > 1)
|
|
75
|
+
return `${negate}${subject} was hit by ${n}+ ${atk}attacks${weapon} this phase`;
|
|
76
|
+
return `${negate}${subject} was hit by ${atk === "" ? "an attack" : `a ${atk}attack`}${weapon} this phase`;
|
|
77
|
+
}
|
|
67
78
|
case "opponent-unit-within-range":
|
|
68
79
|
return `${negate}an enemy unit is within ${p.range === "engagement" ? "engagement range" : `${str(p.range)}"`}`;
|
|
69
80
|
case "unit-within-range-of":
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"condition.js","sourceRoot":"","sources":["../../src/translate/condition.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Humanize an Ability-DSL / scoring `condition` into plain English.\n *\n * Shared by the ability-text CLI (`commands/translate.ts`) and the scoring-card\n * translator (`scoring.ts`). Output is **ASCII-only** with a fixed clause and\n * parameter order: it is pinned byte-for-byte across the TS and Rust ports by\n * the `conformance/scoring-translation` corpus, so any phrasing change here is a\n * semantic corpus change (bump `conformance/SPEC_VERSION`).\n */\n\n/**\n * Minimal structural view of a condition node. Matches both the ability-dsl\n * condition schema and the `secondary-card` award `when` field (a simple node\n * carries `type` + `parameters` + `negated`; a compound node carries\n * `operator` + `operands`).\n */\nexport interface Condition {\n type?: string;\n operator?: \"and\" | \"or\" | \"not\";\n operands?: Condition[];\n parameters?: Record<string, unknown>;\n negated?: boolean;\n}\n\n/** kebab-case → space-separated words (`enemy-territory` → `enemy territory`). */\nexport function dekebab(s: string): string {\n return s.replace(/-/g, \" \");\n}\n\nfunction str(v: unknown): string {\n return typeof v === \"string\" ? v : String(v);\n}\n\n/** `2` + `objective` → `2+ objectives`. Nouns here are all regular plurals. */\nfunction count(n: unknown, noun: string): string {\n return `${str(n)}+ ${noun}s`;\n}\n\nexport function describeCondition(c: Condition): string {\n // Compound nodes first — join the operands with lowercase connectives so the\n // result reads naturally inside a \"... when X and Y\" clause.\n if (c.operator === \"and\" && c.operands) {\n return c.operands.map(describeCondition).join(\" and \");\n }\n if (c.operator === \"or\" && c.operands) {\n return c.operands.map(describeCondition).join(\" or \");\n }\n if (c.operator === \"not\" && c.operands) {\n return `not (${c.operands.map(describeCondition).join(\", \")})`;\n }\n\n const negate = c.negated ? \"not \" : \"\";\n const p = c.parameters ?? {};\n\n switch (c.type) {\n // ── Ability-DSL conditions (ported from commands/translate.ts) ──────────\n case \"phase-is\":\n return `${negate}during the ${str(p.phase)} phase`;\n case \"timing-is\":\n return `${negate}at ${dekebab(str(p.timing))}`;\n case \"player-turn-is\":\n return `${negate}in ${p.turn === \"your-turn\" ? \"your\" : p.turn === \"opponent-turn\" ? \"the opponent's\" : \"either player's\"} turn`;\n case \"charged-this-turn\":\n return `${negate}the unit charged this turn`;\n case \"advanced-this-turn\":\n return `${negate}the unit advanced this turn`;\n case \"remained-stationary\":\n return `${negate}the unit remained stationary`;\n case \"unit-below-starting-strength\":\n return `${negate}the unit is below starting strength`;\n case \"unit-below-half-strength\":\n return `${negate}the unit is below half strength`;\n case \"unit-has-keyword\":\n return `${negate}the unit has \"${str(p.keyword)}\"`;\n case \"target-has-keyword\":\n return `${negate}the target has \"${str(p.keyword)}\"`;\n case \"model-is-leader\":\n return `${negate}the model is leading a unit`;\n case \"is-attached\":\n return `${negate}attached to a ${p.keyword ? `${str(p.keyword)} ` : \"\"}unit`;\n case \"attack-is-type\":\n return `${negate}for ${str(p.attack_type)} attacks`;\n case \"is-battle-shocked\":\n return `${negate}the unit is battle-shocked`;\n case \"has-lost-wounds\":\n return `${negate}the model has lost wounds`;\n case \"opponent-unit-within-range\":\n return `${negate}an enemy unit is within ${p.range === \"engagement\" ? \"engagement range\" : `${str(p.range)}\"`}`;\n case \"unit-within-range-of\":\n return `${negate}within ${str(p.range)}\" of ${str(p.target_type ?? \"target\")}${p.keyword ? ` (${str(p.keyword)})` : \"\"}`;\n case \"within-range-of-objective\":\n return `${negate}within range of an objective`;\n case \"has-fought-this-phase\":\n return `${negate}has fought this phase`;\n case \"destroyed-by-attack-type\":\n return `${negate}destroyed by a ${str(p.attack_type)} attack`;\n\n // ── Scoring conditions (secondary-card award `when`) ────────────────────\n case \"objective-majority\":\n return `${negate}you hold more objectives than the ${dekebab(str(p.relative_to ?? \"opponent\"))}`;\n case \"controls-objective\": {\n const noun = p.objective_role ? `${dekebab(str(p.objective_role))} objective` : \"objective\";\n let s = `${negate}you control ${count(p.count_min ?? 1, noun)}`;\n if (p.objective != null) s += ` (${dekebab(str(p.objective))})`;\n if (p.scope != null) s += ` in ${dekebab(str(p.scope))}`;\n if (p.exclude != null) s += ` (excluding ${dekebab(str(p.exclude))})`;\n return s;\n }\n case \"units-destroyed\":\n return `${negate}${count(p.count_min ?? 1, `${str(p.side)} unit`)} destroyed ${dekebab(str(p.window))}`;\n case \"units-destroyed-comparison\": {\n const subj = (p.subject ?? {}) as Record<string, unknown>;\n const ref = (p.reference ?? {}) as Record<string, unknown>;\n const cmp = p.comparator === \"greater-or-equal\" ? \"at least as many\" : \"more\";\n const link = p.comparator === \"greater-or-equal\" ? \"as\" : \"than\";\n return `${negate}you destroyed ${cmp} ${str(subj.side)} units ${dekebab(str(subj.window))} ${link} ${str(ref.side)} units ${dekebab(str(ref.window))}`;\n }\n case \"new-objective-controlled\":\n return `${negate}you newly control ${count(p.count_min ?? 1, \"objective\")} this turn`;\n case \"destroyed-while-on-objective\": {\n const obj = p.objective_role ? `a ${dekebab(str(p.objective_role))} objective` : \"an objective\";\n let s = `${negate}${count(p.count_min ?? 1, \"enemy unit\")} destroyed`;\n if (p.destroyer_on_objective) s += ` by a unit on ${obj}`;\n if (p.victim_on_objective) s += ` while on ${obj}`;\n if (p.victim_started_turn_on_objective) s += ` that started the turn on ${obj}`;\n return s;\n }\n case \"destroyed-in-tagged-terrain\": {\n const where = p.at_start_of_turn ? \"that started the turn in\" : \"while in\";\n const terrain = p.tag != null ? `${dekebab(str(p.tag))} terrain` : \"a terrain area\";\n return `${negate}${count(p.count_min ?? 1, \"enemy unit\")} destroyed ${where} ${terrain}`;\n }\n case \"operation-markers\": {\n const side = p.side != null ? `${str(p.side)} ` : \"\";\n const min = typeof p.count_min === \"number\" ? p.count_min : undefined;\n const max = typeof p.count_max === \"number\" ? p.count_max : undefined;\n let s: string;\n if (max === 0) {\n s = `no ${side}operation markers on the battlefield`;\n } else if (min != null && max != null && min === max) {\n s = `exactly ${min} ${side}operation marker${min === 1 ? \"\" : \"s\"} on the battlefield`;\n } else {\n s = `${str(min ?? 1)}+ ${side}operation markers on the battlefield`;\n }\n if (p.within_range_of != null) s += ` within range of ${dekebab(str(p.within_range_of))}`;\n if (p.friendly_unit_in_same_terrain_area) s += \" with a friendly unit in the same terrain area\";\n if (p.no_enemy_in_terrain_area) s += \" and no enemy units in that terrain area\";\n return `${negate}${s}`;\n }\n case \"action-completed\": {\n let s = `${negate}${count(p.count_min ?? 1, \"action\")} completed`;\n if (p.action_id != null) s += ` (${dekebab(str(p.action_id))})`;\n if (p.target_kind != null) s += ` on ${dekebab(str(p.target_kind))}`;\n const tf = (p.target_filter ?? {}) as Record<string, unknown>;\n if (tf.objective_role != null) s += ` (${dekebab(str(tf.objective_role))})`;\n if (tf.in_enemy_territory) s += \" in enemy territory\";\n if (tf.exclude != null) s += ` (excluding ${dekebab(str(tf.exclude))})`;\n if (p.window != null) s += ` ${dekebab(str(p.window))}`;\n return s;\n }\n case \"objective-has-tag\": {\n let s = `${negate}${count(p.count_min ?? 1, \"objective\")} tagged ${dekebab(str(p.tag))}`;\n if (p.count_max != null) s += ` (at most ${str(p.count_max)})`;\n if (p.objective != null) s += ` (${dekebab(str(p.objective))})`;\n if (p.scope != null) s += ` in ${dekebab(str(p.scope))}`;\n if (p.last_marked) s += \" (most recently marked)\";\n return s;\n }\n case \"unit-has-tag\": {\n let s = `${negate}${count(p.count_min ?? 1, `${str(p.side)} unit`)} tagged ${dekebab(str(p.tag))}`;\n if (p.window != null) s += ` (${dekebab(str(p.window))})`;\n return s;\n }\n case \"terrain-has-tag\": {\n let s = `${negate}terrain tagged ${dekebab(str(p.tag))}`;\n if (p.friendly_units_min != null) s += ` with ${str(p.friendly_units_min)}+ friendly units`;\n if (p.enemy_units_max != null) s += ` and at most ${str(p.enemy_units_max)} enemy units`;\n if (p.last_marked) s += \" (most recently marked)\";\n if (p.in_enemy_dz) s += \" in the enemy deployment zone\";\n return s;\n }\n case \"terrain-area-control\":\n return `${negate}you control a terrain area with ${str(p.min_models ?? 1)}+ models`;\n case \"territory-control\": {\n let s = `${negate}you control ${dekebab(str(p.territory_ref ?? \"your-territory\"))}`;\n if (p.enemy_units_max != null) s += ` with at most ${str(p.enemy_units_max)} enemy units`;\n return s;\n }\n case \"engagement-fronts\":\n return `${negate}you are engaged on ${str(p.count_min ?? 1)}+ fronts`;\n\n default:\n return `${negate}${dekebab(c.type ?? \"unknown\")}`;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"condition.js","sourceRoot":"","sources":["../../src/translate/condition.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Humanize an Ability-DSL / scoring `condition` into plain English.\n *\n * Shared by the ability-text CLI (`commands/translate.ts`) and the scoring-card\n * translator (`scoring.ts`). Output is **ASCII-only** with a fixed clause and\n * parameter order: it is pinned byte-for-byte across the TS and Rust ports by\n * the `conformance/scoring-translation` corpus, so any phrasing change here is a\n * semantic corpus change (bump `conformance/SPEC_VERSION`).\n */\n\n/**\n * Minimal structural view of a condition node. Matches both the ability-dsl\n * condition schema and the `secondary-card` award `when` field (a simple node\n * carries `type` + `parameters` + `negated`; a compound node carries\n * `operator` + `operands`).\n */\nexport interface Condition {\n type?: string;\n operator?: \"and\" | \"or\" | \"not\";\n operands?: Condition[];\n parameters?: Record<string, unknown>;\n negated?: boolean;\n}\n\n/** kebab-case → space-separated words (`enemy-territory` → `enemy territory`). */\nexport function dekebab(s: string): string {\n return s.replace(/-/g, \" \");\n}\n\nfunction str(v: unknown): string {\n if (v == null) return \"?\";\n return typeof v === \"string\" ? v : String(v);\n}\n\n/** `2` + `objective` → `2+ objectives`. Nouns here are all regular plurals. */\nfunction count(n: unknown, noun: string): string {\n return `${str(n)}+ ${noun}s`;\n}\n\nexport function describeCondition(c: Condition): string {\n // Compound nodes first — join the operands with lowercase connectives so the\n // result reads naturally inside a \"... when X and Y\" clause.\n if (c.operator === \"and\" && c.operands) {\n return c.operands.map(describeCondition).join(\" and \");\n }\n if (c.operator === \"or\" && c.operands) {\n return c.operands.map(describeCondition).join(\" or \");\n }\n if (c.operator === \"not\" && c.operands) {\n return `not (${c.operands.map(describeCondition).join(\", \")})`;\n }\n\n const negate = c.negated ? \"not \" : \"\";\n const p = c.parameters ?? {};\n\n switch (c.type) {\n // ── Ability-DSL conditions (ported from commands/translate.ts) ──────────\n case \"phase-is\":\n return `${negate}during the ${str(p.phase)} phase`;\n case \"timing-is\":\n return `${negate}at ${dekebab(str(p.timing))}`;\n case \"player-turn-is\":\n return `${negate}in ${p.turn === \"your-turn\" ? \"your\" : p.turn === \"opponent-turn\" ? \"the opponent's\" : \"either player's\"} turn`;\n case \"charged-this-turn\":\n return `${negate}the unit charged this turn`;\n case \"advanced-this-turn\":\n return `${negate}the unit advanced this turn`;\n case \"remained-stationary\":\n return `${negate}the unit remained stationary`;\n case \"unit-below-starting-strength\":\n return `${negate}the unit is below starting strength`;\n case \"unit-below-half-strength\":\n return `${negate}the unit is below half strength`;\n case \"unit-has-keyword\":\n return `${negate}the unit has \"${str(p.keyword)}\"`;\n case \"target-has-keyword\":\n return `${negate}the target has \"${str(p.keyword)}\"`;\n case \"model-is-leader\":\n return `${negate}the model is leading a unit`;\n case \"is-attached\":\n return `${negate}attached to a ${p.keyword ? `${str(p.keyword)} ` : \"\"}unit`;\n case \"attack-is-type\":\n return `${negate}for ${str(p.attack_type)} attacks`;\n case \"is-battle-shocked\":\n return `${negate}the unit is battle-shocked`;\n case \"has-lost-wounds\":\n return `${negate}the model has lost wounds`;\n case \"was-hit-by-attack\": {\n const subject = p.subject === \"target\" ? \"the target\" : \"the unit\";\n const atk = p.attack_type ? `${str(p.attack_type)} ` : \"\";\n const weapon = p.weapon_name ? ` by ${str(p.weapon_name)}` : \"\";\n const n = Number(p.count_min ?? 1);\n if (n > 1) return `${negate}${subject} was hit by ${n}+ ${atk}attacks${weapon} this phase`;\n return `${negate}${subject} was hit by ${atk === \"\" ? \"an attack\" : `a ${atk}attack`}${weapon} this phase`;\n }\n case \"opponent-unit-within-range\":\n return `${negate}an enemy unit is within ${p.range === \"engagement\" ? \"engagement range\" : `${str(p.range)}\"`}`;\n case \"unit-within-range-of\":\n return `${negate}within ${str(p.range)}\" of ${str(p.target_type ?? \"target\")}${p.keyword ? ` (${str(p.keyword)})` : \"\"}`;\n case \"within-range-of-objective\":\n return `${negate}within range of an objective`;\n case \"has-fought-this-phase\":\n return `${negate}has fought this phase`;\n case \"destroyed-by-attack-type\":\n return `${negate}destroyed by a ${str(p.attack_type)} attack`;\n\n // ── Scoring conditions (secondary-card award `when`) ────────────────────\n case \"objective-majority\":\n return `${negate}you hold more objectives than the ${dekebab(str(p.relative_to ?? \"opponent\"))}`;\n case \"controls-objective\": {\n const noun = p.objective_role ? `${dekebab(str(p.objective_role))} objective` : \"objective\";\n let s = `${negate}you control ${count(p.count_min ?? 1, noun)}`;\n if (p.objective != null) s += ` (${dekebab(str(p.objective))})`;\n if (p.scope != null) s += ` in ${dekebab(str(p.scope))}`;\n if (p.exclude != null) s += ` (excluding ${dekebab(str(p.exclude))})`;\n return s;\n }\n case \"units-destroyed\":\n return `${negate}${count(p.count_min ?? 1, `${str(p.side)} unit`)} destroyed ${dekebab(str(p.window))}`;\n case \"units-destroyed-comparison\": {\n const subj = (p.subject ?? {}) as Record<string, unknown>;\n const ref = (p.reference ?? {}) as Record<string, unknown>;\n const cmp = p.comparator === \"greater-or-equal\" ? \"at least as many\" : \"more\";\n const link = p.comparator === \"greater-or-equal\" ? \"as\" : \"than\";\n return `${negate}you destroyed ${cmp} ${str(subj.side)} units ${dekebab(str(subj.window))} ${link} ${str(ref.side)} units ${dekebab(str(ref.window))}`;\n }\n case \"new-objective-controlled\":\n return `${negate}you newly control ${count(p.count_min ?? 1, \"objective\")} this turn`;\n case \"destroyed-while-on-objective\": {\n const obj = p.objective_role ? `a ${dekebab(str(p.objective_role))} objective` : \"an objective\";\n let s = `${negate}${count(p.count_min ?? 1, \"enemy unit\")} destroyed`;\n if (p.destroyer_on_objective) s += ` by a unit on ${obj}`;\n if (p.victim_on_objective) s += ` while on ${obj}`;\n if (p.victim_started_turn_on_objective) s += ` that started the turn on ${obj}`;\n return s;\n }\n case \"destroyed-in-tagged-terrain\": {\n const where = p.at_start_of_turn ? \"that started the turn in\" : \"while in\";\n const terrain = p.tag != null ? `${dekebab(str(p.tag))} terrain` : \"a terrain area\";\n return `${negate}${count(p.count_min ?? 1, \"enemy unit\")} destroyed ${where} ${terrain}`;\n }\n case \"operation-markers\": {\n const side = p.side != null ? `${str(p.side)} ` : \"\";\n const min = typeof p.count_min === \"number\" ? p.count_min : undefined;\n const max = typeof p.count_max === \"number\" ? p.count_max : undefined;\n let s: string;\n if (max === 0) {\n s = `no ${side}operation markers on the battlefield`;\n } else if (min != null && max != null && min === max) {\n s = `exactly ${min} ${side}operation marker${min === 1 ? \"\" : \"s\"} on the battlefield`;\n } else {\n s = `${str(min ?? 1)}+ ${side}operation markers on the battlefield`;\n }\n if (p.within_range_of != null) s += ` within range of ${dekebab(str(p.within_range_of))}`;\n if (p.friendly_unit_in_same_terrain_area) s += \" with a friendly unit in the same terrain area\";\n if (p.no_enemy_in_terrain_area) s += \" and no enemy units in that terrain area\";\n return `${negate}${s}`;\n }\n case \"action-completed\": {\n let s = `${negate}${count(p.count_min ?? 1, \"action\")} completed`;\n if (p.action_id != null) s += ` (${dekebab(str(p.action_id))})`;\n if (p.target_kind != null) s += ` on ${dekebab(str(p.target_kind))}`;\n const tf = (p.target_filter ?? {}) as Record<string, unknown>;\n if (tf.objective_role != null) s += ` (${dekebab(str(tf.objective_role))})`;\n if (tf.in_enemy_territory) s += \" in enemy territory\";\n if (tf.exclude != null) s += ` (excluding ${dekebab(str(tf.exclude))})`;\n if (p.window != null) s += ` ${dekebab(str(p.window))}`;\n return s;\n }\n case \"objective-has-tag\": {\n let s = `${negate}${count(p.count_min ?? 1, \"objective\")} tagged ${dekebab(str(p.tag))}`;\n if (p.count_max != null) s += ` (at most ${str(p.count_max)})`;\n if (p.objective != null) s += ` (${dekebab(str(p.objective))})`;\n if (p.scope != null) s += ` in ${dekebab(str(p.scope))}`;\n if (p.last_marked) s += \" (most recently marked)\";\n return s;\n }\n case \"unit-has-tag\": {\n let s = `${negate}${count(p.count_min ?? 1, `${str(p.side)} unit`)} tagged ${dekebab(str(p.tag))}`;\n if (p.window != null) s += ` (${dekebab(str(p.window))})`;\n return s;\n }\n case \"terrain-has-tag\": {\n let s = `${negate}terrain tagged ${dekebab(str(p.tag))}`;\n if (p.friendly_units_min != null) s += ` with ${str(p.friendly_units_min)}+ friendly units`;\n if (p.enemy_units_max != null) s += ` and at most ${str(p.enemy_units_max)} enemy units`;\n if (p.last_marked) s += \" (most recently marked)\";\n if (p.in_enemy_dz) s += \" in the enemy deployment zone\";\n return s;\n }\n case \"terrain-area-control\":\n return `${negate}you control a terrain area with ${str(p.min_models ?? 1)}+ models`;\n case \"territory-control\": {\n let s = `${negate}you control ${dekebab(str(p.territory_ref ?? \"your-territory\"))}`;\n if (p.enemy_units_max != null) s += ` with at most ${str(p.enemy_units_max)} enemy units`;\n return s;\n }\n case \"engagement-fronts\":\n return `${negate}you are engaged on ${str(p.count_min ?? 1)}+ fronts`;\n\n default:\n return `${negate}${dekebab(c.type ?? \"unknown\")}`;\n }\n}\n"]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Humanize an Ability-DSL `effect` tree into plain English — the
|
|
3
|
+
* `ability.print()` of the dataset. Output is an *approximation* generated
|
|
4
|
+
* purely from the structured data (no external rules text), ASCII-only, with
|
|
5
|
+
* a fixed clause order: it is pinned byte-for-byte across the TS and Rust
|
|
6
|
+
* ports by the `conformance/effect-translation` corpus, so any phrasing
|
|
7
|
+
* change here is a semantic corpus change (bump `conformance/SPEC_VERSION`).
|
|
8
|
+
*
|
|
9
|
+
* Container nodes (`sequence`, `conditional`, `choice`, `dice-gated`,
|
|
10
|
+
* `dice-pool-allocation`) render block-style with two-space indentation and
|
|
11
|
+
* an ASCII `-> ` arrow; leaves render as single clauses. Unknown leaf types
|
|
12
|
+
* and unrecognized modifier shapes degrade to a deterministic bracketed form
|
|
13
|
+
* (`[the-type]`) rather than failing — coverage improves as authoring does.
|
|
14
|
+
*/
|
|
15
|
+
import { type Condition } from "./condition.js";
|
|
16
|
+
/**
|
|
17
|
+
* Minimal structural view of an effect node. Matches the ability-dsl effect
|
|
18
|
+
* schema: a single effect carries `type` + `target` + `modifier`; containers
|
|
19
|
+
* carry their own shape (`steps`, `options`, `condition`/`effect`, dice
|
|
20
|
+
* fields).
|
|
21
|
+
*/
|
|
22
|
+
export interface Effect {
|
|
23
|
+
type?: string;
|
|
24
|
+
target?: string;
|
|
25
|
+
modifier?: Record<string, unknown>;
|
|
26
|
+
condition?: Condition;
|
|
27
|
+
effect?: Effect;
|
|
28
|
+
steps?: Effect[];
|
|
29
|
+
options?: (Effect & {
|
|
30
|
+
name?: string;
|
|
31
|
+
requirement?: Record<string, unknown>;
|
|
32
|
+
})[];
|
|
33
|
+
choice_label?: string;
|
|
34
|
+
dice?: string;
|
|
35
|
+
threshold?: number | string;
|
|
36
|
+
comparison?: string;
|
|
37
|
+
on_success?: Effect | null;
|
|
38
|
+
on_fail?: Effect | null;
|
|
39
|
+
pool?: {
|
|
40
|
+
count: number;
|
|
41
|
+
die: string;
|
|
42
|
+
};
|
|
43
|
+
max_activations?: number;
|
|
44
|
+
}
|
|
45
|
+
/** Ability scope, as carried on enrichment ability entries. */
|
|
46
|
+
export interface AbilityScope {
|
|
47
|
+
range?: string;
|
|
48
|
+
duration?: string;
|
|
49
|
+
range_inches?: number;
|
|
50
|
+
}
|
|
51
|
+
/** Minimal ability view for `describeAbility`. */
|
|
52
|
+
export interface AbilityLike {
|
|
53
|
+
name?: string;
|
|
54
|
+
effect?: Effect;
|
|
55
|
+
scope?: AbilityScope;
|
|
56
|
+
}
|
|
57
|
+
/** Single-clause translation for leaf effects (and inline container forms). */
|
|
58
|
+
export declare function describeEffectInline(e: Effect): string;
|
|
59
|
+
/**
|
|
60
|
+
* Block translation of an effect tree. Containers expand over multiple lines
|
|
61
|
+
* with two-space indentation; leaves delegate to `describeEffectInline`.
|
|
62
|
+
*/
|
|
63
|
+
export declare function describeEffect(e: Effect, depth?: number): string;
|
|
64
|
+
/** `Scope: aura (6"). Duration: phase.` — empty string when absent. */
|
|
65
|
+
export declare function describeScope(s?: AbilityScope): string;
|
|
66
|
+
/**
|
|
67
|
+
* Full generated text for an ability: the effect tree plus a trailing scope
|
|
68
|
+
* line. This is the `ability.print()` consumers render when the dataset
|
|
69
|
+
* carries no rules prose.
|
|
70
|
+
*/
|
|
71
|
+
export declare function describeAbility(a: AbilityLike): string;
|
|
72
|
+
//# sourceMappingURL=effect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effect.d.ts","sourceRoot":"","sources":["../../src/translate/effect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAA8B,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE5E;;;;;GAKG;AACH,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,CAAC,EAAE,CAAC,MAAM,GAAG;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACvC,CAAC,EAAE,CAAC;IACL,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,+DAA+D;AAC/D,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,kDAAkD;AAClD,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAoCD,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAmItD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,GAAE,MAAU,GAAG,MAAM,CAmCnE;AAED,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CAMtD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,CAItD"}
|