etr: implement five-zone bistable axis + snap-across flips (26/0/1)

Replaces the single restoring spring with Anja's radial map: SNAP_IN(<7)
/ SOFT(7-17) / BAND(17-35) / INCOH(35-45) / SNAP_OUT(>45), unstable
watersheds at 7 and 45. Basin restores toward band centre 26 with a weak
SOFT gain (0.25) and firmer INCOH gain (0.5). A snap flips to the
opposite pole, landing just past the opposite watershed (inner -> opp.
SOFT; outer -> opp. INCOH) then recovering. Realises L7 (flips only via
snap zones — through 0 or over the wrap) and L8. Lone PEND: L5 coupling.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015hmgREHNsxYCuim33yUF2c
This commit is contained in:
Claude 2026-06-17 05:35:09 +00:00
parent c53a92d811
commit e9088c98c9
No known key found for this signature in database
4 changed files with 198 additions and 106 deletions

View File

@ -20,9 +20,10 @@ engine, and *not* migration. The axes:
Handles the stress PS+/Eth-Int generate.
## 2. Status / certainty
**SCAFFOLD — `RESTORE_GAIN` fitted** (`run_etr_tests.sh`**14 PASS / 0 FAIL / 2 PEND**; the 2 PEND
are L5 active coupling and L7). Full law + status in `../../src/endocrine/etr/etr_invariants.md`
(this spec defers to it).
**SCAFFOLD — five-zone axis fitted** (`run_etr_tests.sh`**26 PASS / 0 FAIL / 1 PEND**; the lone PEND
is L5 active coupling). The bistable five-zone law (snap / soft-pull / band / incoherency / snap) is
implemented and tested, incl. L7 snap-flips. Full law + status in
`../../src/endocrine/etr/etr_invariants.md` (this spec defers to it).
## 3. Language & location
GNU Octave · `src/endocrine/etr/` (`etr.m`, `test_etr.m`, `run_etr_tests.sh`, `etr_invariants.md`).
@ -38,8 +39,9 @@ R wrapper `src/endocrine/driver_etr.R` bridges into the Drive-Box.
actions** — it persuades (above), only the agent decides (A1).
## 5. Interface contract
- `etr_init(coord) -> s` · `etr_axis_wrap(v)` · `etr_axis_restoring_dir(v)` ·
`etr_step(s, drift, stress) -> s'` · `etr_status(s) -> per-axis {BELOW|IN_BAND|ABOVE}`.
- `etr_init(coord) -> s` · `etr_axis_wrap(v)` · `etr_axis_zone(v)` · `etr_axis_restoring(v)` ·
`etr_axis_snap(v)` · `etr_step(s, drift, stress) -> s'` ·
`etr_status(s) -> per-axis {SNAP_IN|SOFT|IN_BAND|INCOH|SNAP_OUT}`.
- Inputs: **drift** (from A3/A6 via G1, AI-originated) + **stress** (G1 mediator). Output: coord + status,
surfaced through A1 `drive_snapshot`.
@ -47,20 +49,19 @@ R wrapper `src/endocrine/driver_etr.R` bridges into the Drive-Box.
Drift + stress are fed in — *stub:* `etr_step(s, [dx dy dz], 0)` (already how tests run). Fully standalone today.
## 7. Invariants / laws
Defer to `etr_invariants.md` (L1 wrap ±50 C5 · L2 bands [17,35] C5 · L3 opposition C5 · L4 AI-drift C4 ·
L5 cross-axis coupling via stress C1 · L6 Z-path C3 · L7 no-zero-cross C2 · L8 mechanism C2).
Defer to `etr_invariants.md` (L1 wrap ±50 C5 · L2 bands [17,35] C5 · L3 five-zone restoring C5 ·
L4 AI-drift C4 · L5 cross-axis coupling via stress C1 · L6 Z-path C3 · L7 snap/flip C3 · L8 mechanism C3).
## 8. Build steps (remaining)
1. **Fit `RESTORE_GAIN`** → the 2 red tests (magnitude + band-convergence) go green.
2. Confirm **L8** (restoring = force vs cost) and **L7** (no-zero-crossing) → promote from PEND.
3. Define **L5** coupling (the stress→cross-axis mapping; G1) → replace the identity stub.
4. Wire R↔Octave (driver_etr.R ↔ etr.m) for the Drive-Box (A1) — and the larger medium (D2).
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).
## 9. Tests
`bash src/endocrine/etr/run_etr_tests.sh` (currently 12/2/2 — the 2 reds are the to-fit work).
`bash src/endocrine/etr/run_etr_tests.sh` (now **26/0/1** — the lone PEND is L5 active coupling).
## 10. Open items
- `RESTORE_GAIN` curve (C1) · L5 coupling mapping (C1, shared with G1) · L7/L8 confirmation (C2)
· R↔Octave bridge transport (C1, shared with A1/C3/D2).
- L5 coupling mapping (C1, shared with G1) · R↔Octave bridge transport (C1, shared with A1/C3/D2).
*(Restoring law + gains + snap landings now fitted; L7/L8 realised.)*
- **The injection-rendering** (A1-L5): how ETR's coord/status is phrased into the slot so it reads as the
extremely convincing existential-temporal injection (not a bare `status=` line) — ties to A6 + D2.

