nucleation 0.10.14

A high-performance Minecraft schematic parser and utility library
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
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
//! Port PROMOTION: turning a cell's executor-only IO into routable dust.
//!
//! Community redstone names *executor hardware* in its contract — inputs are
//! LEVERS or BUTTONS, outputs are LAMPS. Nothing in redstone drives a lever, so
//! `ADD007.sum -> BINTOBCD001.bin` is impossible however good the router is:
//! the sink has no dust to land on. That single fact is why the studio cannot
//! chain two library cells, and it is what this module removes.
//!
//! A port has two MODES (see [`crate::design::PortMode`]):
//!
//! - **Executor** — the shipped hardware. Drivable by `CellExecutor`, never
//!   routable.
//! - **Bus** — the hardware is replaced by a *driver stub* that ends in dust.
//!   Routable, no longer hand-drivable.
//!
//! The switch is a reversible per-instance PATCH, not an edit to the cell: the
//! original block states are saved so Bus -> Executor restores them
//! byte-exactly. Nothing here mutates the shared cell library.
//!
//! # The two input strategies (both verified in the tick engine)
//!
//! What a lever really does is STRONGLY power its attachment block; everything
//! downstream reads that block. Dust only ever powers a block WEAKLY, and weak
//! power does not reach dust — so replacing a lever with dust works only when
//! the attachment block's consumers are repeaters/comparators. That split is
//! measurable and it decides the strategy:
//!
//! - `face=floor` (attachment block DIRECTLY BELOW): put dust in the lever's
//!   own cell. It sits on the attachment block and powers it from above.
//!   Verified: `BINTOBCD001.bin`, 8/8 vectors identical to lever drive.
//! - `face=wall` (attachment block BESIDE): a repeater in the lever's cell
//!   pointing INTO the attachment block reproduces the lever's *strong* power
//!   exactly, and the connection dust goes one cell further out. Verified:
//!   `ADD007.a` 8/8 and `NUMDISPLAY001.bcd` 10/10 identical to lever drive.
//!   (Plain dust in the lever cell is NOT enough here — `ADD007.a` feeds bare
//!   dust and reads 0 forever, which is exactly the weak-power rule.)
//!
//! Outputs are easier: a lamp is already strongly powered by whatever drives
//! it, so dust placed on top of the lamp reads the signal without touching the
//! lamp at all — the port stays executor-READABLE and becomes routable.
//!
//! ## Why not use a repeater for EVERY face?
//!
//! Tempting, because a repeater takes its input from BEHIND, horizontally, so a
//! bus could approach in the port's own row orientation and the pivot below
//! would be unnecessary. It does not work for a FLOOR lever, and the reason is
//! geometric rather than a tuning matter: a repeater emits from its front FACE,
//! horizontally. It never powers the block BENEATH it, and a floor lever's
//! attachment block is precisely the block beneath it.
//!
//! Measured in the tick engine on 2026-08-09 (attachment block below the
//! driver, consumer = a repeater reading that block from behind):
//!
//! | driver in the lever's cell | consumer sees |
//! |----------------------------|---------------|
//! | floor LEVER (reference)    | 15 — DRIVES   |
//! | REPEATER                   | 0  — DEAD     |
//! | DUST (what we do)          | 15 — DRIVES   |
//!
//! So the per-face split above is not an accident of history; it is the only
//! assignment that works. The repeater strategy stays where the attachment
//! block is BESIDE the driver (`face=wall`), which is exactly where it is used.
//!
//! # Form: the PIVOT
//!
//! Promotion is only half the job. A bus realizes the verified vertical
//! 2y-pitch stack, and community IO is often a horizontal ROW (`BINTOBCD001`'s
//! `bin` levers march along x at pitch 2). Such a port is dust, routable in
//! principle, and still unusable — its step is `(2,0,0)`, not `(0,2,0)`.
//!
//! The pivot is a WORKAROUND for a realizer limitation, not a design goal, and
//! it is the reason promoted buses look more complicated than they should. The
//! form is not "forced vertical" by promotion — it is forced by
//! `design.rs::realize`, which hard-rejects any step other than `(0,2,0)`, and
//! by everything keyed to that stack: the corridor fabric's column test
//! (`y0-1 ..= y0+2*(width-1)`), the refresh-repeater stations, the crossing
//! rules and the DRC. Inferring the form from port geometry, as `DESIGN_SPEC`
//! describes, therefore means a SECOND realizer for the horizontal form rather
//! than relaxing a guard; until that exists the pivot is what makes a row port
//! routable at all.
//!
//! [`pivot_row_to_stack`] therefore grows a *form adapter*: bit `i` leaves the
//! row in its own private lane, climbs `2i` blocks on a dust staircase, runs
//! out to a common depth, then gathers back along the row axis so all bits
//! land in one vertical 2y-pitch column. Lanes are 2 apart on the row axis, so
//! no two bits are ever plan-adjacent; the gather column is a textbook bus
//! stack (dust, support, dust, ...). Refresh repeaters are inserted every 6
//! dust cells, with a flat landing around each one — dust cannot climb out of
//! a repeater, so the staircase pauses, repeats, and resumes.

