delvewright-dsl 0.38.0

Staged JSON DSL types and schemas for Delvewright adventure-map campaigns — the format the delvec compiler reads.
Documentation
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
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
//! Seams, allocated on a face both boxes already have: the shared face, the
//! opening, the sill, the stair and the drop (`DW0828`–`DW0831`, `DW0876`).

use super::*;

// ---------------------------------------------------------------------------
// Seams: allocated on a face both boxes already have
// ---------------------------------------------------------------------------

/// The rectangle two boxes have in common on one face, in the face's own two
/// in-plane world axes, plus the plane the wall between them stands in.
#[derive(Debug, Clone, Copy)]
pub(super) struct SharedFace {
    pub(super) plane: i64,
    pub(super) u: (i64, i64),
    pub(super) v: (i64, i64),
    u_axis: &'static str,
    v_axis: &'static str,
}

/// Why two boxes do not share the declared face.
#[derive(Debug, Clone)]
pub(super) enum NotShared {
    /// They are not neighbours across it: the gap is `gap` cells where the one
    /// wall they would have in common needs exactly 1.
    NotAdjacent { gap: i64 },
    /// They are neighbours, but the face they would share is empty because their
    /// spans miss each other on one of the two in-plane axes.
    NoCommonArea { axis: &'static str },
}

/// Do these two boxes share `face` **of `a`**, and where?
///
/// A shared face is a **one-cell gap** — the wall the two places have in common,
/// which the derivation writes once. See [`Placed`] for why the box is the play
/// space rather than the play space plus its shell.
/// The geometry a shared-face question needs of one box: its footprint and its
/// vertical span.
///
/// A tiny value rather than `&Placed` so that the **one** implementation of "do
/// these two boxes share this face" serves both readers of the resolved plan:
/// the stage-4 checks, which hold a partially-resolved box, and the derivation
/// and battery in the compiler, which hold a [`PlacedBox`]. A second copy of
/// this arithmetic is how a plan-time green and a byte-time green come to be
/// about different walls.
#[derive(Clone, Copy)]
struct FaceSide {
    foot: [i64; 4],
    y: (i64, i64),
}

impl Placed<'_> {
    fn side(&self) -> FaceSide {
        FaceSide {
            foot: self.foot,
            y: self.y_span(),
        }
    }
}

impl PlacedBox {
    fn side(&self) -> FaceSide {
        let (lo, hi) = self.space();
        FaceSide {
            foot: self.foot,
            y: (lo[1], hi[1]),
        }
    }
}

/// [`shared_face`] over two fully resolved boxes.
pub(super) fn shared_face_of(
    a: &PlacedBox,
    b: &PlacedBox,
    face: Face,
) -> Result<SharedFace, NotShared> {
    shared_face(a.side(), b.side(), face)
}

fn shared_face(a: FaceSide, b: FaceSide, face: Face) -> Result<SharedFace, NotShared> {
    let horizontal_pair = |plane: i64, u: (i64, i64), v: (i64, i64)| -> SharedFace {
        SharedFace {
            plane,
            u,
            v,
            u_axis: "x",
            v_axis: "z",
        }
    };
    match face {
        Face::East | Face::West | Face::South | Face::North => {
            // The axis the face's normal runs along, and the horizontal axis
            // that stays in the plane.
            let (normal, along) = match face {
                Face::East | Face::West => (0usize, 2usize),
                _ => (2usize, 0usize),
            };
            let a_span = span(a.foot, normal);
            let b_span = span(b.foot, normal);
            let positive = matches!(face, Face::East | Face::South);
            let (plane, gap) = if positive {
                (a_span.1 + 1, b_span.0 - a_span.1 - 1)
            } else {
                (a_span.0 - 1, a_span.0 - b_span.1 - 1)
            };
            if gap != SHARED_FACE_GAP_CELLS {
                return Err(NotShared::NotAdjacent { gap });
            }
            let a_along = span(a.foot, along);
            let b_along = span(b.foot, along);
            let u = overlap(a_along, b_along).ok_or(NotShared::NoCommonArea {
                axis: if along == 0 { "x" } else { "z" },
            })?;
            let v = overlap(a.y, b.y).ok_or(NotShared::NoCommonArea { axis: "y" })?;
            Ok(SharedFace {
                plane,
                u,
                v,
                u_axis: if along == 0 { "x" } else { "z" },
                v_axis: "y",
            })
        }
        Face::Up | Face::Down => {
            let (ya, yb) = (a.y, b.y);
            let (plane, gap) = if face == Face::Up {
                (ya.1 + 1, yb.0 - ya.1 - 1)
            } else {
                (ya.0 - 1, ya.0 - yb.1 - 1)
            };
            if gap != SHARED_FACE_GAP_CELLS {
                return Err(NotShared::NotAdjacent { gap });
            }
            let u = overlap(span(a.foot, 0), span(b.foot, 0))
                .ok_or(NotShared::NoCommonArea { axis: "x" })?;
            let v = overlap(span(a.foot, 2), span(b.foot, 2))
                .ok_or(NotShared::NoCommonArea { axis: "z" })?;
            Ok(horizontal_pair(plane, u, v))
        }
    }
}

