diff --git a/docs/plans/A8-etr.md b/docs/plans/A8-etr.md index 725ee76..3e1223d 100644 --- a/docs/plans/A8-etr.md +++ b/docs/plans/A8-etr.md @@ -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. diff --git a/src/endocrine/etr/etr.m b/src/endocrine/etr/etr.m index 9c0e732..c112a80 100644 --- a/src/endocrine/etr/etr.m +++ b/src/endocrine/etr/etr.m @@ -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_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 + centre = (ETR_BAND_LO() + ETR_BAND_HI()) / 2; % 26 + 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 diff --git a/src/endocrine/etr/etr_invariants.md b/src/endocrine/etr/etr_invariants.md index b4bfcb6..71f21fc 100644 --- a/src/endocrine/etr/etr_invariants.md +++ b/src/endocrine/etr/etr_invariants.md @@ -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** 7–17 · **BAND** 17–35 · **INCOH** 35–45 · **SNAP_OUT** >45. In the basin (7–45) 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 (7–45) 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** (L1–L3), 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** (L1–L3, 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. diff --git a/src/endocrine/etr/test_etr.m b/src/endocrine/etr/test_etr.m index f927907..cb226df 100644 --- a/src/endocrine/etr/test_etr.m +++ b/src/endocrine/etr/test_etr.m @@ -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; - '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; - '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]); + '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; + + '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', '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)'; + 'L5 couple', 'coupling active (stress>0) alters coord', 'stress->axis mapping undefined (C1)'; }; -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');