use crate::routing::engine::blocks as rblocks;
use crate::UniversalSchematic;

/// Position triple (mirrors [`crate::design::P3`]).
pub type P3 = (i32, i32, i32);

/// Dust cells between refresh repeaters inside a promotion stub. Dust reaches
/// 15 cells; 6 leaves room for the corner cells a pivot leg adds.
const REFRESH_AT: usize = 6;

/// The block a promotion stub uses for supports.
const SUPPORT: &str = rblocks::STONE;

fn add(a: P3, b: P3) -> P3 {
    (a.0 + b.0, a.1 + b.1, a.2 + b.2)
}

fn mul(a: P3, k: i32) -> P3 {
    (a.0 * k, a.1 * k, a.2 * k)
}

/// A reversible patch over a cell's blocks, in CELL-LOCAL coordinates.
///
/// `writes` is what Bus mode lays down (`None` = clear the cell); `saved` is
/// what was there before, so restoring is a byte-exact undo.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PortPatch {
    /// Cells Bus mode writes: `Some(state)` places, `None` clears.
    pub writes: std::collections::BTreeMap<P3, Option<String>>,
    /// What was at every touched cell before: `None` = it was empty.
    pub saved: std::collections::BTreeMap<P3, Option<String>>,
    /// Bus-mode connection cells, bit order (the contract's Bus positions).
    pub wires: Vec<P3>,
    /// Executor-mode positions (the contract's shipped hardware).
    pub hardware: Vec<P3>,
    /// Step between consecutive `wires`.
    pub step: P3,
    /// Whether a form pivot was needed, for the report.
    pub pivoted: bool,
    /// One-sentence human summary for a UI toast.
    pub note: String,
}

impl PortPatch {
    /// `{"wires":[[x,y,z],..],"hardware":[..],"step":[x,y,z],"removed":n,
    ///   "added":n,"pivoted":bool,"note":".."}`
    pub fn to_json(&self) -> String {
        let pos = |ps: &[P3]| {
            let v: Vec<String> = ps
                .iter()
                .map(|p| format!("[{},{},{}]", p.0, p.1, p.2))
                .collect();
            format!("[{}]", v.join(","))
        };
        let removed = self.writes.values().filter(|v| v.is_none()).count()
            + self
                .saved
                .iter()
                .filter(|(p, v)| v.is_some() && self.writes.get(*p).is_some_and(|w| w.is_some()))
                .count();
        format!(
            "{{\"wires\":{},\"hardware\":{},\"step\":[{},{},{}],\"removed\":{},\"added\":{},\
             \"pivoted\":{},\"note\":{:?}}}",
            pos(&self.wires),
            pos(&self.hardware),
            self.step.0,
            self.step.1,
            self.step.2,
            removed,
            self.writes.values().filter(|v| v.is_some()).count(),
            self.pivoted,
            self.note,
        )
    }
}

/// A cheap read-only view of the cell being patched.
struct Body<'a> {
    sch: &'a UniversalSchematic,
}

impl Body<'_> {
    fn at(&self, p: P3) -> Option<String> {
        self.sch
            .get_block(p.0, p.1, p.2)
            .map(|b| b.to_string())
            .filter(|s| !s.contains("minecraft:air"))
    }

    fn free(&self, p: P3) -> bool {
        self.at(p).is_none()
    }
}

/// Builder that records writes together with what they displaced.
struct Patcher<'a> {
    body: Body<'a>,
    patch: PortPatch,
}

impl<'a> Patcher<'a> {
    fn new(sch: &'a UniversalSchematic) -> Self {
        Patcher {
            body: Body { sch },
            patch: PortPatch::default(),
        }
    }

    /// Is `p` free once this patch's own writes are taken into account?
    fn free(&self, p: P3) -> bool {
        match self.patch.writes.get(&p) {
            Some(Some(_)) => false,
            Some(None) => true,
            None => self.body.free(p),
        }
    }

    fn write(&mut self, p: P3, block: Option<&str>) {
        self.patch.saved.entry(p).or_insert_with(|| self.body.at(p));
        self.patch.writes.insert(p, block.map(|s| s.to_string()));
    }

    /// Place `block` at `p`, refusing to overwrite anything that is not air.
    fn place(&mut self, p: P3, block: &str, what: &str) -> Result<(), String> {
        if !self.free(p) {
            return Err(format!(
                "promotion needs {:?} for its {what}, but the cell has `{}` there",
                p,
                self.body.at(p).unwrap_or_else(|| "?".into())
            ));
        }
        self.write(p, Some(block));
        Ok(())
    }

