chiark / gitweb /
Structure: Break out vpid.rs
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 14 Mar 2021 18:55:51 +0000 (18:55 +0000)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 14 Mar 2021 18:55:51 +0000 (18:55 +0000)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
src/hidden.rs
src/vpid.rs [new file with mode: 0644]

index 03223394e945ddd7d1cc610af1ee18f5d451d9f6..84bef6f2cdea8f058d65e8a72da5872b7b4d9c85 100644 (file)
@@ -4,6 +4,9 @@
 
 use crate::prelude::*;
 
+#[path="vpid.rs"] mod vpid;
+pub use vpid::{PerPlayerIdMap, NotchNumber, Notch, Notches, consistency_check};
+
 use slotmap::secondary;
 
 type ONI = OldNewIndex;
@@ -145,417 +148,6 @@ impl GameOccults {
   }
 }
 
-// ========== PerPlayerIdMap ==========
-
-mod vpid {
-  use super::*;
-
-  #[derive(Clone,Debug,Default,Serialize,Deserialize)]
-  pub struct PerPlayerIdMap {
-    pub(in super) f: SecondarySlotMap<PieceId, VisiblePieceId>,
-    pub(in super) r: DenseSlotMap<VisiblePieceId, PieceId>,
-  }
-
-  impl PerPlayerIdMap {
-    pub fn fwd(&self, piece: PieceId) -> Option<VisiblePieceId> {
-      Some(*self.f.get(piece)?)
-    }
-    pub fn rev(&self, vis: VisiblePieceId) -> Option<PieceId> {
-      Some(*self.r.get(vis)?)
-    }
-
-    fn fwd_or_insert_internal<R, VF, OF>
-      (&mut self, piece: PieceId, vf: VF, of: OF) -> R
-    where VF: FnOnce(VisiblePieceId) -> R,
-          OF: FnOnce(secondary::OccupiedEntry<PieceId,VisiblePieceId>) -> R,
-    {
-      match self.f.entry(piece).expect("stale PieceId !") {
-        secondary::Entry::Vacant(mut vac) => {
-          if let Some((_, stale_vis)) = vac.remove_stale_entry() {
-            self.r.remove(stale_vis);
-          }
-          let vis = self.r.insert(piece);
-          vac.insert(vis);
-          vf(vis)
-        }
-        secondary::Entry::Occupied(occ) => {
-          of(occ)
-        }
-      }
-    }
-
-    pub fn insert(&mut self, piece: PieceId) {
-      self.fwd_or_insert_internal(piece, |_vis|(), |vis|{
-        panic!("duplicate insert of {:?} {:?}", piece, vis)
-      })
-    }
-
-    pub fn fwd_or_insert(&mut self, piece: PieceId) -> VisiblePieceId {
-      self.fwd_or_insert_internal(piece, |vis|vis, |occ| *occ.get())
-    }
-  }
-
-  pub type NotchNumber = u32;
-
-  define_index_type!{
-    pub struct Notch = NotchNumber;
-  }
-
-  impl From<Notch> for u32 {
-    fn from(n: Notch) -> u32 { n.index().try_into().unwrap() }
-  }
-
-  type NotchPtr = Option<Notch>;
-
-  #[derive(Clone,Copy,Debug,Serialize,Deserialize)]
-  #[derive(Eq,PartialEq)]
-  enum NotchRecord {
-    Free(NotchPtr),
-    Piece(PieceId),
-  }
-  type NR = NotchRecord;
-
-  impl NotchRecord {
-    fn piece(&self) -> Option<PieceId> { match self {
-      &NR::Piece(p) => Some(p),
-      &NR::Free(_) => None,
-    }}
-
-    fn unwrap_free(&self) -> NotchPtr { match self {
-      &NR::Free(ptr) => ptr,
-      _ => panic!(),
-    }}
-
-    fn unwrap_free_mut(&mut self) -> &mut NotchPtr { match self {
-      NR::Free(ptr) => ptr,
-      _ => panic!(),
-    }}
-  }
-
-  #[derive(Clone,Debug,Serialize,Deserialize,Default)]
-  pub struct Notches {
-    freelist: NotchPtr,
-    table: IndexVec<Notch, NotchRecord>,
-    zg: IndexVec<Notch, Generation>, // last time notch was (re)filled
-  }
-
-  impl Occultation {
-    pub fn notch_zg(&self, notch: Notch) -> Option<Generation> {
-      self.notches.zg.get(notch).copied()
-    }
-  }
-
-  impl Notches {
-    pub fn iter<'i>(&'i self) -> impl Iterator<Item=PieceId> + 'i {
-      self.table.iter()
-        .filter_map(|nr| match nr {
-          NR::Free(_) => None,
-          &NR::Piece(p) => Some(p),
-        })
-    }
-
-    /// correctness: piece must not already be in this `Notches`
-    pub fn insert(&mut self, zg: Generation, piece: PieceId) -> Notch {
-      let new = NR::Piece(piece);
-      match self.freelist.take() {
-        None => {
-          let a = self.table.push(new);
-          let b = self.zg.push(zg);
-          // xxx maybe update z_max in gs
-          assert_eq!(a,b);
-          a
-        },
-        Some(old_free_head) => {
-          self.freelist = self.table[old_free_head].unwrap_free();
-          self.table[old_free_head] = new;
-          self.zg[old_free_head] = zg;
-          old_free_head
-        },
-      }
-    }
-
-    #[throws(IE)]
-    pub fn remove(&mut self, piece: PieceId, notch: Notch) {
-      match self.table.get(notch) {
-        Some(&NR::Piece(p)) if p == piece => { },
-        _ => throw!(internal_error_bydebug(&(piece, notch, self))),
-      }
-      {
-        let mut insert_here = &mut self.freelist;
-        loop {
-          let next = *insert_here;
-          let next = if let Some(y) = next { y } else { break };
-          if notch < next { break }
-          if notch == next { panic!() };
-          let next = &mut self.table[next];
-          insert_here = next.unwrap_free_mut();
-        }
-        // Now either *insert_here==NULL or notch < insert_here->next
-        let old_next = mem::replace(insert_here, Some(notch));
-        self.table[notch] = NR::Free(old_next);
-      }
-    }
-
-    #[cfg(test)]
-    fn check(&self) {
-      let mut count_free = 0;
-      let mut walk = self.freelist;
-      while let Some(here) = walk {
-        count_free += 1;
-        let next_walk = self.table[here].unwrap_free();
-        if let Some(more) = next_walk {
-          assert!(more > here);
-        }
-        walk = next_walk;
-      }
-      assert_eq!(
-        count_free,
-        self.table.iter()
-          .filter(|nr| matches!(nr, NR::Free(_)))
-          .count()
-      );
-      let pieces = self.table.iter()
-        .filter_map(|nr| if let NR::Piece(p) = nr { Some(p) } else { None })
-        .collect::<HashSet<_>>();
-      assert_eq!(
-        count_free + pieces.len(),
-        self.table.len(),
-      );
-    }
-  }
-    
-  #[test]
-  fn exhaustive() {
-    enum Can {
-      Insert(PieceId),
-      Remove(PieceId),
-    }
-    impl Debug for Can {
-      fn fmt(&self, f: &mut fmt::Formatter) -> Result<(),fmt::Error> {
-        let (c, p) = match self {
-          Can::Insert(p) => ('+', p),
-          Can::Remove(p) => ('-', p),
-        };
-        let (idx, _vsn) = p.data().get_idx_version();
-        write!(f, "{}{}", c, idx)
-      }
-    }
-
-    #[derive(Debug)]
-    struct State {
-      todo: Vec<Can>,
-      can: Vec<Can>,
-    }
-
-    const NPIECES: usize = 4;
-    const DEPTH: usize = 7;
-
-    impl State {
-      fn implement(&self) {
-        eprintln!("implement {:?}", self);
-        let mut notches: Notches = default();
-        let mut pieces = SecondarySlotMap::new();
-        for op in &self.todo { match op {
-          &Can::Insert(p) => {
-            let notch = notches.insert(Generation(0), p);
-            notches.check();
-            pieces.insert(p, notch);
-          },
-          &Can::Remove(p) => {
-            let notch = pieces[p];
-            notches.remove(p, notch).unwrap();
-            notches.check();
-          },
-        }}
-      }
-
-      fn recurse(&mut self) {
-        if self.todo.len() == DEPTH { self.implement(); return; }
-        eprintln!("  recurse {:?}", &self);
-
-        for i in 0..self.can.len() {
-          let op = self.can.swap_remove(i);
-
-          let l = self.can.len();
-          match &op {
-            &Can::Insert(p) => { self.can.push(Can::Remove(p)) },
-            &Can::Remove(_) => { },
-          }
-
-          self.todo.push(op);
-          self.recurse();
-          let op = self.todo.pop().unwrap();
-
-          self.can.truncate(l);
-
-          let last = if i < self.can.len() {
-            mem::replace(&mut self.can[i], op)
-          } else {
-            op
-          };
-          self.can.push(last);
-        }
-      }
-    }
-
-
-    let mut st = State {
-      todo: vec![],
-      can: {
-        let mut slotmap: DenseSlotMap<PieceId,()> = default();
-        (0..NPIECES)
-          .map(|_| slotmap.insert(()))
-          .map(|p| Can::Insert(p))
-          .collect()
-      },
-    };
-
-    st.recurse();
-  }                                        
-
-  #[allow(unused_variables)]
-  pub fn permute(occid: OccId,
-                 occ: &mut Occultation,
-                 gplayers: &mut GPlayers,
-                 gpieces: &mut GPieces,
-                 ipieces: &IPieces) {
-    let new_notches = {
-
-      let mut ilks: HashMap<OccultIlkId, (
-        Vec<Notch>,
-        Vec<PieceId>,
-      )> = HashMap::new();
-
-      for (notch, nr) in occ.notches.table.iter_enumerated() {
-        let piece = if let Some(p) = nr.piece() { p } else { continue };
-        let occilk = (|| Some(ipieces.get(piece)?.occilk.as_ref()?))();
-        let occilk = if let Some(o) = occilk { *o.borrow() } else {
-          error!("{}", internal_error_bydebug(&(occid, &occ, &nr, piece)));
-          continue;
-        };
-        let (notches, pieces) = ilks.entry(occilk).or_default();
-        notches.push(notch);
-        pieces.push(piece);
-      }
-
-      let mut new_notches = occ.notches.table.clone();
-      for (_occilk, (notches, pieces)) in ilks {
-        // We must permute for if we have any views that are scrambled
-        // or displaced obviously.  For invisible too, so that when they
-        // reappear the ids have been permuted.  And that's all the
-        // non-Visible views which an occultation ought to have at least
-        // one of...
-        //
-        // We choose a single permutation of each of the subsets (for a
-        // partiular ilk) of the pieces in the Occultation::notches.
-
-        let permu = {
-          let mut permu = pieces;
-          config().game_rng.shuffle(&mut permu);
-          permu
-        };
-
-        for (notch, new_piece) in itertools::zip_eq(notches, permu) {
-          new_notches[notch] = NR::Piece(new_piece);
-          gpieces.get_mut(new_piece).unwrap()
-            .occult.passive.as_mut().unwrap()
-            .notch
-            = notch;
-        }
-      }
-
-      new_notches
-    };
-
-    for (player, gpl) in gplayers.iter_mut() {
-      match occ.views.get_kind(player) {
-        OccK::Visible => continue,
-        OccK::Scrambled |
-        OccK::Displaced{..} |
-        OccK::Invisible => (),
-      };
-
-      let mut fwd_updates = vec![];
-      for (old, new) in izip!(&occ.notches.table, &new_notches) {
-        if_chain! {
-          if let Some((old, new)) = zip_eq(old.piece(), new.piece()).next();
-          if let Some(vpid) = gpl.idmap.fwd(old);
-          then {
-            fwd_updates.push((new, vpid));
-            if let Some(e) = gpl.idmap.r.get_mut(vpid) {
-              *e = new;
-            }
-          }
-        }
-      }
-
-      for (new, vpid) in fwd_updates {
-        gpl.idmap.f.insert(new, vpid);
-      }
-
-    }
-    occ.notches.table = new_notches;
-    dbgc!(&occ);
-  }
-
-  #[cfg(debug_assertions)]
-  pub fn consistency_check(
-    gplayers: &GPlayers,
-    gpieces: &GPieces,
-    goccults: &GameOccults,
-  ) {
-    for (_player, gpl) in gplayers.iter() {
-      for (piece, &vpid) in gpl.idmap.f.iter() {
-        if let Some(&rpiece) = gpl.idmap.r.get(vpid) {
-          assert_eq!(piece, rpiece);
-        }
-      }
-      for (vpid, &piece) in gpl.idmap.r.iter() {
-        if let Some(&fvpid) = gpl.idmap.f.get(piece) {
-          assert_eq!(vpid, fvpid);
-        }
-      }
-    }
-    
-    for (piece, gpc) in gpieces.iter() {
-      if let Some(occid) = gpc.occult.active {
-        let occ = goccults.occults.get(occid).unwrap();
-        assert_eq!(occ.occulter, piece);
-        assert_eq!(&gpc.occult.passive, &None);
-      }
-      
-      if let Some(Passive { occid, notch }) = gpc.occult.passive {
-        let occ = goccults.occults.get(occid).unwrap();
-        assert_eq!(occ.notches.table[notch], NR::Piece(piece));
-      }
-    }
-
-    for (occid, occ) in goccults.occults.iter() {
-      let ogpc = gpieces.get(occ.occulter).unwrap();
-      assert_eq!(ogpc.occult.active, Some(occid));
-      assert_eq!(occ.notches.table.len(), occ.notches.zg.len());
-      let nfree1 = occ.notches.table.iter()
-        .filter(|nr| nr.piece().is_none()).count();
-      let mut walk = occ.notches.freelist;
-      let mut nfree2 = 0;
-      while let Some(here) = walk {
-        nfree2 += 1;
-        let next = match occ.notches.table[here] {
-          NR::Free(next) => next,
-          NR::Piece(_) => panic!(),
-        };
-        if let Some(next) = next {
-          assert!(next > here);
-        }
-        walk = next;
-      }
-      assert_eq!(nfree1,  nfree2);
-    }
-  }
-}
-
-pub use vpid::{PerPlayerIdMap, NotchNumber, Notch, Notches, consistency_check};
-
 // ========== public entrypoints ==========
 
 /// None => do not render at all
