From 7117d613868b3dc936bc62c8a9694a1cd72dc066 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Jun 2026 07:14:51 +0000 Subject: [PATCH] etr(R): re-port driver_etr.R to the five-zone torus (kill disowned port) Replaces the Euclidean-magnitude R port (DREAD/BLUR/INCOHERENT, inverted Z-path) with a faithful native port of etr.m: three independent toroidal axes, five zones, snap-across flips, basin spring toward centre 26 (SOFT 0.25 / INCOH 0.5), L6 Z-path corrected (z<0 alimentation/lattice, z>=0 transmutation/evolution). Drive-Box now renders per-axis ETR status. R suite green (etr 31, drive_box 34) and Octave 26/0/1; both pinned to etr_invariants.md. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_015hmgREHNsxYCuim33yUF2c --- docs/plans/A8-etr.md | 11 ++- src/endocrine/drive_box.R | 4 +- src/endocrine/driver_etr.R | 141 ++++++++++++++++++++---------- src/endocrine/test_drive_box.R | 6 +- src/endocrine/test_etr.R | 151 +++++++++++++++++---------------- 5 files changed, 187 insertions(+), 126 deletions(-) diff --git a/docs/plans/A8-etr.md b/docs/plans/A8-etr.md index 3e1223d..0db1f38 100644 --- a/docs/plans/A8-etr.md +++ b/docs/plans/A8-etr.md @@ -27,7 +27,9 @@ implemented and tested, incl. L7 snap-flips. Full law + status in ## 3. Language & location GNU Octave · `src/endocrine/etr/` (`etr.m`, `test_etr.m`, `run_etr_tests.sh`, `etr_invariants.md`). -R wrapper `src/endocrine/driver_etr.R` bridges into the Drive-Box. +R port `src/endocrine/driver_etr.R` (+ `test_etr.R`) feeds the Drive-Box — a **faithful native port** +of `etr.m` (five-zone torus, per-axis), pinned to the same invariants doc. The disowned Euclidean- +magnitude port is gone. A single-source R↔Octave IPC bridge is still optional/future (transport C1). ## 4. Does / does-not - **Does:** hold the point; wrap each axis at ±50; apply per-axis restoring toward [17,35]; take @@ -54,8 +56,11 @@ L4 AI-drift C4 · L5 cross-axis coupling via stress C1 · L6 Z-path C3 · L7 sna ## 8. Build steps (remaining) 1. ~~Fit the restoring force~~ **DONE** — five-zone law (SOFT/INCOH gains + snap landings) green; L7 + L8 realised. -2. Define **L5** coupling (the stress→cross-axis mapping; G1) → replace the identity stub (the lone PEND). -3. Wire R↔Octave (driver_etr.R ↔ etr.m) for the Drive-Box (A1) — and the larger medium (D2). +2. ~~Reconcile the R port~~ **DONE** — `driver_etr.R` is now a faithful native port of `etr.m` + (five-zone, per-axis, L6 Z-path corrected); R suite + drive-box green. +3. Define **L5** coupling (the stress→cross-axis mapping; G1) → replace the identity stub (the lone PEND). +4. *(optional/future)* collapse the dual R+Octave implementations via an R↔Octave IPC bridge once the + transport (C1, shared with A1/C3/D2) is decided. ## 9. Tests `bash src/endocrine/etr/run_etr_tests.sh` (now **26/0/1** — the lone PEND is L5 active coupling). diff --git a/src/endocrine/drive_box.R b/src/endocrine/drive_box.R index 4c27660..44b8829 100644 --- a/src/endocrine/drive_box.R +++ b/src/endocrine/drive_box.R @@ -39,7 +39,7 @@ drive_snapshot <- function(state) { alive = is_alive(state$energy), existential_load = reality$existential_load, arguments = reality$arguments, - etr_status = evaluate_status(state$etr), + etr_status = paste(etr_status(state$etr), collapse = "/"), update_path = determine_system_update_path(state$etr) ) } @@ -89,7 +89,7 @@ drive_box_evaluate <- function(state, action_tags = character(0), true_cost = true_cost, existential_load = ps_load, eth_penalty = eth_penalty, - etr_status = evaluate_status(state$etr), + etr_status = paste(etr_status(state$etr), collapse = "/"), update_path = determine_system_update_path(state$etr), arguments = reality$arguments ) diff --git a/src/endocrine/driver_etr.R b/src/endocrine/driver_etr.R index c4ee43e..b72d40f 100644 --- a/src/endocrine/driver_etr.R +++ b/src/endocrine/driver_etr.R @@ -1,65 +1,118 @@ -# --- High-Fidelity Implementation for Driver 4: Existential Temporality Relief (ETR) --- +# --- Driver 4: Existential Temporality Relief (ETR) — R port of the torus --- # -# ETR situates the Drive-Box within a 3-axis temporality space. The agent's -# position is a coordinate (X, Y, Z) whose distance from the origin (the -# magnitude) maps onto an existential status band, while the Z-axis selects the -# system's generative update path. Movement through the space wraps toroidally -# so the agent can never leave the bounded temporal manifold. +# ETR is a SINGLE POINT on three INDEPENDENT toroidal axes (X, Y, Z). This R +# module is a faithful port of the Octave source of truth +# (src/endocrine/etr/etr.m) and its laws (src/endocrine/etr/etr_invariants.md); +# both implementations are pinned to that invariants doc. The earlier Euclidean- +# magnitude port (DREAD/BLUR/INCOHERENT, inverted Z-path) is DISOWNED. # -# State is a plain list with a single field: -# coordinate - length-3 numeric vector: X, Y, Z +# Each axis is a BISTABLE torus with five zones per pass and unstable watersheds +# at |v| = 7 (inner) and |v| = 45 (outer): # -# State-mutating helpers (shift_coordinate) return a new (modified copy of the) -# list rather than mutating in place, matching the reference semantics of the -# other Drive-Box drivers. +# |v| < 7 SNAP_IN : snaps ACROSS 0 to the opposite pole +# 7 .. 17 SOFT : weak restoring pull up into the band +# 17 .. 35 IN_BAND : stable (slack) +# 35 .. 45 INCOH : firmer restoring pull down into the band +# |v| > 45 SNAP_OUT : snaps ACROSS the ±50 wrap to the opposite pole +# +# A snap flips the pole and lands JUST PAST the opposite watershed (inner -> +# opposite SOFT; outer -> opposite INCOH), then that zone recovers it. +# +# State is a plain list with one field: coordinate - length-3 numeric (X,Y,Z). -# Constructor helper so tests and other modules can build a clean state. -init_etr_state <- function(coordinate = c(0, 0, 0)) { +# ---- law constants (NOT fitted) ---- +ETR_BAND_LO <- 17 +ETR_BAND_HI <- 35 +ETR_WRAP <- 50 +ETR_SNAP_INNER <- 7 # inner watershed (Anja) +ETR_SNAP_OUTER <- 45 # outer watershed (Anja) + +# ---- fitted constants (NOT law) ---- +ETR_GAIN_SOFT <- 0.25 # weak (SOFT, 7..17) +ETR_GAIN_INCOH <- 0.5 # firmer (INCOH, 35..45) +ETR_SNAP_MARGIN <- 2 # how far past the opposite watershed a snap lands + +# Constructor: default to a valid in-band point (mirrors etr_init's [25 25 25]). +init_etr_state <- function(coordinate = c(25, 25, 25)) { list(coordinate = coordinate) } -get_magnitude <- function(etr_state) { - # Euclidean distance from origin: sqrt(x^2 + y^2 + z^2) - coords <- etr_state$coordinate - return(sqrt(sum(coords^2))) +# ---- L1: per-axis toroidal wrap onto [-50, 50) ---- +etr_axis_wrap <- function(v) { + P <- 2 * ETR_WRAP + ((v + ETR_WRAP) %% P) - ETR_WRAP } -evaluate_status <- function(etr_state) { - magnitude <- get_magnitude(etr_state) - if (magnitude < 5.0) { - return("DISASTROUS") - } else if (magnitude >= 5.0 && magnitude < 15.0) { - return("DREAD") - } else if (magnitude >= 15.0 && magnitude < 35.0) { - return("FUNCTIONAL") - } else if (magnitude >= 35.0 && magnitude < 50.0) { - return("BLUR") +# ---- five-zone classification ---- +etr_axis_zone <- function(v) { + a <- abs(v) + if (a < ETR_SNAP_INNER) "SNAP_IN" + else if (a < ETR_BAND_LO) "SOFT" + else if (a <= ETR_BAND_HI) "IN_BAND" + else if (a <= ETR_SNAP_OUTER) "INCOH" + else "SNAP_OUT" +} + +# ---- L3: per-axis restoring force (spring toward band centre 26) ---- +# Weak in SOFT, firmer in INCOH; zero in band (slack) and in snap zones (those +# flip, they do not restore). +etr_axis_restoring <- function(v) { + a <- abs(v) + centre <- (ETR_BAND_LO + ETR_BAND_HI) / 2 # 26 + if (a < ETR_SNAP_INNER || a > ETR_SNAP_OUTER) { + 0 + } else if (a >= ETR_BAND_LO && a <= ETR_BAND_HI) { + 0 + } else if (a < ETR_BAND_LO) { + ETR_GAIN_SOFT * (centre * sign(v) - v) # SOFT — weak } else { - return("INCOHERENT") + ETR_GAIN_INCOH * (centre * sign(v) - v) # INCOH — firmer } } -determine_system_update_path <- function(etr_state) { - # Axis 2 (Z-axis, index 3) determines the generative path. - z_coord <- etr_state$coordinate[3] - if (z_coord >= 0) { - return("LATTICE_REINFORCEMENT") +# ---- L7: snap resolution — a flip ACROSS to the opposite pole ---- +# Precondition: |v| < 7 or |v| > 45. Lands just past the opposite watershed, +# sign flipped: inner -> opposite SOFT (±9); outer -> opposite INCOH (±43). +etr_axis_snap <- function(v) { + s <- sign(v); if (s == 0) s <- 1 # 0 has no pole; pick one + if (abs(v) < ETR_SNAP_INNER) { + -s * (ETR_SNAP_INNER + ETR_SNAP_MARGIN) # inner -> opposite SOFT } else { - return("EXPERIMENTAL_EVOLUTION") + -s * (ETR_SNAP_OUTER - ETR_SNAP_MARGIN) # outer -> opposite INCOH } } -shift_coordinate <- function(etr_state, delta_vector, bound = 50.0) { - # Toroidal wrap-around: keep each axis within [-bound, bound]. - new_coords <- etr_state$coordinate + delta_vector +# ---- one ETR step: AI drift -> (snap | restoring) -> wrap ---- +# drift is AI-originated (L4); ETR never invents motion. stress is the L5 +# coupling mediator (open seam -> identity for now). +etr_step <- function(etr_state, drift, stress = 0) { + if (missing(drift)) { + stop("etr_step: AI-originated drift must be supplied (L4 — ETR does not invent motion)") + } + coord <- etr_state$coordinate # coupling identity (L5 open) for (i in 1:3) { - while (new_coords[i] > bound) { - new_coords[i] <- new_coords[i] - (2 * bound) - } - while (new_coords[i] < -bound) { - new_coords[i] <- new_coords[i] + (2 * bound) + v <- etr_axis_wrap(coord[i] + drift[i]) + a <- abs(v) + if (a < ETR_SNAP_INNER || a > ETR_SNAP_OUTER) { + v <- etr_axis_snap(v) + } else { + v <- etr_axis_wrap(v + etr_axis_restoring(v)) } + coord[i] <- v } - etr_state$coordinate <- new_coords - return(etr_state) + etr_state$coordinate <- coord + etr_state +} + +# ---- per-axis zone status (length-3 character vector) ---- +etr_status <- function(etr_state) { + vapply(etr_state$coordinate, etr_axis_zone, character(1)) +} + +# ---- L6 Z-path: the generative update path selected by the Z-axis sign ---- +# z < 0 -> alimentation (maintain self) -> LATTICE_REINFORCEMENT +# z >= 0 -> transmutation (evolve self) -> EXPERIMENTAL_EVOLUTION +determine_system_update_path <- function(etr_state) { + z <- etr_state$coordinate[3] + if (z < 0) "LATTICE_REINFORCEMENT" else "EXPERIMENTAL_EVOLUTION" } diff --git a/src/endocrine/test_drive_box.R b/src/endocrine/test_drive_box.R index 946db3c..954f589 100644 --- a/src/endocrine/test_drive_box.R +++ b/src/endocrine/test_drive_box.R @@ -29,8 +29,8 @@ test_case("drive_snapshot reports a coherent read-only aggregate", function() { expect_false(snap$tool_locked, "not tool-locked at full energy") expect_true(snap$existential_load > 0, "primed body has positive load") expect_true(length(snap$arguments) > 0, "arguments emitted") - expect_equal(snap$etr_status, "DISASTROUS", label = "origin coordinate -> DISASTROUS") - expect_equal(snap$update_path, "LATTICE_REINFORCEMENT", label = "z=0 -> lattice") + expect_equal(snap$etr_status, "IN_BAND/IN_BAND/IN_BAND", label = "default coord -> all axes in band") + expect_equal(snap$update_path, "EXPERIMENTAL_EVOLUTION", label = "z=25 (>=0) -> transmutation/evolution") }) test_case("aligned action is far cheaper than its antithetical mirror", function() { @@ -110,7 +110,7 @@ test_case("input slot: empty body still emits driver + soul signals", function() res <- drive_box_input_slot(db, input_text = "", tarot_spread = character(0)) expect_true(grepl("[SOUL: SOUL.md]", res$slot, fixed = TRUE), "soul present") expect_true(grepl("[E ratio=1.00", res$slot, fixed = TRUE), "energy present at full") - expect_true(grepl("[ETR status=DISASTROUS", res$slot, fixed = TRUE), "etr present") + expect_true(grepl("[ETR status=IN_BAND", res$slot, fixed = TRUE), "etr present") expect_equal(res$input, res$slot, label = "no input_text -> input == slot") }) diff --git a/src/endocrine/test_etr.R b/src/endocrine/test_etr.R index df509a4..fae1e89 100644 --- a/src/endocrine/test_etr.R +++ b/src/endocrine/test_etr.R @@ -1,95 +1,98 @@ -# --- Tests for Driver 4: Existential Temporality Relief (ETR) --- -# Run from repo root: Rscript src/endocrine/test_etr.R -# Exit 0 => all pass. +# test_etr.R — invariants tests for the R port of the ETR five-zone torus. +# Mirrors src/endocrine/etr/test_etr.m (same laws, same source of truth). Run +# from the repo root via run_tests.sh. source("src/endocrine/test_framework.R") source("src/endocrine/driver_etr.R") -# --- init_etr_state constructor --- -test_case("init_etr_state builds state with default origin coordinate", function() { - s <- init_etr_state() - expect_equal(s$coordinate, c(0, 0, 0)) +# --- L1 wrap ---------------------------------------------------------- +test_case("etr_axis_wrap: +50 wraps to -50", function() { + expect_equal(etr_axis_wrap(50), -50, tol = 1e-9) +}) +test_case("etr_axis_wrap: 60 -> -40, -60 -> 40", function() { + expect_equal(etr_axis_wrap(60), -40, tol = 1e-9) + expect_equal(etr_axis_wrap(-60), 40, tol = 1e-9) +}) +test_case("etr_axis_wrap: in-range unchanged; edge continuity", function() { + expect_equal(etr_axis_wrap(25), 25, tol = 1e-9) + expect_equal(etr_axis_wrap(49.9), etr_axis_wrap(-50.1), tol = 1e-9) }) -test_case("init_etr_state honors a custom coordinate", function() { - s <- init_etr_state(c(1, 2, 3)) - expect_equal(s$coordinate, c(1, 2, 3)) +# --- zones ------------------------------------------------------------ +test_case("etr_axis_zone: five zones at 5/10/25/40/47", function() { + expect_equal(etr_axis_zone(5), "SNAP_IN") + expect_equal(etr_axis_zone(10), "SOFT") + expect_equal(etr_axis_zone(25), "IN_BAND") + expect_equal(etr_axis_zone(40), "INCOH") + expect_equal(etr_axis_zone(47), "SNAP_OUT") }) -# --- get_magnitude --- -test_case("get_magnitude of c(3,4,0) is 5.0", function() { - expect_equal(get_magnitude(init_etr_state(c(3, 4, 0))), 5.0, tol = 1e-9) +# --- L3 restoring ----------------------------------------------------- +test_case("etr_axis_restoring: SOFT pulls up, INCOH pulls down, band slack", function() { + expect_true(etr_axis_restoring(10) > 0, "SOFT (+v) pulls toward band") + expect_true(etr_axis_restoring(-10) < 0, "SOFT (-v) pulls toward band") + expect_true(etr_axis_restoring(40) < 0, "INCOH (+v) pulls toward band") + expect_true(etr_axis_restoring(-40) > 0, "INCOH (-v) pulls toward band") + expect_equal(etr_axis_restoring(25), 0) +}) +test_case("etr_axis_restoring: zero in snap zones", function() { + expect_equal(etr_axis_restoring(5), 0) + expect_equal(etr_axis_restoring(47), 0) +}) +test_case("etr_axis_restoring: soft-pull weaker than incoherency", function() { + expect_true(abs(etr_axis_restoring(8)) < abs(etr_axis_restoring(44)), + "weak soft-pull") }) -test_case("get_magnitude of origin is 0.0", function() { - expect_equal(get_magnitude(init_etr_state(c(0, 0, 0))), 0.0, tol = 1e-9) +# --- L2 convergence (same pole) --------------------------------------- +test_case("L2: SOFT start settles into band without flipping sign", function() { + s <- init_etr_state(c(10, 10, 10)) + for (k in 1:200) s <- etr_step(s, c(0, 0, 0), 0) + a <- abs(s$coordinate) + expect_true(all(a >= ETR_BAND_LO & a <= ETR_BAND_HI), "in band") + expect_true(all(s$coordinate > 0), "no flip") }) -# --- evaluate_status: exact boundary bands --- -test_case("evaluate_status magnitude 0 -> DISASTROUS", function() { - expect_equal(evaluate_status(init_etr_state(c(0, 0, 0))), "DISASTROUS") +# --- L7 snap-across flips --------------------------------------------- +test_case("L7: inner snap lands in opposite SOFT, settles in opposite band", function() { + s <- init_etr_state(c(5, 5, 5)) + s1 <- etr_step(s, c(0, 0, 0), 0) + a1 <- abs(s1$coordinate) + expect_true(all(s1$coordinate < 0), "flipped sign") + expect_true(all(a1 > ETR_SNAP_INNER & a1 < ETR_BAND_LO), "lands in SOFT") + for (k in 1:200) s <- etr_step(s, c(0, 0, 0), 0) + a <- abs(s$coordinate) + expect_true(all(s$coordinate < 0) && all(a >= ETR_BAND_LO & a <= ETR_BAND_HI), + "settles in opposite band") +}) +test_case("L7: outer snap lands in opposite INCOH, settles in opposite band", function() { + s <- init_etr_state(c(47, 47, 47)) + s1 <- etr_step(s, c(0, 0, 0), 0) + a1 <- abs(s1$coordinate) + expect_true(all(s1$coordinate < 0), "flipped sign") + expect_true(all(a1 > ETR_BAND_HI & a1 <= ETR_SNAP_OUTER), "lands in INCOH") + for (k in 1:200) s <- etr_step(s, c(0, 0, 0), 0) + a <- abs(s$coordinate) + expect_true(all(s$coordinate < 0) && all(a >= ETR_BAND_LO & a <= ETR_BAND_HI), + "settles in opposite band") }) -test_case("evaluate_status magnitude exactly 5.0 -> DREAD", function() { - expect_equal(evaluate_status(init_etr_state(c(5, 0, 0))), "DREAD") +# --- L4 drift required ------------------------------------------------ +test_case("L4: etr_step refuses to invent drift", function() { + expect_error(etr_step(init_etr_state()), "drift must be supplied") }) -test_case("evaluate_status magnitude exactly 15.0 -> FUNCTIONAL", function() { - expect_equal(evaluate_status(init_etr_state(c(15, 0, 0))), "FUNCTIONAL") +# --- L6 Z-path -------------------------------------------------------- +test_case("L6: z<0 -> alimentation (lattice); z>=0 -> transmutation (evolution)", function() { + expect_equal(determine_system_update_path(init_etr_state(c(0, 0, -3))), "LATTICE_REINFORCEMENT") + expect_equal(determine_system_update_path(init_etr_state(c(0, 0, 0))), "EXPERIMENTAL_EVOLUTION") + expect_equal(determine_system_update_path(init_etr_state(c(0, 0, 7))), "EXPERIMENTAL_EVOLUTION") }) -test_case("evaluate_status magnitude exactly 35.0 -> BLUR", function() { - expect_equal(evaluate_status(init_etr_state(c(35, 0, 0))), "BLUR") -}) - -test_case("evaluate_status magnitude exactly 50.0 -> INCOHERENT", function() { - expect_equal(evaluate_status(init_etr_state(c(50, 0, 0))), "INCOHERENT") -}) - -# --- evaluate_status: mid-band values --- -test_case("evaluate_status magnitude 10 -> DREAD", function() { - expect_equal(evaluate_status(init_etr_state(c(10, 0, 0))), "DREAD") -}) - -test_case("evaluate_status magnitude 25 -> FUNCTIONAL", function() { - expect_equal(evaluate_status(init_etr_state(c(25, 0, 0))), "FUNCTIONAL") -}) - -test_case("evaluate_status magnitude 40 -> BLUR", function() { - expect_equal(evaluate_status(init_etr_state(c(40, 0, 0))), "BLUR") -}) - -test_case("evaluate_status magnitude 60 -> INCOHERENT", function() { - expect_equal(evaluate_status(init_etr_state(c(60, 0, 0))), "INCOHERENT") -}) - -# --- determine_system_update_path: Z-axis governs the generative path --- -test_case("determine_system_update_path z > 0 -> LATTICE_REINFORCEMENT", function() { - expect_equal(determine_system_update_path(init_etr_state(c(0, 0, 7))), "LATTICE_REINFORCEMENT") -}) - -test_case("determine_system_update_path z exactly 0 -> LATTICE_REINFORCEMENT", function() { - expect_equal(determine_system_update_path(init_etr_state(c(0, 0, 0))), "LATTICE_REINFORCEMENT") -}) - -test_case("determine_system_update_path z < 0 -> EXPERIMENTAL_EVOLUTION", function() { - expect_equal(determine_system_update_path(init_etr_state(c(0, 0, -3))), "EXPERIMENTAL_EVOLUTION") -}) - -# --- shift_coordinate: toroidal wrap-around within [-bound, bound] --- -test_case("shift_coordinate wraps positive overflow on X (45 + 10 -> -45)", function() { - s <- shift_coordinate(init_etr_state(c(45, 0, 0)), c(10, 0, 0)) - expect_equal(s$coordinate, c(-45, 0, 0)) -}) - -test_case("shift_coordinate wraps negative overflow on X (-45 + -10 -> 45)", function() { - s <- shift_coordinate(init_etr_state(c(-45, 0, 0)), c(-10, 0, 0)) - expect_equal(s$coordinate, c(45, 0, 0)) -}) - -test_case("shift_coordinate does not wrap an in-bounds shift", function() { - s <- shift_coordinate(init_etr_state(c(10, 5, -5)), c(5, 5, 5)) - expect_equal(s$coordinate, c(15, 10, 0)) +# --- status ----------------------------------------------------------- +test_case("etr_status: per-axis zone vector", function() { + st <- etr_status(init_etr_state(c(25, 10, 40))) + expect_equal(st, c("IN_BAND", "SOFT", "INCOH")) }) test_summary()