diff --git a/docs/fabric/FABRIC-3.md b/docs/fabric/FABRIC-3.md index f00b1bad..378faf5c 100644 --- a/docs/fabric/FABRIC-3.md +++ b/docs/fabric/FABRIC-3.md @@ -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 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. diff --git a/docs/reference/L8_ADAPTIVE_CONFIG_TABLE.md b/docs/reference/L8_ADAPTIVE_CONFIG_TABLE.md index 5a05b239..86de7dfb 100644 --- a/docs/reference/L8_ADAPTIVE_CONFIG_TABLE.md +++ b/docs/reference/L8_ADAPTIVE_CONFIG_TABLE.md @@ -4,6 +4,14 @@ **Author:** Claude Code session, June 2026 **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 diff --git a/kernel/src/vm/vm_core.c b/kernel/src/vm/vm_core.c index 918c7ea9..aa191e3f 100644 --- a/kernel/src/vm/vm_core.c +++ b/kernel/src/vm/vm_core.c @@ -785,8 +785,13 @@ void execute_colon_word(VM* vm) uint32_t word_id = w->word_id; if (word_id < DICTIONARY_SIZE) { - /* L8 FINAL INTEGRATION: Loop #2 always-on - Rolling Window of Truth */ - rolling_window_record_execution(&vm->rolling_window, word_id); + /* Loop #2: Rolling Window of Truth -- L8's live toggle (#188: it was always on, + * 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 */ if (prev_word && prev_word->transition_metrics && ENABLE_PIPELINING) diff --git a/kernel/tests/Makefile b/kernel/tests/Makefile index fccd8ce5..aca9f740 100644 --- a/kernel/tests/Makefile +++ b/kernel/tests/Makefile @@ -19,7 +19,9 @@ CC ?= cc CFLAGS := -std=c99 -Wall -Wextra -Werror -O2 -g -I$(HERE) -I$(ROOT)/kernel/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_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 diff --git a/kernel/tests/test_physics_l2.c b/kernel/tests/test_physics_l2.c new file mode 100644 index 00000000..1dbfb89e --- /dev/null +++ b/kernel/tests/test_physics_l2.c @@ -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 +#include + +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(); +} diff --git a/kernel/tests/test_ssm_jacquard.c b/kernel/tests/test_ssm_jacquard.c new file mode 100644 index 00000000..46ff28df --- /dev/null +++ b/kernel/tests/test_ssm_jacquard.c @@ -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 + +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(); +} diff --git a/logs/20261112-210510/kernel-tests/all-after.log b/logs/20261112-210510/kernel-tests/all-after.log new file mode 100644 index 00000000..0e24c3b7 --- /dev/null +++ b/logs/20261112-210510/kernel-tests/all-after.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13bcf89889793171b3af6a22237020c7dce4d8540acc51c81e7d4fe1f4ba03bf +size 474 diff --git a/logs/20261112-210510/kernel-tests/physics_l2-before.log b/logs/20261112-210510/kernel-tests/physics_l2-before.log new file mode 100644 index 00000000..43a984e4 --- /dev/null +++ b/logs/20261112-210510/kernel-tests/physics_l2-before.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c6d484ae6bb8bd92ec35bcc728317b59fb894ae0966149bee0a9d6f72bbdf38 +size 296 diff --git a/logs/20261112-210510/kernel-tests/ssm_jacquard-before.log b/logs/20261112-210510/kernel-tests/ssm_jacquard-before.log new file mode 100644 index 00000000..182efea1 --- /dev/null +++ b/logs/20261112-210510/kernel-tests/ssm_jacquard-before.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4171cb3fb250885abbc800e3d4a37c49938558d07a75379dbb00e69f4d207155 +size 679 diff --git a/v3/CLAUDE.md b/v3/CLAUDE.md index a9680202..a2544e3e 100644 --- a/v3/CLAUDE.md +++ b/v3/CLAUDE.md @@ -12,7 +12,9 @@ Uses thermodynamic metaphors as modeling language (see `ONTOLOGY.md`): 6. **Loop #6 — Decay Slope Inference** (`inference_engine.c`) — exponential regression 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 Attractor physics — see `docs/working/architecture/VM-FLEET-ATTRACTOR-DESIGN-20260705.md`), replacing hardcoded compudynamics constants with a dynamically-inferred rate. diff --git a/v3/include/compudynamics.h b/v3/include/compudynamics.h index 845680e8..bf209e43 100644 --- a/v3/include/compudynamics.h +++ b/v3/include/compudynamics.h @@ -141,7 +141,9 @@ typedef struct { * a referenced global; these accessors are the aggregate-typed * 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 * ssm_jacquard.c/.h used before this module existed -- this is a lift, * not a retune. */ diff --git a/v3/include/ssm_jacquard.h b/v3/include/ssm_jacquard.h index e339208b..8e5dd5fd 100644 --- a/v3/include/ssm_jacquard.h +++ b/v3/include/ssm_jacquard.h @@ -127,7 +127,18 @@ typedef struct { * macros (ENABLE_HOTWORDS_CACHE, ENABLE_PIPELINING, HEARTBEAT_THREAD_ENABLED) * for the whole process lifetime. Adding these fields, and wiring them at * 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 { int L1_heat_tracking; /* 1 = hotwords cache 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 * both run every tick -- no derived multi-tick batching unit exists * 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 - * 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(). */ diff --git a/v3/src/compudynamics.c b/v3/src/compudynamics.c index a1ed459a..987fb537 100644 --- a/v3/src/compudynamics.c +++ b/v3/src/compudynamics.c @@ -49,7 +49,7 @@ CDTuning cd_tuning_word(void) t.volatility_high_threshold_q16 = 9830u; /* 0.15 * 65536 */ t.locality_high_threshold_q16 = 32768u; /* 0.5 * 65536 */ /* 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.score_max = 65535u; t.score_min = 0u; diff --git a/v3/src/physics_execution_hooks.c b/v3/src/physics_execution_hooks.c index 33a1a0b7..e0414224 100644 --- a/v3/src/physics_execution_hooks.c +++ b/v3/src/physics_execution_hooks.c @@ -94,8 +94,13 @@ void physics_pre_execute(VM* vm, DictEntry* word, DictEntry* prev) uint32_t word_id = word->word_id; if (word_id >= DICTIONARY_SIZE) return; - /* Loop #2: Rolling Window of Truth */ - rolling_window_record_execution(&vm->rolling_window, word_id); + /* Loop #2: Rolling Window of Truth -- L8's live toggle, as L4 below (#188: it recorded + * 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 * compile-time ceiling; ssm_config's L4_pipelining is L8's live diff --git a/v3/src/ssm_jacquard.c b/v3/src/ssm_jacquard.c index 84ffdf31..b3d5691d 100644 --- a/v3/src/ssm_jacquard.c +++ b/v3/src/ssm_jacquard.c @@ -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 * 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) { /* 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; 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_l4 = (bits & SSM_CFG_L4) != 0; int is_top5 = 0; @@ -256,8 +262,8 @@ static void ssm_l8_seed_from_doe(CDConfigTable *table) initial_scores[c] = score; } - /* Start on C4-equivalent (config index 17: L3+L7 only) */ - cd_config_table_seed(table, &tuning, initial_scores, 17u); + /* Start on C4-equivalent (7-bit 17, L3+L7 only: card 8) */ + 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) { 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; /* --- 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 */ { - 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)( (((bits & SSM_CFG_L2) ? 1u : 0u) << 3) | (((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; 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->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 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) { 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); } \ No newline at end of file