From c6adefd20a8c7389c7886644ae097007c003285a Mon Sep 17 00:00:00 2001 From: AdaWorldAPI Date: Thu, 22 Oct 2026 22:38:18 +0200 Subject: [PATCH 1/4] test(lab): test(hebbian): bounded synaptic plasticity on immutable palette references --- .../tests/palette256_hebbian_overlay_probe.rs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 crates/causal-edge/tests/palette256_hebbian_overlay_probe.rs diff --git a/crates/causal-edge/tests/palette256_hebbian_overlay_probe.rs b/crates/causal-edge/tests/palette256_hebbian_overlay_probe.rs new file mode 100644 index 000000000..f9cf8d70b --- /dev/null +++ b/crates/causal-edge/tests/palette256_hebbian_overlay_probe.rs @@ -0,0 +1,45 @@ +//! Local associative learning belongs to a versioned overlay, not to the +//! immutable Palette256 codebook or its family-calibrated pairwise LUT. +#[derive(Default)] +struct Overlay { + latent: f64, + seen_sources: u64, +} +impl Overlay { + fn apply(&mut self, source_id: u8, pre: f64, post: f64, eta: f64) -> Result { + if source_id >= 64 || !pre.is_finite() || !post.is_finite() + || !eta.is_finite() || !(0.0..=1.0).contains(&eta) { + return Err("invalid update contract"); + } + let source = 1_u64 << source_id; + if self.seen_sources & source != 0 { return Ok(false); } + // Hypothesis under test, NOT NARS revision or a proven Bayesian update. + let damping = 1.0 - self.latent.abs().tanh(); + self.latent = (self.latent + eta * pre * post * damping).clamp(-8.0, 8.0); + self.seen_sources |= source; + Ok(true) + } + fn bounded_readout(&self) -> f64 { self.latent.tanh() } +} +#[test] +fn repeated_eligible_updates_remain_bounded_and_do_not_move_reference() { + let frozen_reference: [u8; 4] = [13, 42, 99, 241]; + let reference_before = frozen_reference; + let mut synapse = Overlay::default(); + for source in 0..64_u8 { + assert!(synapse.apply(source, 0.8, 0.9, 0.15).unwrap()); + } + assert!(synapse.latent.is_finite()); + assert!(synapse.bounded_readout() > 0.0 && synapse.bounded_readout() < 1.0); + assert_eq!(frozen_reference, reference_before); +} +#[test] +fn a_duplicate_root_does_not_reinforce_the_synapse_again() { + let mut synapse = Overlay::default(); + assert!(synapse.apply(4, 0.8, 0.9, 0.1).unwrap()); + let first = synapse.latent; + assert!(!synapse.apply(4, 0.8, 0.9, 0.1).unwrap()); + assert_eq!(synapse.latent, first); + assert!(synapse.apply(5, -0.8, 0.9, 0.1).unwrap()); + assert!(synapse.latent < first, "anticorrelated activation must decrease the overlay"); +} From f56ddbadf1af98f6455ba6c25ed9ec115919726e Mon Sep 17 00:00:00 2001 From: AdaWorldAPI Date: Thu, 22 Oct 2026 22:38:21 +0200 Subject: [PATCH 2/4] test(lab): test(hebbian): bounded synaptic plasticity on immutable palette references --- ...026-10-22-pal256-hebbian-overlay-plasticity.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .claude/board/entries/2026-10-22-pal256-hebbian-overlay-plasticity.md diff --git a/.claude/board/entries/2026-10-22-pal256-hebbian-overlay-plasticity.md b/.claude/board/entries/2026-10-22-pal256-hebbian-overlay-plasticity.md new file mode 100644 index 000000000..1720aa0f2 --- /dev/null +++ b/.claude/board/entries/2026-10-22-pal256-hebbian-overlay-plasticity.md @@ -0,0 +1,15 @@ +# Hebbian / anti-Hebbian plasticity as a separate overlay, not codebook retraining + +Status: **lab-only**. The Google-search formula `Δz = η pre post (1 − tanh(|z|))` is a local update proposal. It is neither canonical NARS revision nor a Bayesian posterior. + +Existing owner: `.claude/knowledge/reference-frame-vs-motion.md`: immutable calibrated reference versus dynamic motion/alpha/thought. Keep the Fisher-Z pairwise LUT unchanged and version the update overlay independently. + +Probe claims ONLY: +- bounded finite latent and bounded tanh readout on tested activations; +- independent source gating prevents immediate evidence echo; +- negative coactivation can decrease the plastic overlay; +- codebook address never changes. + +Refusals/next experiments: dependent sources sharing ancestry, adversarial wrong associations, negative saturation recovery, catastrophic reinforcement, forgetting/decay, convergence, calibration vs a held-out event oracle. The toy 64-bit witness cannot replace full provenance handling. + +Run: `cargo test --manifest-path crates/causal-edge/Cargo.toml --test palette256_hebbian_overlay_probe`. From dfd60cfbc18a174b38e0dde9f0ad6100faec7b05 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Oct 2026 21:41:44 +0000 Subject: [PATCH 3/4] fix(ci): rustfmt the probe test, regenerate the board entries index Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg --- .claude/board/entries/README.md | 3 +- .../tests/palette256_hebbian_overlay_probe.rs | 29 +++++++++++++++---- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/.claude/board/entries/README.md b/.claude/board/entries/README.md index dd6231c6a..66271b7e4 100644 --- a/.claude/board/entries/README.md +++ b/.claude/board/entries/README.md @@ -25,10 +25,11 @@ index row, (3) no duplicate entry id. Checks 1 and 2 are deliberately opposite directions; the stranding this convention prevents shows up in exactly one of them, never both. -429 entries, 2026-08-06 .. 2026-10-22. +430 entries, 2026-08-06 .. 2026-10-22. | date | entry id | finding | file | |---|---|---|---| +| 2026-10-22 | `pal256-hebbian-overlay-plasticity` | | [2026-10-22-pal256-hebbian-overlay-plasticity.md](2026-10-22-pal256-hebbian-overlay-plasticity.md) | | 2026-10-22 | `math-compiler-stage-b-e1` | | [2026-10-22-math-compiler-stage-b-e1.md](2026-10-22-math-compiler-stage-b-e1.md) | | 2026-10-22 | `math-compiler-gap-matrix` | | [2026-10-22-math-compiler-gap-matrix.md](2026-10-22-math-compiler-gap-matrix.md) | | 2026-10-22 | `D-MPS-9` | | [2026-10-22-d-mps-9-mork-oracle.md](2026-10-22-d-mps-9-mork-oracle.md) | diff --git a/crates/causal-edge/tests/palette256_hebbian_overlay_probe.rs b/crates/causal-edge/tests/palette256_hebbian_overlay_probe.rs index f9cf8d70b..77b8045c8 100644 --- a/crates/causal-edge/tests/palette256_hebbian_overlay_probe.rs +++ b/crates/causal-edge/tests/palette256_hebbian_overlay_probe.rs @@ -6,20 +6,34 @@ struct Overlay { seen_sources: u64, } impl Overlay { - fn apply(&mut self, source_id: u8, pre: f64, post: f64, eta: f64) -> Result { - if source_id >= 64 || !pre.is_finite() || !post.is_finite() - || !eta.is_finite() || !(0.0..=1.0).contains(&eta) { + fn apply( + &mut self, + source_id: u8, + pre: f64, + post: f64, + eta: f64, + ) -> Result { + if source_id >= 64 + || !pre.is_finite() + || !post.is_finite() + || !eta.is_finite() + || !(0.0..=1.0).contains(&eta) + { return Err("invalid update contract"); } let source = 1_u64 << source_id; - if self.seen_sources & source != 0 { return Ok(false); } + if self.seen_sources & source != 0 { + return Ok(false); + } // Hypothesis under test, NOT NARS revision or a proven Bayesian update. let damping = 1.0 - self.latent.abs().tanh(); self.latent = (self.latent + eta * pre * post * damping).clamp(-8.0, 8.0); self.seen_sources |= source; Ok(true) } - fn bounded_readout(&self) -> f64 { self.latent.tanh() } + fn bounded_readout(&self) -> f64 { + self.latent.tanh() + } } #[test] fn repeated_eligible_updates_remain_bounded_and_do_not_move_reference() { @@ -41,5 +55,8 @@ fn a_duplicate_root_does_not_reinforce_the_synapse_again() { assert!(!synapse.apply(4, 0.8, 0.9, 0.1).unwrap()); assert_eq!(synapse.latent, first); assert!(synapse.apply(5, -0.8, 0.9, 0.1).unwrap()); - assert!(synapse.latent < first, "anticorrelated activation must decrease the overlay"); + assert!( + synapse.latent < first, + "anticorrelated activation must decrease the overlay" + ); } From b3d26a4b6076598cda6879fbc876023451fa648c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Oct 2026 21:53:48 +0000 Subject: [PATCH 4/4] probe: fold #1722's production-surface test into this series Moves crates/lance-graph-contract/tests/episodic_hebb_saturation_probe.rs here from #1722. Unlike the local helpers already in this series, it exercises the real implementation, so the series keeps its experiment contract and gains the concrete counter-probe. #1722 is closed as superseded once this lands. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg --- .../tests/episodic_hebb_saturation_probe.rs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 crates/lance-graph-contract/tests/episodic_hebb_saturation_probe.rs diff --git a/crates/lance-graph-contract/tests/episodic_hebb_saturation_probe.rs b/crates/lance-graph-contract/tests/episodic_hebb_saturation_probe.rs new file mode 100644 index 000000000..23d9e63da --- /dev/null +++ b/crates/lance-graph-contract/tests/episodic_hebb_saturation_probe.rs @@ -0,0 +1,49 @@ +//! Candidate Hebbian update, probe-only. Never writes CE64 or frozen codebooks. +use lance_graph_contract::episodic_edges::{EdgeRef, EpisodicEdges64}; + +fn candidate(z: f64, s: f64, p: f64, eta: f64) -> f64 { + let delta = eta * s * p * (1.0 - z.abs().tanh()); + z + delta +} + +#[test] +fn eta_or_inactive_stimulus_is_exactly_inert() { + for z in [-7.0, -1.0, 0.0, 1.0, 7.0] { + assert_eq!(candidate(z, 0.9, 0.9, 0.0), z); + assert_eq!(candidate(z, 0.0, 0.9, 0.05), z); + assert_eq!(candidate(z, 0.9, 0.0, 0.05), z); + } +} + +#[test] +fn coincident_and_opposed_stimuli_choose_direction() { + assert!(candidate(0.0, 1.0, 1.0, 0.1) > 0.0); + assert!(candidate(0.0, 1.0, -1.0, 0.1) < 0.0); +} + +#[test] +fn saturation_damps_update_but_does_not_create_immutable_certainty() { + let fresh = candidate(0.0, 1.0, 1.0, 0.1); + let saturated = candidate(12.0, 1.0, 1.0, 0.1) - 12.0; + assert!(fresh > 0.09); + assert!(saturated.abs() < 1e-10); + let mut z = 0.0; + for _ in 0..64 { + z = candidate(z, 1.0, 1.0, 0.1); + } + assert!(z > 0.0 && z < 6.4, "damped growth is below linear baseline"); +} + +#[test] +fn hot_episodic_mru_remains_separate_from_candidate_weight() { + let a = EdgeRef::intra(1).expect("valid reference"); + let b = EdgeRef::intra(2).expect("valid reference"); + let (one, eviction) = EpisodicEdges64::empty().promote(a); + assert!(eviction.is_none()); + let (two, eviction) = one.promote(b); + assert!(eviction.is_none()); + assert_eq!(two.strongest(), Some(b)); + assert!(two.contains(a)); + // Experimenting with an external scalar has not rewritten the MRU register. + assert_eq!(candidate(0.0, 1.0, 1.0, 0.1), 0.1); +}