/// One seam and everything already resolved about it: the two places it joins,
/// the connection it allocates, and the face they share. Carried as one value so
/// each rule below takes the seam and its context rather than eight positional
/// arguments — the shape a `clippy::too_many_arguments` allow would otherwise
/// have papered over.
struct SeamCtx<'a> {
    index: usize,
    seam: &'a Seam,
    edge: &'a Edge,
    a: &'a Placed<'a>,
    b: &'a Placed<'a>,
    face: SharedFace,
    /// The crossing's low corner on the face's own two world axes, from the
    /// packing — `[along, sill]` on a wall, `[x, z]` through a floor.
    at: [i64; 2],
}

/// `DW0828`–`DW0831`: every seam sits on a face its two boxes share, at cells
/// that face has, through a standard opening a body can use, and — where the two
/// places are on different planes — by a climb or a fall the standards allow.
pub(super) fn seams(
    plan: &SitePlanContent,
    graph: &LayoutGraphContent,
    placed: &[Placed<'_>],
    packed: &Packed,
    table: &Metrics,
    reads: &mut Reads,
    d: &mut Vec<Diagnostic>,
) {
    let by_node: BTreeMap<&str, &Placed<'_>> =
        placed.iter().map(|p| (p.plan.node.0.as_str(), p)).collect();
    let edges: BTreeMap<&str, &Edge> = graph.edges.iter().map(|e| (e.id().0.as_str(), e)).collect();

    for (i, s) in plan.seams.iter().enumerate() {
        if packed.refused.contains(&i) {
            continue; // the packing refused this seam by name already.
        }
        let Some(edge) = edges.get(s.edge.0.as_str()) else {
            continue; // `DW0824` refused the reference.
        };
        if !edge.has_seam() {
            continue; // `DW0824` said this carries a sightline.
        }
        let (Some(a), Some(b)) = (
            by_node.get(edge.a().0.as_str()).copied(),
            by_node.get(edge.b().0.as_str()).copied(),
        ) else {
            continue; // `DW0824` reported the missing box, or nothing placed it.
        };
        let Some(at) = packed.seam_at[i] else {
            continue; // an end has no plane; `DW0112` said so.
        };

        let face = match shared_face(a.side(), b.side(), s.face) {
            Ok(f) => f,
            Err(why) => {
                d.push(not_shared(i, s, edge, a, b, &why));
                continue;
            }
        };

        let ctx = SeamCtx {
            index: i,
            seam: s,
            edge,
            a,
            b,
            face,
            at,
        };

        // `DW0876`, first: a seam that does not state exactly one kind of
        // connection has no crossing for any rule below to judge, and telling
        // an author both that and what the crossing they did not state would
        // have meant prescribes two repairs for one mistake.
        if !contact_declaration(&ctx, table, d) {
            continue;
        }

        let opening = if s.contact.is_some() {
            // A contact has no opening name to resolve and no single sill, so
            // `DW0829` does not run over it. That is stated rather than
            // shoehorned: calling a 55-cell front a door would make every
            // downstream door check wrong (spec-0053 §4).
            None
        } else {
            let Some(spec) = s.opening.as_ref() else {
                continue; // `DW0876` refused a seam with neither kind.
            };
            match spec.resolve(table, reads) {
                Ok(o) => Some(o),
                Err(unknown) => {
                    d.push(unknown.diagnostic("site-plan", &format!("/content/seams/{i}/opening")));
                    continue;
                }
            }
        };

        if let Some(opening) = opening {
            opening_fits(&ctx, opening, d);
        }
        match edge {
            Edge::Stair { .. } => stair(&ctx, table, reads, d),
            Edge::Climb { .. } => climb(&ctx, d),
            Edge::Drop { falls, .. } => drop_seam(&ctx, *falls, plan.max_drop, d),
            Edge::Walk { .. } | Edge::Barred { .. } => {
                if let Some(opening) = opening {
                    sill(&ctx, opening, d);
                }
            }
            Edge::Carry { .. } | Edge::Vision { .. } => {}
        }
        if matches!(edge, Edge::Stair { .. }) && s.stair_in.is_none() {
            d.push(Diagnostic::error(
                DW_STAIR_PITCH,
                "site-plan",
                format!("/content/seams/{i}"),
                format!(
                    "the seam for stair `{id}` does not say which place hosts its treads. A \
                     stair is massing, and massing stands somewhere: name `{a}` or `{b}` in \
                     `stair_in`, so that the run it costs comes out of a footprint the plan has \
                     already allocated rather than out of whatever space happens to be left.",
                    id = s.edge,
                    a = edge.a(),
                    b = edge.b(),
                ),
            ));
        }
    }
}

/// `DW0828`, with the arithmetic that produced it.
fn not_shared(
    i: usize,
    s: &Seam,
    edge: &Edge,
    a: &Placed<'_>,
    b: &Placed<'_>,
    why: &NotShared,
) -> Diagnostic {
    let detail = match why {
        NotShared::NotAdjacent { gap } if *gap < 0 => format!(
            "they overlap by {} cell(s) across it rather than standing one apart",
            -gap
        ),
        NotShared::NotAdjacent { gap } if matches!(s.face, Face::Up | Face::Down) => {
            let (low, high) = if s.face == Face::Up { (a, b) } else { (b, a) };
            let (_, top) = low.y_span();
            let open = matches!(low.plan.ceiling, Ceiling::Open(_));
            let want = i64::from(low.clearance) + gap - SHARED_FACE_GAP_CELLS;
            format!(
                "there are {gap} cells between them across that face where a shared wall is \
                 exactly {SHARED_FACE_GAP_CELLS}: `{ln}`'s headroom tops out at y {top} and \
                 `{hn}`'s floor course is y {fc}, so the one course between them is the upper's \
                 floor only when the lower's top is y {want_top}. A climb or a hole through a \
                 floor needs the lower place's headroom to reach one course under the upper's \
                 floor course — give `{ln}` `{kind}: {want}`, or move `{hn}`'s floor",
                ln = low.plan.node,
                hn = high.plan.node,
                fc = high.floor - 1,
                want_top = high.floor - 2,
                kind = if open { "open" } else { "clearance" },
            )
        }
        NotShared::NotAdjacent { gap } => format!(
            "there are {gap} cells between them across that face where a shared wall is exactly \
             {SHARED_FACE_GAP_CELLS}"
        ),
        NotShared::NoCommonArea { axis } => format!(
            "they are neighbours across it, but their spans on {axis} miss each other entirely, \
             so the face they share has no area to cut an opening in"
        ),
    };
    Diagnostic::error(
        DW_SEAM_NOT_SHARED,
        "site-plan",
        format!("/content/seams/{i}/face"),
        format!(
            "the seam for `{id}` is declared on the {face} face of `{an}`, and `{an}` and `{bn}` \
             do not share it: {detail}. **A seam is allocated on a face both boxes already \
             have** — that is the whole of why the plan places it while both are still free to \
             move, instead of two finished places discovering later that they cannot mate. \
             `{an}` is x {ax0}..{ax1}, z {az0}..{az1} at floor {af}; `{bn}` is x {bx0}..{bx1}, \
             z {bz0}..{bz1} at floor {bf}. Move one box against the other, or put the seam on \
             the face they really share.",
            id = s.edge,
            face = s.face.as_str(),
            an = edge.a(),
            bn = edge.b(),
            ax0 = a.x0(),
            ax1 = a.x1(),
            az0 = a.z0(),
            az1 = a.z1(),
            af = a.floor,
            bx0 = b.x0(),
            bx1 = b.x1(),
            bz0 = b.z0(),
            bz1 = b.z1(),
            bf = b.floor,
        ),
    )
}

/// **`DW0876`**: this seam states exactly one kind of
/// connection, and if it is a contact, one this engine builds (spec-0053 §4).
///
/// Returns `false` when the seam has no usable crossing, in which case the
/// caller stops: everything below reads the crossing rectangle.
fn contact_declaration(ctx: &SeamCtx<'_>, table: &Metrics, d: &mut Vec<Diagnostic>) -> bool {
    let (i, s) = (ctx.index, ctx.seam);
    let mut refuse = |what: String, remedy: String| {
        d.push(Diagnostic::error(
            DW_CONTACT,
            "site-plan",
            format!("/content/seams/{i}"),
            format!(
                "the seam for `{edge}` {what}. To fix it, {remedy}.",
                edge = s.edge,
            ),
        ));
    };

    // ---- Shape 1: exactly one kind.
    match (s.opening.as_ref(), s.contact.as_ref()) {
        (Some(o), Some(_)) => {
            refuse(
                format!(
                    "declares BOTH an `opening` ({o}) and a `contact` — a hand-off is one \
                     kind or the other",
                    o = o.describe(),
                ),
                "delete whichever this is not. A portal allocates the cells a body crosses \
                 at and every one of them must be passable; a contact is a front along which \
                 two places simply meet and needs only one crossable column. The derivation \
                 builds them differently and the byte observer measures them differently, so \
                 there is no world in which a seam is both"
                    .to_string(),
            );
            return false;
        }
        (None, None) => {
            refuse(
                "declares neither an `opening` nor a `contact`, so it states no way across"
                    .to_string(),
                format!(
                    "give it one. A doorway is `\"opening\": \"<name>\"` — defined \
                     standards: {names} — or a size the seam declares, \
                     `\"opening\": {{\"width\": w, \"height\": h}}`. A front where the two places simply meet is \
                     `\"contact\": {{}}`, which spans from `at` to the far edge of the \
                     shared face",
                    names = table.names_of(MetricKind::Opening).join(", "),
                ),
            );
            return false;
        }
        (Some(_), None) => return true,
        (None, Some(_)) => {}
    }

    // ---- Shape 3: the classes a contact may carry.
    //
    // `walk` and `drop` only. A rim falling to a lower court is a genuine broad
    // hand-off, so `drop` is in; `stair`, `barred` and `vision` are excluded
    // until a campaign brief demands one (spec-0053 §4, the falsifier re-armed).
    if !matches!(ctx.edge, Edge::Walk { .. } | Edge::Drop { .. }) {
        refuse(
            format!(
                "is a contact on a `{class}` connection, and a contact carries `walk` or \
                 `drop` only",
                class = ctx.edge.class(),
            ),
            "give the seam a standard `opening`, or declare the connection `walk` or \
             `drop` in the layout graph. A stair needs a run and a pitch, a barred door \
             needs a gate region that seals and clears, and a sightline is not a crossing \
             at all — none of the three is a thing a front can be, and this engine does \
             not have them as contacts until a campaign brief demands one"
                .to_string(),
        );
        return false;
    }

    let (u_span, v_span) = (ctx.face.u, ctx.face.v);
    let (u_hi, v_hi) = crossing_hi(ctx);

    // ---- Shape 2: the span lies on the shared face.
    let mut off: Vec<String> = Vec::new();
    if ctx.at[0] < u_span.0 || u_hi > u_span.1 {
        off.push(format!(
            "{}..{} on {}, against the face's {}..{}",
            ctx.at[0], u_hi, ctx.face.u_axis, u_span.0, u_span.1
        ));
    }
    if ctx.at[1] < v_span.0 || v_hi > v_span.1 {
        off.push(format!(
            "{}..{} on {}, against the face's {}..{}",
            ctx.at[1], v_hi, ctx.face.v_axis, v_span.0, v_span.1
        ));
    }
    if !off.is_empty() {
        refuse(
            format!(
                "is a contact whose span leaves the face the two boxes share: {}",
                off.join("; ")
            ),
            "move `at` onto the shared face, or shorten `contact.extent` — the span is \
             where the derivation writes no wall, and a span running off the face would \
             ask it to open a wall that is not there. Omitting `contact.extent` runs the \
             span from `at` to the far edge of the face, which never leaves it"
                .to_string(),
        );
        return false;
    }
    true
}

/// The far corner of a seam's crossing rectangle, on the face's own two in-plane
/// axes — [`contact_extent`] resolved against the seam's own anchor, so this
/// rule and the derivation describe one rectangle.
fn crossing_hi(ctx: &SeamCtx<'_>) -> (i64, i64) {
    let e = contact_extent(ctx.seam, ctx.at, &ctx.face);
    (ctx.at[0] + e[0] - 1, ctx.at[1] + e[1] - 1)
}

/// `DW0828`'s anchor half and `DW0829`'s geometric half: the opening's cells are
/// cells the shared face has.
fn opening_fits(ctx: &SeamCtx<'_>, opening: crate::metrics::Opening, d: &mut Vec<Diagnostic>) {
    let (i, s, edge, face, at) = (ctx.index, ctx.seam, ctx.edge, &ctx.face, ctx.at);
    let anchor_in =
        at[0] >= face.u.0 && at[0] <= face.u.1 && at[1] >= face.v.0 && at[1] <= face.v.1;
    if !anchor_in {
        d.push(Diagnostic::error(
            DW_SEAM_NOT_SHARED,
            "site-plan",
            format!("/content/seams/{i}/at"),
            format!(
                "the seam for `{id}` is anchored at {ua} {u}, {va} {v}, which is not on the face \
                 `{an}` and `{bn}` share — that face runs {ua} {u0}..{u1} by {va} {v0}..{v1} in \
                 the plane at {plane}. `at` names the opening's low corner in the face's own two \
                 axes, so a corner off the face allocates the seam nowhere.",
                id = s.edge,
                an = edge.a(),
                bn = edge.b(),
                ua = face.u_axis,
                va = face.v_axis,
                u = at[0],
                v = at[1],
                u0 = face.u.0,
                u1 = face.u.1,
                v0 = face.v.0,
                v1 = face.v.1,
                plane = face.plane,
            ),
        ));
        return;
    }
    let u_hi = at[0] + i64::from(opening.width) - 1;
    let v_hi = at[1] + i64::from(opening.height) - 1;
    if u_hi <= face.u.1 && v_hi <= face.v.1 {
        return;
    }
    d.push(Diagnostic::error(
        DW_SEAM_OPENING,
        "site-plan",
        format!("/content/seams/{i}/opening"),
        format!(
            "the {name} opening ({w}x{h}) does not fit on the face `{an}` and `{bn}` share. \
             Anchored at {ua} {u}, {va} {v} it would run to {ua} {u_hi}, {va} {v_hi}, and the \
             shared face ends at {ua} {u1}, {va} {v1}. Move the anchor, choose or declare a \
             smaller opening, or grow the overlap between the two boxes — the opening is the \
             author's declaration, so it is never quietly cropped to fit.",
            name = s
                .opening
                .as_ref()
                .map(OpeningSpec::describe)
                .unwrap_or_default(),
            w = opening.width,
            h = opening.height,
            an = edge.a(),
            bn = edge.b(),
            ua = face.u_axis,
            va = face.v_axis,
            u = at[0],
            v = at[1],
            u1 = face.u.1,
            v1 = face.v.1,
        ),
    ));
}

/// `DW0829`'s step-rule half: a body standing on the floor of a side it enters
/// from can get onto the sill.
fn sill(ctx: &SeamCtx<'_>, opening: crate::metrics::Opening, d: &mut Vec<Diagnostic>) {
    let (i, s, edge, a, b, face) = (ctx.index, ctx.seam, ctx.edge, ctx.a, ctx.b, &ctx.face);
    if face.v_axis != "y" {
        // A seam in a floor has no sill, but a body walking it still has to
        // get from one floor to the other: a walk through a floor between two
        // planes further apart than a jump is a connection nobody can take.
        let rise = (b.floor - a.floor).abs();
        let max_rise = MAX_JUMP_RISE_16 / crate::metrics::FULL_16;
        if rise > max_rise {
            d.push(Diagnostic::error(
                DW_SEAM_OPENING,
                "site-plan",
                format!("/content/seams/{i}"),
                format!(
                    "the seam for `{id}` is a hole in a floor between `{an}` (floor {af}) and \
                     `{bn}` (floor {bf}), {rise} blocks apart, and it is a `{class}`: a body \
                     reaches at most {max_rise} block(s) by jumping, so nothing carries it \
                     between the two. Declare the connection a `climb` (a ladder or a vine \
                     the lower place hangs), a `stair` (treads the plan allocates), or a \
                     `drop`.",
                    id = s.edge,
                    an = edge.a(),
                    bn = edge.b(),
                    af = a.floor,
                    bf = b.floor,
                    class = edge.class(),
                ),
            ));
        }
        return;
    }
    let sources: Vec<(&NodeId, &Placed<'_>)> = match edge.direction() {
        Some(crate::layout::Direction::AToB) => vec![(edge.a(), a)],
        Some(crate::layout::Direction::BToA) => vec![(edge.b(), b)],
        None => vec![(edge.a(), a), (edge.b(), b)],
    };
    let max_rise = MAX_JUMP_RISE_16 / crate::metrics::FULL_16;
    for (name, p) in sources {
        let rise = ctx.at[1] - p.floor;
        if rise <= max_rise {
            continue;
        }
        d.push(Diagnostic::error(
            DW_SEAM_OPENING,
            "site-plan",
            format!("/content/seams/{i}/at"),
            format!(
                "the seam for `{id}` has its sill at y {sill}, {rise} blocks over the floor of \
                 `{name}` at y {floor}, and a body reaches at most {max_rise} block(s) by \
                 jumping ({j}/16 of vanilla's apex). A body entering from `{name}` cannot get \
                 into the opening at all, so the connection the graph declares is not one. The \
                 sill is the higher of the two floors: bring the floors within a step of each \
                 other, or declare the connection a `stair` and let the treads carry the climb. \
                 (The opening is {w}x{h}.)",
                id = s.edge,
                sill = ctx.at[1],
                j = MAX_JUMP_RISE_16,
                floor = p.floor,
                w = opening.width,
                h = opening.height,
            ),
        ));
    }
}

/// `DW0992`: a climb rises: the two floors the plan put its ends on differ.
/// A climb has no treads and no sill — the ladder or vine the lower place
/// hangs carries the whole rise, and the proofs that move a body count the
/// climb (spec-0099) — so the one thing the plan can get wrong is a climb
/// between two places at one level.
fn climb(ctx: &SeamCtx<'_>, d: &mut Vec<Diagnostic>) {
    let (i, s, edge, a, b) = (ctx.index, ctx.seam, ctx.edge, ctx.a, ctx.b);
    if b.floor != a.floor {
        return;
    }
    d.push(Diagnostic::error(
        DW_CLIMB_RISES_NOTHING,
        "site-plan",
        format!("/content/seams/{i}"),
        format!(
            "`{id}` is a climb, and `{an}` and `{bn}` are both on plane y {f} — so it climbs \
             nothing. A climb's rise is the difference between the two floors the plan has \
             already chosen, and a ladder between two places at one level is a doorway that \
             has been called a climb. Move one floor, or declare the connection a `walk`.",
            id = s.edge,
            an = edge.a(),
            bn = edge.b(),
            f = a.floor,
        ),
    ));
}

/// `DW0830`: the stair the plan allocated has a host, climbs something and rises
/// off the lower floor; and, as a stand-in finding, whether the stand-in can lay
/// it at a standard pitch inside the box the plan said hosts it.
fn stair(ctx: &SeamCtx<'_>, table: &Metrics, reads: &mut Reads, d: &mut Vec<Diagnostic>) {
    let (i, s, edge, a, b, face) = (ctx.index, ctx.seam, ctx.edge, ctx.a, ctx.b, &ctx.face);
    let rise = b.floor - a.floor;
    if rise == 0 {
        d.push(Diagnostic::error(
            DW_STAIR_PITCH,
            "site-plan",
            format!("/content/seams/{i}"),
            format!(
                "`{id}` is a stair, and `{an}` and `{bn}` are both on plane y {f} — so it climbs \
                 nothing. A stair's rise is not authored here: it is the difference between the \
                 two floors the plan has already chosen, which means a stair between two places \
                 at one level is a walk that has been called a stair. Move one floor, or declare \
                 the connection a `walk`.",
                id = s.edge,
                an = edge.a(),
                bn = edge.b(),
                f = a.floor,
            ),
        ));
        return;
    }
    let Some(host_id) = &s.stair_in else {
        return; // the missing declaration is reported by `seams`.
    };
    // **The treads stand in the LOWER place**, and that is geometry rather than
    // taste: a stair is a stack of courses rising off a walk plane, and the only
    // walk plane it can rise off is the lower of the two. Hosting it in the
    // upper place asks for a stack that starts at that place's floor and has to
    // reach a level *below* it, which is not a stair — it is a hole with treads
    // drawn in the air under it.
    //
    // Found by building. This code checked only that the host affords the RUN,
    // so a plan naming the upper place reached green at stage 4 and the
    // derivation then laid a mound on the wrong side of the opening; the
    // stage-5 observer caught it as a seam whose hole was still solid, which is
    // the right refusal for the wrong defect. `stair_in` stays authored rather
    // than derived because it says WHICH of the two footprints pays for the run
    // when both are candidates — but when only one can be, saying the other is
    // a refusal.
    let (low, high) = if b.floor > a.floor {
        (edge.a(), edge.b())
    } else {
        (edge.b(), edge.a())
    };
    if host_id == high {
        d.push(Diagnostic::error(
            DW_STAIR_PITCH,
            "site-plan",
            format!("/content/seams/{i}/stair_in"),
            format!(
                "the stair for `{id}` hosts its treads in `{high}`, which is the HIGHER of the two \
                 places (`{an}` stands at y {af}, `{bn}` at y {bf}). Treads rise off a walk plane, \
                 and the only plane this stair can rise off is the lower one — massing in the \
                 upper place would have to start at that place's floor and reach a level beneath \
                 it, which is not a stair. Host it in `{low}`, and check that `{low}` affords the \
                 run: a stair costs its footprint, and moving the host moves who pays.",
                id = s.edge,
                an = edge.a(),
                bn = edge.b(),
                af = a.floor,
                bf = b.floor,
            ),
        ));
        return;
    }
    let host = if host_id == edge.a() { a } else { b };
    // **The run is measured the way the derivation lays it**, by the function
    // that lays it — [`stair_run`]. What this check used to measure instead was
    // the seam's rise against the host's whole extent, and neither is what a
    // tread run costs: the courses carry a body to the OPENING, and through a
    // pierced floor they leave along one side of the hole. See [`stair_run`] for
    // the plan that reached green at `needs 8, affords 8` and built no stair.
    let Some((_, extent)) = crossing_rect(s, ctx.at, face, table, reads) else {
        return; // `DW0812` refused the opening; there is no rectangle to measure.
    };
    let Some(run) = stair_run(
        host.floor,
        host.foot,
        normal_axis_of(s.face),
        face.plane,
        crossing_aabb(s, ctx.at, face, extent),
    ) else {
        return; // no run to lay: the higher host is refused above, the stray hole by `DW0828`.
    };
    let run_axis = if run.run_axis == 0 { "x" } else { "z" };
    if gentlest_pitch(table, reads, run.climb, run.available).is_some() {
        return; // some standard pitch fits.
    }
    let Some((name, needed)) = tightest_pitch(table, reads, run.climb) else {
        return; // the table defines no pitch; `Metrics::self_check` owns that.
    };
    let carries = if run.climb == rise.abs() {
        String::new()
    } else {
        format!(
            " The treads carry {climb}, not {rise}: they rise off `{host_id}`'s floor at \
             {hf} and stop at {target}, which is where the opening puts a body.",
            climb = run.climb,
            rise = rise.abs(),
            hf = host.floor,
            target = host.floor + run.climb,
        )
    };
    // A finding about the STAND-IN, never a refusal of the plan: the stand-in's
    // treads are laid at a standard pitch and the stand-in never ships. A piece
    // bound to the host carries its own stair, which the byte observers judge
    // (`DW0836`, `DW0837`); an unbound host whose stand-in cannot lay a standard
    // run lays none, and the place it leads to is unreached (`DW0837`).
    d.push(Diagnostic::warning(
        DW_STAIR_PITCH,
        "site-plan",
        format!("/content/seams/{i}"),
        format!(
            "the stair for `{id}` climbs {rise} block(s) between `{an}` (floor {af}) and `{bn}` \
             (floor {bf}), and no standard pitch fits inside `{host_id}`, so the stand-in cannot \
             lay its treads. The tightest standard is `{name}`, which needs {needed} block(s) of \
             run for a climb of {climb}, and `{host_id}` affords {available} of run on \
             {run_axis}.{carries} A piece detailed into `{host_id}` carries its own stair, judged \
             over its bytes (`DW0836`, `DW0837`); left to the stand-in, the climb is unbuilt and \
             the place above it is unreached (`DW0837`). For a standard stand-in stair, give the \
             host a longer footprint on that axis, move the opening so the run has more room \
             beside it, host the stair in the other place, or bring the two floors closer.",
            id = s.edge,
            an = edge.a(),
            bn = edge.b(),
            af = a.floor,
            bf = b.floor,
            rise = rise.abs(),
            climb = run.climb,
            available = run.available,
        ),
    ));
}

/// `DW0831`: a designed drop falls the way it says it falls, no further than an
/// unarmoured body survives, and no further than the plan's declared `max_drop`.
fn drop_seam(
    ctx: &SeamCtx<'_>,
    falls: crate::layout::Direction,
    max_drop: Option<NonZeroU32>,
    d: &mut Vec<Diagnostic>,
) {
    let (i, s, edge, a, b) = (ctx.index, ctx.seam, ctx.edge, ctx.a, ctx.b);
    let (from, from_p, to, to_p) = match falls {
        crate::layout::Direction::AToB => (edge.a(), a, edge.b(), b),
        crate::layout::Direction::BToA => (edge.b(), b, edge.a(), a),
    };
    let depth = from_p.floor - to_p.floor;
    if depth <= 0 {
        d.push(Diagnostic::error(
            DW_DROP_POLICY,
            "site-plan",
            format!("/content/seams/{i}"),
            format!(
                "`{id}` falls from `{from}` (floor {ff}) into `{to}` (floor {tf}), which is \
                 {what}. A drop is one-way because a body cannot climb back up the way it came, \
                 and that is only true going down — this one is a mislabelled stair. Swap the \
                 declared direction, move the floors, or declare the connection a `stair`.",
                id = s.edge,
                ff = from_p.floor,
                tf = to_p.floor,
                what = if depth == 0 {
                    "the same plane".to_string()
                } else {
                    format!("{} block(s) HIGHER", -depth)
                },
            ),
        ));
        return;
    }
    // The physical ceiling holds every drop, declared policy or not: a fall
    // deeper than an unarmoured body survives lands a body nowhere.
    let physical = crate::metrics::unarmoured_survivable_fall_blocks() as i64;
    if depth > physical {
        d.push(Diagnostic::error(
            DW_DROP_POLICY,
            "site-plan",
            format!("/content/seams/{i}"),
            format!(
                "`{id}` drops {depth} blocks from `{from}` into `{to}`, and an unarmoured body \
                 at full health survives a fall of {physical} at most — a drop deeper than \
                 that is a death, not a connection. Bring the two floors closer, or break the \
                 fall with a place between them.",
                id = s.edge,
            ),
        ));
        return;
    }
    // The author's own policy, when the plan declares one.
    let Some(cap) = max_drop else {
        return;
    };
    let cap = cap.get();
    if depth <= i64::from(cap) {
        return;
    }
    d.push(Diagnostic::error(
        DW_DROP_POLICY,
        "site-plan",
        format!("/content/seams/{i}"),
        format!(
            "`{id}` drops {depth} blocks from `{from}` into `{to}`, and this plan's `max_drop` \
             caps a designed fall at {cap}. Bring the two floors closer, break the fall with a \
             place between them, or raise the plan's `max_drop` if the deeper fall is the \
             design.",
            id = s.edge,
        ),
    ));
}

/// The floor a designed opening may never be chosen below: the cells a standing
/// body needs to pass at all.
///
/// Not a check of its own — [`Metrics::self_check`] already holds every standard
/// opening over it, and a second refusal here would be this module re-asking a
/// question the table has already answered about itself. It is re-exported so a
/// reader of `DW0829` can see what the standard set is bounded by.
#[must_use]
pub fn passable_opening_cells() -> (u32, u32) {
    (passable_width_cells(), passable_clearance_cells())
}