Skip to main content

vla_lang/calc/
dense.rs

1//! A sheet's values kept as an array over its extent (KERNEL.20, slice 1,
2//! 2026-10-10): a read of a cell is an index into it and a range a walk of
3//! it, where a sheet's cells are a B-tree looked up one cell at a time.
4//!
5//! An engine's machine keeps one for each `.last` twin, filled at every
6//! frame's end in the walk that fills the twin's cells (`crate::machine`),
7//! since most of a frame's reads are of a twin: Life's rule reads 21 cells of
8//! `Screen.last` for each cell it computes, 18 of them in two ranges
9//! (Alonzo's `CART.1`, its profile). The twin's cells stay a sheet that every
10//! other reader reads, the record, the plane and a save; the array is the
11//! evaluator's, through [`super::Env::dense`]. A slot holds what a read of
12//! its cell answers, a [`Value`], or [`Value::Empty`] for a cell that holds
13//! nothing, which a walk skips as a walk of a sheet's cells skips a cell that
14//! is not there; so a walk of the array is the walk of the cells, value for
15//! value and in the same order, and an aggregate folding over either gives
16//! the same bits.
17//!
18//! The array is never larger than its owner allows (`limit`), so that a sheet
19//! of two cells far apart is not a million empty slots; such a sheet is read
20//! through its cells.
21//!
22//! Beside each slot the store keeps its number and its kind, and for each row
23//! of the extent how many of its slots hold no number (KERNEL.43), each
24//! written wherever a slot is written: so that an evaluation of many cells
25//! side by side in one row reads a reference's cells as one slice of numbers,
26//! and the slots themselves where any of them holds something else. A store
27//! no formula may read so keeps none (its owner says which), so that the end
28//! of a spreadsheet's frame, whose twins no formula reads, writes nothing
29//! more than its slots; one that comes to be read has them made from its
30//! slots.
31//!
32//! Since KERNEL.31 a machine keeps one for each of a frame's own sheets that
33//! its plan reads by a range or by a run's moving cell, each slot written
34//! where the frame keeps a value that changed, its numbers beside, so that a
35//! range of the frame's own cells is folded and walked by index as a twin's
36//! is, and a run's cells read one by slices: side by side in a row, a slice
37//! of one row (KERNEL.43), and one below the other in a column, the slots of
38//! one column read at the store's width.
39
40use super::graph::{Area, Extent};
41use super::value::Value;
42
43/// The most slots a store holds for each cell of its sheet (KERNEL.20): a
44/// slot is 24 bytes, its number and kind beside it 9 more (KERNEL.43), and a
45/// cell of a sheet's B-tree about 137 (measured 2026-10-10), so a store
46/// within this is smaller than the cells it shadows, and a sheet sparser than
47/// that, two cells far apart, is read through its cells.
48pub(crate) const SLOTS_A_CELL: u64 = 4;
49
50/// The most slots a store of a sheet of `cells` cells holds: [`SLOTS_A_CELL`]
51/// a cell, and never more than `most`, a grid's own limit. A twin's rule, and
52/// a frame's own sheet's (KERNEL.31), so that a machine and a door's census
53/// of the same grid keep the same sheets as arrays.
54pub(crate) fn limit(cells: usize, most: u64) -> u64 {
55    (cells as u64).saturating_mul(SLOTS_A_CELL).min(most)
56}
57
58/// Whether a store over an extent is within a limit, as [`Dense::over`]
59/// asks: a sheet that holds a cell, over no more cells than `limit`.
60pub(crate) fn fits(extent: Extent, limit: u64) -> bool {
61    extent.is_some_and(|((top, left), (bottom, right))| {
62        u64::from(right - left + 1) * u64::from(bottom - top + 1) <= limit
63    })
64}
65
66/// A slot's kind beside its number (KERNEL.43): a number, or anything else (a
67/// text, a truth value, an error, nothing), which a read by slice leaves to
68/// the slot itself.
69const NUMBER: u8 = 0;
70const OTHER: u8 = 1;
71
72/// A slot's number, 0 where it holds none, and its kind (KERNEL.43).
73#[inline]
74fn number_of(v: &Value) -> (f64, u8) {
75    match v {
76        Value::Number(n) => (*n, NUMBER),
77        _ => (0.0, OTHER),
78    }
79}
80
81/// A sheet's values over its extent, row by row. See the module's comment.
82#[derive(Clone, Debug, PartialEq)]
83pub struct Dense {
84    top: u32,
85    left: u32,
86    width: usize,
87    height: usize,
88    values: Vec<Value>,
89    /// The numbers beside the slots, for a store a formula may read by
90    /// slices (KERNEL.43); `None` for one none may.
91    shadow: Option<Shadow>,
92}
93
94/// Each slot's number and its kind, and by row of the extent how many of
95/// its slots hold no number (KERNEL.43).
96#[derive(Clone, Debug, Default, PartialEq)]
97struct Shadow {
98    numbers: Vec<f64>,
99    kinds: Vec<u8>,
100    odd: Vec<u32>,
101}
102
103impl Dense {
104    /// A store over an extent, in an old store's memory when one is given:
105    /// the old store as it stands when its extent is the same, for the walk
106    /// that keeps it to overwrite slot by slot, so that a text kept from one
107    /// frame to the next is not copied again; every slot empty otherwise.
108    /// Its numbers kept beside its slots where `numbers` asks (KERNEL.43).
109    /// `None` for a sheet with no cell, or for one whose extent holds more
110    /// than `limit` cells.
111    pub(crate) fn over(
112        extent: Extent,
113        limit: u64,
114        old: Option<Dense>,
115        numbers: bool,
116    ) -> Option<Dense> {
117        let ((top, left), (bottom, right)) = extent?;
118        if !fits(extent, limit) {
119            return None;
120        }
121        let width = (right - left + 1) as usize;
122        let height = (bottom - top + 1) as usize;
123        let (mut values, spare) = match old {
124            Some(mut d) if d.stands_over(extent) => {
125                d.shade(numbers);
126                return Some(d);
127            }
128            Some(d) => (d.values, d.shadow),
129            None => (Vec::new(), None),
130        };
131        values.clear();
132        values.resize(width * height, Value::Empty);
133        let mut d = Dense {
134            top,
135            left,
136            width,
137            height,
138            values,
139            shadow: None,
140        };
141        if numbers {
142            d.shadow = Some(d.made(spare.unwrap_or_default()));
143        }
144        Some(d)
145    }
146
147    /// The numbers kept beside the slots, or not (KERNEL.43): made from the
148    /// slots when the store comes to keep them, dropped when it stops.
149    pub(crate) fn shade(&mut self, numbers: bool) {
150        match (numbers, self.shadow.is_some()) {
151            (true, false) => self.shadow = Some(self.made(Shadow::default())),
152            (false, true) => self.shadow = None,
153            _ => {}
154        }
155    }
156
157    /// Whether the store keeps its numbers beside its slots (KERNEL.43).
158    #[cfg(test)]
159    pub(crate) fn keeps_numbers(&self) -> bool {
160        self.shadow.is_some()
161    }
162
163    /// The numbers beside the slots made from the slots, in a shadow's
164    /// memory.
165    fn made(&self, mut s: Shadow) -> Shadow {
166        let n = self.values.len();
167        s.numbers.clear();
168        s.numbers.reserve(n);
169        s.kinds.clear();
170        s.kinds.reserve(n);
171        for v in &self.values {
172            let (x, kind) = number_of(v);
173            s.numbers.push(x);
174            s.kinds.push(kind);
175        }
176        s.odd.clear();
177        s.odd.extend(
178            s.kinds
179                .chunks(self.width)
180                .map(|row| row.iter().filter(|&&k| k != NUMBER).count() as u32),
181        );
182        s
183    }
184
185    /// Whether the store stands over an extent, as [`Dense::over`] keeps it
186    /// whole when it does (KERNEL.36: the walk reads a slot before it
187    /// overwrites it only in a store kept so).
188    pub(crate) fn stands_over(&self, extent: Extent) -> bool {
189        extent.is_some_and(|((top, left), (bottom, right))| {
190            let width = (right - left + 1) as usize;
191            let height = (bottom - top + 1) as usize;
192            (self.top, self.left, self.width, self.height) == (top, left, width, height)
193        })
194    }
195
196    /// One cell's slot overwritten with a value, its number and kind beside
197    /// it; `false` for a cell outside the extent, which a store made over its
198    /// sheet's own extent never meets. The tests'; the walk finds a slot's
199    /// index first, to read the bit of a twin's cell behind it (KERNEL.36).
200    #[cfg(test)]
201    pub(crate) fn set(&mut self, row: u32, col: u32, v: Value) -> bool {
202        let Some(i) = self.index(row, col) else {
203            return false;
204        };
205        self.write_at(i, |slot| *slot = v);
206        true
207    }
208
209    /// A cell's slot: inside the extent, what a read of the cell answers,
210    /// `Value::Empty` for a cell that holds nothing; `None` outside it.
211    pub fn get(&self, row: u32, col: u32) -> Option<&Value> {
212        self.index(row, col).map(|i| &self.values[i])
213    }
214
215    /// A cell's slot's index, row by row; `None` outside the extent.
216    pub(crate) fn index(&self, row: u32, col: u32) -> Option<usize> {
217        let r = row.checked_sub(self.top)? as usize;
218        let c = col.checked_sub(self.left)? as usize;
219        (r < self.height && c < self.width).then(|| r * self.width + c)
220    }
221
222    /// How many slots the store holds, one a cell of its extent.
223    pub(crate) fn slots(&self) -> usize {
224        self.values.len()
225    }
226
227    /// The slot at an index as it stands, which a frame's end reads before
228    /// it writes it (KERNEL.27).
229    pub(crate) fn slot_at(&self, i: usize) -> &Value {
230        &self.values[i]
231    }
232
233    /// The slot at an index overwritten by `f` (KERNEL.27), and where the
234    /// store keeps its numbers, its number and its kind written beside it,
235    /// and its row's count of slots holding no number with them (KERNEL.43):
236    /// the one way a slot is written.
237    #[inline]
238    pub(crate) fn write_at(&mut self, i: usize, f: impl FnOnce(&mut Value)) {
239        f(&mut self.values[i]);
240        let Some(s) = self.shadow.as_mut() else {
241            return;
242        };
243        let (n, kind) = number_of(&self.values[i]);
244        if s.kinds[i] != kind {
245            let row = i / self.width;
246            if kind == NUMBER {
247                s.odd[row] -= 1;
248            } else {
249                s.odd[row] += 1;
250            }
251            s.kinds[i] = kind;
252        }
253        s.numbers[i] = n;
254    }
255
256    /// Of `n` cells side by side whose reference covers rows `top` to
257    /// `bottom` and `width` columns from `col` plus the cell's place, the
258    /// places `a..b` of those it keeps inside the extent (KERNEL.43), every
259    /// place where it `moves`, or none or all where it stands still: so that
260    /// a run's cells whose reference leaves the array at the row's ends are
261    /// read alone and the rest by slices.
262    pub(crate) fn inside(
263        &self,
264        (top, bottom): (u32, u32),
265        col: u32,
266        width: usize,
267        n: usize,
268        moves: bool,
269    ) -> (usize, usize) {
270        let (first, last) = (self.top, self.top + (self.height as u32 - 1));
271        if top < first || bottom > last {
272            return (0, 0);
273        }
274        let (left, right) = (
275            i64::from(self.left),
276            i64::from(self.left) + self.width as i64 - 1,
277        );
278        let (col, width) = (i64::from(col), width as i64);
279        if !moves {
280            let fits = col >= left && col + width - 1 <= right;
281            return if fits { (0, n) } else { (0, 0) };
282        }
283        let a = (left - col).clamp(0, n as i64);
284        let b = (right - (col + width - 1) + 1).clamp(0, n as i64);
285        if a >= b {
286            (0, 0)
287        } else {
288            (a as usize, b as usize)
289        }
290    }
291
292    /// The numbers of `n` slots side by side in one row, from a cell's
293    /// (KERNEL.43): what an evaluation of many cells reads a reference's
294    /// cells of one row as, when every one is inside the extent and holds a
295    /// number; `None` otherwise, or where the store keeps no numbers, its
296    /// slots read one at a time.
297    #[inline]
298    pub(crate) fn numbers_in_row(&self, row: u32, col: u32, n: usize) -> Option<&[f64]> {
299        let s = self.shadow.as_ref()?;
300        let r = row.checked_sub(self.top)? as usize;
301        let c = col.checked_sub(self.left)? as usize;
302        if r >= self.height || c + n > self.width {
303            return None;
304        }
305        let i = r * self.width + c;
306        if s.odd[r] != 0 && s.kinds[i..i + n].iter().any(|&k| k != NUMBER) {
307            return None;
308        }
309        Some(&s.numbers[i..i + n])
310    }
311
312    /// The numbers of `n` slots one below the other in one column, from a
313    /// cell's, pushed to `out` (KERNEL.31): what an evaluation of many cells
314    /// reads a reference's cells as where its cells stand one below the
315    /// other, when every one is inside the extent and holds a number;
316    /// `false`, nothing pushed, otherwise, or where the store keeps no
317    /// numbers, its slots read one at a time.
318    #[inline]
319    pub(crate) fn numbers_in_column(
320        &self,
321        row: u32,
322        col: u32,
323        n: usize,
324        out: &mut Vec<f64>,
325    ) -> bool {
326        let Some(s) = self.shadow.as_ref() else {
327            return false;
328        };
329        let (Some(r), Some(c)) = (row.checked_sub(self.top), col.checked_sub(self.left)) else {
330            return false;
331        };
332        let (r, c, w) = (r as usize, c as usize, self.width);
333        if r + n > self.height || c >= w {
334            return false;
335        }
336        let first = r * w + c;
337        if s.odd[r..r + n].iter().any(|&o| o != 0)
338            && (0..n).any(|j| s.kinds[first + j * w] != NUMBER)
339        {
340            return false;
341        }
342        out.extend((0..n).map(|j| s.numbers[first + j * w]));
343        true
344    }
345
346    /// The value at an index, and its cell (KERNEL.27).
347    pub(crate) fn at(&self, i: usize) -> (&Value, (u32, u32)) {
348        let (r, c) = (i / self.width, i % self.width);
349        (&self.values[i], (self.top + r as u32, self.left + c as u32))
350    }
351
352    /// Every value an area holds, row by row, to `f`, the area clamped to the
353    /// extent first, as a walk of the cells clamps it; an empty slot is
354    /// skipped. `f` is a type of its own and not a pointer, so that an
355    /// aggregate's fold is compiled into the walk (KERNEL.25).
356    pub fn each<E, F>(&self, area: &Area, mut f: F) -> Result<(), E>
357    where
358        F: FnMut(Value) -> Result<(), E>,
359    {
360        self.each_ref(area, |v| f(v.clone()))
361    }
362
363    /// [`Dense::each`]'s walk, each value lent where it stands: what an
364    /// evaluation of many cells at once folds an aggregate over, copying
365    /// nothing (KERNEL.26).
366    pub(crate) fn each_ref<E, F>(&self, area: &Area, mut f: F) -> Result<(), E>
367    where
368        F: FnMut(&Value) -> Result<(), E>,
369    {
370        let bottom = self.top + (self.height as u32 - 1);
371        let right = self.left + (self.width as u32 - 1);
372        let (top, left) = (area.top.max(self.top), area.left.max(self.left));
373        let (last_row, last_col) = (area.bottom.min(bottom), area.right.min(right));
374        if top > last_row || left > last_col {
375            return Ok(());
376        }
377        let n = (last_col - left + 1) as usize;
378        for row in top..=last_row {
379            let from = (row - self.top) as usize * self.width + (left - self.left) as usize;
380            for v in &self.values[from..from + n] {
381                if !matches!(v, Value::Empty) {
382                    f(v)?;
383                }
384            }
385        }
386        Ok(())
387    }
388}
389
390#[cfg(test)]
391mod tests {
392    use super::*;
393    use crate::calc::ErrorKind;
394    use crate::sheet::{MAX_COLUMN, MAX_ROW};
395
396    fn rect(top: u32, left: u32, bottom: u32, right: u32) -> Area {
397        Area {
398            sheet: 0,
399            top,
400            left,
401            bottom,
402            right,
403        }
404    }
405
406    fn walk(d: &Dense, area: Area) -> Vec<Value> {
407        let mut out = Vec::new();
408        d.each::<(), _>(&area, |v| {
409            out.push(v);
410            Ok(())
411        })
412        .unwrap();
413        out
414    }
415
416    #[test]
417    fn a_store_is_its_extent_row_by_row_and_skips_what_is_empty() {
418        // B2:D4, every cell but C3 holding a value, one of each kind.
419        let mut d = Dense::over(Some(((2, 2), (4, 4))), 9, None, true).unwrap();
420        let n = Value::Number;
421        let kinds = [
422            n(1.0),
423            Value::Text("x".to_string()),
424            Value::Bool(true),
425            n(4.0),
426            Value::Error(ErrorKind::NA),
427            n(-0.5),
428            n(7.0),
429            n(8.0),
430        ];
431        let cells = [
432            (2, 2),
433            (2, 3),
434            (2, 4),
435            (3, 2),
436            (3, 4),
437            (4, 2),
438            (4, 3),
439            (4, 4),
440        ];
441        for ((row, col), v) in cells.iter().zip(&kinds) {
442            assert!(d.set(*row, *col, v.clone()));
443        }
444        assert!(!d.set(1, 2, Value::Empty), "above the extent");
445        assert!(!d.set(2, 5, Value::Empty), "right of it");
446        assert_eq!(d.get(2, 3), Some(&kinds[1]));
447        assert_eq!(d.get(3, 3), Some(&Value::Empty), "a cell holding nothing");
448        assert_eq!(d.get(5, 2), None, "outside");
449        assert_eq!(d.get(2, 1), None);
450        // The extent, then areas past it, across it and outside it, each
451        // clamped as a walk of the cells clamps it.
452        assert_eq!(walk(&d, rect(2, 2, 4, 4)), kinds.to_vec());
453        assert_eq!(walk(&d, rect(1, 1, MAX_ROW, MAX_COLUMN)), kinds.to_vec());
454        assert_eq!(
455            walk(&d, rect(3, 3, 9, 9)),
456            vec![kinds[4].clone(), n(7.0), n(8.0)]
457        );
458        assert_eq!(walk(&d, rect(1, 3, 3, 3)), vec![kinds[1].clone()]);
459        assert_eq!(walk(&d, rect(3, 3, 3, 3)), Vec::<Value>::new());
460        assert_eq!(walk(&d, rect(5, 1, 9, 9)), Vec::<Value>::new());
461        assert_eq!(walk(&d, rect(1, 5, 1, 9)), Vec::<Value>::new());
462        // A fold stops at its first error, as a walk of the cells does.
463        let mut seen = 0;
464        let stopped = d.each(&rect(2, 2, 4, 4), |v| {
465            seen += 1;
466            if matches!(v, Value::Bool(_)) {
467                Err("stop")
468            } else {
469                Ok(())
470            }
471        });
472        assert_eq!((stopped, seen), (Err("stop"), 3));
473    }
474
475    #[test]
476    fn a_store_keeps_its_memory_and_refuses_what_passes_its_limit() {
477        let extent = Some(((1, 1), (10, 10)));
478        let mut d = Dense::over(extent, 100, None, true).unwrap();
479        assert!(d.set(10, 10, Value::Text("kept".to_string())));
480        let at = d.values.as_ptr();
481        let Some(Value::Text(text)) = d.get(10, 10) else {
482            panic!("a text")
483        };
484        let text_at = text.as_ptr();
485        // The next frame's store over the same extent is the store as it
486        // stands, for the walk to overwrite: a text in it is not copied.
487        let same = Dense::over(extent, 100, Some(d), true).unwrap();
488        assert_eq!(same.values.as_ptr(), at);
489        let Some(Value::Text(text)) = same.get(10, 10) else {
490            panic!("the text kept")
491        };
492        assert_eq!(text.as_ptr(), text_at, "the same text, not a copy");
493        // Over another extent, the same memory with every slot empty again.
494        let again = Dense::over(Some(((3, 3), (5, 5))), 100, Some(same), true).unwrap();
495        assert_eq!(again.values.as_ptr(), at, "the old store's memory");
496        assert_eq!(again.values.len(), 9);
497        assert!(again.values.iter().all(|v| *v == Value::Empty));
498        assert_eq!(again.get(3, 3), Some(&Value::Empty));
499        assert!(Dense::over(Some(((1, 1), (10, 10))), 99, None, true).is_none());
500        assert!(
501            Dense::over(None, 100, None, true).is_none(),
502            "a sheet with no cell"
503        );
504        // The largest extent a sheet can have is refused, never allocated.
505        assert!(Dense::over(Some(((1, 1), (MAX_ROW, MAX_COLUMN))), 1 << 20, None, true).is_none());
506    }
507
508    /// What a read of slices must answer, from the slots alone: the numbers
509    /// of `n` slots of a row from a cell, when each is inside the extent and
510    /// holds a number.
511    fn slice_of(d: &Dense, row: u32, col: u32, n: usize) -> Option<Vec<f64>> {
512        (0..n)
513            .map(|j| match d.get(row, col + j as u32)? {
514                Value::Number(x) => Some(*x),
515                _ => None,
516            })
517            .collect()
518    }
519
520    #[test]
521    fn the_cells_a_reference_keeps_inside_are_those_it_leaves_nowhere() {
522        // KERNEL.43: over a store of B2:F6, for references of every width
523        // moving with cells side by side, and standing still, from columns
524        // before, inside and past the extent and rows inside and out, the
525        // places `inside` answers are the cells whose reference stays wholly
526        // inside the extent, found one by one.
527        let d = Dense::over(Some(((2, 2), (6, 6))), 100, None, true).unwrap();
528        for (top, bottom) in [(2, 2), (3, 5), (1, 3), (6, 7), (2, 6)] {
529            for col in 0..=8u32 {
530                for width in 1..=4usize {
531                    for n in 1..=7usize {
532                        for moves in [true, false] {
533                            let fits = |i: usize| {
534                                let left = col + if moves { i as u32 } else { 0 };
535                                let right = left + width as u32 - 1;
536                                top >= 2 && bottom <= 6 && left >= 2 && right <= 6
537                            };
538                            let want: Vec<usize> = (0..n).filter(|&i| fits(i)).collect();
539                            let (a, b) = d.inside((top, bottom), col, width, n, moves);
540                            assert_eq!(
541                                (a..b).collect::<Vec<_>>(),
542                                want,
543                                "rows {top}-{bottom}, column {col}, width {width}, {n} cells, moving {moves}"
544                            );
545                        }
546                    }
547                }
548            }
549        }
550    }
551
552    /// What a read of a column must answer, from the slots alone: the
553    /// numbers of `n` slots one below the other from a cell, when each is
554    /// inside the extent and holds a number (KERNEL.31).
555    fn column_of(d: &Dense, row: u32, col: u32, n: usize) -> Option<Vec<f64>> {
556        (0..n)
557            .map(|j| match d.get(row + j as u32, col)? {
558                Value::Number(x) => Some(*x),
559                _ => None,
560            })
561            .collect()
562    }
563
564    /// A store's numbers spelled by their bits, so that `-0.0` is not `0.0`.
565    fn spelled(s: &Shadow) -> (Vec<u64>, &[u8], &[u32]) {
566        (
567            s.numbers.iter().map(|x| x.to_bits()).collect(),
568            &s.kinds,
569            &s.odd,
570        )
571    }
572
573    #[test]
574    fn a_store_s_numbers_are_its_slots_after_every_write() {
575        // KERNEL.43: slots written at random with values of every kind, the
576        // store kept and made again over the same extent and another, its
577        // numbers kept, dropped and kept again; after each write, a store
578        // keeping its numbers holds the ones its slots make, to the bit, and
579        // every slice a read may ask for, inside the extent, across its edges
580        // and outside it, is what the slots say, and a store keeping none
581        // answers none.
582        let mut seed: u64 = 0x5EED;
583        let mut next = |n: u64| {
584            seed = seed
585                .wrapping_mul(6_364_136_223_846_793_005)
586                .wrapping_add(1_442_695_040_888_963_407);
587            (seed >> 33) % n
588        };
589        let values = [
590            Value::Number(0.0),
591            Value::Number(-0.0),
592            Value::Number(1.5),
593            Value::Number(-3.0),
594            Value::Number(1e300),
595            Value::Text("x".to_string()),
596            Value::Bool(true),
597            Value::Error(ErrorKind::NA),
598            Value::Empty,
599        ];
600        let extents = [Some(((2, 3), (6, 9))), Some(((1, 1), (3, 4)))];
601        let mut d = Dense::over(extents[0], 100, None, true).unwrap();
602        let (mut kept, mut columns) = (0, 0);
603        for round in 0..2000 {
604            // Every hundredth round the numbers dropped or kept again, every
605            // five hundredth the store made again, keeping them but once.
606            if round % 500 == 499 {
607                d = Dense::over(extents[next(2) as usize], 100, Some(d), round != 999).unwrap();
608            } else if round % 100 == 99 {
609                d.shade(round / 100 % 2 == 1);
610            }
611            let (top, left) = (d.top, d.left);
612            let (row, col) = (
613                top + next(d.height as u64) as u32,
614                left + next(d.width as u64) as u32,
615            );
616            let v = values[next(values.len() as u64) as usize].clone();
617            assert!(d.set(row, col, v));
618            if let Some(s) = &d.shadow {
619                kept += 1;
620                for r in 0..d.height {
621                    let row = top + r as u32;
622                    let odd = (0..d.width as u32)
623                        .filter(|&c| !matches!(d.get(row, left + c), Some(Value::Number(_))))
624                        .count();
625                    assert_eq!(s.odd[r] as usize, odd, "round {round}, row {row}");
626                }
627                assert_eq!(
628                    spelled(s),
629                    spelled(&d.made(Shadow::default())),
630                    "round {round}"
631                );
632            }
633            for _ in 0..4 {
634                let row = (top + next(d.height as u64 + 2) as u32).saturating_sub(1);
635                let col = (left + next(d.width as u64 + 2) as u32).saturating_sub(1);
636                let n = 1 + next(d.width as u64 + 1) as usize;
637                let got = d
638                    .numbers_in_row(row, col, n)
639                    .map(|s| s.iter().map(|x| x.to_bits()).collect::<Vec<_>>());
640                let want = slice_of(&d, row, col, n)
641                    .filter(|_| d.shadow.is_some())
642                    .map(|s| s.iter().map(|x| x.to_bits()).collect::<Vec<_>>());
643                assert_eq!(got, want, "round {round}: {n} slots from ({row}, {col})");
644                // And the slots one below the other from it (KERNEL.31),
645                // pushed after what the lane holds already, or nothing.
646                let n = 1 + next(d.height as u64 + 1) as usize;
647                let mut out = vec![7.0];
648                let read = d.numbers_in_column(row, col, n, &mut out);
649                let want = column_of(&d, row, col, n).filter(|_| d.shadow.is_some());
650                let got = read.then(|| out[1..].iter().map(|x| x.to_bits()).collect::<Vec<_>>());
651                assert_eq!(
652                    got,
653                    want.map(|s| s.iter().map(|x| x.to_bits()).collect::<Vec<_>>()),
654                    "round {round}: {n} slots down from ({row}, {col})"
655                );
656                assert_eq!(out.len(), if read { 1 + n } else { 1 }, "round {round}");
657                columns += usize::from(read);
658            }
659        }
660        assert!(
661            0 < kept && kept < 2000,
662            "rounds with numbers and without: {kept}"
663        );
664        assert!(columns > 0, "a column read whole somewhere");
665    }
666}