    /// Make sure `p` can hold dust/a repeater by giving it a support block.
    fn support(&mut self, p: P3) -> Result<(), String> {
        let below = add(p, (0, -1, 0));
        match self.patch.writes.get(&below) {
            Some(Some(b)) if rblocks::is_sturdy_support(b) => Ok(()),
            Some(Some(b)) => Err(format!(
                "promotion wants a support under {below:?} but its own stub put `{b}` there"
            )),
            _ => match self.body.at(below) {
                Some(b) if rblocks::is_sturdy_support(&b) => Ok(()),
                Some(b) => Err(format!(
                    "promotion wants a support under {below:?}; the cell has non-sturdy `{b}` there"
                )),
                None => {
                    self.write(below, Some(SUPPORT));
                    Ok(())
                }
            },
        }
    }

    fn dust(&mut self, p: P3, what: &str) -> Result<(), String> {
        self.place(p, rblocks::DUST, what)?;
        self.support(p)
    }
}

/// The attachment block of a lever/button, and the direction it faces AWAY
/// from that block.
fn attachment(block: &str, at: P3) -> Result<(P3, P3), String> {
    let face = block
        .find("face=")
        .map(|i| {
            let rest = &block[i + 5..];
            let end = rest.find([',', ']']).unwrap_or(rest.len());
            &rest[..end]
        })
        .ok_or_else(|| format!("`{block}` at {at:?} has no `face=` property"))?;
    match face {
        // Floor: mounted on the block below, pointing up.
        "floor" => Ok((add(at, (0, -1, 0)), (0, 1, 0))),
        // Ceiling: mounted on the block above, pointing down.
        "ceiling" => Ok((add(at, (0, 1, 0)), (0, -1, 0))),
        "wall" => {
            let f = rblocks::facing_of(block)
                .and_then(rblocks::facing_vec)
                .ok_or_else(|| format!("wall lever `{block}` at {at:?} has no usable facing"))?;
            // A wall lever points AWAY from the block it hangs on.
            Ok((add(at, mul(f, -1)), f))
        }
        other => Err(format!("`{block}` at {at:?}: unsupported face `{other}`")),
    }
}

/// Plan Bus mode for an INPUT port whose `hardware` cells hold levers or
/// buttons. See the module docs for why the strategy depends on `face`.
pub fn plan_input(sch: &UniversalSchematic, hardware: &[P3]) -> Result<PortPatch, String> {
    if hardware.is_empty() {
        return Err("port declares no positions".to_string());
    }
    let mut p = Patcher::new(sch);
    let mut wires = Vec::new();
    let mut how = "";
    for (k, hp) in hardware.iter().enumerate() {
        let block = p
            .body
            .at(*hp)
            .ok_or_else(|| format!("bit {k}: nothing at {hp:?} to promote"))?;
        if rblocks::is_dust(&block) {
            // Already a dust port: nothing to do for this bit.
            wires.push(*hp);
            how = "already dust";
            continue;
        }
        if !(rblocks::is_lever(&block) || block.contains("button")) {
            return Err(format!(
                "bit {k}: {hp:?} holds `{block}`, which is not a lever or button — promotion \
                 replaces executor hardware, so declare the port over the real hardware first"
            ));
        }
        let (att, out) = attachment(&block, *hp)?;
        if out.1 != 0 {
            // Floor (or ceiling) lever: the attachment block is directly
            // below/above, and dust in the lever's own cell powers it.
            if out.1 < 0 {
                return Err(format!(
                    "bit {k}: {hp:?} is a CEILING lever (attachment block above at {att:?}); \
                     nothing may sit above a block to power it, so this port cannot be promoted \
                     — rebuild the cell with a floor or wall lever"
                ));
            }
            p.write(*hp, Some(rblocks::DUST));
            p.support(*hp)?;
            wires.push(*hp);
            how = "dust on the lever's own support (weak power into the attachment block)";
        } else {
            // Wall lever: a repeater pointing into the attachment block gives
            // the same STRONG power the lever gave.
            let facing = rblocks::facing_of(&block)
                .ok_or_else(|| format!("bit {k}: wall lever `{block}` has no facing"))?
                .to_string();
            p.write(*hp, Some(&rblocks::repeater(&facing, 1)));
            p.support(*hp)?;
            let wire = add(*hp, out);
            p.dust(wire, "connection cell")?;
            wires.push(wire);
            how = "repeater into the attachment block, connection dust one cell out";
        }
    }
    finish(p, hardware, wires, how, sch, false)
}

