paint.rs

23.8 kB · rust · 769 lines

1use super::cell::{moore, Cell};2use super::colors::{gradient, Color};3use super::colors::{4    BLACK, BLUE, BROWN, CYAN, GRAY, GREEN, INDIGO, MINT, ORANGE, PINK, PURPLE, RED, TEAL, WHITE,5    YELLOW,6};7use super::enums::Mode;8use super::errors::{value_error, MrlyError, Result};9use super::rng::Rng;10use super::state::{choice, randint, sample, shuffle};11use super::tensor::{Dtype, Tensor};12use serde::{Deserialize, Serialize};13use std::collections::HashMap;1415/// The seven ways a paint distributes its colors over a cell.16#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]17pub enum Edition {18    /// One color per cell type.19    Simple,20    /// Color by cell index.21    Index,22    /// Color by concentric layer.23    Layers,24    /// Color by neighbor count.25    Neighbors,26    /// Color by row.27    Rows,28    /// Color by column.29    Columns,30    /// A random color per cell.31    Random,32}3334/// The fifteen named inks a paint draws from.35#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]36pub enum Ink {37    /// Black (0, 0, 0).38    Black,39    /// White (255, 255, 255).40    White,41    /// Red (255, 61, 64).42    Red,43    /// Orange (255, 143, 44).44    Orange,45    /// Yellow (255, 209, 0).46    Yellow,47    /// Green (50, 204, 88).48    Green,49    /// Mint (0, 209, 187).50    Mint,51    /// Teal (0, 202, 216).52    Teal,53    /// Cyan (30, 201, 243).54    Cyan,55    /// Blue (0, 140, 255).56    Blue,57    /// Indigo (103, 104, 250).58    Indigo,59    /// Purple (211, 50, 233).60    Purple,61    /// The palette pink.62    Pink,63    /// Brown (177, 132, 98).64    Brown,65    /// Gray (142, 142, 147).66    Gray,67}6869/// The two ways secondary colors are drawn.70#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]71pub enum Scheme {72    /// Distinct secondary inks.73    Multicolor,74    /// One secondary ink stepped through shades.75    Multitone,76}7778/// The side of the figure the primary ink lands on.79#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]80pub enum Target {81    /// The primary on the filled cells, secondaries on the empty.82    Fill,83    /// The primary on the empty cells, secondaries on the filled.84    Void,85}8687impl Edition {88    /// Returns every edition in canonical order.89    pub fn all() -> [Edition; 7] {90        [91            Edition::Simple,92            Edition::Index,93            Edition::Layers,94            Edition::Neighbors,95            Edition::Rows,96            Edition::Columns,97            Edition::Random,98        ]99    }100    /// Returns the cell-painting mode this edition renders with.101    pub fn mode(self) -> Mode {102        match self {103            Edition::Simple => Mode::Type,104            Edition::Index => Mode::Index,105            Edition::Layers => Mode::Tag,106            Edition::Neighbors => Mode::Tag,107            Edition::Rows => Mode::Row,108            Edition::Columns => Mode::Column,109            Edition::Random => Mode::Random,110        }111    }112    /// Returns the edition's display name.113    pub fn name(self) -> &'static str {114        match self {115            Edition::Simple => "Simple",116            Edition::Index => "Index",117            Edition::Layers => "Layers",118            Edition::Neighbors => "Neighbors",119            Edition::Rows => "Rows",120            Edition::Columns => "Columns",121            Edition::Random => "Random",122        }123    }124    /// Parses a display name back into its edition, or an error for an unknown name.125    pub fn parse(name: &str) -> Result<Edition> {126        match name {127            "Simple" => Ok(Edition::Simple),128            "Index" => Ok(Edition::Index),129            "Layers" => Ok(Edition::Layers),130            "Neighbors" => Ok(Edition::Neighbors),131            "Rows" => Ok(Edition::Rows),132            "Columns" => Ok(Edition::Columns),133            "Random" => Ok(Edition::Random),134            other => value_error(format!("unknown edition {other:?}.")),135        }136    }137}138139impl Ink {140    /// Returns the ink's color.141    pub fn color(self) -> Color {142        match self {143            Ink::Black => BLACK,144            Ink::White => WHITE,145            Ink::Red => RED,146            Ink::Orange => ORANGE,147            Ink::Yellow => YELLOW,148            Ink::Green => GREEN,149            Ink::Mint => MINT,150            Ink::Teal => TEAL,151            Ink::Cyan => CYAN,152            Ink::Blue => BLUE,153            Ink::Indigo => INDIGO,154            Ink::Purple => PURPLE,155            Ink::Pink => PINK,156            Ink::Brown => BROWN,157            Ink::Gray => GRAY,158        }159    }160    /// Returns every ink in canonical order.161    pub fn all() -> [Ink; 15] {162        [163            Ink::Black,164            Ink::White,165            Ink::Red,166            Ink::Orange,167            Ink::Yellow,168            Ink::Green,169            Ink::Mint,170            Ink::Teal,171            Ink::Cyan,172            Ink::Blue,173            Ink::Indigo,174            Ink::Purple,175            Ink::Pink,176            Ink::Brown,177            Ink::Gray,178        ]179    }180    /// Returns the ink's display name.181    pub fn name(self) -> &'static str {182        match self {183            Ink::Black => "Black",184            Ink::White => "White",185            Ink::Red => "Red",186            Ink::Orange => "Orange",187            Ink::Yellow => "Yellow",188            Ink::Green => "Green",189            Ink::Mint => "Mint",190            Ink::Teal => "Teal",191            Ink::Cyan => "Cyan",192            Ink::Blue => "Blue",193            Ink::Indigo => "Indigo",194            Ink::Purple => "Purple",195            Ink::Pink => "Pink",196            Ink::Brown => "Brown",197            Ink::Gray => "Gray",198        }199    }200    /// Parses a display name back into its ink, or an error for an unknown name.201    pub fn parse(name: &str) -> Result<Ink> {202        Ink::all()203            .into_iter()204            .find(|ink| ink.name() == name)205            .ok_or_else(|| MrlyError::Value(format!("unknown ink {name:?}.")))206    }207}208209impl Scheme {210    /// Returns both schemes.211    pub fn all() -> [Scheme; 2] {212        [Scheme::Multicolor, Scheme::Multitone]213    }214    /// Returns the scheme's display name.215    pub fn name(self) -> &'static str {216        match self {217            Scheme::Multicolor => "Multicolor",218            Scheme::Multitone => "Multitone",219        }220    }221    /// Parses a display name back into its scheme, or an error for an unknown name.222    pub fn parse(name: &str) -> Result<Scheme> {223        match name {224            "Multicolor" => Ok(Scheme::Multicolor),225            "Multitone" => Ok(Scheme::Multitone),226            other => value_error(format!("unknown scheme {other:?}.")),227        }228    }229}230231impl Target {232    /// Returns both targets.233    pub fn all() -> [Target; 2] {234        [Target::Fill, Target::Void]235    }236    /// Returns the target's display name.237    pub fn name(self) -> &'static str {238        match self {239            Target::Fill => "Fill",240            Target::Void => "Void",241        }242    }243    /// Parses a display name back into its target, or an error for an unknown name.244    pub fn parse(name: &str) -> Result<Target> {245        match name {246            "Fill" => Ok(Target::Fill),247            "Void" => Ok(Target::Void),248            other => value_error(format!("unknown target {other:?}.")),249        }250    }251}252253const LEVELS: [u8; 2] = [33, 66];254255/// The constraints a caller may put on a random paint.256#[derive(Clone, Debug, Default)]257pub struct Config {258    /// The editions allowed, or None for all seven.259    pub editions: Option<Vec<Edition>>,260    /// The primary inks allowed, or None for black and white.261    pub primaries: Option<Vec<Ink>>,262    /// The forced target, or None for a coin flip.263    pub target: Option<Target>,264}265266/// A complete coloring recipe for one cell.267#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]268pub struct Paint {269    /// The coloring edition.270    pub edition: Edition,271    /// The secondary color scheme.272    pub scheme: Scheme,273    /// The side the primary ink lands on.274    pub target: Target,275    /// The primary ink.276    pub primary: Ink,277    /// The secondary inks.278    pub secondary: Vec<Ink>,279    /// The shade indices of a multitone ramp.280    pub shades: Vec<usize>,281}282283impl Paint {284    /// Builds a black-primary, fill-target, multicolor paint for an edition.285    pub fn new(edition: Edition) -> Paint {286        Paint {287            edition,288            scheme: Scheme::Multicolor,289            target: Target::Fill,290            primary: Ink::Black,291            secondary: Vec::new(),292            shades: Vec::new(),293        }294    }295    /// Returns true for the Simple edition.296    pub fn is_simple(&self) -> bool {297        self.edition == Edition::Simple298    }299    fn wipe(&mut self) {300        self.secondary.clear();301        self.shades.clear();302    }303}304305/// Draws a random edition from the allowed list, or from all seven.306pub fn random_edition(editions: Option<&[Edition]>) -> Edition {307    match editions {308        Some(list) if !list.is_empty() => choice(list),309        _ => choice(&Edition::all()),310    }311}312313fn random_primary(primaries: Option<&[Ink]>) -> Ink {314    if let Some(list) = primaries {315        if list.len() == 1 {316            return list[0];317        }318    }319    let mut choices = vec![Ink::Black, Ink::White];320    if let Some(list) = primaries {321        choices.retain(|ink| list.contains(ink));322    }323    if choices.is_empty() {324        choices = vec![Ink::Black, Ink::White];325    }326    choice(&choices)327}328329fn random_secondary(count: Option<usize>, primary: Option<Ink>) -> Vec<Ink> {330    let mut inks = Ink::all().to_vec();331    match primary {332        Some(p) => inks.retain(|&ink| ink != p),333        None => inks.retain(|&ink| ink != Ink::Black && ink != Ink::White),334    }335    let count = count.unwrap_or_else(|| randint(2, 9) as usize);336    let count = count.min(inks.len());337    sample(&inks, count)338}339340fn random_shades(count: Option<usize>, primary: Option<Ink>) -> Vec<usize> {341    if count == Some(1) {342        return if primary == Some(Ink::Black) {343            vec![0]344        } else {345            vec![1]346        };347    }348    let count = count.unwrap_or_else(|| randint(2, 9) as usize);349    let mut shades: Vec<usize> = (0..count).collect();350    shuffle(&mut shades);351    shades352}353354/// Redraws the paint's secondary inks and shades under its scheme.355pub fn reroll(mut paint: Paint) -> Paint {356    let (colors, shades) = if paint.is_simple() {357        (Some(1), Some(1))358    } else if paint.edition == Edition::Index {359        (Some(2), Some(2))360    } else {361        (None, None)362    };363    match paint.scheme {364        Scheme::Multicolor => {365            paint.wipe();366            paint.secondary = random_secondary(colors, Some(paint.primary));367        }368        Scheme::Multitone => {369            paint.wipe();370            paint.secondary = random_secondary(Some(1), None);371            paint.shades = random_shades(shades, Some(paint.primary));372        }373    }374    paint375}376377/// Draws the paint's scheme, target and primary under the config, then rerolls the rest.378pub fn setup(mut paint: Paint, config: &Config) -> Paint {379    paint.scheme = choice(&[Scheme::Multicolor, Scheme::Multitone]);380    paint.target = config381        .target382        .unwrap_or_else(|| choice(&[Target::Fill, Target::Void]));383    paint.primary = random_primary(config.primaries.as_deref());384    reroll(paint)385}386387fn remap_tags(target: Target, cell: &mut Cell) -> usize {388    let target_value = match target {389        Target::Fill => 0,390        Target::Void => 1,391    };392    let tags = match &cell.tags {393        Some(tags) => tags.clone(),394        None => return 0,395    };396    let relevant: Vec<u8> = cell397        .types398        .bytes()399        .iter()400        .zip(tags.bytes().iter())401        .filter(|(&t, _)| t == target_value)402        .map(|(_, &tag)| tag)403        .collect();404    if relevant.is_empty() {405        return 0;406    }407    let mut unique: Vec<u8> = relevant.clone();408    unique.sort_unstable();409    unique.dedup();410    let lookup: HashMap<u8, u8> = unique411        .iter()412        .enumerate()413        .map(|(i, &tag)| (tag, i as u8))414        .collect();415    let data: Vec<u8> = tags416        .bytes()417        .iter()418        .map(|tag| *lookup.get(tag).unwrap_or(&0))419        .collect();420    cell.tags = Some(Tensor::of(data, tags.shape.clone()));421    unique.len()422}423424fn apply_colors(mut paint: Paint, max_val: usize) -> Paint {425    match paint.scheme {426        Scheme::Multicolor => {427            paint.wipe();428            paint.secondary = random_secondary(Some(max_val), Some(paint.primary));429        }430        Scheme::Multitone => {431            paint.wipe();432            paint.secondary = random_secondary(Some(1), None);433            paint.shades = random_shades(Some(max_val), Some(paint.primary));434        }435    }436    paint437}438439/// Tags the cell for the Layers and Neighbors editions and returns the distinct tag count on the secondary side.440pub fn tag(441    cell: &mut Cell,442    edition: Edition,443    target: Target,444    mask: Option<&Tensor>,445) -> Result<usize> {446    match edition {447        Edition::Layers => {448            *cell = cell.clone().layers(Dtype::U8);449            Ok(remap_tags(target, cell))450        }451        Edition::Neighbors => {452            let owned;453            let neighbor_mask = match mask {454                Some(m) => m,455                None => {456                    owned = moore(cell.types.shape.len());457                    &owned458                }459            };460            *cell = cell.clone().neighbors(neighbor_mask, 1, false, Dtype::U8)?;461            Ok(remap_tags(target, cell))462        }463        _ => Ok(0),464    }465}466467/// Tags the cell for Layers and Neighbors paints and sizes the palette to the tag count.468pub fn prime(mut paint: Paint, cell: &mut Cell, mask: Option<&Tensor>) -> Result<Paint> {469    if matches!(paint.edition, Edition::Layers | Edition::Neighbors) {470        let max_val = tag(cell, paint.edition, paint.target, mask)?;471        paint = apply_colors(paint, max_val.max(1));472    }473    Ok(paint)474}475476fn primary_colors(paint: &Paint) -> Vec<Color> {477    vec![paint.primary.color()]478}479480fn secondary_colors(paint: &Paint) -> Result<Vec<Color>> {481    let mut colors: Vec<Color> = paint.secondary.iter().map(|ink| ink.color()).collect();482    if paint.scheme == Scheme::Multitone {483        if colors.is_empty() {484            return value_error("multitone paint needs a base color.");485        }486        let c1 = colors[0].lightness(LEVELS[0])?;487        let c2 = colors[0].lightness(LEVELS[1])?;488        let mut ramp = vec![c1, c2];489        let steps = paint.shades.len();490        if steps > 2 {491            ramp = gradient(&ramp, steps)?;492        }493        colors = paint494            .shades495            .iter()496            .map(|&i| ramp[i.min(ramp.len() - 1)])497            .collect();498    }499    Ok(colors)500}501502/// Colors the cell from the paint's inks under its edition mode.503pub fn apply(paint: &Paint, cell: &mut Cell) -> Result<()> {504    let primary = primary_colors(paint);505    let secondary = secondary_colors(paint)?;506    let mapping: HashMap<u8, Vec<Color>> = match paint.target {507        Target::Fill => HashMap::from([(0, secondary), (1, primary)]),508        Target::Void => HashMap::from([(0, primary), (1, secondary)]),509    };510    *cell = cell.clone().paint(&mapping, paint.edition.mode());511    Ok(())512}513514fn scatter(paint: &Paint, cell: &mut Cell) -> Result<()> {515    let rgba = |colors: Vec<Color>| -> Vec<[u8; 4]> {516        colors.iter().map(|c| [c.r, c.g, c.b, c.a]).collect()517    };518    let primary = rgba(primary_colors(paint));519    let secondary = rgba(secondary_colors(paint)?);520    let (void_inks, fill_inks) = match paint.target {521        Target::Fill => (secondary, primary),522        Target::Void => (primary, secondary),523    };524    let mut rng = Rng::new(0);525    let size = cell.size();526    let mut colors = cell527        .colors528        .take()529        .unwrap_or_else(|| vec![[0, 0, 0, 0]; size]);530    for (flat, &t) in cell.types.bytes().iter().enumerate() {531        let palette = match t {532            0 => &void_inks,533            1 => &fill_inks,534            _ => continue,535        };536        if !palette.is_empty() {537            colors[flat] = palette[rng.below(palette.len())];538        }539    }540    cell.colors = Some(colors);541    Ok(())542}543544/// Replays a stored paint onto a cell, tagging first and rendering deterministically.545pub fn coat(cell: &mut Cell, paint: &Paint, mask: Option<&Tensor>) -> Result<()> {546    tag(cell, paint.edition, paint.target, mask)?;547    if paint.edition.mode() == Mode::Random {548        scatter(paint, cell)549    } else {550        apply(paint, cell)551    }552}553554/// Draws a random paint under the config, applies it to the cell, and returns the recipe.555pub fn paint(cell: &mut Cell, config: &Config, mask: Option<&Tensor>) -> Result<Paint> {556    let edition = random_edition(config.editions.as_deref());557    let mut p = Paint::new(edition);558    p = setup(p, config);559    p = prime(p, cell, mask)?;560    apply(&p, cell)?;561    Ok(p)562}563564#[cfg(test)]565mod tests {566    use super::*;567    use crate::atoms;568    use crate::json;569    use crate::state::{guard, seed};570    fn round_trip(paint: &Paint) -> Paint {571        serde_json::from_value(serde_json::to_value(paint).unwrap()).unwrap()572    }573    #[test]574    fn simple_paint_colors_every_cell() {575        let _g = guard();576        seed(1);577        let mut cell = Cell::new(atoms::carpet_2d(9));578        let config = Config::default();579        let _ = paint(&mut cell, &config, None).unwrap();580        assert!(cell.colors.is_some());581        let colors = cell.colors.as_ref().unwrap();582        assert_eq!(colors.len(), cell.size());583        assert!(colors.iter().all(|rgba| rgba[3] == 255));584        assert_eq!(cell.size(), 81);585    }586    #[test]587    fn every_edition_paints_2d() {588        let _g = guard();589        for (i, edition) in Edition::all().into_iter().enumerate() {590            seed(i as u64);591            let mut cell = Cell::new(atoms::carpet_2d(9));592            let mut p = Paint::new(edition);593            p = setup(p, &Config::default());594            p = prime(p, &mut cell, None).unwrap();595            apply(&p, &mut cell).unwrap();596            let colors = cell.colors.as_ref().unwrap();597            assert_eq!(colors.len(), cell.size(), "edition {:?}", edition);598        }599    }600    #[test]601    fn every_edition_paints_3d() {602        let _g = guard();603        for (i, edition) in Edition::all().into_iter().enumerate() {604            seed(100 + i as u64);605            let mut cell = Cell::new(atoms::carpet_3d(3));606            let mut p = Paint::new(edition);607            p = setup(p, &Config::default());608            p = prime(p, &mut cell, None).unwrap();609            apply(&p, &mut cell).unwrap();610            let colors = cell.colors.as_ref().unwrap();611            assert_eq!(colors.len(), cell.size(), "edition {:?} 3d", edition);612        }613    }614    #[test]615    fn paint_is_seeded() {616        let _g = guard();617        seed(7);618        let mut a = Cell::new(atoms::carpet_2d(5));619        let pa = paint(&mut a, &Config::default(), None).unwrap();620        seed(7);621        let mut b = Cell::new(atoms::carpet_2d(5));622        let pb = paint(&mut b, &Config::default(), None).unwrap();623        assert_eq!(pa, pb);624        assert_eq!(a, b);625    }626    #[test]627    fn multitone_builds_shade_ramp() {628        let _g = guard();629        seed(3);630        let mut p = Paint::new(Edition::Layers);631        p.scheme = Scheme::Multitone;632        p.primary = Ink::Black;633        p.secondary = vec![Ink::Blue];634        p.shades = vec![0, 1, 2, 1, 0];635        let colors = secondary_colors(&p).unwrap();636        assert_eq!(colors.len(), p.shades.len());637    }638    #[test]639    fn paint_json_round_trips() {640        let mut p = Paint::new(Edition::Layers);641        p.scheme = Scheme::Multitone;642        p.target = Target::Void;643        p.primary = Ink::White;644        p.secondary = vec![Ink::Blue];645        p.shades = vec![2, 0, 1];646        assert_eq!(p, round_trip(&p));647        let json = serde_json::to_value(&p).unwrap();648        assert_eq!(json["edition"], "Layers");649        assert_eq!(json["secondary"], json!(["Blue"]));650        let mut q = Paint::new(Edition::Simple);651        q.secondary = vec![Ink::Teal];652        assert_eq!(q, round_trip(&q));653    }654    #[test]655    fn paint_json_rejects_garbage() {656        let read = |value| serde_json::from_value::<Paint>(value);657        assert!(read(json!({})).is_err());658        assert!(read(json!({659            "edition": "Sparkle", "scheme": "Multicolor", "target": "Fill",660            "primary": "Black", "secondary": [], "shades": [],661        }))662        .is_err());663        assert!(read(json!({664            "edition": "Simple", "scheme": "Multicolor", "target": "Fill",665            "primary": "Black", "secondary": ["Beige"], "shades": [],666        }))667        .is_err());668        assert!(read(json!({669            "edition": "Simple", "scheme": "Multicolor", "target": "Fill",670            "primary": "Black", "secondary": [], "shades": ["soup"],671        }))672        .is_err());673    }674    #[test]675    fn names_parse_back() {676        for edition in Edition::all() {677            assert_eq!(edition, Edition::parse(edition.name()).unwrap());678        }679        for ink in Ink::all() {680            assert_eq!(ink, Ink::parse(ink.name()).unwrap());681        }682        for scheme in Scheme::all() {683            assert_eq!(scheme, Scheme::parse(scheme.name()).unwrap());684        }685        for target in Target::all() {686            assert_eq!(target, Target::parse(target.name()).unwrap());687        }688    }689    #[test]690    fn coat_renders_a_stored_paint_exactly() {691        let _g = guard();692        for edition in Edition::all() {693            seed(11);694            let mut primed = Cell::new(atoms::carpet_2d(9));695            let mut p = Paint::new(edition);696            p = setup(p, &Config::default());697            p = prime(p, &mut primed, None).unwrap();698            let stored = round_trip(&p);699            seed(1);700            let mut a = Cell::new(atoms::carpet_2d(9));701            coat(&mut a, &stored, None).unwrap();702            seed(2);703            let mut b = Cell::new(atoms::carpet_2d(9));704            coat(&mut b, &stored, None).unwrap();705            assert_eq!(a, b, "edition {:?}", edition);706            assert_eq!(a.colors.as_ref().unwrap().len(), a.size());707        }708    }709    #[test]710    fn coat_matches_the_generative_render() {711        let _g = guard();712        for edition in [713            Edition::Simple,714            Edition::Index,715            Edition::Layers,716            Edition::Rows,717        ] {718            seed(21);719            let mut lived = Cell::new(atoms::carpet_2d(9));720            let p = paint(721                &mut lived,722                &Config {723                    editions: Some(vec![edition]),724                    ..Config::default()725                },726                None,727            )728            .unwrap();729            let mut coated = Cell::new(atoms::carpet_2d(9));730            coat(&mut coated, &p, None).unwrap();731            assert_eq!(lived.colors, coated.colors, "edition {:?}", edition);732        }733    }734    #[test]735    fn default_neighbors_mask_is_the_moore_ring() {736        let offsets: [(usize, usize); 8] = [737            (0, 0),738            (0, 1),739            (0, 2),740            (1, 0),741            (1, 2),742            (2, 0),743            (2, 1),744            (2, 2),745        ];746        let mut types = Tensor::new(vec![45, 5]);747        for count in 0..9 {748            for &(dy, dx) in offsets.iter().take(count) {749                types.set(&[5 * count + 1 + dy, 1 + dx], 1);750            }751        }752        let mut cell = Cell::new(types);753        let classes = tag(&mut cell, Edition::Neighbors, Target::Fill, None).unwrap();754        assert_eq!(classes, 9);755    }756    #[test]757    fn tag_is_deterministic() {758        let mut a = Cell::new(atoms::carpet_2d(9));759        let mut b = Cell::new(atoms::carpet_2d(9));760        let ka = tag(&mut a, Edition::Layers, Target::Fill, None).unwrap();761        let kb = tag(&mut b, Edition::Layers, Target::Fill, None).unwrap();762        assert_eq!(a, b);763        assert_eq!(ka, kb);764        assert!(ka >= 1);765        let mut c = Cell::new(atoms::carpet_2d(9));766        assert_eq!(tag(&mut c, Edition::Simple, Target::Fill, None).unwrap(), 0);767        assert!(c.tags.is_none());768    }769}