@alpaca-software/40kdc-data 0.4.14 → 0.4.16
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/data/bundle.generated.js +1 -1
- package/dist/data/bundle.generated.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.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/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ export * from "./data/index.js";
|
|
|
2
2
|
export * from "./generated.js";
|
|
3
3
|
export * from "./translate/index.js";
|
|
4
4
|
export type { ScoringTrigger } from "./generated.js";
|
|
5
|
-
export { resolveLayout, polygonCentroid, footprintVertices, orientedOffsets, TerrainResolveError, solveCentroid, solveCentroidTriangulated, TerrainSolveError, keystoneMeasurements, BOARD_INCHES, TerrainKeystoneError, } from "./terrain/index.js";
|
|
6
|
-
export type { ResolvedPiece, ResolvedVec2, BoardEdge, FeatureRef, Keystone, KeystoneMeasurement, DimensionLine, SolveInput, TriangulationLine, TriangulateInput, } from "./terrain/index.js";
|
|
5
|
+
export { resolveLayout, polygonCentroid, footprintVertices, orientedOffsets, TerrainResolveError, solveCentroid, solveCentroidTriangulated, solveCentroidAttached, TerrainSolveError, keystoneMeasurements, BOARD_INCHES, TerrainKeystoneError, } from "./terrain/index.js";
|
|
6
|
+
export type { ResolvedPiece, ResolvedVec2, BoardEdge, FeatureRef, Keystone, KeystoneMeasurement, DimensionLine, SolveInput, TriangulationLine, TriangulateInput, AttachLine, AttachPiece, AttachInput, } from "./terrain/index.js";
|
|
7
7
|
export * from "./scoring/index.js";
|
|
8
8
|
export { createValidator, findSchemaFiles, listSchemaIds, SCHEMAS_ROOT, } from "./schema-loader.js";
|
|
9
9
|
export { importListForge, importNewRecruit, importRoster, tryImportRoster, decodeListForge, } from "./import/index.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,iBAAiB,CAAC;AAGhC,cAAc,gBAAgB,CAAC;AAI/B,cAAc,sBAAsB,CAAC;AAKrC,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAKrD,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,yBAAyB,EACzB,iBAAiB,EACjB,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,aAAa,EACb,YAAY,EACZ,SAAS,EACT,UAAU,EACV,QAAQ,EACR,mBAAmB,EACnB,aAAa,EACb,UAAU,EACV,iBAAiB,EACjB,gBAAgB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,iBAAiB,CAAC;AAGhC,cAAc,gBAAgB,CAAC;AAI/B,cAAc,sBAAsB,CAAC;AAKrC,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAKrD,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,yBAAyB,EACzB,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,aAAa,EACb,YAAY,EACZ,SAAS,EACT,UAAU,EACV,QAAQ,EACR,mBAAmB,EACnB,aAAa,EACb,UAAU,EACV,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,WAAW,GACZ,MAAM,oBAAoB,CAAC;AAK5B,cAAc,oBAAoB,CAAC;AAInC,OAAO,EACL,eAAe,EACf,eAAe,EACf,aAAa,EACb,YAAY,GACb,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,YAAY,EACZ,wBAAwB,EACxB,0BAA0B,EAC1B,8BAA8B,EAC9B,2BAA2B,EAC3B,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACxE,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EACV,aAAa,EACb,YAAY,EACZ,mBAAmB,EACnB,YAAY,EACZ,MAAM,EACN,UAAU,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,WAAW,EACX,SAAS,EACT,WAAW,EACX,OAAO,EACP,WAAW,EACX,YAAY,EACZ,UAAU,EACV,aAAa,GACd,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ export * from "./translate/index.js";
|
|
|
8
8
|
// Terrain geometry resolver (template-anchored layout → board-space vertices).
|
|
9
9
|
// Curated (not wildcard) so its internal type aliases don't clash with the
|
|
10
10
|
// generated Footprint/TerrainTemplate/TerrainLayout types. Cross-impl pinned.
|
|
11
|
-
export { resolveLayout, polygonCentroid, footprintVertices, orientedOffsets, TerrainResolveError, solveCentroid, solveCentroidTriangulated, TerrainSolveError, keystoneMeasurements, BOARD_INCHES, TerrainKeystoneError, } from "./terrain/index.js";
|
|
11
|
+
export { resolveLayout, polygonCentroid, footprintVertices, orientedOffsets, TerrainResolveError, solveCentroid, solveCentroidTriangulated, solveCentroidAttached, TerrainSolveError, keystoneMeasurements, BOARD_INCHES, TerrainKeystoneError, } from "./terrain/index.js";
|
|
12
12
|
// Card-driven secondary-mission scoring: compute VP from asserted awards and
|
|
13
13
|
// track per-round, per-player scoring. Mirrored by the Rust `wh40kdc::scoring`
|
|
14
14
|
// module and pinned by the `conformance/scoring` corpus.
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,cAAc,iBAAiB,CAAC;AAEhC,mDAAmD;AACnD,cAAc,gBAAgB,CAAC;AAE/B,0EAA0E;AAC1E,6EAA6E;AAC7E,cAAc,sBAAsB,CAAC;AAOrC,+EAA+E;AAC/E,2EAA2E;AAC3E,8EAA8E;AAC9E,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,yBAAyB,EACzB,iBAAiB,EACjB,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,cAAc,iBAAiB,CAAC;AAEhC,mDAAmD;AACnD,cAAc,gBAAgB,CAAC;AAE/B,0EAA0E;AAC1E,6EAA6E;AAC7E,cAAc,sBAAsB,CAAC;AAOrC,+EAA+E;AAC/E,2EAA2E;AAC3E,8EAA8E;AAC9E,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,yBAAyB,EACzB,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAiB5B,6EAA6E;AAC7E,+EAA+E;AAC/E,yDAAyD;AACzD,cAAc,oBAAoB,CAAC;AAEnC,8EAA8E;AAC9E,uCAAuC;AACvC,OAAO,EACL,eAAe,EACf,eAAe,EACf,aAAa,EACb,YAAY,GACb,MAAM,oBAAoB,CAAC;AAE5B,4EAA4E;AAC5E,+EAA+E;AAC/E,uCAAuC;AACvC,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAE3B,+EAA+E;AAC/E,OAAO,EACL,YAAY,EACZ,wBAAwB,EACxB,0BAA0B,EAC1B,8BAA8B,EAC9B,2BAA2B,EAC3B,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC","sourcesContent":["// The linked, typed dataset — the primary entry point.\nexport * from \"./data/index.js\";\n\n// Generated types for every entity in the dataset.\nexport * from \"./generated.js\";\n\n// Plain-English translation of structured data (scoring-card awards + the\n// shared Ability-DSL condition humanizer). Cross-impl pinned by conformance.\nexport * from \"./translate/index.js\";\n\n// `ScoringTrigger` is emitted by both ./generated.js (schema-derived) and\n// ./translate (hand-authored). They are structurally identical; disambiguate the\n// two wildcard re-exports in favour of the generated, schema-canonical type.\nexport type { ScoringTrigger } from \"./generated.js\";\n\n// Terrain geometry resolver (template-anchored layout → board-space vertices).\n// Curated (not wildcard) so its internal type aliases don't clash with the\n// generated Footprint/TerrainTemplate/TerrainLayout types. Cross-impl pinned.\nexport {\n resolveLayout,\n polygonCentroid,\n footprintVertices,\n orientedOffsets,\n TerrainResolveError,\n solveCentroid,\n solveCentroidTriangulated,\n solveCentroidAttached,\n TerrainSolveError,\n keystoneMeasurements,\n BOARD_INCHES,\n TerrainKeystoneError,\n} from \"./terrain/index.js\";\nexport type {\n ResolvedPiece,\n ResolvedVec2,\n BoardEdge,\n FeatureRef,\n Keystone,\n KeystoneMeasurement,\n DimensionLine,\n SolveInput,\n TriangulationLine,\n TriangulateInput,\n AttachLine,\n AttachPiece,\n AttachInput,\n} from \"./terrain/index.js\";\n\n// Card-driven secondary-mission scoring: compute VP from asserted awards and\n// track per-round, per-player scoring. Mirrored by the Rust `wh40kdc::scoring`\n// module and pinned by the `conformance/scoring` corpus.\nexport * from \"./scoring/index.js\";\n\n// Schema access + AJV validation (secondary: this package also validates data\n// against the canonical JSON Schemas).\nexport {\n createValidator,\n findSchemaFiles,\n listSchemaIds,\n SCHEMAS_ROOT,\n} from \"./schema-loader.js\";\n\n// Army-list importer (ListForge → resolved 40kdc roster). Types are curated\n// rather than re-exported wholesale to avoid name clashes with generated types\n// (e.g. BattleSize, LeaderAttachment).\nexport {\n importListForge,\n importNewRecruit,\n importRoster,\n tryImportRoster,\n decodeListForge,\n} from \"./import/index.js\";\n\n// Army-list exporter (Roster → text or JSON for any of the supported formats).\nexport {\n exportRoster,\n newRecruitJsonSerializer,\n newRecruitSimpleSerializer,\n newRecruitWtcCompactSerializer,\n newRecruitWtcFullSerializer,\n rosterJsonSerializer,\n rosterizerSerializer,\n} from \"./export/index.js\";\nexport type { ExportFormat, RosterSerializer } from \"./export/index.js\";\nexport type { FormatAdapter } from \"./import/index.js\";\nexport type {\n ImportOptions,\n ImportResult,\n ImportFailureReason,\n AdapterTrial,\n Roster,\n RosterUnit,\n RosterWargear,\n RosterSource,\n RosterFormat,\n RosterPoints,\n RosterLeaderAttachment,\n ResolvedRef,\n Candidate,\n Diagnostics,\n Warning,\n WarningCode,\n ParsedRoster,\n ParsedUnit,\n ParsedWargear,\n} from \"./import/index.js\";\n"]}
|
package/dist/terrain/index.d.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { resolveLayout, polygonCentroid, footprintVertices, orientedOffsets, TerrainResolveError, } from "./resolve.js";
|
|
8
8
|
export type { Keystone, ResolvedPiece, Vec2 as ResolvedVec2 } from "./resolve.js";
|
|
9
|
-
export { solveCentroid, solveCentroidTriangulated, TerrainSolveError } from "./solve.js";
|
|
10
|
-
export type { BoardEdge, FeatureRef, DimensionLine, SolveInput, TriangulationLine, TriangulateInput, } from "./solve.js";
|
|
9
|
+
export { solveCentroid, solveCentroidTriangulated, solveCentroidAttached, TerrainSolveError, } from "./solve.js";
|
|
10
|
+
export type { BoardEdge, FeatureRef, DimensionLine, SolveInput, TriangulationLine, TriangulateInput, AttachLine, AttachPiece, AttachInput, } from "./solve.js";
|
|
11
11
|
export { keystoneMeasurements, BOARD_INCHES, TerrainKeystoneError } from "./keystones.js";
|
|
12
12
|
export type { KeystoneMeasurement } from "./keystones.js";
|
|
13
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/terrain/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,IAAI,IAAI,YAAY,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/terrain/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,IAAI,IAAI,YAAY,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,EACL,aAAa,EACb,yBAAyB,EACzB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,SAAS,EACT,UAAU,EACV,aAAa,EACb,UAAU,EACV,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC1F,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/terrain/index.js
CHANGED
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
* @packageDocumentation
|
|
6
6
|
*/
|
|
7
7
|
export { resolveLayout, polygonCentroid, footprintVertices, orientedOffsets, TerrainResolveError, } from "./resolve.js";
|
|
8
|
-
export { solveCentroid, solveCentroidTriangulated, TerrainSolveError } from "./solve.js";
|
|
8
|
+
export { solveCentroid, solveCentroidTriangulated, solveCentroidAttached, TerrainSolveError, } from "./solve.js";
|
|
9
9
|
export { keystoneMeasurements, BOARD_INCHES, TerrainKeystoneError } from "./keystones.js";
|
|
10
10
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/terrain/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAEtB,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/terrain/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,aAAa,EACb,yBAAyB,EACzB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,YAAY,CAAC;AAYpB,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["/**\n * Terrain geometry: resolve template-anchored layouts to absolute board-space\n * vertices. See {@link resolveLayout} for the transform contract.\n *\n * @packageDocumentation\n */\nexport {\n resolveLayout,\n polygonCentroid,\n footprintVertices,\n orientedOffsets,\n TerrainResolveError,\n} from \"./resolve.js\";\nexport type { Keystone, ResolvedPiece, Vec2 as ResolvedVec2 } from \"./resolve.js\";\nexport {\n solveCentroid,\n solveCentroidTriangulated,\n solveCentroidAttached,\n TerrainSolveError,\n} from \"./solve.js\";\nexport type {\n BoardEdge,\n FeatureRef,\n DimensionLine,\n SolveInput,\n TriangulationLine,\n TriangulateInput,\n AttachLine,\n AttachPiece,\n AttachInput,\n} from \"./solve.js\";\nexport { keystoneMeasurements, BOARD_INCHES, TerrainKeystoneError } from \"./keystones.js\";\nexport type { KeystoneMeasurement } from \"./keystones.js\";\n"]}
|
package/dist/terrain/solve.d.ts
CHANGED
|
@@ -81,4 +81,75 @@ export declare function solveCentroidTriangulated(input: TriangulateInput): {
|
|
|
81
81
|
y: number;
|
|
82
82
|
rotation: number;
|
|
83
83
|
};
|
|
84
|
+
/**
|
|
85
|
+
* One card dimension line in an attachment solve: `distance` inches from board
|
|
86
|
+
* `edge` to the piece's lock vertex. No feature ref — both of a piece's lines
|
|
87
|
+
* reach the same locked vertex (the card pattern this solver exists for).
|
|
88
|
+
*/
|
|
89
|
+
export interface AttachLine {
|
|
90
|
+
edge: BoardEdge;
|
|
91
|
+
distance: number;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* One piece of a two-body attachment solve. The card pins exactly ONE vertex
|
|
95
|
+
* (the keystone anchor) with two perpendicular dimension lines; the piece is
|
|
96
|
+
* otherwise free to rotate about that point. The `attach` feature is what
|
|
97
|
+
* connects it to the other piece and removes that rotational freedom.
|
|
98
|
+
*/
|
|
99
|
+
export interface AttachPiece {
|
|
100
|
+
footprint: Footprint;
|
|
101
|
+
mirror: Mirror;
|
|
102
|
+
/** The vertex pinned by `lines` — the piece's keystone anchor vertex. */
|
|
103
|
+
lockVertex: number;
|
|
104
|
+
/** Two perpendicular card lines pinning the lock vertex: one must pin x, one y. */
|
|
105
|
+
lines: [AttachLine, AttachLine];
|
|
106
|
+
/**
|
|
107
|
+
* The attached feature: a specific vertex, or the footprint edge running
|
|
108
|
+
* from vertex `index` to vertex `index + 1` (wrapping).
|
|
109
|
+
*/
|
|
110
|
+
attach: {
|
|
111
|
+
kind: "vertex" | "edge";
|
|
112
|
+
index: number;
|
|
113
|
+
};
|
|
114
|
+
/** Current rotation in degrees, used to pick among the candidate roots. */
|
|
115
|
+
rotationHint?: number;
|
|
116
|
+
}
|
|
117
|
+
export interface AttachInput {
|
|
118
|
+
/** Board extents in inches (40kdc standard is 60 × 44). */
|
|
119
|
+
board: {
|
|
120
|
+
width: number;
|
|
121
|
+
height: number;
|
|
122
|
+
};
|
|
123
|
+
/** The piece being placed. */
|
|
124
|
+
a: AttachPiece;
|
|
125
|
+
/** The piece it attaches to. */
|
|
126
|
+
b: AttachPiece;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Back-solve the centroid AND rotation of TWO pieces at once from the cluster
|
|
130
|
+
* card pattern: each piece's card prints exactly two dimension lines, both to
|
|
131
|
+
* one vertex — pinning that vertex's board position but not the rotation. The
|
|
132
|
+
* rotations come from the pieces' attachment to each other: matched vertices
|
|
133
|
+
* coincide (the pair pivots), or matched edges lie on one line (the contact
|
|
134
|
+
* slides — each piece's position along the line is fixed by its lock vertex).
|
|
135
|
+
*
|
|
136
|
+
* Closed form: with each lock vertex pinned, one rotation per piece is the
|
|
137
|
+
* only unknown. Vertex mode intersects the two circles the attach vertices
|
|
138
|
+
* sweep; edge mode finds the common line via the rotation-invariant signed
|
|
139
|
+
* offset of each lock vertex from its own edge. Both yield a small candidate
|
|
140
|
+
* set; the pair nearest the pieces' current rotations is chosen — rough both
|
|
141
|
+
* pieces in first.
|
|
142
|
+
*/
|
|
143
|
+
export declare function solveCentroidAttached(input: AttachInput): {
|
|
144
|
+
a: {
|
|
145
|
+
x: number;
|
|
146
|
+
y: number;
|
|
147
|
+
rotation: number;
|
|
148
|
+
};
|
|
149
|
+
b: {
|
|
150
|
+
x: number;
|
|
151
|
+
y: number;
|
|
152
|
+
rotation: number;
|
|
153
|
+
};
|
|
154
|
+
};
|
|
84
155
|
//# sourceMappingURL=solve.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"solve.d.ts","sourceRoot":"","sources":["../../src/terrain/solve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,MAAM,EACX,KAAK,IAAI,EACV,MAAM,cAAc,CAAC;AAItB,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1D,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,UAAU,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,SAAS,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,iFAAiF;IACjF,KAAK,EAAE,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;CACvC;AAED,qBAAa,iBAAkB,SAAQ,KAAK;CAAG;AAkD/C;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAYrD;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC;;;OAGG;IACH,KAAK,EAAE,CAAC,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,CAAC,CAAC;IACjE,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AASD;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,gBAAgB,GACtB;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAuF5C"}
|
|
1
|
+
{"version":3,"file":"solve.d.ts","sourceRoot":"","sources":["../../src/terrain/solve.ts"],"names":[],"mappings":"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"}
|
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":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,eAAe,GAMhB,MAAM,cAAc,CAAC;AAuBtB,MAAM,OAAO,iBAAkB,SAAQ,KAAK;CAAG;AAE/C,wFAAwF;AACxF,SAAS,aAAa,CAAC,OAAe,EAAE,OAAmB,EAAE,IAAe;IAC1E,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,CAAC,CAAC;YAAE,MAAM,IAAI,iBAAiB,CAAC,gBAAgB,OAAO,CAAC,KAAK,eAAe,CAAC,CAAC;QAClF,OAAO,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAClC,CAAC;IACD,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,OAAO;YACV,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;QACzB,KAAK,OAAO;YACV,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;QACzB,KAAK,OAAO;YACV,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;QACzB,KAAK,OAAO;YACV,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,IAAe;IACjC,OAAO,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;AACzD,CAAC;AAED,mEAAmE;AACnE,SAAS,SAAS,CAAC,IAAmB,EAAE,OAAe,EAAE,KAAwC;IAC/F,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,CAAC,GAAG,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACrD,8EAA8E;IAC9E,IAAI,KAAa,CAAC;IAClB,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,MAAM;YACT,KAAK,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;YAC1B,MAAM;QACR,KAAK,OAAO;YACV,KAAK,GAAG,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;YACxC,MAAM;QACR,KAAK,KAAK;YACR,KAAK,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;YAC1B,MAAM;QACR,KAAK,QAAQ;YACX,KAAK,GAAG,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;YACzC,MAAM;IACV,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACzB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAAiB;IAC7C,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/E,MAAM,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAC1D,MAAM,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAC1D,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;QACtB,MAAM,IAAI,iBAAiB,CACzB,wFAAwF,CACzF,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAC7C,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAC7C,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;AAClB,CAAC;AA0BD,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;AAC3B,sEAAsE;AACtE,SAAS,UAAU,CAAC,CAAS,EAAE,CAAS;IACtC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC;IACjD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,yBAAyB,CACvC,KAAuB;IAEvB,6EAA6E;IAC7E,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAClE,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAClC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAC5B,IAAI,CAAC,CAAC;YAAE,MAAM,IAAI,iBAAiB,CAAC,gBAAgB,CAAC,CAAC,MAAM,eAAe,CAAC,CAAC;QAC7E,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,MAAc,CAAC;QACnB,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;YACf,KAAK,MAAM;gBACT,MAAM,GAAG,CAAC,CAAC,QAAQ,CAAC;gBACpB,MAAM;YACR,KAAK,OAAO;gBACV,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,QAAQ,CAAC;gBACxC,MAAM;YACR,KAAK,KAAK;gBACR,MAAM,GAAG,CAAC,CAAC,QAAQ,CAAC;gBACpB,MAAM;YACR,KAAK,QAAQ;gBACX,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,QAAQ,CAAC;gBACzC,MAAM;QACV,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IAC7B,CAAC,CAAC,CAAC;IACH,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;IAC/C,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;IAE/C,IAAI,KAAmB,CAAC;IACxB,IAAI,SAAoB,CAAC;IACzB,IAAI,EAAE,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QACrC,KAAK,GAAG,EAAE,CAAC;QACX,SAAS,GAAG,GAAG,CAAC;IAClB,CAAC;SAAM,IAAI,EAAE,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QAC5C,KAAK,GAAG,EAAE,CAAC;QACX,SAAS,GAAG,GAAG,CAAC;IAClB,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,iBAAiB,CACzB,+GAA+G,CAChH,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACjB,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACjB,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC;IAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC1C,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/E,IAAI,CAAC,GAAG,MAAM,EAAE,CAAC;gBACf,MAAM,GAAG,CAAC,CAAC;gBACX,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACb,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACf,CAAC;QACH,CAAC;IACH,CAAC;IAED,8DAA8D;IAC9D,0DAA0D;IAC1D,0DAA0D;IAC1D,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9B,MAAM,CAAC,GAAG,SAAS,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACtC,MAAM,CAAC,GAAG,SAAS,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACvC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3B,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC;QACb,MAAM,IAAI,iBAAiB,CAAC,iEAAiE,CAAC,CAAC;IACjG,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC;IACpB,IAAI,KAAK,GAAG,CAAC,GAAG,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC;QAC1C,MAAM,IAAI,iBAAiB,CAAC,qDAAqD,CAAC,CAAC;IACrF,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACzD,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,YAAY,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC;IACzD,MAAM,KAAK,GAAG,CAAC,GAAG,GAAG,IAAI,EAAE,GAAG,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CACxD,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CACxD,CAAC;IAEF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC5B,MAAM,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IACpB,MAAM,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IACpB,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,MAAM,QAAQ,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;IAC/D,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC;AAC5B,CAAC","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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alpaca-software/40kdc-data",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.16",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The 40kdc Warhammer 40K dataset behind a linked, typed API — find units, follow them to their weapons, abilities, phases, and factions. Also validates data against the canonical JSON Schemas.",
|
|
6
6
|
"keywords": [
|