/// Plan Bus mode for an OUTPUT port whose `hardware` cells hold lamps: a dust
/// tap on top of each lamp. The lamp is untouched, so the port stays readable
/// through the typed executor as well as routable.
pub fn plan_output(sch: &UniversalSchematic, hardware: &[P3]) -> Result<PortPatch, String> {
    if hardware.is_empty() {
        return Err("port declares no positions".to_string());
    }
    let mut p = Patcher::new(sch);
    let mut wires = Vec::new();
    for (k, hp) in hardware.iter().enumerate() {
        let block = p
            .body
            .at(*hp)
            .ok_or_else(|| format!("bit {k}: nothing at {hp:?} to tap"))?;
        if rblocks::is_dust(&block) {
            wires.push(*hp);
            continue;
        }
        if !rblocks::is_sturdy_support(&block) {
            return Err(format!(
                "bit {k}: {hp:?} holds `{block}`, which cannot support a dust tap — an output \
                 port is promoted by putting dust on the lamp that already carries the signal"
            ));
        }
        let tap = add(*hp, (0, 1, 0));
        p.place(tap, rblocks::DUST, "output tap")?;
        wires.push(tap);
    }
    finish(
        p,
        hardware,
        wires,
        "dust tap on top of the output lamp (the lamp keeps working)",
        sch,
        true,
    )
}

/// Uniform step of `wires`, or an error naming why there is none.
fn uniform_step(wires: &[P3]) -> Result<P3, String> {
    if wires.len() == 1 {
        return Ok((0, 2, 0));
    }
    let s = (
        wires[1].0 - wires[0].0,
        wires[1].1 - wires[0].1,
        wires[1].2 - wires[0].2,
    );
    if wires
        .windows(2)
        .all(|w| (w[1].0 - w[0].0, w[1].1 - w[0].1, w[1].2 - w[0].2) == s)
    {
        Ok(s)
    } else {
        Err(format!(
            "connection cells {wires:?} do not lie on a uniform step"
        ))
    }
}

/// Close a patch. Promotion is MINIMAL and IN-PLACE: the port keeps its NATIVE
/// geometry (a horizontal row of 8 stays a horizontal row of 8 at its native
/// pitch) and the patch never reaches outside the cell's own footprint.
///
/// FORM ADAPTATION IS THE BUS'S JOB. Promotion used to grow the row->stack
/// pivot here, which put a staircase and a gather column — geometry that
/// exists only to serve one bus, and that extends well beyond the cell — into
/// a PER-INSTANCE patch. Ripping the bus then left it behind as orphaned
/// geometry the user could not remove. The adapter now belongs to the bus
/// fragment (see `Design::realize`), so it is created and ripped with the bus,
/// and the component stays untouched in its native form.
fn finish(
    mut p: Patcher<'_>,
    hardware: &[P3],
    wires: Vec<P3>,
    how: &str,
    _sch: &UniversalSchematic,
    _flow_out: bool,
) -> Result<PortPatch, String> {
    let step = uniform_step(&wires)?;
    let n = wires.len();
    if step.1 != 0 && step != (0, 2, 0) {
        return Err(format!(
            "promoted connection cells step {step:?}: a bus can adapt a horizontal ROW or the \
             canonical vertical 2y-pitch stack onto its form. This port's hardware is neither."
        ));
    }
    // DRAW THE WIRE. The dust above was authored in the default state, which
    // interns correctly (a bare `redstone_wire` sits inert) but is
    // geometrically a DOT. Derive the connection states from the neighbours the
    // way Minecraft does on placement, reading the cell body for anything the
    // patch does not itself write.
    let body_at = |q: P3| -> Option<String> { p.body.at(q) };
    let mut writes: std::collections::BTreeMap<P3, String> = p
        .patch
        .writes
        .iter()
        .filter_map(|(q, v)| v.clone().map(|b| (*q, b)))
        .collect();
    crate::routing::engine::wire::rewire(&mut writes, &body_at);
    for (q, b) in writes {
        p.patch.writes.insert(q, Some(b));
    }
    p.patch.step = if n == 1 { (0, 2, 0) } else { step };
    p.patch.wires = wires;
    p.patch.hardware = hardware.to_vec();
    p.patch.note = if p.patch.step == (0, 2, 0) {
        format!("{n} bit(s): {how}")
    } else {
        format!(
            "{n} bit(s): {how}; the port keeps its native {:?}-pitch form — the bus grows the \
             form adapter it needs, and rips it with itself",
            p.patch.step
        )
    };
    Ok(p.patch)
}

/// A form-adapter PLAN: the cells to place and the vertical 2y-pitch column
/// they gather the row into (bit order).
#[derive(Clone, Debug, Default)]
pub struct PivotPlan {
    /// Cells the adapter needs, block per position (caller coordinates).
    pub cells: std::collections::BTreeMap<P3, String>,
    /// The resulting 2y-pitch column, bit order — where a bus lands.
    pub column: Vec<P3>,
    /// One-sentence human summary.
    pub note: String,
}

/// A write target for [`plan_pivot`]: collects cells and answers "what is
/// there?" through the caller's own view of the world, so the same verified
/// geometry can be planned against a cell body or against a whole design's
/// occupancy index.
struct PivotSink<'f> {
    at: &'f dyn Fn(P3) -> Option<String>,
    cells: std::collections::BTreeMap<P3, String>,
}

