Skip to main content

delvewright_dsl/siteplan/
place.rs

1//! The plan resolved once — every box's footprint, plane and headroom —
2//! and the resolved plan in world cells that the checks, the derivation and the
3//! battery all read: placed boxes, placed seams, and the stair run.
4
5use super::*;
6
7// ---------------------------------------------------------------------------
8// Resolution — the plan read once, so no check re-derives a number
9// ---------------------------------------------------------------------------
10
11/// One box with everything the checks below need already worked out.
12///
13/// **The model every geometric check depends on** is stated where an author
14/// reads it — on [`PlanBox`], whose schema description carries it — and the
15/// number itself is [`SHARED_FACE_GAP_CELLS`]. In short: a box is the **play
16/// space** of a place, the shell stands in the one-cell gap between two
17/// neighbours, `extent` is therefore the interior footprint the author
18/// declared, and two connected places sit exactly
19/// [`SHARED_FACE_GAP_CELLS`] apart on the face they share (`DW0828`).
20#[derive(Debug, Clone)]
21pub(super) struct Placed<'a> {
22    pub(super) index: usize,
23    pub(super) plan: &'a PlanBox,
24    /// Footprint, inclusive: `[x0, x1, z0, z1]`.
25    pub(super) foot: [i64; 4],
26    /// The walk plane.
27    pub(super) floor: i64,
28    /// Cells of headroom over the walk plane — the lid's clearance, or the
29    /// sky-open place's declared courses of air.
30    pub(super) clearance: u32,
31    /// How its corner was obtained (spec-0059 §3).
32    pub(super) by: Provenance,
33}
34
35impl Placed<'_> {
36    pub(super) fn x0(&self) -> i64 {
37        self.foot[0]
38    }
39    pub(super) fn x1(&self) -> i64 {
40        self.foot[1]
41    }
42    pub(super) fn z0(&self) -> i64 {
43        self.foot[2]
44    }
45    pub(super) fn z1(&self) -> i64 {
46        self.foot[3]
47    }
48
49    /// The inclusive vertical span of the play space.
50    pub(super) fn y_span(&self) -> (i64, i64) {
51        (self.floor, self.floor + i64::from(self.clearance) - 1)
52    }
53
54    /// The centre of the footprint, in blocks.
55    pub(super) fn centre_xz(&self) -> (f64, f64) {
56        (
57            (self.x0() as f64 + self.x1() as f64) / 2.0,
58            (self.z0() as f64 + self.z1() as f64) / 2.0,
59        )
60    }
61}
62
63// ---------------------------------------------------------------------------
64// The resolved plan, in world cells — ONE authority, three readers
65// ---------------------------------------------------------------------------
66
67/// One place, resolved into world cells: the play space the plan gives it.
68///
69/// Public because three readers need the same answer and two of them are in
70/// another crate: the stage-4 checks here, the **blockout derivation** that
71/// builds the mass, and the **stage-5 battery** that judges the built bytes
72/// against the plan. Two of those computing "where is this box" independently is
73/// how a builder and its observer come to agree about a world neither of them
74/// describes.
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct PlacedBox {
77    /// The place this embeds.
78    pub node: NodeId,
79    /// Inclusive footprint `[x0, x1, z0, z1]`.
80    pub foot: [i64; 4],
81    /// The walk plane's world `y`.
82    pub floor: i64,
83    /// Cells of headroom over the walk plane.
84    pub clearance: u32,
85    /// True when the plan declared the place sky-open — a courtyard, a shore,
86    /// a summit. It claims exactly its declared courses of air (which is what
87    /// [`PlacedBox::clearance`] holds); what it makes no claim on is the air
88    /// above them.
89    pub open: bool,
90    /// What it stands on: the site's ground, or hung `n` courses under its
91    /// floor course (spec-0098 §2).
92    pub base: super::Base,
93    /// The roof the plan reserves over this place (spec-0098 §3), when it
94    /// declares one. Never present on an open box (`DW0988`).
95    pub roof: Option<super::Roof>,
96}
97
98impl PlacedBox {
99    /// The play space's inclusive world AABB.
100    #[must_use]
101    pub fn space(&self) -> ([i64; 3], [i64; 3]) {
102        (
103            [self.foot[0], self.floor, self.foot[2]],
104            [
105                self.foot[1],
106                self.floor + i64::from(self.clearance) - 1,
107                self.foot[3],
108            ],
109        )
110    }
111
112    /// The floor centre — where a body seated in this place stands.
113    #[must_use]
114    pub fn centre(&self) -> [i64; 3] {
115        [
116            (self.foot[0] + self.foot[1]) / 2,
117            self.floor,
118            (self.foot[2] + self.foot[3]) / 2,
119        ]
120    }
121}
122
123/// One connection, resolved into world cells: the wall the two places share and
124/// the hole the plan cut in it.
125#[derive(Debug, Clone, PartialEq, Eq)]
126pub struct PlacedSeam {
127    /// The connection this allocates.
128    pub edge: EdgeId,
129    /// Its class, as the graph spells it.
130    pub class: &'static str,
131    /// The `a` end.
132    pub a: NodeId,
133    /// The `b` end.
134    pub b: NodeId,
135    /// Which face **of `a`** the seam sits on.
136    pub face: Face,
137    /// The axis the shared wall is flat in: 0 = x, 1 = y, 2 = z.
138    pub normal_axis: usize,
139    /// The wall's coordinate on that axis — one cell thick, so one number.
140    pub plane: i64,
141    /// The opening's inclusive world AABB (flat in [`Self::normal_axis`]).
142    pub opening: ([i64; 3], [i64; 3]),
143    /// The whole rectangle the two boxes share on that wall, inclusive.
144    pub shared: ([i64; 3], [i64; 3]),
145    /// Which kind of connection this seam allocates (spec-0053 §4). A portal's
146    /// `opening` is a standard's rectangle; a contact's is its span.
147    pub crossing: Crossing,
148    /// `floor(b) − floor(a)`, derived — never authored (see [`Seam`]).
149    pub rise: i64,
150    /// Which place hosts the stair massing, on a `stair`.
151    pub stair_in: Option<NodeId>,
152    /// The crossing's declared form ([`Seam::form`]), handed to both places.
153    pub form: String,
154}
155
156/// The plan's boxes, resolved by the code the stage-4 checks judge with.
157///
158/// A box whose floor names an undeclared datum is **absent** — `DW0112` has
159/// refused it, and a place with no plane has no cells for any reader to work in.
160#[must_use]
161pub fn placed_boxes(c: &Campaign, reads: &mut Reads) -> Vec<PlacedBox> {
162    let (Some(plan), Some(graph)) = (
163        c.site_plan.as_ref().map(|p| &p.content),
164        c.layout_graph.as_ref().map(|g| &g.content),
165    ) else {
166        return Vec::new();
167    };
168    let table = Metrics::table();
169    let mut sink = Vec::new();
170    resolve(plan, graph, &table, reads, &mut sink)
171        .0
172        .into_iter()
173        .map(|p| PlacedBox {
174            node: p.plan.node.clone(),
175            foot: p.foot,
176            floor: p.floor,
177            clearance: p.clearance,
178            open: matches!(p.plan.ceiling, Ceiling::Open(_)),
179            base: p.plan.base,
180            roof: p.plan.roof,
181        })
182        .collect()
183}
184
185/// **The one place a seam's crossing rectangle is computed**, for either kind
186/// of connection (spec-0053 §4).
187///
188/// A portal's rectangle is its opening's `width × height` — the named
189/// standard's or the declared one — anchored at `at`. A contact's is its span: `at` plus the declared `extent`, or `at` to
190/// the far edge of the shared face when no extent is declared.
191///
192/// One function rather than one per kind, and one call rather than a copy in
193/// each reader, because this rectangle is simultaneously the derivation's carve,
194/// `DW0836`'s allocation, `DW0838`'s allocation set and `DW0877`'s span. Two
195/// implementations of it would be a plan-time green and a byte-time green about
196/// two different rectangles, which is the defect `shared_face` already has one
197/// implementation to prevent.
198///
199/// `None` when the seam names an opening the table does not define — `DW0812`
200/// refused it and there is no rectangle to build or measure.
201pub(super) fn crossing_rect(
202    s: &Seam,
203    at: [i64; 2],
204    face: &SharedFace,
205    table: &Metrics,
206    reads: &mut Reads,
207) -> Option<(Crossing, [i64; 2])> {
208    if s.contact.is_some() {
209        return Some((Crossing::Contact, contact_extent(s, at, face)));
210    }
211    let o = s.opening.as_ref()?.resolve(table, reads).ok()?;
212    Some((Crossing::Portal, [i64::from(o.width), i64::from(o.height)]))
213}
214
215/// **How big a contact's span is** — the one authority, read by
216/// [`crossing_rect`] and by the refusal that judges it (`DW0876`).
217///
218/// A declared `extent` is taken as written. With none declared the span runs
219/// from `at` to the far edge of the shared face on both axes, which is how a
220/// contact along the whole of a face is spelled. An `at` already past that edge
221/// would give a negative extent, so it is clamped to one cell: the rectangle
222/// stays well-formed and `DW0876` describes it, rather than the arithmetic
223/// producing a rectangle nothing downstream could reason about.
224pub(super) fn contact_extent(s: &Seam, at: [i64; 2], face: &SharedFace) -> [i64; 2] {
225    match s.contact.as_ref().and_then(|c| c.extent) {
226        Some(e) => [i64::from(e[0].get()), i64::from(e[1].get())],
227        None => [(face.u.1 - at[0] + 1).max(1), (face.v.1 - at[1] + 1).max(1)],
228    }
229}
230
231/// Which axis a face is flat in: 0 = x, 1 = y, 2 = z.
232#[must_use]
233pub fn normal_axis_of(face: Face) -> usize {
234    match face {
235        Face::East | Face::West => 0,
236        Face::Up | Face::Down => 1,
237        Face::South | Face::North => 2,
238    }
239}
240
241/// A seam's crossing rectangle as an inclusive world AABB, flat in the face's
242/// normal axis — [`crossing_rect`]'s two numbers put where the world is.
243///
244/// Extracted rather than written twice because [`stair_run`] needs the same
245/// rectangle at validation tier, before any `PlacedSeam` exists.
246pub(super) fn crossing_aabb(
247    s: &Seam,
248    at: [i64; 2],
249    face: &SharedFace,
250    extent: [i64; 2],
251) -> ([i64; 3], [i64; 3]) {
252    let normal_axis = normal_axis_of(s.face);
253    let (u_axis, v_axis) = in_plane_axes(s.face);
254    let mut lo = [0i64; 3];
255    let mut hi = [0i64; 3];
256    lo[normal_axis] = face.plane;
257    hi[normal_axis] = face.plane;
258    lo[u_axis] = at[0];
259    hi[u_axis] = at[0] + extent[0] - 1;
260    lo[v_axis] = at[1];
261    hi[v_axis] = at[1] + extent[1] - 1;
262    (lo, hi)
263}
264
265/// **What a stair costs the box that hosts it** — the one place the geometry of
266/// a run is worked out, for both the check that refuses a plan (`DW0830`) and
267/// the derivation that lays the treads.
268///
269/// # Why this is one function and not two
270///
271/// It was two, and the two disagreed. `DW0830` measured the run against the
272/// seam's **rise** and against the host's whole **extent**; the derivation
273/// measures it against the **climb** the opening really asks for and against
274/// the run the host really has beside the hole. Both readings are defensible
275/// in isolation and neither is the other, so a plan could pass the check by one
276/// arithmetic and be refused by the other at build time — the treads then went
277/// unlaid, and the place they were the only way into came back as `DW0837` with
278/// nothing pointing at the stair. Found by building: a two-place plan whose
279/// lower room is entered through a hole in the upper room's floor reached green
280/// at stage 4 with `needs 8, affords 8` and built no stair at all, because the
281/// climb to the pierced plane is 7 and the run beside a centred hole is 6.
282///
283/// So the rule lives here and both readers call it. A plan that reaches green
284/// is a plan the derivation can build, by construction rather than by two
285/// arithmetics agreeing.
286///
287/// # What the numbers mean
288///
289/// * `climb` — how high the courses must carry a body. Across a **vertical**
290///   face that is the seam's own sill, because the body stands at the sill and
291///   steps through; through a **floor or ceiling** it is the pierced plane,
292///   because the body stands in the hole and steps out beside it. Both come out
293///   as the floor difference on an ordinary plan and differ exactly where the
294///   plan puts the sill somewhere other than the far floor.
295/// * `run_axis` — the horizontal axis the run is spent on. Across a vertical
296///   face it is that face's normal; through a floor or ceiling the run may go
297///   either way, so it is the host's longer horizontal axis.
298/// * `start`/`step` — the stair arrives AT its seam, so course 0 is the one the
299///   body steps off and the run walks back into the room.
300/// * `available` — how many cells of that walk the host really has. Across a
301///   vertical face the run walks the whole footprint. Through a floor or a
302///   ceiling it leaves along **one** side of the hole, so it is the room on the
303///   roomier side plus the hole's own width — never the host's whole extent,
304///   which is a run that only exists if the stair could run both ways at once.
305///
306/// `None` where there is no run to lay or judge: a host at or above what the
307/// stair has to reach (the plan named the higher place, which `DW0830` refuses
308/// by name), or a hole that is not over this host at all (`DW0828`'s finding).
309#[must_use]
310pub fn stair_run(
311    host_floor: i64,
312    host_foot: [i64; 4],
313    normal_axis: usize,
314    plane: i64,
315    opening: ([i64; 3], [i64; 3]),
316) -> Option<StairRun> {
317    let (olo, ohi) = opening;
318    let lo = [host_foot[0], host_floor, host_foot[2]];
319    let hi = [host_foot[1], host_floor, host_foot[3]];
320    let target = if normal_axis == 1 { plane } else { olo[1] };
321    let climb = target - host_floor;
322    if climb <= 0 {
323        return None;
324    }
325    let run_axis = if normal_axis == 1 {
326        let ex = host_foot[1] - host_foot[0] + 1;
327        let ez = host_foot[3] - host_foot[2] + 1;
328        if ex >= ez { 0usize } else { 2 }
329    } else {
330        normal_axis
331    };
332    let (start, step, available) = if normal_axis == 1 {
333        let (olo_r, ohi_r) = (
334            olo[run_axis].max(lo[run_axis]),
335            ohi[run_axis].min(hi[run_axis]),
336        );
337        if olo_r > ohi_r {
338            return None;
339        }
340        let width = ohi_r - olo_r + 1;
341        let room_lo = olo_r - lo[run_axis];
342        let room_hi = hi[run_axis] - ohi_r;
343        if room_lo >= room_hi {
344            (ohi_r, -1, room_lo + width)
345        } else {
346            (olo_r, 1, room_hi + width)
347        }
348    } else if plane > hi[run_axis] {
349        (hi[run_axis], -1, hi[run_axis] - lo[run_axis] + 1)
350    } else {
351        (lo[run_axis], 1, hi[run_axis] - lo[run_axis] + 1)
352    };
353    Some(StairRun {
354        climb,
355        run_axis,
356        start,
357        step,
358        available,
359    })
360}
361
362/// What a stair's treads cost their host — see [`stair_run`].
363#[derive(Debug, Clone, Copy, PartialEq, Eq)]
364pub struct StairRun {
365    /// How high the courses must carry a body, off the host's own walk plane.
366    pub climb: i64,
367    /// The horizontal axis the run walks: 0 = x, 2 = z.
368    pub run_axis: usize,
369    /// Where course 0 — the one the body steps off — stands on `run_axis`.
370    pub start: i64,
371    /// Which way the run walks back into the host: `+1` or `-1`.
372    pub step: i64,
373    /// How many cells of that walk the host really affords.
374    pub available: i64,
375}
376
377/// The run one standard pitch costs a climb, rounded up: a course is a whole
378/// cell, and a run two-thirds of a cell short is a run the host does not have.
379#[must_use]
380pub fn run_of(pitch: &Pitch, climb: i64) -> i64 {
381    let (span, per) = (climb.abs() * i64::from(pitch.run), i64::from(pitch.rise));
382    if per == 0 {
383        span
384    } else {
385        span / per + i64::from(span % per != 0)
386    }
387}
388
389/// **The gentlest standard pitch a climb fits in `available` cells of run**, or
390/// `None` when the table defines none that does.
391///
392/// The walk is over [`Metrics::names_of`] in table order — gentlest first — and
393/// takes the first that fits. One authority, so the verdict `DW0830` reaches and
394/// the geometry the derivation lays cannot be about different standards.
395#[must_use]
396pub fn gentlest_pitch(
397    table: &Metrics,
398    reads: &mut Reads,
399    climb: i64,
400    available: i64,
401) -> Option<Pitch> {
402    for name in table.names_of(MetricKind::Pitch) {
403        let Ok(entry) = table.resolve(MetricKind::Pitch, name) else {
404            continue;
405        };
406        let MetricValue::Pitch(p) = entry.value(reads) else {
407            continue;
408        };
409        if p.rise == 0 {
410            continue;
411        }
412        if run_of(p, climb) <= available {
413            return Some(*p);
414        }
415    }
416    None
417}
418
419/// The standard pitch that costs a climb the LEAST run, and what that run is —
420/// the number `DW0830`'s refusal quotes, because it is the shortest run any
421/// standard could do the climb in.
422#[must_use]
423pub fn tightest_pitch(
424    table: &Metrics,
425    reads: &mut Reads,
426    climb: i64,
427) -> Option<(&'static str, i64)> {
428    let mut best: Option<(&'static str, i64)> = None;
429    for name in table.names_of(MetricKind::Pitch) {
430        let Ok(entry) = table.resolve(MetricKind::Pitch, name) else {
431            continue;
432        };
433        let MetricValue::Pitch(p) = entry.value(reads) else {
434            continue;
435        };
436        if p.rise == 0 {
437            continue;
438        }
439        let needed = run_of(p, climb);
440        if best.is_none_or(|(_, b)| needed < b) {
441            best = Some((name, needed));
442        }
443    }
444    best
445}
446
447/// The plan's seams, resolved by the code the stage-4 checks judge with.
448///
449/// A seam whose face the two boxes do not share, or whose opening the table does
450/// not define, is **absent**: `DW0828`/`DW0812` refused it, and there is no hole
451/// for a reader to build or measure.
452#[must_use]
453pub fn placed_seams(c: &Campaign, boxes: &[PlacedBox], reads: &mut Reads) -> Vec<PlacedSeam> {
454    let (Some(plan), Some(graph)) = (
455        c.site_plan.as_ref().map(|p| &p.content),
456        c.layout_graph.as_ref().map(|g| &g.content),
457    ) else {
458        return Vec::new();
459    };
460    let table = Metrics::table();
461    let by_node: BTreeMap<&str, &PlacedBox> =
462        boxes.iter().map(|b| (b.node.0.as_str(), b)).collect();
463    let edges: BTreeMap<&str, &Edge> = graph.edges.iter().map(|e| (e.id().0.as_str(), e)).collect();
464    // The seam anchors come from the same packing that placed `boxes`: one
465    // arithmetic, so the derivation and its observer cannot disagree about
466    // where a hole is.
467    let mut sink = Vec::new();
468    let (_, packed) = resolve(plan, graph, &table, reads, &mut sink);
469    let mut out = Vec::new();
470    for (i, s) in plan.seams.iter().enumerate() {
471        let Some(at) = packed.seam_at[i] else {
472            continue; // the packing refused or could not place this seam
473        };
474        let Some(edge) = edges.get(s.edge.0.as_str()) else {
475            continue;
476        };
477        if !edge.has_seam() {
478            continue;
479        }
480        let (Some(a), Some(b)) = (
481            by_node.get(edge.a().0.as_str()).copied(),
482            by_node.get(edge.b().0.as_str()).copied(),
483        ) else {
484            continue;
485        };
486        let Ok(face) = shared_face_of(a, b, s.face) else {
487            continue;
488        };
489        let Some((crossing, extent)) = crossing_rect(s, at, &face, &table, reads) else {
490            continue;
491        };
492        let normal_axis = normal_axis_of(s.face);
493        // The face's two in-plane axes, in the order `at` names them.
494        let (u_axis, v_axis) = in_plane_axes(s.face);
495        let (lo, hi) = crossing_aabb(s, at, &face, extent);
496        let mut smin = [0i64; 3];
497        let mut smax = [0i64; 3];
498        smin[normal_axis] = face.plane;
499        smax[normal_axis] = face.plane;
500        smin[u_axis] = face.u.0;
501        smax[u_axis] = face.u.1;
502        smin[v_axis] = face.v.0;
503        smax[v_axis] = face.v.1;
504        out.push(PlacedSeam {
505            edge: s.edge.clone(),
506            class: edge.class(),
507            a: edge.a().clone(),
508            b: edge.b().clone(),
509            face: s.face,
510            normal_axis,
511            plane: face.plane,
512            opening: (lo, hi),
513            shared: (smin, smax),
514            crossing,
515            rise: b.floor - a.floor,
516            stair_in: s.stair_in.clone(),
517            form: s.form.clone(),
518        });
519    }
520    out
521}
522
523/// **A contact**: the span of a shared face along which two places simply meet
524/// (spec-0053 §4).
525///
526/// # What a contact MEANS
527///
528/// The boundary is continuous ground. The derivation writes **no wall along the
529/// span** — and wall as ever outside it — and crossing is legitimate anywhere
530/// along it the step rule admits. It is not a wide door: `DW0829`'s standard-name
531/// resolution and sill rule are portal checks and do not apply, because a
532/// contact has no opening name to resolve and no single sill. Calling a 55-cell
533/// front a door would make every downstream door check wrong.
534///
535/// # What the author allocates and what the engine measures
536///
537/// The author allocates **where** the places meet. The engine measures the
538/// **crossing profile** from assembled bytes — which columns of the span a body
539/// actually crosses under the step rule — and `DW0877` refuses a contact nothing
540/// can cross. *"This face is fine"* is never a declaration this engine accepts.
541///
542/// Seams stay **allocated, never discovered**: the span is the edge's allocation
543/// set for `DW0838`, so a crossing outside it is still a refusal.
544#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
545#[serde(deny_unknown_fields)]
546pub struct Contact {
547    /// The span, `[u, v]` in cells on the face's own two in-plane axes, anchored
548    /// at the seam's `at`.
549    ///
550    /// **Omitted, the span runs from `at` to the far edge of the shared face on
551    /// both axes** — which is how a contact along the whole of a face is
552    /// written, by putting `at` at the face's low corner. `DW0828`'s refusal
553    /// prints that corner, so the number an author needs is in the message they
554    /// would already be reading.
555    ///
556    /// There is no `width` standard and no `length` here, and both absences are
557    /// the design (spec-0053 §7).
558    #[serde(default, skip_serializing_if = "Option::is_none")]
559    pub extent: Option<[NonZeroU32; 2]>,
560}
561
562/// **Which kind of connection a seam allocates** (spec-0053 §4).
563///
564/// Carried on the resolved seam rather than re-derived from the authored one at
565/// each reader, so that the derivation and the byte observer cannot disagree
566/// about which kind a seam is — the same reason `shared_face` has one
567/// implementation.
568#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
569#[serde(rename_all = "kebab-case")]
570pub enum Crossing {
571    /// A standard opening. Every allocated cell must be passable (`DW0836`).
572    Portal,
573    /// A front where two places meet. No wall along the span, and **at least
574    /// one** passable column of body width somewhere in it (`DW0877`) — not
575    /// every cell, because a contact is ground rather than a hole and the
576    /// massing standing on it is content.
577    Contact,
578}
579
580/// The two world axes a face's `at` names, in that order.
581fn in_plane_axes(face: Face) -> (usize, usize) {
582    match face {
583        // `[along, y]` — `z` for east/west, `x` for north/south.
584        Face::East | Face::West => (2, 1),
585        Face::South | Face::North => (0, 1),
586        // `[x, z]`.
587        Face::Up | Face::Down => (0, 2),
588    }
589}
590
591/// A footprint's inclusive span on one WORLD axis (0 = x, 2 = z). Axis 1 has no
592/// answer here — a footprint is horizontal — and no caller asks for it.
593pub(super) fn span(foot: [i64; 4], axis: usize) -> (i64, i64) {
594    if axis == 0 {
595        (foot[0], foot[1])
596    } else {
597        (foot[2], foot[3])
598    }
599}
600
601/// Inclusive overlap of two ranges, or `None`.
602pub(super) fn overlap(a: (i64, i64), b: (i64, i64)) -> Option<(i64, i64)> {
603    let lo = a.0.max(b.0);
604    let hi = a.1.min(b.1);
605    (lo <= hi).then_some((lo, hi))
606}
607
608/// Is `[lo, hi]` inside `[within_lo, within_hi]`?
609pub(super) fn within(r: (i64, i64), w: (i64, i64)) -> bool {
610    r.0 >= w.0 && r.1 <= w.1
611}
612
613/// The region's inclusive span on one axis.
614pub(super) fn region_span(region: &WorldBox, axis: usize) -> (i64, i64) {
615    (region.min[axis], region.max()[axis])
616}
617
618#[cfg(test)]
619mod tests {
620    use super::*;
621
622    /// **The run beside a pierced floor is not the host's extent**, and this is
623    /// the arithmetic the whole one-authority repair turns on.
624    ///
625    /// An eight-by-eight host with a three-wide hole cut through the ceiling
626    /// above it: the treads leave along ONE side of the hole, so what they have
627    /// is the room on the roomier side plus the hole's own width. Centre the
628    /// hole and that is 3 + 3 = 6, not 8 — and the courses have to carry the
629    /// body to the pierced plane, which is one below the floor of the place
630    /// above it.
631    ///
632    /// The numbers are read off the geometry by hand rather than recomputed
633    /// here, so a change to the rule shows up as a failure rather than as two
634    /// arithmetics agreeing.
635    #[test]
636    fn a_run_beside_a_pierced_floor_is_the_room_on_one_side_plus_the_hole() {
637        // Host x 4..11, z 4..11, walk plane y 56; the hole is cut at y 63 and
638        // spans x 7..9 — three of the eight cells, three to the low side and
639        // two to the high.
640        let run = stair_run(56, [4, 11, 4, 11], 1, 63, ([7, 63, 6], [9, 63, 8]))
641            .expect("the hole is over this host and the plane is above its floor");
642        assert_eq!(run.climb, 7, "56 up to the pierced plane at 63");
643        assert_eq!(run.run_axis, 0, "the host is square, so the run takes x");
644        assert_eq!(
645            run.available, 6,
646            "three cells of room on the low side, plus the hole's three"
647        );
648        assert_eq!(
649            run.start, 9,
650            "course 0 stands under the far edge of the hole"
651        );
652        assert_eq!(run.step, -1, "and the run walks back into the room");
653    }
654
655    /// The same host, the same hole, and the check and the derivation asking the
656    /// same question: seven courses do not fit six cells, so no standard pitch
657    /// does either. Measured against the host's extent it would — which is the
658    /// green a plan used to reach before building nothing.
659    #[test]
660    fn no_standard_pitch_fits_a_climb_of_seven_in_six_cells_of_run() {
661        let table = Metrics::table();
662        let mut reads = Reads::default();
663        assert!(gentlest_pitch(&table, &mut reads, 7, 6).is_none());
664        assert!(gentlest_pitch(&table, &mut reads, 7, 7).is_some());
665        assert_eq!(tightest_pitch(&table, &mut reads, 7), Some(("stair", 7)));
666    }
667
668    /// Across a vertical face the run walks the whole footprint and the climb is
669    /// the seam's own sill — the body stands at the sill and steps through.
670    #[test]
671    fn a_run_across_a_vertical_face_is_the_whole_footprint() {
672        // Host x 4..11, z 4..11, walk plane y 64; the wall is at x 12 and the
673        // opening's low corner is at y 72.
674        let run = stair_run(64, [4, 11, 4, 11], 0, 12, ([12, 72, 6], [12, 74, 8]))
675            .expect("the wall is beyond the host, so the run walks back from it");
676        assert_eq!(run.climb, 8, "64 up to the sill at 72");
677        assert_eq!(run.run_axis, 0);
678        assert_eq!(run.available, 8, "the host's whole extent on x");
679        assert_eq!(run.start, 11, "course 0 stands against the wall");
680        assert_eq!(run.step, -1);
681    }
682}