diff --git a/src/vpid.rs b/src/vpid.rs
new file mode 100644 (file)
index 0000000..e17a016
--- /dev/null
@@ -0,0 +1,409 @@
+// Copyright 2020-2021 Ian Jackson and contributors to Otter
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// There is NO WARRANTY.
+
+use super::*;
+
+#[derive(Clone,Debug,Default,Serialize,Deserialize)]
+pub struct PerPlayerIdMap {
+  pub(in super) f: SecondarySlotMap<PieceId, VisiblePieceId>,
+  pub(in super) r: DenseSlotMap<VisiblePieceId, PieceId>,
+}
+
+impl PerPlayerIdMap {
+  pub fn fwd(&self, piece: PieceId) -> Option<VisiblePieceId> {
+    Some(*self.f.get(piece)?)
+  }
+  pub fn rev(&self, vis: VisiblePieceId) -> Option<PieceId> {
+    Some(*self.r.get(vis)?)
+  }
+
+  fn fwd_or_insert_internal<R, VF, OF>
+    (&mut self, piece: PieceId, vf: VF, of: OF) -> R
+  where VF: FnOnce(VisiblePieceId) -> R,
+        OF: FnOnce(secondary::OccupiedEntry<PieceId,VisiblePieceId>) -> R,
+  {
+    match self.f.entry(piece).expect("stale PieceId !") {
+      secondary::Entry::Vacant(mut vac) => {
+        if let Some((_, stale_vis)) = vac.remove_stale_entry() {
+          self.r.remove(stale_vis);
+        }
+        let vis = self.r.insert(piece);
+        vac.insert(vis);
+        vf(vis)
+      }
+      secondary::Entry::Occupied(occ) => {
+        of(occ)
+      }
+    }
+  }
+
+  pub fn insert(&mut self, piece: PieceId) {
+    self.fwd_or_insert_internal(piece, |_vis|(), |vis|{
+      panic!("duplicate insert of {:?} {:?}", piece, vis)
+    })
+  }
+
+  pub fn fwd_or_insert(&mut self, piece: PieceId) -> VisiblePieceId {
+    self.fwd_or_insert_internal(piece, |vis|vis, |occ| *occ.get())
+  }
+}
+
+pub type NotchNumber = u32;
+
+define_index_type!{
+  pub struct Notch = NotchNumber;
+}
+
+impl From<Notch> for u32 {
+  fn from(n: Notch) -> u32 { n.index().try_into().unwrap() }
+}
+
+type NotchPtr = Option<Notch>;
+
+#[derive(Clone,Copy,Debug,Serialize,Deserialize)]
+#[derive(Eq,PartialEq)]
+enum NotchRecord {
+  Free(NotchPtr),
+  Piece(PieceId),
+}
+type NR = NotchRecord;
+
+impl NotchRecord {
+  fn piece(&self) -> Option<PieceId> { match self {
+    &NR::Piece(p) => Some(p),
+    &NR::Free(_) => None,
+  }}
+
+  fn unwrap_free(&self) -> NotchPtr { match self {
+    &NR::Free(ptr) => ptr,
+    _ => panic!(),
+  }}
+
+  fn unwrap_free_mut(&mut self) -> &mut NotchPtr { match self {
+    NR::Free(ptr) => ptr,
+    _ => panic!(),
+  }}
+}
+
+#[derive(Clone,Debug,Serialize,Deserialize,Default)]
+pub struct Notches {
+  freelist: NotchPtr,
+  table: IndexVec<Notch, NotchRecord>,
+  zg: IndexVec<Notch, Generation>, // last time notch was (re)filled
+}
+
+impl Occultation {
+  pub fn notch_zg(&self, notch: Notch) -> Option<Generation> {
+    self.notches.zg.get(notch).copied()
+  }
+}
+
+impl Notches {
+  pub fn iter<'i>(&'i self) -> impl Iterator<Item=PieceId> + 'i {
+    self.table.iter()
+      .filter_map(|nr| match nr {
+        NR::Free(_) => None,
+        &NR::Piece(p) => Some(p),
+      })
+  }
+
+  /// correctness: piece must not already be in this `Notches`
+  pub fn insert(&mut self, zg: Generation, piece: PieceId) -> Notch {
+    let new = NR::Piece(piece);
+    match self.freelist.take() {
+      None => {
+        let a = self.table.push(new);
+        let b = self.zg.push(zg);
+        // xxx maybe update z_max in gs
+        assert_eq!(a,b);
+        a
+      },
+      Some(old_free_head) => {
+        self.freelist = self.table[old_free_head].unwrap_free();
+        self.table[old_free_head] = new;
+        self.zg[old_free_head] = zg;
+        old_free_head
+      },
+    }
+  }
+
+  #[throws(IE)]
+  pub fn remove(&mut self, piece: PieceId, notch: Notch) {
+    match self.table.get(notch) {
+      Some(&NR::Piece(p)) if p == piece => { },
+      _ => throw!(internal_error_bydebug(&(piece, notch, self))),
+    }
+    {
+      let mut insert_here = &mut self.freelist;
+      loop {
+        let next = *insert_here;
+        let next = if let Some(y) = next { y } else { break };
+        if notch < next { break }
+        if notch == next { panic!() };
+        let next = &mut self.table[next];
+        insert_here = next.unwrap_free_mut();
+      }
+      // Now either *insert_here==NULL or notch < insert_here->next
+      let old_next = mem::replace(insert_here, Some(notch));
+      self.table[notch] = NR::Free(old_next);
+    }
+  }
+
+  #[cfg(test)]
+  fn check(&self) {
+    let mut count_free = 0;
+    let mut walk = self.freelist;
+    while let Some(here) = walk {
+      count_free += 1;
+      let next_walk = self.table[here].unwrap_free();
+      if let Some(more) = next_walk {
+        assert!(more > here);
+      }
+      walk = next_walk;
+    }
+    assert_eq!(
+      count_free,
+      self.table.iter()
+        .filter(|nr| matches!(nr, NR::Free(_)))
+        .count()
+    );
+    let pieces = self.table.iter()
+      .filter_map(|nr| if let NR::Piece(p) = nr { Some(p) } else { None })
+      .collect::<HashSet<_>>();
+    assert_eq!(
+      count_free + pieces.len(),
+      self.table.len(),
+    );
+  }
+}
+
+#[test]
+fn exhaustive() {
+  enum Can {
+    Insert(PieceId),
+    Remove(PieceId),
+  }
+  impl Debug for Can {
+    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(),fmt::Error> {
+      let (c, p) = match self {
+        Can::Insert(p) => ('+', p),
+        Can::Remove(p) => ('-', p),
+      };
+      let (idx, _vsn) = p.data().get_idx_version();
+      write!(f, "{}{}", c, idx)
+    }
+  }
+
+  #[derive(Debug)]
+  struct State {
+    todo: Vec<Can>,
+    can: Vec<Can>,
+  }
+
+  const NPIECES: usize = 4;
+  const DEPTH: usize = 7;
+
+  impl State {
+    fn implement(&self) {
+      eprintln!("implement {:?}", self);
+      let mut notches: Notches = default();
+      let mut pieces = SecondarySlotMap::new();
+      for op in &self.todo { match op {
+        &Can::Insert(p) => {
+          let notch = notches.insert(Generation(0), p);
+          notches.check();
+          pieces.insert(p, notch);
+        },
+        &Can::Remove(p) => {
+          let notch = pieces[p];
+          notches.remove(p, notch).unwrap();
+          notches.check();
+        },
+      }}
+    }
+
+    fn recurse(&mut self) {
+      if self.todo.len() == DEPTH { self.implement(); return; }
+      eprintln!("  recurse {:?}", &self);
+
+      for i in 0..self.can.len() {
+        let op = self.can.swap_remove(i);
+
+        let l = self.can.len();
+        match &op {
+          &Can::Insert(p) => { self.can.push(Can::Remove(p)) },
+          &Can::Remove(_) => { },
+        }
+
+        self.todo.push(op);
+        self.recurse();
+        let op = self.todo.pop().unwrap();
+
+        self.can.truncate(l);
+
+        let last = if i < self.can.len() {
+          mem::replace(&mut self.can[i], op)
+        } else {
+          op
+        };
+        self.can.push(last);
+      }
+    }
+  }
+
+
+  let mut st = State {
+    todo: vec![],
+    can: {
+      let mut slotmap: DenseSlotMap<PieceId,()> = default();
+      (0..NPIECES)
+        .map(|_| slotmap.insert(()))
+        .map(|p| Can::Insert(p))
+        .collect()
+    },
+  };
+
+  st.recurse();
+}                                        
+
+#[allow(unused_variables)]
+pub fn permute(occid: OccId,
+               occ: &mut Occultation,
+               gplayers: &mut GPlayers,
+               gpieces: &mut GPieces,
+               ipieces: &IPieces) {
+  let new_notches = {
+
+    let mut ilks: HashMap<OccultIlkId, (
+      Vec<Notch>,
+      Vec<PieceId>,
+    )> = HashMap::new();
+
+    for (notch, nr) in occ.notches.table.iter_enumerated() {
+      let piece = if let Some(p) = nr.piece() { p } else { continue };
+      let occilk = (|| Some(ipieces.get(piece)?.occilk.as_ref()?))();
+      let occilk = if let Some(o) = occilk { *o.borrow() } else {
+        error!("{}", internal_error_bydebug(&(occid, &occ, &nr, piece)));
+        continue;
+      };
+      let (notches, pieces) = ilks.entry(occilk).or_default();
+      notches.push(notch);
+      pieces.push(piece);
+    }
+
+    let mut new_notches = occ.notches.table.clone();
+    for (_occilk, (notches, pieces)) in ilks {
+      // We must permute for if we have any views that are scrambled
+      // or displaced obviously.  For invisible too, so that when they
+      // reappear the ids have been permuted.  And that's all the
+      // non-Visible views which an occultation ought to have at least
+      // one of...
+      //
+      // We choose a single permutation of each of the subsets (for a
+      // partiular ilk) of the pieces in the Occultation::notches.
+
+      let permu = {
+        let mut permu = pieces;
+        config().game_rng.shuffle(&mut permu);
+        permu
+      };
+
+      for (notch, new_piece) in itertools::zip_eq(notches, permu) {
+        new_notches[notch] = NR::Piece(new_piece);
+        gpieces.get_mut(new_piece).unwrap()
+          .occult.passive.as_mut().unwrap()
+          .notch
+          = notch;
+      }
+    }
+
+    new_notches
+  };
+
+  for (player, gpl) in gplayers.iter_mut() {
+    match occ.views.get_kind(player) {
+      OccK::Visible => continue,
+      OccK::Scrambled |
+      OccK::Displaced{..} |
+      OccK::Invisible => (),
+    };
+
+    let mut fwd_updates = vec![];
+    for (old, new) in izip!(&occ.notches.table, &new_notches) {
+      if_chain! {
+        if let Some((old, new)) = zip_eq(old.piece(), new.piece()).next();
+        if let Some(vpid) = gpl.idmap.fwd(old);
+        then {
+          fwd_updates.push((new, vpid));
+          if let Some(e) = gpl.idmap.r.get_mut(vpid) {
+            *e = new;
+          }
+        }
+      }
+    }
+
+    for (new, vpid) in fwd_updates {
+      gpl.idmap.f.insert(new, vpid);
+    }
+
+  }
+  occ.notches.table = new_notches;
+  dbgc!(&occ);
+}
+
+#[cfg(debug_assertions)]
+pub fn consistency_check(
+  gplayers: &GPlayers,
+  gpieces: &GPieces,
+  goccults: &GameOccults,
+) {
+  for (_player, gpl) in gplayers.iter() {
+    for (piece, &vpid) in gpl.idmap.f.iter() {
+      if let Some(&rpiece) = gpl.idmap.r.get(vpid) {
+        assert_eq!(piece, rpiece);
+      }
+    }
+    for (vpid, &piece) in gpl.idmap.r.iter() {
+      if let Some(&fvpid) = gpl.idmap.f.get(piece) {
+        assert_eq!(vpid, fvpid);
+      }
+    }
+  }
+
+  for (piece, gpc) in gpieces.iter() {
+    if let Some(occid) = gpc.occult.active {
+      let occ = goccults.occults.get(occid).unwrap();
+      assert_eq!(occ.occulter, piece);
+      assert_eq!(&gpc.occult.passive, &None);
+    }
+
+    if let Some(Passive { occid, notch }) = gpc.occult.passive {
+      let occ = goccults.occults.get(occid).unwrap();
+      assert_eq!(occ.notches.table[notch], NR::Piece(piece));
+    }
+  }
+
+  for (occid, occ) in goccults.occults.iter() {
+    let ogpc = gpieces.get(occ.occulter).unwrap();
+    assert_eq!(ogpc.occult.active, Some(occid));
+    assert_eq!(occ.notches.table.len(), occ.notches.zg.len());
+    let nfree1 = occ.notches.table.iter()
+      .filter(|nr| nr.piece().is_none()).count();
+    let mut walk = occ.notches.freelist;
+    let mut nfree2 = 0;
+    while let Some(here) = walk {
+      nfree2 += 1;
+      let next = match occ.notches.table[here] {
+        NR::Free(next) => next,
+        NR::Piece(_) => panic!(),
+      };
+      if let Some(next) = next {
+        assert!(next > here);
+      }
+      walk = next;
+    }
+    assert_eq!(nfree1,  nfree2);
+  }
+}
+