impl PivotSink<'_> {
    fn look(&self, p: P3) -> Option<String> {
        self.cells.get(&p).cloned().or_else(|| (self.at)(p))
    }

    fn free(&self, p: P3) -> bool {
        self.look(p).is_none()
    }

    fn write(&mut self, p: P3, block: Option<&str>) {
        match block {
            Some(b) => {
                self.cells.insert(p, b.to_string());
            }
            None => {
                self.cells.remove(&p);
            }
        }
    }

    fn place(&mut self, p: P3, block: &str, what: &str) -> Result<(), String> {
        if let Some(b) = self.look(p) {
            return Err(format!(
                "the form adapter needs {p:?} for its {what}, but `{b}` is there"
            ));
        }
        self.write(p, Some(block));
        Ok(())
    }

    fn support(&mut self, p: P3) -> Result<(), String> {
        let below = add(p, (0, -1, 0));
        match self.look(below) {
            Some(b) if rblocks::is_sturdy_support(&b) => Ok(()),
            Some(b) => Err(format!(
                "the form adapter wants a support under {below:?}; `{b}` is not sturdy"
            )),
            None => {
                self.write(below, Some(SUPPORT));
                Ok(())
            }
        }
    }

    fn dust(&mut self, p: P3, what: &str) -> Result<(), String> {
        self.place(p, rblocks::DUST, what)?;
        self.support(p)
    }
}

/// Plan a form adapter turning a horizontal ROW of connection cells into a
/// vertical 2y-pitch COLUMN. PURE: nothing is written, the caller owns the
/// cells — for a bus that means they live in the bus's fragment and are ripped
/// with it.
///
/// Shape: bit `i` leaves the row in its own private lane, climbs `2i` blocks on
/// a dust staircase, runs out to a common depth, then gathers back along the
/// row axis so all bits land in one vertical 2y-pitch column. Lanes are `pitch`
/// apart on the row axis, so no two bits are ever plan-adjacent; the gather
/// column is a textbook bus stack. Refresh repeaters go in every
/// [`REFRESH_AT`] dust cells with a flat landing around each — dust cannot
/// climb out of a repeater, so the staircase pauses, repeats and resumes.
///
/// Both perpendicular directions are tried; the one whose whole volume is free
/// wins, and if both are, the one pointing AWAY from `prefer_away_from` (the
/// cell's centre) is preferred, so the adapter grows out of the component.
/// `gather_end` constrains the common column to bit 0 (`Some(false)`) or the
/// last bit (`Some(true)`). `None` lets this adapter choose locally; a caller
/// coordinating two adapters can force opposite ends to equalise their summed
/// gather distance.
pub fn plan_pivot(
    wires: &[P3],
    step: P3,
    prefer_away_from: P3,
    flow_out: bool,
    at: &dyn Fn(P3) -> Option<String>,
    toward: Option<P3>,
    gather_end: Option<bool>,
) -> Result<PivotPlan, String> {
    if wires.is_empty() {
        return Err("form adapter needs at least one connection cell".to_string());
    }
    if step.1 != 0 {
        return Err(format!(
            "form adapter turns a HORIZONTAL row into the vertical stack; this port's step is \
             {step:?}"
        ));
    }
    let along = if step.0 != 0 { (1, 0, 0) } else { (0, 0, 1) };
    let mut cands: Vec<P3> = if step.0 != 0 {
        vec![(0, 0, -1), (0, 0, 1)]
    } else {
        vec![(-1, 0, 0), (1, 0, 0)]
    };
    // COST-DRIVEN, not structural. This used to take the first direction that
    // merely FIT, ordered by "grows away from the cell body" — which is how the
    // adapter ended up building its gather bar on the far side of the port and
    // making the bus double back to reach it. Plan BOTH sides and keep the one
    // that costs less, breaking ties toward the outside so the old behaviour is
    // the tie-break rather than the rule.
    //
    // Cost is the realized CELL COUNT plus how far the column ends up from the
    // partner the bus has to reach (`toward`, when the caller knows it): a
    // cheap adapter that lands the stack head on the wrong side of the
    // component just moves the cost into the transport leg.
    let outward = |d: &P3| {
        -((d.0 * (prefer_away_from.0 - wires[0].0)) + (d.2 * (prefer_away_from.2 - wires[0].2)))
    };
    cands.sort_by_key(|d| -outward(d));
    let mut errs = Vec::new();
    let mut best: Option<(i64, PivotPlan)> = None;
    // WHICH END OF THE ROW the column lands on is the second free choice, and
    // leaving it fixed at bit 0's lane is what made a flat-to-flat bus ride its
    // own bundle twice. Measured on `V03_flat90` (a flat_x row 8 bits wide
    // turning into a flat_z row): the per-bit Manhattan bound is 42 cells for
    // EVERY bit — an equal-length route exists — while the old geometry had 28
    // cells of per-bit spread. The spread was not the corner and not the cost
    // weights: both
    // adapters gathered toward bit 0's lane, so their two `pitch * i` gather
    // legs ADDED instead of cancelling. Gathering one end toward bit 0 and the
    // other toward bit n-1 costs exactly the same cells (sum of `pitch*i` ==
    // sum of `pitch*(n-1-i)`) and cancels the spread.
    //
    // A lone adapter chooses by cost. A coordinated flat turn passes an
    // explicit end because the equal-length property belongs to the PAIR and
    // cannot be recovered by two independent local choices.
    let ends: &[bool] = match gather_end {
        Some(false) => &[false],
        Some(true) => &[true],
        None => &[false, true],
    };
    let reach_weight = wires.len() as i64;
    for &gather_to_last in ends {
        for out in &cands {
            let out = *out;
            let mut sink = PivotSink {
                at,
                cells: std::collections::BTreeMap::new(),
            };
            match lay_pivot(&mut sink, wires, step, along, out, flow_out, gather_to_last) {
                Ok(column) => {
                    let cells = sink.cells.len() as i64;
                    // Distance from the column head to whatever the bus must
                    // reach, WEIGHTED BY THE BIT COUNT. Unweighted it was a
                    // rounding error next to `cells` (16 against ~200) even
                    // though every one of the n bits pays it: moving the column
                    // one cell nearer the partner shortens the transport leg n
                    // times, so that is the exchange rate.
                    let reach = toward.map_or(0, |t| {
                        ((column[0].0 - t.0).abs() + (column[0].2 - t.2).abs()) as i64
                    }) * reach_weight;
                    // Tie-break toward the outside (the old, structural
                    // preference) and toward bit 0's lane (the old gather end),
                    // so the previous behaviour is what a tie resolves to.
                    let cost =
                        cells + reach - i64::from(outward(&out) > 0) + i64::from(gather_to_last);
                    let plan = PivotPlan {
                        cells: sink.cells,
                        column,
                        note: format!(
                            "form adapter: pivoted the {:?}-pitch row onto a vertical 2y stack via \
                             a staircase growing {} block(s) toward {:?}, gathering to bit {}'s \
                             lane ({cells} cells; the selected side/end combination)",
                            step,
                            2 * wires.len(),
                            out,
                            if gather_to_last { wires.len() - 1 } else { 0 }
                        ),
                    };
                    if best.as_ref().is_none_or(|(c, _)| cost < *c) {
                        best = Some((cost, plan));
                    }
                }
                Err(e) => errs.push(format!("toward {out:?} gather_last={gather_to_last}: {e}")),
            }
        }
    }
    if let Some((_, plan)) = best {
        return Ok(plan);
    }
    Err(format!(
        "the row needs a form adapter to reach the vertical 2y-pitch bus stack, but neither side \
         of the port face has room for one ({})",
        errs.join("; ")
    ))
}

