1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
//! Vertex and edge data types: how an edge identifies its tile of
//! origin, how junction vertices on a patch boundary are classified,
//! and which side of a junction a glue transition consumed.
//!
//! The data types are small (no behaviour beyond construction and
//! trivial accessors); the module also hosts the small set of
//! boundary-level helpers that operate purely on
//! `(angles, edges, tileset)` triples without needing a full
//! `GrowingPatch` -- `is_junction_at`, `compute_junctions`,
//! `compute_segments`. They live here so the partitioning of a
//! boundary into junctions / per-tile segments is in the same module
//! as the vertex / segment types it produces, and so
//! [`crate::geom::patch`] can stay focused on the live-patch state
//! machine.
use crateIsRing;
use crate;
use cratelex_min_rot;
use crateTileSet;
// ============================================================
// EdgeInfo: identifying one edge of one tile.
// ============================================================
/// Identifies a single tile edge by `(tile_id, tile_offset)`.
///
/// `tile_id` indexes into the patch's [`crate::geom::tileset::TileSet`];
/// `tile_offset` is the edge index within that tile's boundary
/// (`0..tile.len()`).
///
/// On a [`crate::geom::patch::GrowingPatch`] boundary, an `EdgeInfo`
/// at position `i` says "the boundary edge at position `i` is edge
/// `tile_offset` of an instance of tile `tile_id`". Different
/// boundary positions can share the same `EdgeInfo` if the same tile
/// shape appears multiple times in the patch (each tile *instance*
/// gets a separate `patch_tile_id`, tracked elsewhere in
/// `GrowingPatch`).
// ============================================================
// OpenVertexType / ClosedVertexType: junction-vertex configurations.
// ============================================================
/// An **open** junction vertex: the arrangement of tiles meeting at a
/// boundary vertex that is *not* fully surrounded.
///
/// At a junction (where the boundary angle differs from the tile's
/// internal angle), multiple tile instances converge. `cw` is the
/// edge arriving from the CW direction, `ccw` is the edge departing
/// in the CCW direction, and `inner` lists any enclosed tile edges
/// between them.
///
/// "Open" means the vertex is on the patch boundary -- there is still
/// room for additional tiles to be glued in (i.e. the cumulative
/// angle around the vertex is strictly less than a full turn).
/// Equivalently, the boundary angle at the vertex is **not**
/// `±half-turn` -- a half-turn boundary angle would mean the
/// boundary doubles back on itself (a degenerate pinched vertex),
/// which is excluded by construction.
///
/// In contrast, a [`ClosedVertexType`] describes a fully-surrounded
/// interior vertex, where the petals cover the entire turn and the
/// vertex no longer lies on any boundary.
/// A **closed** junction vertex: a fully-surrounded interior vertex
/// whose petals cover the entire turn (no boundary gap).
///
/// Stored as the cyclic CCW sequence of [`EdgeInfo`]s around the
/// vertex, canonicalised to its lex-min cyclic rotation so two
/// configurations that differ only by which petal is listed first
/// compare equal. Two consecutive entries (cyclically) flank a single
/// incident tile-instance at the vertex.
///
/// # Construction
///
/// Built from the petal sequence of an [`OpenVertexType`] at the
/// moment a closing glue ([`TransitionSide::Both`] -- both incident
/// boundary edges consumed by the same match) seals the focus
/// vertex: the closed petal ring is `[cw, inner[0], ..., inner[m-1],
/// ccw]`, with the new tile contributing implicitly between `ccw`
/// and `cw` cyclically. See [`Self::from_open_via_closure`].
///
/// # Identity caveat
///
/// Two distinct *new tiles* that close the same `OpenVertexType` and
/// produce the same cyclic edge ring compare equal under this type --
/// the new tile's identity is implicit in the `(ccw, cw)` cyclic
/// adjacency. If the call site cares about which closing tile was
/// used, it must track that separately (the transition that produced
/// the closure already records `tile_id` and `tile_offset`).
// ============================================================
// CoarseJunction: equivalence-by-boundary-shape at a junction.
// ============================================================
/// A coarser variant of [`OpenVertexType`] that ignores the interior
/// tile arrangement at the junction and instead records the
/// **boundary angle** at the vertex.
///
/// At a junction vertex, the boundary angle (= what the patch stores
/// in its `angles` vec) is `full_turn - sum(incident interior tile
/// angles)`. It captures the boundary's local "shape" at the
/// junction -- what matters for future tile attachments -- without
/// depending on which specific tiles are buried in the interior.
///
/// Two junctions with the same `(cw_edge, ccw_edge, angle)` admit
/// the same outgoing tile attachments, even if their interior
/// populations differ. This makes `CoarseJunction` the right
/// equivalence for seg enumeration: it conflates configurations
/// whose only difference is invisible from outside the boundary.
// ============================================================
// TransitionSide: which incident edges a glue consumed at a focus vertex.
// ============================================================
/// Which incident edge(s) of a focus junction vertex a transition
/// consumed.
///
/// Used by both VT and NT enumeration. At an open vertex with two
/// incident boundary edges (CW and CCW), a glue can:
///
/// - consume only the CW edge -> [`TransitionSide::Cw`],
/// - consume only the CCW edge -> [`TransitionSide::Ccw`],
/// - consume **both** incident edges, sealing the vertex ->
/// [`TransitionSide::Both`].
///
/// When the side is `Both`, the destination of the transition is
/// always the closed-vertex sentinel
/// (`analysis::vertextypes::CLOSED_ID`); the transition's
/// `tile_offset` is normalized to the **CW edge's** matching
/// tile-offset by convention.
///
/// For NT transitions (in `neighborhood`), only `Cw` and `Ccw` arise
/// -- NT enumeration steps the central tile along one direction at
/// a time and never produces a `Both` step.
// ============================================================
// Boundary-level helpers: junction detection + segment partition.
// ============================================================
//
// These work on raw `(angles, edges, tileset)` triples and don't
// need a live `GrowingPatch` -- they're shared by the patch state
// machine and by the witness-construction / raw-boundary code paths.
// Keeping them next to `EdgeInfo`, `OpenVertexType`, and `PatchSegment`
// puts the partitioning of a boundary into junctions / per-tile
// segments in the same module as the types it produces.
/// `true` if position `pos` on the boundary is a junction vertex --
/// i.e. the boundary angle there differs from the natural internal
/// angle of the tile occupying the boundary edge at that position.
/// Equivalently: at a non-junction position, the boundary curves
/// exactly the way the tile's own outgoing edge prescribes.
pub
/// Indices of every junction vertex on the boundary, in increasing
/// order. See [`is_junction_at`] for the per-position predicate.
pub
/// Partition the boundary into [`PatchSegment`]s -- maximal contiguous
/// runs of edges from the same tile instance (no junction between them).
///
/// The segment-break condition is the canonical junction check
/// ([`is_junction_at`]): `angles[i] != tile.seq()[edges[i].tile_offset]`.
/// Every glue updates the boundary angle at the new junction (the
/// compute_glue_angles path produces an angle that for non-degenerate
/// tiles is provably different from either tile's natural internal
/// angle there), so this check reliably detects every tile-instance
/// boundary.
///
/// A simpler offset-arithmetic heuristic (same `tile_id` + contiguous
/// `tile_offset` from the segment start) is **not** used here. It
/// would over-segment on intra-tile wrap-arounds (a single tile
/// instance whose surviving boundary edges have offsets like
/// `2,3,4,5,0`) and could under-segment in pathological same-tile-id
/// glues whose offsets happen to line up. The angle-based check is
/// canonical and avoids both.
///
/// Cyclic-vs-linear caveat: when position 0 is not at a junction, a
/// single cyclic tile-instance run is split into two linear segments
/// at the array seam (`[0, first_junction)` and `[last_junction, n)`).
/// See [`PatchSegment`] for details.
pub
/// Raw `(cw, inner, ccw)` triple extraction at a boundary position
/// (no junction check). Test-only; the live-patch path goes through
/// [`crate::geom::patch::GrowingPatch::junction_vertex_type_at`] which
/// returns `None` at non-junction positions.
pub