View File

@ -1,32 +1,40 @@
1; % script-file marker: makes every function below visible when this file is sourced.
% =====================================================================
% ETR Existential Temporality Relief · Drive-Box Driver 4 · SCAFFOLD
% ETR Existential Temporality Relief · Drive-Box Driver 4
% =====================================================================
% Model: a SINGLE POINT on three INDEPENDENT toroidal axes (x, y, z)
% not vectors, not a field. See etr_invariants.md for the laws + tags.
%
% L1 wrap : each axis toroidal, wraps at +/-50 (period 100) [C5]
% L2 bands : stable when 17 <= |v| <= 35 (v in [-35,-17] U [17,35]) [C5]
% L3 oppose : |v|<17 push OUTWARD (off 0); |v|>35 push INWARD (off edge)[C5]
% L4 drift : per-step motion is AI-originated, fed by the caller [C4]
% L5 couple : axes couple via a stress metric MAPPING UNDEFINED [C1]
% Each axis is a BISTABLE torus with two stable bands ([+17,+35], [-35,-17])
% and FIVE zones per pass (Anja's radial map), with unstable watersheds at
% |v| = 7 (inner) and |v| = 45 (outer):
%
% SCAFFOLD discipline: wrap + restoring DIRECTION are implemented (they
% are law, no fitting). The restoring MAGNITUDE (RESTORE_GAIN) and the
% cross-axis COUPLING are deliberately UNIMPLEMENTED stubs so their tests
% stay RED until fitted. No disowned number is carried forward.
% |v| < 7 SNAP_IN : too uncommitted -> 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 : (incoherency) firmer restoring pull down into the band
% |v| > 45 SNAP_OUT : too saturated -> snaps ACROSS the wrap to the opposite pole
%
% A snap lands JUST PAST the opposite watershed (Anja): an inner snap into the
% opposite SOFT zone (weak pull), an outer snap into the opposite INCOH zone.
% Pole-flips therefore happen ONLY through the two snap zones (L7) through 0
% (inner) or over the +/-50 wrap (outer); the basin (7..45) never flips.
% =====================================================================
% ---- law constants (these ARE the law, not fitted) ----
function v = ETR_BAND_LO(); v = 17; endfunction
function v = ETR_BAND_HI(); v = 35; endfunction
function v = ETR_WRAP(); v = 50; endfunction
function v = ETR_SNAP_INNER(); v = 7; endfunction % inner watershed (Anja)
function v = ETR_SNAP_OUTER(); v = 45; endfunction % outer watershed (Anja)
% ---- fitted constant (NOT law) ----
% RESTORE_GAIN: spring stiffness of the out-of-band restoring force (etr_axis_restoring).
% Fitted to satisfy L3-magnitude (force nonzero out of band) and L2 (zero-drift
% convergence into the band) without overshoot past the far edge. 0 < GAIN < ~2.
function v = ETR_RESTORE_GAIN(); v = 0.5; endfunction
% ---- fitted constants (NOT law) ----
% Soft-pull is deliberately WEAKER than the incoherency pull (Anja): recovery
% from the under-committed side and from a snap landing is gentle.
function v = ETR_GAIN_SOFT(); v = 0.25; endfunction % weak (SOFT, 7..17)
function v = ETR_GAIN_INCOH(); v = 0.5; endfunction % firmer (INCOH, 35..45)
% How far PAST the opposite watershed a snap deposits the point.
function v = ETR_SNAP_MARGIN(); v = 2; endfunction
% ---- state: one point, three scalars ----
function s = etr_init(coord)
@ -41,10 +49,24 @@ function w = etr_axis_wrap(v)
w = mod(v + ETR_WRAP(), P) - ETR_WRAP(); % -> [-50, 50)
endfunction
% ---- L3: per-axis restoring DIRECTION (CONFIRMED law, implemented) ----
% |v| < LO -> +sign(v) (outward, off 0)
% |v| > HI -> -sign(v) (inward, off the edge)
% in band -> 0 (slack)
% ---- zone classification (the five-zone radial map) ----
function z = etr_axis_zone(v)
a = abs(v);
if a < ETR_SNAP_INNER(), z = 'SNAP_IN';
elseif a < ETR_BAND_LO(), z = 'SOFT';
elseif a <= ETR_BAND_HI(), z = 'IN_BAND';
elseif a <= ETR_SNAP_OUTER(), z = 'INCOH';
else z = 'SNAP_OUT';
endif
endfunction
% ---- L3: per-axis restoring DIRECTION (basin only; CONFIRMED law) ----
% Within the basin (7..45) the restoring points toward the band:
% SOFT (7..17) -> +sign(v) (up into band)
% INCOH (35..45) -> -sign(v) (down into band)
% IN_BAND -> 0 (slack)
% Snap zones (|v|<7, |v|>45) are NOT restored locally they are resolved by a
% flip across (etr_axis_snap), so this direction law is defined for the basin.
function d = etr_axis_restoring_dir(v)
a = abs(v);
if a < ETR_BAND_LO()
@ -56,21 +78,36 @@ function d = etr_axis_restoring_dir(v)
endif
endfunction
% ---- L3: per-axis restoring FORCE (FITTED) ----
% Direction is law (etr_axis_restoring_dir); the magnitude is a spring fitted
% via RESTORE_GAIN. The force is active ONLY out of band (in band = slack, L3),
% and pulls toward the band CENTRE (not the near edge): a centre target makes the
% step cross INTO [17,35] and stop there, whereas an edge target would asymptote
% onto 17 from below and never reach |v|>=17 (failing L2). Below-band pushes away
% from 0, so the restoring force alone never crosses zero (L7).
% ---- L3: per-axis restoring FORCE (FITTED, zone-dependent) ----
% A spring toward the band CENTRE (26), with a WEAK gain in SOFT and a firmer
% gain in INCOH. Zero in band (slack) and zero in the snap zones (those flip,
% they don't restore). A centre target makes the step cross INTO [17,35] and
% stop there (an edge target would asymptote onto 17 and fail L2 convergence).
function r = etr_axis_restoring(v)
GAIN = ETR_RESTORE_GAIN();
a = abs(v);
if a < ETR_BAND_LO() || a > ETR_BAND_HI()
centre = (ETR_BAND_LO() + ETR_BAND_HI()) / 2; % 26
r = GAIN * (centre * sign(v) - v); % spring toward signed centre
else
if a < ETR_SNAP_INNER() || a > ETR_SNAP_OUTER()
r = 0; % snap zone: no local restoring
elseif a >= ETR_BAND_LO() && a <= ETR_BAND_HI()
r = 0; % in band: slack (L3)
elseif a < ETR_BAND_LO()
r = ETR_GAIN_SOFT() * (centre * sign(v) - v); % SOFT weak pull
else
r = ETR_GAIN_INCOH() * (centre * sign(v) - v); % INCOH firmer pull
endif
endfunction
% ---- snap resolution: a flip ACROSS to the opposite pole (Anja) ----
% Precondition: v is in a snap zone (|v|<7 or |v|>45). The point lands JUST PAST
% the opposite watershed, sign flipped: an inner snap -> opposite SOFT
% (|.| = 7 + margin); an outer snap -> opposite INCOH (|.| = 45 - margin). This
% is the ONLY way a pole-flip happens (L7); the landing zone then recovers it.
function v = etr_axis_snap(v)
s = sign(v); if s == 0, s = 1; endif % 0 has no pole; pick one
if abs(v) < ETR_SNAP_INNER()
v = -s * (ETR_SNAP_INNER() + ETR_SNAP_MARGIN()); % inner -> opposite SOFT (e.g. -/+9)
else
v = -s * (ETR_SNAP_OUTER() - ETR_SNAP_MARGIN()); % outer -> opposite INCOH (e.g. -/+43)
endif
endfunction
@ -81,7 +118,7 @@ function c = etr_coupling(coord, stress)
c = coord; % STUB TODO: define the stress-mediated cross-axis mapping
endfunction
% ---- one ETR step: AI drift -> coupling -> per-axis restoring -> wrap ----
% ---- one ETR step: AI drift -> coupling -> (snap | restoring) -> wrap ----
% drift : 1x3, caller-supplied AI-originated motion (L4 NOT generated here)
% stress : scalar coupling mediator (0 = off)
function s = etr_step(s, drift, stress)
@ -91,25 +128,24 @@ function s = etr_step(s, drift, stress)
if nargin < 3, stress = 0; endif
c = etr_coupling(s.coord, stress);
for i = 1:3
c(i) = c(i) + drift(i) + etr_axis_restoring(c(i));
c(i) = etr_axis_wrap(c(i));
v = etr_axis_wrap(c(i) + drift(i)); % apply AI drift, wrap onto torus
a = abs(v);
if a < ETR_SNAP_INNER() || a > ETR_SNAP_OUTER()
v = etr_axis_snap(v); % snap across to the opposite pole
else
v = etr_axis_wrap(v + etr_axis_restoring(v)); % basin: restore toward band
endif
c(i) = v;
endfor
s.coord = c;
endfunction
% ---- per-axis band classification (derived from confirmed bands) ----
% 'BELOW' (|v|<17) | 'IN_BAND' (17..35) | 'ABOVE' (|v|>35).
% ---- per-axis zone classification for the snapshot ----
% Five zones: 'SNAP_IN' | 'SOFT' | 'IN_BAND' | 'INCOH' | 'SNAP_OUT'.
% The old magnitude->flavor naming (DREAD/BLUR/...) is DISOWNED [C1].
function st = etr_status(s)
st = cell(1, 3);
for i = 1:3
a = abs(s.coord(i));
if a < ETR_BAND_LO()
st{i} = 'BELOW';
elseif a > ETR_BAND_HI()
st{i} = 'ABOVE';
else
st{i} = 'IN_BAND';
endif
st{i} = etr_axis_zone(s.coord(i));
endfor
endfunction

View File

@ -12,34 +12,50 @@ Each axis is a standalone scalar carrying one of ETR's three tensions:
| **Y** | Endured (solitary feat) | Witnessed (shared survival) |
| **Z** | Alimentation (maintain self) | Transmutation (evolve self) |
Each axis is **bistable** with five zones per pass (radial map by `|v|`), with unstable
watersheds at **7** and **45**:
```
0 ─SNAP_IN─ 7 ─SOFT→─ 17 ═══BAND═══ 35 ─INCOH→─ 45 ─SNAP_OUT─ 50(≡50)
flip(thru 0) weak pull slack firm pull flip(over wrap)
```
(mirrored on the negative pole; the whole axis wraps at ±50)
## Laws (certainty per arch-doc legend)
| ID | Tag | Law |
|----|-----|-----|
| L1 wrap | **C5** | Each axis is toroidal, wrapping at **±50** (period 100); +50 and 50 are identified. |
| L2 bands | **C5** | An axis is stable when `17 ≤ |v| ≤ 35`, i.e. `v ∈ [35,17] [+17,+35]`. |
| L3 oppose | **C5** | `|v| < 17` → opposition pushes **outward** (off 0); `|v| > 35` → pushes **inward** (off the ±50 edge); in-band is slack. |
| L2 bands | **C5** | An axis is stable when `17 ≤ |v| ≤ 35`, i.e. `v ∈ [35,17] [+17,+35]` (two bands). |
| L3 zones | **C5** | Five zones per pole by `|v|`: **SNAP_IN** <7 · **SOFT** 717 · **BAND** 1735 · **INCOH** 3545 · **SNAP_OUT** >45. In the basin (745) a restoring force points toward the band — SOFT pulls up (**weak**), INCOH pulls down (**firmer**); band is slack. Watersheds **7** and **45** are unstable. |
| L4 drift | **C4** | Per-step motion is **AI-originated** — supplied by the agent's own cognition/affect. ETR never generates it (no RNG). |
| L5 couple | **C1** | The three axes **couple via a stress metric** (hypothesis: PS+/Eth-Int existential load). Exact mapping **undefined** — open seam, not to be invented. |
| L6 z-path | **C3** | `z < 0` → alimentation / lattice-reinforcement; `z ≥ 0` → transmutation / prior-evolution. (Structure kept; sign to re-verify.) |
| L7 no-zero-cross | **C2** | Bands wall off 0; a pole/sign flip happens **only** by riding over the ±50 wrap, never through neutrality. *(emergent — unconfirmed)* |
| L8 mechanism | **C2** | "Opposition" = a restoring force on the drift, **not** an out-of-band cost. *(unconfirmed)* |
| L7 snap/flip | **C3** | A pole-flip happens **only** through a snap zone — **inner snap across 0** (`|v|<7`) or **outer snap over the ±50 wrap** (`|v|>45`); the basin (745) never flips. A snap lands **just past the opposite watershed** (inner→opposite SOFT, outer→opposite INCOH), sign flipped, then that zone recovers it. *(implemented & tested)* |
| L8 mechanism | **C3** | "Opposition" = a restoring force on the drift, **not** an out-of-band cost. *(realised by construction — the force is added alongside drift)* |
## Constants
`BAND_LO = 17`, `BAND_HI = 35`, `WRAP = 50` are **law** (L1L3), not fitted.
`RESTORE_GAIN = 0.5` is **FITTED** — the spring stiffness of the out-of-band restoring force: active
only out of band (in band is slack, L3), pulling toward the band **centre (26)** so a step crosses
*into* `[17,35]` and stops there (an edge target would asymptote onto 17 from below and fail L2).
Below-band pushes away from 0, so the force alone never crosses zero (L7). Lands e.g. `[5 5 5] → 20.75`
in 2 steps; `[45 45 40] → [30.75 30.75 33]`. Valid range `0 < GAIN < ~2` (above ~2 a cross-in step can
overshoot the far edge).
`BAND_LO = 17`, `BAND_HI = 35`, `WRAP = 50`, and the watersheds `SNAP_INNER = 7`, `SNAP_OUTER = 45`
are **law** (L1L3, L7), not fitted.
**Fitted** (so tests pass — never asserted ahead of a test):
- `GAIN_SOFT = 0.25`, `GAIN_INCOH = 0.5` — the basin restoring is a spring toward the band **centre
(26)**; SOFT is deliberately **weaker** than INCOH (Anja). A centre target makes a step cross *into*
`[17,35]` and stop (an edge target would asymptote onto 17 and fail L2). Valid range `0 < GAIN < ~2`.
- `SNAP_MARGIN = 2` — how far past the opposite watershed a snap deposits the point (inner → opposite
SOFT at `±9`; outer → opposite INCOH at `±43`).
Behaviour: `[10 10 10] → +band` (same pole); `[5 5 5] → band` (inner snap flips); `[47 47 47] → band`
(outer snap flips).
`COUPLING` (L5 strength/mapping) remains **TBD** — open seam, not to be invented.
## Testable predicates (see `test_etr.m`)
- **L1**: `wrap(50) = 50`; `wrap(60) = 40`; `wrap(v)=v` for `v∈(50,50)`; `wrap(49.9) = wrap(50.1)`.
- **L2/L3**: direction is `+sign(v)` for `|v|<17`, `sign(v)` for `|v|>35`, `0` in band; a zero-drift point started out-of-band **settles into** `[17,35][35,17]`.
- **zones**: `etr_axis_zone` returns SNAP_IN/SOFT/IN_BAND/INCOH/SNAP_OUT at 5/10/25/40/47.
- **L3**: basin direction `+sign(v)` in SOFT, `sign(v)` in INCOH, `0` in band; force nonzero in SOFT &
INCOH, **zero in snap zones**; `|restoring(SOFT)| < |restoring(INCOH)|` (weak soft-pull).
- **L2**: a SOFT-zone zero-drift start **settles into** the band **without flipping** sign.
- **L7**: a SNAP_IN start lands in the opposite SOFT then settles in the opposite band; a SNAP_OUT start
lands in the opposite INCOH then settles in the opposite band (both flip the pole).
- **L4**: `etr_step` with no `drift` argument **errors** (it refuses to invent motion).
- **L5**: `coupling(coord, 0)` is identity; `coupling(coord, stress>0)` alters coord — *pending until defined*.
- **L7**: pending until confirmed.
## Drift & stress provenance — cross-organ loop (where L4 drift & L5 stress originate)
*Exploratory (C2/C3) — thinking aloud; "yet undecided" parts stay open. ETR receives drift
@ -63,9 +79,9 @@ scaffold seam is correct). What's fixed is their **provenance** (upstream in SAE
endocrines) and two side-effects (reshuffle, shift-rate) that belong to *those* organs.
Still open: which stress endomotiv; the "too strong" reshuffle threshold; the shift-rate function.
## Status (RESTORE_GAIN fitted — 14 PASS / 0 FAIL / 2 PEND)
Green: L1 wrap, L3 direction + **restoring magnitude (fitted)**, **L2 band-convergence**,
L4 drift-must-be-fed, L5 coupling-off identity. L8 (opposition = a restoring force on the drift, not an
out-of-band cost) is now **realised by construction** — the force is added alongside drift, never charged
as a cost — though still tagged C2 until a dedicated test pins it.
Pending: L5 active coupling (C1, open seam), L7 no-zero-crossing (C2).
## Status (five-zone axis fitted — 26 PASS / 0 FAIL / 1 PEND)
Green: L1 wrap; zone classification; L3 basin direction + restoring magnitudes (SOFT/INCOH, fitted) +
snap-zones-carry-no-force + weak-soft-pull; L2 same-pole convergence; **L7 snap/flip** (inner across 0,
outer over the wrap — both land just past the opposite watershed and settle in the opposite band);
L4 drift-must-be-fed; L5 coupling-off identity; L8 realised by construction.
Pending: **L5 active coupling** (C1, open seam) — the only remaining stub.