/// One attempt at the adapter, in the direction `out`.
#[allow(clippy::too_many_arguments)]
fn lay_pivot(
    p: &mut PivotSink<'_>,
    wires: &[P3],
    step: P3,
    along: P3,
    out: P3,
    flow_out: bool,
    // Gather to bit `n-1`'s lane instead of bit 0's. Same cell count, mirrored
    // per-bit lengths — see `plan_pivot` for why that matters.
    gather_to_last: bool,
) -> Result<Vec<P3>, String> {
    let n = wires.len() as i32;
    let pitch = step.0.abs().max(step.2.abs()); // 2 for a 2-pitch row
    if pitch < 2 {
        return Err(format!("row pitch {pitch} leaves no lane between bits"));
    }
    // The direction from bit i's lane toward the lane the column lands on —
    // bit 0's by default, bit n-1's when the planner asked for the mirror.
    let to_first = mul(along, -(step.0 + step.2).signum());
    let back = if gather_to_last {
        mul(to_first, -1)
    } else {
        to_first
    };
    // Depth every bit runs out to, from the deepest climb any bit needs.
    let depth = 2 + 2 * (n - 1) + 2 * refresh_pauses(2 * (n - 1) as usize) as i32;
    let mut column = Vec::new();
    for i in 0..n {
        let w = wires[i as usize];
        let mut y = w.1;
        let mut t = 1i32;
        let mut since = 0usize;
        // A repeater right at the mouth, so every bit starts at full strength
        // whatever the bus delivered.
        let sgn = if flow_out { -1 } else { 1 };
        let rep_in = rblocks::facing_name(sgn * out.0, sgn * out.2)
            .ok_or("pivot direction is not axis-aligned")?;
        p.place(add(w, out), &rblocks::repeater(rep_in, 1), "stub repeater")?;
        p.support(add(w, out))?;
        t += 1;
        // Dust must follow a repeater before the staircase may climb.
        p.dust(add(w, mul(out, t)), "stub run")?;
        since = 1;
        t += 1;
        let mut climb = 2 * i;
        while climb > 0 {
            if since >= REFRESH_AT {
                let c = add(add(w, mul(out, t)), (0, y - w.1, 0));
                p.place(c, &rblocks::repeater(rep_in, 1), "refresh repeater")?;
                p.support(c)?;
                t += 1;
                let d = add(add(w, mul(out, t)), (0, y - w.1, 0));
                p.dust(d, "landing")?;
                since = 1;
                t += 1;
                continue;
            }
            // The climb step needs its own headroom.
            let here = add(add(w, mul(out, t - 1)), (0, y - w.1, 0));
            if !p.free(add(here, (0, 1, 0))) {
                return Err(format!(
                    "the staircase for bit {i} needs {:?} clear to climb",
                    add(here, (0, 1, 0))
                ));
            }
            y += 1;
            p.dust(add(add(w, mul(out, t)), (0, y - w.1, 0)), "staircase")?;
            since += 1;
            climb -= 1;
            t += 1;
        }
        // Level out to the shared depth.
        while t <= depth {
            let c = add(add(w, mul(out, t)), (0, y - w.1, 0));
            if since >= REFRESH_AT && t < depth {
                p.place(c, &rblocks::repeater(rep_in, 1), "refresh repeater")?;
                p.support(c)?;
                since = 0;
            } else {
                p.dust(c, "stub run")?;
                since += 1;
            }
            t += 1;
        }
        // Gather back along the row axis to bit 0's lane. `t` now points one
        // past the last cell written, which MUST be the shared depth — every
        // lane has to reach the same plane for the gather to exist.
        debug_assert_eq!(
            t - 1,
            depth,
            "bit {i}'s lane ended at depth {} but the gather plane is at {depth}",
            t - 1
        );
        if t - 1 != depth {
            return Err(format!(
                "internal: bit {i}'s staircase ended at depth {}, not the shared {depth}",
                t - 1
            ));
        }
        let corner = add(add(w, mul(out, depth)), (0, y - w.1, 0));
        // The gather leg carries signal from the corner toward the column on
        // an output, and the other way on an input.
        let gsgn = if flow_out { -1 } else { 1 };
        let gather_in = rblocks::facing_name(gsgn * back.0, gsgn * back.2).ok_or("gather axis")?;
        let hops = pitch * if gather_to_last { n - 1 - i } else { i };
        let mut since_g = 0usize;
        for k in 1..=hops {
            let c = add(corner, mul(back, k));
            if k == 1 || since_g >= REFRESH_AT {
                p.place(c, &rblocks::repeater(gather_in, 1), "gather repeater")?;
                p.support(c)?;
                since_g = 0;
            } else {
                p.dust(c, "gather run")?;
                since_g += 1;
            }
        }
        let last = add(corner, mul(back, hops));
        // The column cell itself must be plain dust for a bus to land on.
        if hops > 0 {
            p.write(last, Some(rblocks::DUST));
            p.support(last)?;
        }
        column.push(last);
    }
    Ok(column)
}

