#188: the Jacquard selector's 64 cards, L7 permanently on, L2 a real gate

Captain Bob, 2026-11-12: "Wire L2, keep L7 on". The table held the 128
cards of the 7-bit L1-L7 space; nothing read L7, and the rolling window
recorded whatever the card said, so the 128 were 32 behaviours.

- compudynamics.c: cd_tuning_word() sizes the table to 64 (L1-L6).
- ssm_jacquard.c: card_bits() makes a card's 7-bit mask, L7 set, where a
  card is read, seeded and started (C4: 17 -> card 8);
  ssm_l8_force_config() still takes the 7-bit index, its L7 bit implied.
- physics_execution_hooks.c, kernel vm_core.c: the rolling window records
  only while L2 is on (or before there is a config).
- ssm_jacquard.h says what is wired, bit by bit (it said L7 was);
  v3/CLAUDE.md and the L8 reference corrected in place; FABRIC-3 XLVIII.21.

Tests first, seen to fail against HEAD (logs/20261112-210510/kernel-tests):
test_ssm_jacquard 4 of 7, test_physics_l2 1 of 4; all pass now. v4 reads
no rolling window: acceptance is unchanged; the protocol runs next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
This commit is contained in:
Claude
2026-11-12 21:05:24 +00:00
parent 7e0728e46b
commit c76a742f2b
15 changed files with 233 additions and 19 deletions
+20
View File
@@ -6581,3 +6581,23 @@ and memorize frequently so we can pick up where we left off").
- **Seen to pass:** `mint_part_fail` (the new message, never "nothing changed", the old stick logs - **Seen to pass:** `mint_part_fail` (the new message, never "nothing changed", the old stick logs
in); `mint_fail`, every write failing, still "nothing changed"; `replace`, the success path. in); `mint_fail`, every write failing, still "nothing changed"; `replace`, the success path.
### 21. #188: the Jacquard selector's cards -- 64, each different, L2 a real gate (2026-11-12)
- **Found** (Captain Bob asked, 2026-11-12: "Is the jacquard selector implementing all of the
possible cards?"): the table held 128 cards, the 7-bit L1-L7 DoE space, and wrote all seven bits
into the VM's config, but nothing read L7 and the rolling window (L2) recorded every execution
whatever the card said: the 128 cards were 32 behaviours, four cards each. `ssm_jacquard.h` said
L7 was wired; it was not.
- **Ruled:** "Wire L2, keep L7 on" -- L7 permanently on, as `docs/reference/L8_ADAPTIVE_CONFIG_TABLE.md`
§2 says it is; L2 the selector's to switch.
- **Tested first, seen to fail:** `kernel/tests/test_ssm_jacquard.c` -- 64 cards, the start still
C4 (L3+L7), L7 on in all, all 64 L1-L6 settings distinct, a forced 7-bit index naming its card
with or without its L7 bit: 4 of 7 failed. `kernel/tests/test_physics_l2.c` -- with L2 off an
execution is not recorded: failed against the old hook.
- **Built:** `cd_tuning_word()` sizes the table to 64 (`compudynamics.c`); a card's index is its L1-L6 bits and
`card_bits()` in `ssm_jacquard.c` makes its 7-bit mask with L7 set, at the three places a card is
read, the DoE seeding, and the start (17 -> card 8). `ssm_l8_force_config()` takes the 7-bit index
as before. The rolling window records only while the config's L2 is on, or before there is a
config (`physics_execution_hooks.c`, `kernel/src/vm/vm_core.c`).
`ssm_jacquard.h` says what is wired, bit by bit; `v3/CLAUDE.md` and the L8 reference corrected.
- **Seen to pass:** both tests; v4 does not read the rolling window, so acceptance is unchanged.
@@ -4,6 +4,14 @@
**Author:** Claude Code session, June 2026 **Author:** Claude Code session, June 2026
**Affects:** `include/ssm_jacquard.h`, `src/ssm_jacquard.c`, `src/inference_engine.c`, `include/vm.h` **Affects:** `include/ssm_jacquard.h`, `src/ssm_jacquard.c`, `src/inference_engine.c`, `include/vm.h`
> **Corrected 2026-11-12 (#188; Captain Bob: "Wire L2, keep L7 on").** As built, the table has **64
> cards, not 128**: the six bits L1–L6, with L7 permanently on in every card, as §2's table says L7
> is. The 128 below -- the 2^7 DoE space -- held each card twice, differing only in an L7 bit that
> nothing read, so half the table was the same cards. A card's 7-bit mask is `(card << 1) | L7`;
> `ssm_l8_force_config()` still takes the 7-bit index and ignores its L7 bit. L2 (rolling window) was
> recorded whatever the card said; it is now L8's live toggle, as L4 is. The 128s below are left as
> the proposal said them.
--- ---
## 1. Executive Summary ## 1. Executive Summary
+7 -2
View File
@@ -785,8 +785,13 @@ void execute_colon_word(VM* vm)
uint32_t word_id = w->word_id; uint32_t word_id = w->word_id;
if (word_id < DICTIONARY_SIZE) if (word_id < DICTIONARY_SIZE)
{ {
/* L8 FINAL INTEGRATION: Loop #2 always-on - Rolling Window of Truth */ /* Loop #2: Rolling Window of Truth -- L8's live toggle (#188: it was always on,
rolling_window_record_execution(&vm->rolling_window, word_id); * so the selector's L2 bit did nothing). No config yet: recorded. */
{
const ssm_config_t *ssm_cfg = (const ssm_config_t*)vm->ssm_config;
if (!ssm_cfg || ssm_cfg->L2_rolling_window)
rolling_window_record_execution(&vm->rolling_window, word_id);
}
/* L8 FINAL INTEGRATION: Loop always-on */ /* Loop #4: Pipelining Transition Metrics - WIRED & UTILIZED */ /* L8 FINAL INTEGRATION: Loop always-on */ /* Loop #4: Pipelining Transition Metrics - WIRED & UTILIZED */
if (prev_word && prev_word->transition_metrics && ENABLE_PIPELINING) if (prev_word && prev_word->transition_metrics && ENABLE_PIPELINING)
+3 -1
View File
@@ -19,7 +19,9 @@ CC ?= cc
CFLAGS := -std=c99 -Wall -Wextra -Werror -O2 -g -I$(HERE) -I$(ROOT)/kernel/include \ CFLAGS := -std=c99 -Wall -Wextra -Werror -O2 -g -I$(HERE) -I$(ROOT)/kernel/include \
-I$(ROOT)/kernel/include/starkernel -I$(ROOT)/v3/include -I$(ROOT)/kernel/include/starkernel -I$(ROOT)/v3/include
TESTS := test_gpt test_usb_config test_sk_v4_stop test_sk_v4_slots test_sk_v4_mint test_sk_v4_identity test_heartbeat_export TESTS := test_gpt test_usb_config test_sk_v4_stop test_sk_v4_slots test_sk_v4_mint test_sk_v4_identity test_heartbeat_export test_ssm_jacquard test_physics_l2
SRC_test_physics_l2 := $(ROOT)/v3/src/physics_execution_hooks.c
SRC_test_ssm_jacquard := $(ROOT)/v3/src/ssm_jacquard.c $(ROOT)/v3/src/compudynamics.c
SRC_test_heartbeat_export := $(ROOT)/v3/src/heartbeat_export.c SRC_test_heartbeat_export := $(ROOT)/v3/src/heartbeat_export.c
SRC_test_sk_v4_identity := $(ROOT)/kernel/src/v4/sk_v4_identity.c $(ROOT)/kernel/src/v4/sk_v4_slots.c $(ROOT)/kernel/src/gpt.c SRC_test_sk_v4_identity := $(ROOT)/kernel/src/v4/sk_v4_identity.c $(ROOT)/kernel/src/v4/sk_v4_slots.c $(ROOT)/kernel/src/gpt.c
EXTRA_test_sk_v4_identity := -D__STARKERNEL__ -I$(ROOT)/v4/include -DSTICK_GROW_MIN_BYTES=8000000000ULL -DSTICK_ARTEMIS_CAP_GIB=256ULL EXTRA_test_sk_v4_identity := -D__STARKERNEL__ -I$(ROOT)/v4/include -DSTICK_GROW_MIN_BYTES=8000000000ULL -DSTICK_ARTEMIS_CAP_GIB=256ULL
+80
View File
@@ -0,0 +1,80 @@
/* SPDX-License-Identifier: LicenseRef-Starship-1.0
* Copyright (c) 2023-2026 Robert A. James
*/
/* test_physics_l2.c -- v3/src/physics_execution_hooks.c on the host (#188): the rolling window
* records a word's execution only while the live config's L2 (rolling window) is on -- L8's toggle,
* as L4 is -- and, with no config yet, records it. It recorded whatever the card said (Captain Bob,
* 2026-11-12: "Wire L2, keep L7 on").
*
* What the hook calls outside itself is defined here: the rolling window's recorder counts.
*/
#include "harness.h"
#include "vm.h"
#include "physics_execution_hooks.h"
#include "physics_metadata.h"
#include "platform_time.h"
#include "profiler.h"
#include "rolling_window_of_truth.h"
#include "ssm_jacquard.h"
#include <stdlib.h>
#include <string.h>
static unsigned recorded;
int rolling_window_record_execution(RollingWindowOfTruth *window, uint32_t word_id)
{
(void)window; (void)word_id; recorded++; return 0;
}
void physics_metadata_apply_linear_decay(DictEntry *entry, uint64_t elapsed_ticks, VM *vm)
{
(void)entry; (void)elapsed_ticks; (void)vm;
}
void physics_metadata_touch(DictEntry *entry, cell_t execution_heat, uint64_t now_ns)
{
(void)entry; (void)execution_heat; (void)now_ns;
}
void profiler_word_exit(const DictEntry *entry) { (void)entry; }
void sf_mutex_lock(sf_mutex_t *mutex) { (void)mutex; }
void sf_mutex_unlock(sf_mutex_t *mutex) { (void)mutex; }
void vm_heartbeat_run_cycle(VM *vm) { (void)vm; }
static sf_time_ns_t clock_ns(void) { return 1000u; }
static sf_time_backend_t backend;
const sf_time_backend_t *sf_time_backend = &backend;
static unsigned run(VM *vm, DictEntry *w) /* one execution of w: how many times was it recorded */
{
recorded = 0;
physics_pre_execute(vm, w, NULL);
return recorded;
}
int main(void)
{
VM *vm = calloc(1, sizeof *vm);
DictEntry w;
ssm_config_t cfg;
unsigned n;
backend.get_monotonic_ns = clock_ns;
CHECK(vm != 0, "a VM");
if (!vm) return DONE();
memset(&w, 0, sizeof w);
w.word_id = 7;
memset(&cfg, 0, sizeof cfg);
vm->ssm_config = NULL;
n = run(vm, &w);
CHECK(n == 1u, "no config yet: the execution is recorded (%u)", n);
vm->ssm_config = &cfg;
cfg.L2_rolling_window = 1;
n = run(vm, &w);
CHECK(n == 1u, "L2 on: the execution is recorded (%u)", n);
cfg.L2_rolling_window = 0;
n = run(vm, &w);
CHECK(n == 0u, "#188: L2 off: the execution is not recorded (%u times)", n);
free(vm);
return DONE();
}
+61
View File
@@ -0,0 +1,61 @@
/* SPDX-License-Identifier: LicenseRef-Starship-1.0
* Copyright (c) 2023-2026 Robert A. James
*/
/* test_ssm_jacquard.c -- v3/src/ssm_jacquard.c and compudynamics.c on the host (#188): the L8
* selector's cards are 64, every one different, L7 (adaptive heartrate) on in all of them -- it is
* permanently on (docs/reference/L8_ADAPTIVE_CONFIG_TABLE.md; Captain Bob, 2026-11-12: "Wire L2,
* keep L7 on") -- instead of 128 cards of which those differing only in L7 (or L2) acted alike.
*/
#include "harness.h"
#include "ssm_jacquard.h"
#include "compudynamics.h"
#include <string.h>
static unsigned tuple(const ssm_config_t *c) /* L1..L6 as six bits */
{
return (unsigned)(c->L1_heat_tracking << 5 | c->L2_rolling_window << 4 | c->L3_linear_decay << 3 |
c->L4_pipelining << 2 | c->L5_window_inference << 1 | c->L6_decay_inference);
}
int main(void)
{
ssm_l8_state_t st;
ssm_config_t cfg;
unsigned seen[64], c, n, distinct = 0, all_l7 = 1;
memset(&st, 0, sizeof st);
ssm_l8_init(&st, SSM_MODE_C4);
ssm_l8_init_table(&st);
CHECK(st.table != 0, "the adaptive table is allocated");
if (!st.table) return DONE();
n = st.table->num_configs;
CHECK(n == 64u, "#188: 64 cards -- L7 is not the selector's to choose (%u)", n);
memset(&cfg, 0, sizeof cfg);
ssm_apply_mode_from_table(&st, &cfg);
CHECK(cfg.L3_linear_decay == 1 && cfg.L7_adaptive_heartrate == 1 && tuple(&cfg) == 0x08u,
"the starting card is still C4: L3 and L7 only (L1-L6 bits %#x, L7 %d)", tuple(&cfg), cfg.L7_adaptive_heartrate);
memset(seen, 0, sizeof seen);
for (c = 0; c < n && c < 128u; c++) {
st.table->current_config = c;
memset(&cfg, 0, sizeof cfg);
ssm_apply_mode_from_table(&st, &cfg);
if (!cfg.L7_adaptive_heartrate) all_l7 = 0;
if (tuple(&cfg) < 64u && !seen[tuple(&cfg)]++) distinct++;
}
CHECK(all_l7, "#188: L7 on in every card");
CHECK(distinct == 64u && distinct == n, "#188: every card a different L1-L6 setting, all 64 of them (%u distinct of %u)", distinct, n);
/* forcing a card by its 7-bit index (inference_words.c): the L7 bit is implied on */
memset(&cfg, 0, sizeof cfg);
ssm_l8_force_config(&st, &cfg, 0x31u); /* 49: L2+L3+L7 -- C12 */
CHECK(tuple(&cfg) == 0x18u && cfg.L7_adaptive_heartrate == 1, "forced 0x31: L2, L3 and L7 (L1-L6 %#x)", tuple(&cfg));
memset(&cfg, 0, sizeof cfg);
ssm_l8_force_config(&st, &cfg, 0x30u); /* the same without its L7 bit */
CHECK(tuple(&cfg) == 0x18u && cfg.L7_adaptive_heartrate == 1, "forced 0x30: the same card, L7 still on (L1-L6 %#x, L7 %d)",
tuple(&cfg), cfg.L7_adaptive_heartrate);
ssm_l8_free_table(&st);
return DONE();
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3 -1
View File
@@ -12,7 +12,9 @@ Uses thermodynamic metaphors as modeling language (see `ONTOLOGY.md`):
6. **Loop #6 — Decay Slope Inference** (`inference_engine.c`) — exponential regression 6. **Loop #6 — Decay Slope Inference** (`inference_engine.c`) — exponential regression
7. **Loop #7 — Adaptive Heartrate** (`HeartbeatState`) — background tick coordinator 7. **Loop #7 — Adaptive Heartrate** (`HeartbeatState`) — background tick coordinator
**L8 Jacquard Mode Selector** (`ssm_jacquard.c`) — a 128-state, 7-bit loop-gate selector. **L8 Jacquard Mode Selector** (`ssm_jacquard.c`) — a 64-card loop-gate selector: L1–L6, with L7
permanently on (corrected 2026-11-12, #188 -- this said "a 128-state, 7-bit" selector; the cards
differing only in L7 or the then-unwired L2 acted alike).
In this repo it now has a real per-VM heat channel into fleet-wide tuning (VM Fleet In this repo it now has a real per-VM heat channel into fleet-wide tuning (VM Fleet
Attractor physics — see `docs/working/architecture/VM-FLEET-ATTRACTOR-DESIGN-20260705.md`), Attractor physics — see `docs/working/architecture/VM-FLEET-ATTRACTOR-DESIGN-20260705.md`),
replacing hardcoded compudynamics constants with a dynamically-inferred rate. replacing hardcoded compudynamics constants with a dynamically-inferred rate.
+3 -1
View File
@@ -141,7 +141,9 @@ typedef struct {
* a referenced global; these accessors are the aggregate-typed * a referenced global; these accessors are the aggregate-typed
* equivalent of that same constraint. */ * equivalent of that same constraint. */
/* Word level (ssm_jacquard.c): 128 configs (7-bit L1-L7), 8 regimes /* Word level (ssm_jacquard.c): 64 configs (6-bit L1-L6; L7 permanently on -- #188,
* 2026-11-12; it said 128 configs, 7-bit L1-L7, and the cards differing only in L7 were
* alike), 8 regimes
* (3-bit diversity/volatility/locality). Values match the constants * (3-bit diversity/volatility/locality). Values match the constants
* ssm_jacquard.c/.h used before this module existed -- this is a lift, * ssm_jacquard.c/.h used before this module existed -- this is a lift,
* not a retune. */ * not a retune. */
+16 -3
View File
@@ -127,7 +127,18 @@ typedef struct {
* macros (ENABLE_HOTWORDS_CACHE, ENABLE_PIPELINING, HEARTBEAT_THREAD_ENABLED) * macros (ENABLE_HOTWORDS_CACHE, ENABLE_PIPELINING, HEARTBEAT_THREAD_ENABLED)
* for the whole process lifetime. Adding these fields, and wiring them at * for the whole process lifetime. Adding these fields, and wiring them at
* their four call sites alongside (not instead of) the compile-time macros, * their four call sites alongside (not instead of) the compile-time macros,
* makes the bandit's full selection actually take effect. */ * makes the bandit's full selection actually take effect.
*
* Corrected 2026-11-12 (#188): that was true of L1 and L4, not of L7 -- nothing reads
* L7_adaptive_heartrate -- and L2 was never wired. What is wired now:
* L1 hotwords_cache_set_enabled() vm_time.c, kernel vm_runtime.c
* L2 rolling_window_record_execution(), gated physics_execution_hooks.c, kernel vm_core.c
* L3 linear decay physics_metadata.c
* L4 pipelining physics_execution_hooks.c
* L5 window inference inference_engine.c
* L6 decay inference inference_engine.c
* L7 not chosen: permanently on in every card (Captain Bob: "Wire L2, keep L7 on");
* the table is 64 cards, L1-L6, not 128. */
typedef struct { typedef struct {
int L1_heat_tracking; /* 1 = hotwords cache active, 0 = off */ int L1_heat_tracking; /* 1 = hotwords cache active, 0 = off */
int L2_rolling_window; /* 1 = window tracking active, 0 = off */ int L2_rolling_window; /* 1 = window tracking active, 0 = off */
@@ -146,7 +157,8 @@ typedef struct {
*/ */
/* ============================================================================ /* ============================================================================
* Adaptive Table Constants (L8 Heat-Ranked 128-Config Selector) * Adaptive Table Constants (L8 Heat-Ranked Config Selector: 64 cards, L1-L6, L7
* permanently on -- 128, the 7-bit space, before #188)
* One clock only: the heartbeat tick (rev r). Scoring and UCB reselection * One clock only: the heartbeat tick (rev r). Scoring and UCB reselection
* both run every tick -- no derived multi-tick batching unit exists * both run every tick -- no derived multi-tick batching unit exists
* anywhere in this mechanism. The score/UCB/reward/weight constants live * anywhere in this mechanism. The score/UCB/reward/weight constants live
@@ -173,7 +185,8 @@ typedef struct {
* ============================================================================ * ============================================================================
* *
* The adaptive table itself (CDConfigEntry/CDConfigTable) is the generic * The adaptive table itself (CDConfigEntry/CDConfigTable) is the generic
* compudynamics bandit (compudynamics.h), sized to 128 configs x 8 regimes * compudynamics bandit (compudynamics.h), sized to 64 cards x 8 regimes (#188;
* 128 before)
* via cd_tuning_word() -- see ssm_l8_init_table(). * via cd_tuning_word() -- see ssm_l8_init_table().
*/ */
+1 -1
View File
@@ -49,7 +49,7 @@ CDTuning cd_tuning_word(void)
t.volatility_high_threshold_q16 = 9830u; /* 0.15 * 65536 */ t.volatility_high_threshold_q16 = 9830u; /* 0.15 * 65536 */
t.locality_high_threshold_q16 = 32768u; /* 0.5 * 65536 */ t.locality_high_threshold_q16 = 32768u; /* 0.5 * 65536 */
/* bandit */ /* bandit */
t.num_configs = 128u; /* 7-bit L1-L7 */ t.num_configs = 64u; /* 6-bit L1-L6: L7 is permanently on, not chosen (#188) */
t.num_regimes = 8u; /* 3-bit diversity/volatility/locality */ t.num_regimes = 8u; /* 3-bit diversity/volatility/locality */
t.score_max = 65535u; t.score_max = 65535u;
t.score_min = 0u; t.score_min = 0u;
+7 -2
View File
@@ -94,8 +94,13 @@ void physics_pre_execute(VM* vm, DictEntry* word, DictEntry* prev)
uint32_t word_id = word->word_id; uint32_t word_id = word->word_id;
if (word_id >= DICTIONARY_SIZE) return; if (word_id >= DICTIONARY_SIZE) return;
/* Loop #2: Rolling Window of Truth */ /* Loop #2: Rolling Window of Truth -- L8's live toggle, as L4 below (#188: it recorded
rolling_window_record_execution(&vm->rolling_window, word_id); * whatever the card said, so the selector's L2 bit did nothing). No config yet: recorded. */
{
const ssm_config_t *ssm_cfg = (const ssm_config_t*)vm->ssm_config;
if (!ssm_cfg || ssm_cfg->L2_rolling_window)
rolling_window_record_execution(&vm->rolling_window, word_id);
}
/* Loop #4: Pipelining Transition Metrics. ENABLE_PIPELINING is the /* Loop #4: Pipelining Transition Metrics. ENABLE_PIPELINING is the
* compile-time ceiling; ssm_config's L4_pipelining is L8's live * compile-time ceiling; ssm_config's L4_pipelining is L8's live
+15 -8
View File
@@ -228,6 +228,12 @@ const char* ssm_l8_mode_name(ssm_l8_mode_t mode)
* @param table Pointer to the CDConfigTable to seed (already initialised * @param table Pointer to the CDConfigTable to seed (already initialised
* via cd_config_table_init()) * via cd_config_table_init())
*/ */
/* A card of the table is its index, 0..63: the six bits L1-L6, shifted up by one, with L7 -- the
* adaptive heartrate, permanently on (docs/reference/L8_ADAPTIVE_CONFIG_TABLE.md; Captain Bob,
* 2026-11-12: "Wire L2, keep L7 on") -- set below them. The table held 128, the 7-bit L1-L7 space,
* though L7 is not the selector's to choose: the cards differing only in L7 were the same (#188). */
static uint8_t card_bits(uint32_t card) { return (uint8_t)(((card & 0x3Fu) << 1) | SSM_CFG_L7); }
static void ssm_l8_seed_from_doe(CDConfigTable *table) static void ssm_l8_seed_from_doe(CDConfigTable *table)
{ {
/* Top-5% config indices in 7-bit space */ /* Top-5% config indices in 7-bit space */
@@ -238,7 +244,7 @@ static void ssm_l8_seed_from_doe(CDConfigTable *table)
uint32_t c, i; uint32_t c, i;
for (c = 0u; c < tuning.num_configs; c++) { for (c = 0u; c < tuning.num_configs; c++) {
uint8_t bits = (uint8_t)c; uint8_t bits = card_bits(c);
int has_l1 = (bits & SSM_CFG_L1) != 0; int has_l1 = (bits & SSM_CFG_L1) != 0;
int has_l4 = (bits & SSM_CFG_L4) != 0; int has_l4 = (bits & SSM_CFG_L4) != 0;
int is_top5 = 0; int is_top5 = 0;
@@ -256,8 +262,8 @@ static void ssm_l8_seed_from_doe(CDConfigTable *table)
initial_scores[c] = score; initial_scores[c] = score;
} }
/* Start on C4-equivalent (config index 17: L3+L7 only) */ /* Start on C4-equivalent (7-bit 17, L3+L7 only: card 8) */
cd_config_table_seed(table, &tuning, initial_scores, 17u); cd_config_table_seed(table, &tuning, initial_scores, (uint8_t)(17u >> 1));
} }
/* ============================================================================ /* ============================================================================
@@ -288,7 +294,7 @@ static void ssm_l8_tick_score_and_select(CDConfigTable *table, ssm_config_t *con
uint32_t current_window, uint32_t locality_q16) uint32_t current_window, uint32_t locality_q16)
{ {
CDTuning tuning = cd_tuning_word(); CDTuning tuning = cd_tuning_word();
uint8_t active_bits = table->entries[table->current_config].config_bits; uint8_t active_bits = card_bits(table->entries[table->current_config].config_bits);
int use_joint_convergence = ((active_bits & SSM_CFG_L5) || (active_bits & SSM_CFG_L6)) ? 1 : 0; int use_joint_convergence = ((active_bits & SSM_CFG_L5) || (active_bits & SSM_CFG_L6)) ? 1 : 0;
/* --- ANOVA stability: this tick's own outcome, not an accumulated /* --- ANOVA stability: this tick's own outcome, not an accumulated
@@ -418,7 +424,7 @@ void ssm_l8_update_table(ssm_l8_state_t *state, const ssm_l8_metrics_t *metrics,
/* Mirror L2/L3/L5/L6 bits into the legacy mode field for diagnostics */ /* Mirror L2/L3/L5/L6 bits into the legacy mode field for diagnostics */
{ {
uint8_t bits = table->entries[table->current_config].config_bits; uint8_t bits = card_bits(table->entries[table->current_config].config_bits);
state->current_mode = (ssm_l8_mode_t)( state->current_mode = (ssm_l8_mode_t)(
(((bits & SSM_CFG_L2) ? 1u : 0u) << 3) | (((bits & SSM_CFG_L2) ? 1u : 0u) << 3) |
(((bits & SSM_CFG_L3) ? 1u : 0u) << 2) | (((bits & SSM_CFG_L3) ? 1u : 0u) << 2) |
@@ -445,7 +451,7 @@ void ssm_apply_mode_from_table(const ssm_l8_state_t *state, ssm_config_t *config
if (!state || !state->table || !config) return; if (!state || !state->table || !config) return;
table = state->table; table = state->table;
bits = table->entries[table->current_config].config_bits; bits = card_bits(table->entries[table->current_config].config_bits);
config->L1_heat_tracking = (bits & SSM_CFG_L1) ? 1 : 0; config->L1_heat_tracking = (bits & SSM_CFG_L1) ? 1 : 0;
config->L2_rolling_window = (bits & SSM_CFG_L2) ? 1 : 0; config->L2_rolling_window = (bits & SSM_CFG_L2) ? 1 : 0;
@@ -472,11 +478,12 @@ void ssm_apply_mode_from_table(const ssm_l8_state_t *state, ssm_config_t *config
* *
* @param state L8 state with an attached, initialised adaptive table * @param state L8 state with an attached, initialised adaptive table
* @param config Runtime configuration to update immediately * @param config Runtime configuration to update immediately
* @param config_idx Desired config, masked to the 128-entry (7-bit) space * @param config_idx Desired config in the 7-bit L1-L7 space; its L7 bit is ignored -- L7 is on in
* every card (#188) -- and it names card config_idx >> 1
*/ */
void ssm_l8_force_config(ssm_l8_state_t *state, ssm_config_t *config, uint8_t config_idx) void ssm_l8_force_config(ssm_l8_state_t *state, ssm_config_t *config, uint8_t config_idx)
{ {
if (!state || !state->table || !config) return; if (!state || !state->table || !config) return;
cd_config_table_force(state->table, config_idx & 0x7Fu); cd_config_table_force(state->table, (uint8_t)((config_idx & 0x7Fu) >> 1)); /* its L7 bit is implied (#188) */
ssm_apply_mode_from_table(state, config); ssm_apply_mode_from_table(state, config);
} }