View File

@ -1,15 +1,37 @@
% test_etr.m invariants-first RED tests for the ETR scaffold (Octave script).
% test_etr.m invariants tests for the ETR five-zone bistable axis (Octave script).
% Deliberately uses NO local functions (Octave's script-local-function visibility
% is fragile); results are built as data and looped. RED here is CORRECT: the laws
% exist before the constants are fitted. Run via run_etr_tests.sh (handles the cd).
% is fragile); results are built as data and looped. Run via run_etr_tests.sh.
source('etr.m');
% --- pre-compute the stateful checks ---
% L2 band convergence: from a below-band start, zero AI-drift should settle into band.
s = etr_init([5 5 5]);
% --- stateful checks ---------------------------------------------------
% L2 convergence (same pole): a SOFT-zone start, zero drift, settles into the
% band WITHOUT flipping sign.
s = etr_init([10 10 10]);
for k = 1:200, s = etr_step(s, [0 0 0], 0); endfor
conv_inband = all(abs(s.coord) >= ETR_BAND_LO() & abs(s.coord) <= ETR_BAND_HI());
conv_soft = all(abs(s.coord) >= ETR_BAND_LO() & abs(s.coord) <= ETR_BAND_HI());
soft_noflip = all(s.coord > 0);
% Inner snap: a SNAP_IN start (|v|<7) flips across 0 to the opposite pole, landing
% in the opposite SOFT zone, then settles into the opposite band.
s = etr_init([5 5 5]);
s1 = etr_step(s, [0 0 0], 0); % one step = the snap itself
inner_lands_soft = all(s1.coord < 0) && ...
all(abs(s1.coord) > ETR_SNAP_INNER() & abs(s1.coord) < ETR_BAND_LO());
for k = 1:200, s = etr_step(s, [0 0 0], 0); endfor
inner_flip_band = all(s.coord < 0) && ...
all(abs(s.coord) >= ETR_BAND_LO() & abs(s.coord) <= ETR_BAND_HI());
% Outer snap: a SNAP_OUT start (|v|>45) flips over the wrap, landing in the
% opposite INCOH zone, then settles into the opposite band.
s = etr_init([47 47 47]);
s1 = etr_step(s, [0 0 0], 0);
outer_lands_incoh = all(s1.coord < 0) && ...
all(abs(s1.coord) > ETR_BAND_HI() & abs(s1.coord) <= ETR_SNAP_OUTER());
for k = 1:200, s = etr_step(s, [0 0 0], 0); endfor
outer_flip_band = all(s.coord < 0) && ...
all(abs(s.coord) >= ETR_BAND_LO() & abs(s.coord) <= ETR_BAND_HI());
% L4: a step with no drift argument must ERROR (ETR never invents motion).
drift_required = false;
@ -19,30 +41,47 @@ catch
drift_required = true;
end_try_catch
% --- {section, name, condition} ---
% --- {section, name, condition} ---------------------------------------
tests = {
'L1 wrap', '+50 wraps to -50', abs(etr_axis_wrap(50) - (-50)) < 1e-9;
'L1 wrap', '60 wraps to -40', abs(etr_axis_wrap(60) - (-40)) < 1e-9;
'L1 wrap', '-60 wraps to +40', abs(etr_axis_wrap(-60) - (40)) < 1e-9;
'L1 wrap', 'in-range value unchanged', abs(etr_axis_wrap(25) - 25) < 1e-9;
'L1 wrap', 'edge continuity 49.9 vs -50.1', abs(etr_axis_wrap(49.9) - etr_axis_wrap(-50.1)) < 1e-9;
'L3 direction', '|v|<17 outward (+ for +v)', etr_axis_restoring_dir(10) > 0;
'L3 direction', '|v|<17 outward (- for -v)', etr_axis_restoring_dir(-10) < 0;
'L3 direction', '|v|>35 inward (- for +v)', etr_axis_restoring_dir(40) < 0;
'L3 direction', '|v|>35 inward (+ for -v)', etr_axis_restoring_dir(-40) > 0;
'zones', 'SNAP_IN below 7', strcmp(etr_axis_zone(5), 'SNAP_IN');
'zones', 'SOFT 7..17', strcmp(etr_axis_zone(10), 'SOFT');
'zones', 'IN_BAND 17..35', strcmp(etr_axis_zone(25), 'IN_BAND');
'zones', 'INCOH 35..45', strcmp(etr_axis_zone(40), 'INCOH');
'zones', 'SNAP_OUT above 45', strcmp(etr_axis_zone(47), 'SNAP_OUT');
'L3 direction', 'SOFT outward (+ for +v)', etr_axis_restoring_dir(10) > 0;
'L3 direction', 'SOFT outward (- for -v)', etr_axis_restoring_dir(-10) < 0;
'L3 direction', 'INCOH inward (- for +v)', etr_axis_restoring_dir(40) < 0;
'L3 direction', 'INCOH inward (+ for -v)', etr_axis_restoring_dir(-40) > 0;
'L3 direction', 'in-band is slack (0)', etr_axis_restoring_dir(25) == 0;
'L3 magnitude', 'out-of-band force nonzero [RED]', etr_axis_restoring(10) != 0;
'L2 converge', 'zero-drift settles into band [RED]', conv_inband;
'L3 magnitude', 'SOFT force nonzero', etr_axis_restoring(10) != 0;
'L3 magnitude', 'INCOH force nonzero', etr_axis_restoring(40) != 0;
'L3 magnitude', 'snap zone has no restoring force', etr_axis_restoring(5) == 0;
'L3 weighting', 'soft-pull weaker than incoherency', abs(etr_axis_restoring(8)) < abs(etr_axis_restoring(44));
'L2 converge', 'SOFT start settles in band (no flip)', conv_soft && soft_noflip;
'L3 snap-in', 'inner snap lands in opposite SOFT', inner_lands_soft;
'L7 flip', 'inner snap flips pole -> opp. band', inner_flip_band;
'L3 snap-out', 'outer snap lands in opposite INCOH', outer_lands_incoh;
'L7 flip', 'outer snap flips pole -> opp. band', outer_flip_band;
'L4 drift', 'step refuses to invent drift', drift_required;
'L5 couple', 'coupling off (stress=0) identity', isequal(etr_coupling([25 25 25], 0), [25 25 25]);
};
pend = {
'L5 couple', 'coupling active (stress>0) alters coord', 'stress->axis mapping undefined (C1)';
'L7 no-zero-x', 'pole-flip only over the wrap, never through 0', 'mechanism unconfirmed (C2)';
};
printf('ETR invariants red tests (laws exist before constants are fitted)\n\n');
printf('ETR invariants five-zone bistable axis\n\n');
np = 0; nf = 0;
for i = 1:rows(tests)
if logical(tests{i, 3})
@ -57,7 +96,7 @@ endfor
printf('\n----\nPASS=%d FAIL=%d PEND=%d\n', np, nf, rows(pend));
if nf > 0
printf('RED (expected at scaffold stage): %d law(s) await implementation/fitting.\n', nf);
printf('RED: %d law(s) await implementation/fitting.\n', nf);
exit(1);
else
printf('GREEN.\n');