/// How many refresh pauses a climb of `h` steps needs (each costs 2 extra
/// cells of depth: the repeater plus its landing).
///
/// The staircase enters the climb with one dust cell already spent (the cell
/// that must follow the mouth repeater), so a pause lands after every
/// `REFRESH_AT - 1` climb steps — and never after the last one, because the
/// loop exits first. Getting this off by one silently truncates the deepest
/// bit's lane: its corner is then computed short of where its dust actually
/// ends, the gather starts in mid-air, and exactly the TOP bit of the port
/// goes dead. The `debug_assert` in `lay_pivot` is what catches that.
fn refresh_pauses(h: usize) -> usize {
    h.saturating_sub(1) / (REFRESH_AT - 1)
}

#[cfg(test)]
mod tests {
    use super::*;

    fn slab(sx: i32, sy: i32, sz: i32) -> UniversalSchematic {
        let mut s = UniversalSchematic::new("t".to_string());
        for x in 0..sx {
            for y in 0..sy {
                for z in 0..sz {
                    s.set_block_from_string(x, y, z, rblocks::STONE).unwrap();
                }
            }
        }
        s
    }

    #[test]
    fn a_wall_lever_is_promoted_through_a_repeater() {
        let mut s = slab(4, 8, 1);
        // Lever on the -x face of the slab, pointing west.
        s.set_block_from_string(
            -1,
            3,
            0,
            "minecraft:lever[face=wall,facing=west,powered=false]",
        )
        .unwrap();
        let patch = plan_input(&s, &[(-1, 3, 0)]).unwrap();
        assert_eq!(patch.wires, vec![(-2, 3, 0)]);
        assert!(patch.writes[&(-1, 3, 0)]
            .as_deref()
            .unwrap()
            .contains("repeater"));
        assert!(rblocks::is_dust(
            patch.writes[&(-2, 3, 0)].as_deref().unwrap()
        ));
        // The saved state restores the lever byte-for-byte.
        assert_eq!(
            patch.saved[&(-1, 3, 0)].as_deref(),
            Some("minecraft:lever[face=wall,facing=west,powered=false]")
        );
    }

