Firmware (Soldier + Queen)
SkillDev toolsUse when working on the silken_net STM32 firmware, Soldier (sense→TinyML→Lorenz→encrypt→TX, STOP2 loop) and Queen (LoRa RX→CIFO dedup→CoAP flush, OTA) in firmware/{soldier,queen}/main.c, the mruby bio_contract.rb, and the header-only One-Home libs in firmware/common/ (silken_sha256, lorenz_seed, lora_ccm, silken_crc, queen_attest). Knows the non-obvious gotchas, ECB-restore after CBC, Load_AES_Key before MX_CRYP_Init, RTC DR0..DR19 budget (DR7 freed by FW.54), post-FW.29 StatusByte bit-layout, Lorenz continuation vs cold-start, HAL_GetTick frozen in STOP2 (wall-seconds instead, HAL-free arithmetic in wall_time.h, the RTC read in soldier/main.c), vcap = VDDA-mV not EDLC-Vcap, gated CCM vs live ECB, and the host-test parity discipline (make -C firmware/test). Routes to CLAUDE.md §3 + the 03_01..03_06 canon, does not restate. Examples: \"add a sensor field\", \"change Lorenz params\", \"modify AES / CRYP init\", \"touch RTC-persisted state\", \"why do LoRa decrypts fail after a flush\", \"edit the seed / cold-start crypto\".
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Firmware (Soldier + Queen) skill
What this skill tells your AI
The instructions your AI receives, as published by alexey-lukin/silken_net in .claude/skills/firmware/SKILL.md and read by ahel’s review.
Navigation aid + non-obvious gotchas. The SSOT is the docs + code below — this skill points, it does not restate (so it can't drift). Verify a fact at its home before trusting a summary.
SSOT Documents — Read These First
| Document | What it covers |
|---|---|
CLAUDE.md §3 | High-level architecture, AES table, OTA, Lorenz (summary — for exact packet/Status-byte bit-layout trust the canon docs below, not the summary) |
docs/03_01_Firmware_Lifecycle_and_DMA.md | Soldier/Queen lifecycle, STOP2 phases, ISR map, RTC Backup Register Map §2 (canonical DR0..DR19 + magic markers), binary packet format §8, mesh-relay + rendezvous ladder §1.9 (CCM-era star-only; single-Queen scale ceiling §1.9.1) |
docs/03_02_Queen_Gateway_Firmware.md | Queen: LoRa RX → CIFO dedup → CoAP flush, OTA broadcast, AES mode transitions |
docs/03_03_TinyML_Acoustic_Inference.md | Audio DMA, TinyML INT8 forward-pass (NOT CMSIS-NN), decision logic, confidence thresholds (DR13/DR14) |
docs/03_04_mruby_Lorenz_Attractor.md | Lorenz constants (§1.2; Z-thresholds §4.1), Float-not-BigDecimal parity, RTC continuation vs cold-start (§2.1), Status-byte packing (§4.4) |
docs/03_05_Hardware_Symmetric_Crypto_and_Security.md | AES modes, key rotation (FW.17), SE05x secure element (baseline SE051C2; SEC.6 + SEC.14 provisioning-only — KEYL stays in Protected Flash; ATECC608B = banner-legacy pattern), IV, QATT wire (§2.2), PQC roadmap |
docs/03_06_Factory_Flashing_and_Key_Provisioning.md | Factory home (split from 03_05 §3.4): HKDF per-device derivation, K_seed, K_ota (FW.23 §4), flashing pipeline + ops-security (SEC.3 §5) |
docs/00_07_Action_Plan_Tracker.md | FW./SEC. task status, open items (canonical home of blockers) |
Source Files
| File | Role |
|---|---|
firmware/soldier/main.c | Sensor node: sense → TinyML → Lorenz → encrypt → TX (multi-phase STOP2 loop) |
firmware/queen/main.c | Gateway: RX → decrypt → CIFO cache → batch flush via CoAP (CCM-ера gated: blind courier — air-кадр (30B rev2.1) НЕ розшифровується, запис air+1 via rx_route.h) |
firmware/queen/*.h | Queen-only pure headers (host-tested): at_engine/coap_pdu/sim7070_coap (FW.3/FW.56 модем-труба + Sim7070_Read_Csq), sim7070_udp (FW.60 сира CA*-розмова poll-тракту: Sim7070_Udp_Fetch, CARECV-заголовок посимвольно; механіка poll'а = Queen_Poll_Downlink main.c — канон 03_02 §4а), uart_rx_ring, ota_window (FW.52б), ota_sha_guard (FW.52 SHA-256 cross-check на Magic Re-Request по закритому OTA-вікну — Queen's ВЛАСНА сторінка 125 [номер збігається з Soldier-івською 125 (K_ota/KEYB, 03_01 §2.3) — інший чіп, не колізія, але завжди читай «сторінка 125» з іменем чіпа поруч], magic-last; свідомо сиблінг firmware/common/flash_ota.h, не reuse), rx_route (FW.2 роутер 16/air + запис air+1), soldier_cmd_queue (FW.20-Q2), coap_iv (SEC.12), helium_sos (ARCH.34: 12B SOS-wire byte-parity з HeliumSosWorker + тригер + бюджет сліпоти; MAC-шов Helium_Mac_SendSos = adapter у firmware/queen/lorawan_glue/helium_mac.c; ⚠️ поріг тиші SOS = 2× каденс флашу — заголовок несе лише #ifndef-дефолт для host-тестів, справжній каденс підставляє main.c з FLUSH_INTERVAL_MS+jitter і _Static_assertить нерівність: фіксоване 30 хв було МЕНШЕ за 60-хв каденс і кричало на здоровій Королеві при N ≈ 45–160 (⚖️ 2026-09-10) — той самий клас, що #13(a): pure-заголовок, який дзеркалить константу main.c, мусить мати #ifndef і static-assert, не число) |
firmware/bio_contracts/bio_contract.rb | mruby Lorenz attractor (runs on MCU); calculate_state is the sole entry-point |
firmware/common/*.h | Shared header-only One-Home libs (compile into BOTH firmware + host tests — kill the mirror-drift pattern): silken_sha256.h (SHA-256/HMAC, FIPS/RFC KAT), lorenz_seed.h (FW.30 cold-start deriv), silken_crc.h (CRC16-CCITT, OTA), lora_ccm.h (CCM packet), lora_phy.h (FW.61 базлайн модуляції raw-LoRa P2P — pure, БЕЗ Radio, тож його включає й cad_sniff.h у host-тестах; дім номіналів 03_05 §2.1, модель airtime tools/firmware/lora_airtime.rb) + lora_phy_apply.h (той самий профіль → драйвер: Lora_Phy_Apply_Tx/Rx, єдиний шов, ЄДИНИЙ файл у common/, що тягне вендорський radio.h; ⛔ не зливати з pure-половиною), tx_defer.h (FW.10 Cold-TX: Should_Defer_TX — freeze-contract на < −15°C/4000mV, host-gated One-Home; раніше тест-локальний дубль = false-green; ⚠️ [ARCH.99] Vcap-половина у полі НЕ розрізняє — див. гочу 9), adc_convert.h (FW.50 VREFINT-cal ADC→mV; wired: vcap_voltage = Adc_Vdda_Mv(...) — VDDA-mV, not EDLC-Vcap), queen_attest.h (L1 QATT v2 signed-batch envelope + 8B health-блок пульсу — ARCH.54; wire home 03_05 §2.2; residue 1, ct=0 heartbeat легальний), flash_kv.h+fc_hiwater.h+ota_antirollback.h (journal Flash-KV база + FC/OTA-version high-water — SEC.20 anti-rollback ключ 0x15, строго >; база вимкнена з фіч-фліп-гейтів через SEC20_OTA_ANTIROLLBACK_ENABLED — вкл. декл/mount/compact (Put32 НЕ auto-компактить)), fw_report.h (SEC.20 wire-звіт contract-стану `[semantic:1 |
firmware/test/ | x86 host-based tests — make -C firmware/test (host gate; not gcov-instrumented — per-line coverage gaps → best-TU трюк [[reference_firmware_coverage_gcov_gotcha]]). make -C firmware/test asan = ASan+UBSan dynamic memory-safety lane (TEST.5, CI-gating in firmware_test — keep it green; canon 04_06 §B.1.1). New crypto in common/ ⇒ add a parity test vs OpenSSL, don't re-copy logic into the test file. |
firmware/extern/subghz-phy | Vendored stm32-mw-subghz-phy @v1.5.0 (FW.46 Шлях A): radio_driver/radio.h = THE Semtech API both main.c compile against (owned stub deleted; both register a static RadioEvents_t — driver keeps the pointer). radio.c NOT compiled until radio_conf.h exists (.ioc board-freeze); ⚠️ its lorawan/ dir is the LBM radio layer (SWL2001 support: smtc_ral/ralf, radio_planner, SPI chip drivers) — NOT a MAC stack; the ARCH.34 LoRaMac-node = separate submodule firmware/extern/stm32-mw-lorawan @v2.6.2; its Conf/*_template.h are CubeMX-fill templates → owned copies live in queen/lorawan_glue/ (EU868-only profile, soft_timer/systime pure-cores, helium_mac.c adapter — episode = fresh OTAA join, DevNonce = the ONLY cross-join persist via MIB_NVM_CTXS→flash_kv 0x30). Host-smoke of the REAL MAC on a stub radio: make -C firmware/test helium_mac_smoke — full OTAA join+uplink cycle vs an in-test mock-LNS (crypto-valid JoinAccept on the zero-key se-identity; server-side MIC + FRM parity with LNS-derived session keys; deadline = the real 20s blind budget at SF12-TOA) + a crypto-valid 23B JoinRequest + DevNonce monotonicity/KV-reboot. The mock needs -DAES_DEC_PREKEYED (smoke-target-only — the server ENCRYPTS JoinAccept with AES-decrypt; mirrored in firmware/.clangd). ⚠️ Helium_Mac_Bind_Nvm keeps the pointer forever — bind only a static/file-scope FlashKv (ASan stack-use-after-scope class). ⚠️ The recipe links -lm: RegionCommon.c calls floor(), and the coverage lane's -O0 (make coverage) does NOT inline it as a builtin the way -O1 (asan) / -O2 (normal) do → a green local helium_mac_smoke + asan still red-CIs on coverage without it (the class: a vendored-math TU needs explicit -lm; make coverage is the only lane that catches it). ARM lanes: lorawan_mac_check (glue+MAC) + hal_check_ccm compiles the gated Queen KV-mount block (ARCH34_HELIUM_ENABLED=1). The submodule is OUR FORK Alexey-Lukin/stm32-mw-lorawan @v2.6.2-silken.1 — v2.6.2 + one fix: RegionCommonComputeSymbolTimeLoRa SF11/SF12 signed-overflow (upstream Semtech master has the same hole; UBSan found it, upstream-PR = 00_07 ARCH.34 👤). Bumping the submodule → re-check that fix survives. Include-path lives in CMake hal-lane + firmware/.clangd (change one → sync the other) |
Line counts drift every commit — don't hardcode them (see
[[feedback_no_volatile_counts]]);wc -lif you need a number.
Gotchas Not Obvious From Docs
- Shared peripheral, per-operation reconfig: restore the baseline — and make sure a baseline EXISTS. Named instance: Queen MUST call
Restore_ECB_Mode()after every CBC operation, else subsequent LoRa decrypts silently fail (wrong KeySize + wrong key in CRYP). The class is wider than CRYP — it has bitten in three different shapes, and the breakage always lands on the NEXT user of the peripheral, never on the caller who reconfigured. Check all three whenever a peripheral has more than one caller (CRYP · Radio · SPI1 · ADC · DMA):- (a) configured, never returned — the original SEC.8/FW.16 ECB↔CBC, plus CCM width-units (
MX_CRYP_Restore_From_CCMputs DATATYPE back to WORD, gotcha 7.1). CRYP-specific home:03_05 §7. - (b) nothing to restore TO — ✅ closed 2026-09-11 [FW.61], and the shape is worth keeping because it is the one a restore-reflex cannot reach. Baseline LoRa modulation used to be set by no call at all, so the peripheral carried whatever the last writer left.
RadioInitis not a baseline: it programs timers/IRQ andSUBGRF_SetTxParams(RFO_LP, 0, …)— the low-power PA at 0 dBm — while SF/BW/CR/preamble/CRC stay unwritten, andSubgRf.RxContinuousstaysfalse, which silently turns the Queen's always-on listener into RX-single. A driver default is not a baseline; only an explicit call is. The doc home of the NUMBERS is03_05 §2.1(00_06 §2.3registry); the compiler's declared MIRROR isfirmware/common/lora_phy.h(pure profile) +lora_phy_apply.h(Lora_Phy_Apply_Tx/Rx— the single seam both firmwares andmake -C firmware/test lora_phycall). ⚠️ The two setters share ONE pair of driver structs, so order matters: TX then RX, andRadioSendrewrites payload length per frame. - (c) a partial restore reads as a complete one — ✅ closed by construction in the same pass, and that is the point: the panic-TX restore used to re-set ONE of six
SetTxConfigarguments (preamble) and pass a privateLORA_PANIC_*quartet for the rest, correct only because that quartet happened to equal canon. Those defines are gone; PANIC now differs from baseline in exactly one argument, because there is only one place the other twelve are written. Whenever you see a "restore" that names fewer arguments than the call it undoes, the fix is not to widen the restore but to remove the second copy of the defaults. 1b. Queen CDNSGIP re-resolve on flush-fail — FW.58 host-half SHIPPED (vilize 07-11) —coap_server_ipis cached for the boot, butCoap_Reresolve_Due(coap_consec_fail)(sim7070_coap.h) now invalidates it after N≥3 consecutive flush-fails → next flush re-resolves (an A-record flip = the zero-infra failover is picked up без IWDG-reboot).coap_consec_failstreak resets on anysend_success;g_coap_fail_countstays a lifetime health-odometer (never reset). DNS-fail path already clears the IP (returnbefore the streak logic — no double-count). Silicon-confirm = RUNBOOK §5.5 (👤 bench).CMD_SET_BACKENDnever existed — the doc-phantom is purged. 1c. Retransmit is UPLINK-only — poll has none, and the canon claimed otherwise for months (FW.63).Flush_Cache_To_Railsincrementscoap_midONCE and retries the same PDU insidefor (retry < COAP_MAX_RETRIES)— a genuine same-MID CON retransmit.Queen_Poll_Downlinkdoes the opposite:coap_mid++on every attempt andreturns on an empty reply, andSim7070_Udp_Fetchis a one-shotCAOPEN→CASEND→CADATAIND→CARECV→CACLOSEwith no internal retry. So a lost 2.05 on the downlink poll is never re-requested, and because Rails advances the command state when it BUILDS the reply, the order is lost forever while the trail saysconfirmed. Rails'CoapGate::REPLY_CACHEis real but only dedups a duplicated datagram. ⚠️ And "it cannot fire without a retransmit" — which stood here — was wrong in the direction that costs:coap_midlives in the Queen's RAM and the cache slot has no TTL, so after a reboot the same number arrives carrying a DIFFERENT question. Serving the cache there answers someone else's poll and silently swallows the live pending command. Closed 2026-09-08 by keying the slot on a request FINGERPRINT (route + query): match →poll_duplicate, mismatch →poll_mid_collision+ fresh derivation. The transferable half: a cache keyed on a counter that its writer resets is keyed on nothing — ask what zeroes the key, not just what increments it. Two reflexes: (a) never generalise one CoAP tract's reliability onto the other — that is exactly how the false claim entered three docs and two code comments; (b) the OTA leg shows the correct shape —g_ota_fetch_next_chdoes not advance until the chunk is handled, so a lost chunk is simply re-requested; CMD has no such cursor. ⚠️ CMD now has an equivalent cursor too — closed 2026-09-09, ratified 2026-09-10. Rails no longer advances pastdispatch!at build time;acknowledge!waits forobserve_delivered_command!to see acmd=<token>echo from Queen (g_last_acked_cmd_token, mirroringfw=) on a later poll, so a lost 2.05 now re-serves the same envelope instead of losing the order —Cmd_Dedup_Checkmakes the redelivery safe. Design-justification (why echo over blind re-delivery or a documented open-loop) →03_02 §4а. ⚠️ And the echoed token is the LAST colon-field, never «the 3rd colon» (FW.60, 2026-09-10): anACTION:valuepayload shifted the old window onto ACTUATOR_ID, so the echo never matchedidempotency_tokenand an EXECUTED command died by TTL asfailed— the dedup hash stayed self-consistent, which is exactly why nothing noticed. Locator = pure headerfirmware/queen/cmd_token.h(host-pinned, mutation-verified); Rails forbids a colon in ACTION (ActuatorCommand::ALLOWED_PAYLOAD_FORMAT), so both ends agree by construction — do not reintroduce a colon-counting parser.
- (a) configured, never returned — the original SEC.8/FW.16 ECB↔CBC, plus CCM width-units (
- RTC registers are nearly all spoken-for — but allocated ≠ optimally packed — DR0..DR19 allocated except DR7, freed by FW.54 (DID is now deterministic
f(UID)recomputed on boot viadid_derive.h—03_01 §7; spend DR7 only via the03_01 §2.2procedure). After the FW.2 freeze-contract even DR15 is reserved (CCM Frame Counter, dormant untilFW2_CCM_ENABLEDflips at bench). For new persisted state: first check03_01 §2.3.2reclamation (under-utilized DRs → freeable without Flash: DR2 holds a 1-bit flag, DR14/DR19 pack) before paying Flash-KV (03_01 §2.3) wear. RAM-state inventory + key→field map =03_01 §2.3.1. Canonical register map (bit-fields + magic markers) =03_01 §2— never restate the allocation in code comments, reference it. Load_AES_Key()BEFOREMX_CRYP_Init()— reversing the order means CRYP uses a zeroed key. Both files follow this order but it's easy to break during refactoring.- Cold-TX deferral — at low temp + low Vcap (FW.10 winter ESR guard), Soldier skips TX entirely to avoid brownout. Don't assume every wake cycle emits a packet.
- StatusByte layout is post-FW.29 — packet byte 10 =
[PanicFlag:1 (bit7) | Status:2 (bits6..5) | GrowthPoints:5 (bits4..0)]; firmware packs(status << 5) | growth_points, backend unpacks(byte & 0x1F) * 2.Statusnow has 4 values (0 homeostasis / 1 stress / 2 anomaly / 3 vm_error — [SLASH-1 P0, 2026-07-05] status=3 is written ONLY byBIO_STATUS_VM_ERROR; mrubypack_status_bytereturns 0..2; physical tamper rides PANIC_FLAG, NOT the status bits — backend decodes 3 asvm_error→firmware_faultalert, nevervandalism_breach). Normal frames force bit7=0 (lora_payload[10] &= ~PANIC_FLAG_BIT); panic frames set it. Canon:03_04 §4.4Status-byte +03_01 §8. - Lorenz state: continuation vs cold-start — on boot,
DR19 == LORENZ_STATE_MAGIC (0x4C5A5354 "LZST") && isfinite(x,y,z)→ restore(x,y,z)from DR16/17/18 (warm continuation, >99% of cycles); else cold-start derives(x₀,y₀,z₀)fromK_seed(Flash) viafirmware/common/lorenz_seed.h(pure-C HMAC-SHA256 — byte-parity with backendSeedDerivation, FW.30 closed; mbedTLS no longer needed).epoch_dayprefers beacon UTC (soldier_unix_ts, FW.20) over the RTC calendar; RTC-default after VBAT loss = 10957 (2000-01-01) = backend'sFIRMWARE_RTC_DEFAULT_EPOCH_DAY. After each cycle the C side MUST write DR16-18 + set DR19. Canon:03_04 §2.1; layout03_01 §2. - CCM is gated, ECB is live — WL CCM API is TWO-PHASE, and the CCM era is TWO-KEY — the FW.2 AES-128-CCM path (30B wire-rev2.1, 8B MIC, DR15 FC) is fully integrated behind
FW2_CCM_ENABLED 0(Soldier Phase-4 + panic TX; Queen = blind courier — cleartext-DID demux, NO decrypt, air+1 records viafirmware/queen/rx_route.h). The shipping build is AES-128-ECB transitional. Traps: (1)HAL_CRYPEx_AESCCM_Encrypt/DecryptDO NOT EXIST in WL-HAL (F4/F7/L4 API) — the real flow isInit.B0(Build_CCM_B0) +HAL_CRYP_Encrypt/Decrypt(Size in BYTES) +HAL_CRYPEx_AESCCM_GenerateAuthTAG+ caller-sideFw2_Ccm_Tag_Equal; after CCM restore width-units to WORD (MX_CRYP_Restore_From_CCM). (2) In the 14B CCM plaintext (rev2.1)status_byteis offset 6, offset 10 isdiag, offsets 12..13 areema_delta_t_s(«wire = GP input» contract, E.63 (г)) — never read CCM payload through legacy 16B offsets. (3) Key-scoping (FW.2 (в), 2026-07-03): Soldier's ambienthcryp= KEYB cluster control-plane (bcast_key, page 125,Load_Broadcast_Keyfail-open fallback→KEYL); sessionaes_key(KEYL) lives ONLY inside the CCM scope —MX_CRYP_Init_CCMsetspKeyEXPLICITLY, Restore returns KEYB (a sticky session key would MIC-fail every frame on Rails). Queen's single LoRa key = the KEYB value (her code unchanged). FW.17 ratchet rotates session only. Mesh Scenario Б is gated OUT of the CCM build (star-only; mesh return = wire-rev3 addressing →00_07ARCH.43, scale ceiling03_01 §1.9.1). (4) Gated-branch code compiles NOWHERE locally — host tests mirror, clangd can't see past the gate; a call before itsstaticdefinition needs an explicit prototype or onlyhal_check_ccm(CI) catches it. All flip-gates DECIDED (founder 2026-07-03) — flip day = bench attestation only;03_05 §2.1 flip-checklist+§3.1 two-key model+00_07 FW.2. HAL_GetTick()is frozen in STOP2 — SysTick stops duringEnterSTOP2Mode, so a tick delta measures only active-time, NOT wall-time. Host mocks have a monotonic tick → they hide this. FW.49 S1 host-half is shipped:delta_t_seconds(the primary bio-signal) runs on wall-seconds. ⚠️ The timebase is SPLIT across two files on purpose, and the header is the half that must stay pure:firmware/common/wall_time.hcarries the HAL-free arithmetic (Silken_Wall_Delta_Seconds·Silken_Wall_Elapsed_Seconds·Silken_Wall_Is_Utc·Silken_Civil_From_Unix) so it compiles into the firmware AND the host tests, while the HAL half —Wall_Seconds_Now, which reads the RTC calendar and is ≡0 on silicon until the clock is set — lives infirmware/soldier/main.c. ⛔ Do not add a HAL call to the header: that seam is exactly what makes «host-half is shipped» mean anything. (Wall_Seconds_Now, RTC-calendar timebase, cold-start/backwards/epoch-jump guards → сентинелDELTA_T_UNKNOWN_S; wiredT:2saturates @0xFFFF). ⚠️ On siliconWall_Seconds_Nowreturns 0 (honest refusal → сентинел → GP=0) until the LSE/RTC clock-tree bench bring-up —00_07 FW.49. ⚠️ Absent is the CLOCK-TREE, not the symbol:MX_RTC_Initis declared, called and stubbed in three files (firmware/soldier/main.c,firmware/test/hal_mock.h,firmware/hal_glue/soldier_hal_check.c) — its body is empty, so a grep for the symbol finds it and proves nothing; FW.27-B re-request counts silent wakeups instead of ticks (STOP2-immune by design). Still: never trust a raw tick delta as wall-seconds. Canon03_01 §1.4/§1.10. 🔴 АЛЕ «honest refusal → baseline» вище описує лише ПІВ-контракту, і друга половина коштує ГРОШЕЙ (переміряно 2026-08-16):BASELINE_DELTA_T_S = 60подано як нейтральний, аmetabolic_health(60)= (7200−60)/6600 = 1.08 → clamp 1.0 →expected_homeostasis_gp= 31 = GP_HOMEO_MAX. Тобто відмова чесна, а її посадкова точка — максимум балів росту, які йдуть уWallet#credit!, у мінт і вleaf0L1-якоря. Іронія несуча: коментар над самим викликом називає рівно цей режим тим, що FW.49 і лікував («m(delta_t) ≈ максимум у всіх → over-mint») — полагодили ДЖЕРЕЛО часу, посадку лишили. І оскільки на кремніїWall_Seconds_Now()≡ 0, це не крайній випадок, а стан за замовчуванням: гілка cold-start спрацьовує щоцикла, а EMA прогрівається в те саме число. Рефлекс, ширший за цей сайт: обираючи fallback-константу, підстав її у ВСЮ ланцюжок до кінця і подивись, у що вона мапиться — «нейтральне» значення входу регулярно виявляється екстремумом виходу. ✅ ПОЛАГОДЖЕНО 2026-08-16 (ARCH.102): посадка теперDELTA_T_UNKNOWN_S = 0— окремий СЕНТИНЕЛ, а не значення, і гілка на нього стоїть ПЕРЕД формулою обабіч дзеркала (bio_contract.rb#pack_status_byte⊥Attractor.expected_homeostasis_gp) →status = 0, GP = 0. 🔴 Чому саме окрема гілка, а не «краща константа»: формула на нулі вироджується у свою протилежність —m = 7200/6600 = 1.09 → clamp 1.0 → GP = 31, тобто будь-яке значення-заглушка тут дає максимум. Отже клас лікується не підбором числа, а виведенням «не виміряно» з простору значень. ⚠️ Дзеркальний ризик, що лишається:vcapпоруч і далі падає на nominal3300, і це СВІДОМО — шину стабілізує BQ25570, тож там 3300 не здогад, а специфікація; дві різні відсутності мусять мати дві різні відповіді. 🔴 І коли ти нарешті ПИШЕШ тілоMX_RTC_Initна бенчі — знай, що жоден гейт помилки в ньому не впіймає [FW.49, виміряно 2026-09-05].make -C firmware/test(іasan) glue-TU не компілює взагалі; ARM-джоба збираєfirmware/hal_glue/soldier_hal_check.cяк OBJECT-бібліотеку (firmware/CMakeLists.txt,SILKEN_WITH_HAL=OFFза замовчуванням) — тобто компілює, але НЕ лінкує й НЕ виконує. Обидві впадають в агрегатci-ok, тож він зеленітиме, бо КЛИЧЕ НЕ ТУ ЛАНКУ. ⚠️ Ціна не косметична: типова STM32-пастка (забутийHAL_PWR_EnableBkUpAccess, не тойRTCCLKSource) даєHAL_OKзі СМІТТЄВИМ, але ненульовим часом — це вже не «чесна відмова», а бракованийdelta_t, що годуєmetabolic_health()і мінт GP, тобто рівно той over-mint, задля якого FW.49 і заводився. Верифікація тіла — приладом на бенчі, ніколи зеленим CI.vcap_voltageis honest mV of VDDA, NOT the EDLC Vcap — post-FW.50 the raw-counts-as-mV bug is fixed:vcap_voltage = Adc_Vdda_Mv(...)(factory VREFINT-cal,firmware/common/adc_convert.h, host-tested) → ≈3300 mV while the buck lives. But VREFINT sees VDDA behind the buck, and BQ25570 regulates that rail to 3.3 V from VSTOR ≥ 3.4 V up to the cap's 5.5 (02_03 §7) — so the rail carries no information about stored energy at all, by design of the power tree. Until a real Vcap divider + separate ADC channel lands (hardware residual,00_07 FW.50), **every threshold on this rail is degenerate, and there are FOUR of t
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 22
- Forks
- 1
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
firmware- Source
- github.com/alexey-lukin/silken_net