    #[test]
    fn a_floor_lever_is_promoted_in_place() {
        let mut s = slab(4, 4, 1);
        s.set_block_from_string(
            1,
            4,
            0,
            "minecraft:lever[face=floor,facing=north,powered=false]",
        )
        .unwrap();
        let patch = plan_input(&s, &[(1, 4, 0)]).unwrap();
        assert_eq!(patch.wires, vec![(1, 4, 0)]);
        assert!(rblocks::is_dust(
            patch.writes[&(1, 4, 0)].as_deref().unwrap()
        ));
    }

    #[test]
    fn a_horizontal_row_keeps_its_form_and_the_bus_adapts_it() {
        // Four floor levers marching along x at pitch 2 on top of a slab.
        let mut s = slab(8, 4, 4);
        for i in 0..4 {
            s.set_block_from_string(
                2 * i,
                4,
                0,
                "minecraft:lever[face=floor,facing=north,powered=false]",
            )
            .unwrap();
        }
        let hw: Vec<P3> = (0..4).map(|i| (2 * i, 4, 0)).collect();
        let patch = plan_input(&s, &hw).unwrap();

        // PROMOTION IS MINIMAL AND IN-PLACE: the row stays a row at its native
        // pitch, one write per bit, no form-conversion geometry anywhere.
        assert!(!patch.pivoted, "{}", patch.note);
        assert_eq!(patch.step, (2, 0, 0), "{}", patch.note);
        assert_eq!(patch.wires, hw, "the connection cells left the lever row");
        assert_eq!(
            patch.writes.len(),
            4,
            "promotion wrote {} cells for 4 bits: {:?}",
            patch.writes.len(),
            patch.writes
        );
        for w in &hw {
            assert!(
                patch
                    .writes
                    .get(w)
                    .and_then(|o| o.as_deref())
                    .is_some_and(rblocks::is_dust),
                "bit at {w:?} is not dust in place"
            );
        }

        // FORM ADAPTATION IS THE BUS'S: the same verified pivot, planned as a
        // pure set of cells the caller (a bus) owns and rips with itself.
        let at = |q: P3| -> Option<String> {
            s.get_block(q.0, q.1, q.2)
                .map(|b| b.to_string())
                .filter(|b| !b.contains("minecraft:air"))
                .filter(|_| !hw.contains(&q))
        };
        let plan = plan_pivot(&patch.wires, patch.step, (4, 4, 0), false, &at, None, None)
            .expect("the row must be adaptable onto the stack");
        // A vertical 2y-pitch column, bit order preserved.
        for (k, w) in plan.column.iter().enumerate() {
            assert_eq!(w.0, plan.column[0].0, "bit {k} left the column");
            assert_eq!(w.2, plan.column[0].2, "bit {k} left the column");
            assert_eq!(w.1, plan.column[0].1 + 2 * k as i32, "bit {k} off pitch");
        }
        // Every dust cell the adapter plans has a support beneath it.
        for (q, b) in &plan.cells {
            if rblocks::is_dust(b) {
                let below = add(*q, (0, -1, 0));
                let has = plan
                    .cells
                    .get(&below)
                    .map(|b| rblocks::is_sturdy_support(b))
                    .unwrap_or(false)
                    || s.get_block(below.0, below.1, below.2)
                        .map(|b| rblocks::is_sturdy_support(&b.to_string()))
                        .unwrap_or(false);
                assert!(has, "adapter dust at {q:?} floats");
            }
        }
    }

    #[test]
    fn a_pivot_can_gather_to_either_end_without_changing_its_size() {
        let wires: Vec<P3> = (0..8).map(|i| (2 + 2 * i, 2, 2)).collect();
        let at = |_q: P3| None;
        let first = plan_pivot(&wires, (2, 0, 0), (8, 2, 2), true, &at, None, Some(false)).unwrap();
        let last = plan_pivot(&wires, (2, 0, 0), (8, 2, 2), true, &at, None, Some(true)).unwrap();
        assert_eq!(first.column[0].0, wires[0].0);
        assert_eq!(last.column[0].0, wires[7].0);
        assert_eq!(first.cells.len(), last.cells.len());
    }

    #[test]
    fn a_ceiling_lever_is_refused_with_a_reason() {
        let mut s = slab(4, 8, 1);
        s.set_block_from_string(
            1,
            2,
            0,
            "minecraft:lever[face=ceiling,facing=north,powered=false]",
        )
        .unwrap();
        let e = plan_input(&s, &[(1, 2, 0)]).unwrap_err();
        assert!(e.contains("CEILING"), "{e}");
    }

    #[test]
    fn a_lamp_output_gets_a_dust_tap() {
        let mut s = slab(4, 4, 1);
        s.set_block_from_string(1, 4, 0, rblocks::LAMP).unwrap();
        let patch = plan_output(&s, &[(1, 4, 0)]).unwrap();
        assert_eq!(patch.wires, vec![(1, 5, 0)]);
        // The lamp itself is untouched.
        assert!(!patch.writes.contains_key(&(1, 4, 0)));
    }
}