From 07f0569d8166d593e0b8e23022b7a3a3ba0c99d0 Mon Sep 17 00:00:00 2001 From: locpham Date: Sun, 16 Aug 2026 11:48:47 +0700 Subject: [PATCH] =?UTF-8?q?G2:=20Linux=20review=20+=20verify=20=E2=80=94?= =?UTF-8?q?=20code=20G2=20consistent,=20no=20product=20bugs=20(F1=20double?= =?UTF-8?q?=20flush=20minor,=20F2=20SF=5FSTATE=5FFILE=20Linux=20fallback?= =?UTF-8?q?=20note,=20F3=20cosmetic);=20StateStore=20round-trip=20PASS=20o?= =?UTF-8?q?n=20Linux=20g++;=20report=20section=205.1=20+=20journal=20updat?= =?UTF-8?q?ed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 20260815-tasks.md | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/20260815-tasks.md b/20260815-tasks.md index 2e8fad9..57bdbc2 100644 --- a/20260815-tasks.md +++ b/20260815-tasks.md @@ -1,7 +1,7 @@ # 20260815 — KẾ HOẠCH THỰC HIỆN G2 (PERSISTENCE RESTART) Nguồn: `big_tasks.md` — header **G2 — Persistence restart (2-3 tuần)**. -Trạng thái roadmap: G0 ✅, G1 ✅ (G1.1–G1.5), **G2 ✅ (G2.0–G2.6)**. Branch: `standalone-shm-bridge`. +Trạng thái roadmap: G0 ✅, G1 ✅ (G1.1–G1.5), **G2 ✅ (G2.0–G2.6)**, G3 ✅ (G3.1–G3.3), G4 ✅ (G4.1–G4.3 sandbox PoC). Branch: `standalone-shm-bridge`. File này là plan G2; G3/G4 thực hiện tiếp nối (nhật ký ở mục 5). --- @@ -149,13 +149,36 @@ Lý do: G2.2 phụ thuộc G2.1 nhưng giá trị thấp hơn G2.3; làm G2.3 s > Cột Cài đặt/Kiểm tra/Bugs/Fix: **Windows** điền (người sửa code). Cột Trạng thái + Nhật ký: **Linux** cập nhật sau khi review. | ID | Cài đặt | Kiểm tra | Bugs bắt được | Fix | Trạng thái | |---|---|---|---|---|---| -| G2.0 | StateStore.h/.cpp (JSON thủ công, atomic tmp+rename, UTF-8, API save/load/clear, defaultPath=%APPDATA%/SonicForgeDAW/state/bridge_state.json); them vao CMakeLists target daw_vst_bridge; test_state_store.cpp (round-trip self-check) | Build PASS (daw_vst_bridge.exe), round-trip PASS (save/load 2 inst: path Windows co space+backslash, presetBase64 co newline/quote/backslash; khong con .tmp; clear xoa file) | — (module moi, chua hook vao bridge) | — | ✅ (Win xong) | -| G2.1 | main.cpp: globals g_stateDirty/g_transportPlaying/g_lastSnapMutex/g_lastSnap, snapshotsEqual (bo playhead); stateFile = SF_STATE_FILE fallback defaultPath; playheadSamples -> std::atomic; saveStateJob (UiThread, skip khi isReloading, chi save khi snapshot doi); TRANSPORT set g_transportPlaying + dirty; cuoi LOAD dirty=true; periodic 2s dirty + debounce 300ms. Vst3Instrument.h: khai bao bankOf/programOf. StateStore.cpp: ensureParentDir() (CreateDirectoryA chain / mkdir) goi dau save(). | Build PASS (daw_vst_bridge.exe). Deploy PASS (PID moi: sonicforge-daw 9876, daw_engine 24632, daw_vst_bridge 7352). Verify live PASS: LOAD Nexus ch5 + PLAY -> bridge_state.json co 7 instrument (ch0-6 frontend da load), playing=true, presetBase64 non-empty (Nexus 3468/3488, EZkeys 24764); STOP -> playing=false. State round-trip test_state_store.cpp PASS. | test script ban dau ky vong dung 1 instrument (frontend da load 7) -> FAIL gia, chuc nang PASS | assert dung so instrument | ✅ (Win xong) | -| G2.2 | main.cpp: control type=5 DUMP_STATE (post saveStateJob len uiWorker ngay lap tuc). lib.rs: flush_bridge_state() — push_control(5,0,0,0) + sleep 300ms, goi dau kill_bridge() (cover restart_bridge_with_sample_rate + stall-restart) va truoc khi giet bridge trong window-close Destroyed | Build PASS (daw_vst_bridge.exe) + cargo check PASS (src-tauri) + deploy + verify live PASS: DUMP qua SHM -> log "DUMP_STATE - flushing state now", state file intact (7 inst), writeIndex advancing | Runtime Rust flush chua test duoc khi khong build app Tauri (cargo check xac nhan compile) | | ✅ | -| G2.3 | main.cpp: startup restore block (doc SF_STATE_FILE, guard channel trong, post LOAD job tung channel qua uiWorker, assign + loadSerializedState preset / selectProgram bank-program, g_loadMutex serialize, restore transport PLAY + playhead neu state playing). Vst3Instrument: base64Decode + loadSerializedState (parse [4B BE compLen][comp][4B BE ctrlLen][ctrl], goi restoreState). INativeInstrument.h: virtual loadSerializedState no-op. lib.rs: spawn_bridge set env SF_STATE_FILE = %APPDATA%/SonicForgeDAW/state/bridge_state.json. | Build PASS (bridge + cargo check). Deploy PASS. Verify live PASS: LOAD ch5 + PLAY (playhead 5000) -> kill bridge -> respawn -> log "restoring 7 instruments" + "restored ch=0..6" (Nexus + EZkeys) + "restoring transport PLAY playhead=5000" + ch5 preset 3488B giu nguyen; state file playing=true playhead=5000 inst=7; writeIndex advancing (14859->15234). | - | - | ✅ (Win xong) | -| G2.4 | main.cpp: LOAD case double-load guard — skip khi channel da co cung path (lower_plugin_path(ch) == lower_path_str(path)), giu instance + preset da restore khi JS reload sau respawn/bootstrap | Verify live PASS: LOAD lai Nexus ch5 sau respawn -> log "LOAD skip ch=5 (already loaded)", preset ch5 3488B giu nguyen, writeIndex advancing; 15 LOAD skip trong log (JS reload 7ch x2 + test) | Khong can sua JS: loadVstToBridge/loadTrackInstrumentToBridge khong co guard __bridgePlaying (da chay khi PLAY); chi openVstGuiRetry giu guard OPEN_GUI (G0.2, dung) | | ✅ | -| G2.5 | native_bridge/debug/test_g2_stress.py (mới, khung test_g15_stress.py: ctypes SHM, write_control, wait_log, bridge_pid, bad_since) + bước kill daw_vst_bridge giữa vòng + chờ watchdog respawn; verify restore + preset giữ + LOAD-skip guard (G2.4) + writeIndex advancing; test_g15_stress.py: LOAD ch5 chấp nhận "LOAD skip" (guard G2.4 đổi log) | Chạy 5 + 10 iterations PASS (tổng 20 lần kill/respawn): LOAD skip pre-kill/post-respawn OK, restore ch5/ch6 OK, watchdog "[tauri] bridge restarted" OK, bridge alive; preset Nexus giữ nguyên (exact), EZkeys sim>=0.87; writeIndex advancing (delta 283-376) | Assert preset ch6 byte-exact FAIL giả: EZkeys nhúng PlayHeadPos vào state riêng (đổi cả giữa 2 save thường, ratio 0.872) | ch5 so exact, ch6 so ratio>=0.7 + |len chênh|<=64 | ✅ (Win xong) | -| G2.6 | Chạy lại regression G1.5 (test_g15_stress.py) + G2.5 (5+10 iter) + probe G0.4 (bản cập nhật PID + guard-aware, OPEN_GUI full path) | G1.5 regression PASS 5/5 (LOAD/OPEN_GUI/PLAY/STOP/WM_CLOSE, writeIndex delta 307-551, bridge alive); G2.5 PASS 5+5+10 (20 lần kill/respawn, preset giữ, không crash); G0.4 probe PASS: LOAD skip -> OPEN_GUI hwnd -> WM_CLOSE -> capture 2628B -> reload -> restore 2628B -> OPEN_GUI lại attach OK, không restart/EXCEPTION/FAILED | Không có bug sản phẩm mới; 2 bug test-script: (1) probe G0.4 so sánh PID int vs list -> báo restart giả, (2) preset EZkeys byte-diff do PlayHeadPos runtime (đã phân tích ở G2.5) | Sửa test scripts (guard-aware wait, pid compare); không sửa code sản phẩm | ✅ (Win xong) | +| G2.0 | StateStore.h/.cpp (JSON thủ công, atomic tmp+rename, UTF-8, API save/load/clear, defaultPath=%APPDATA%/SonicForgeDAW/state/bridge_state.json); them vao CMakeLists target daw_vst_bridge; test_state_store.cpp (round-trip self-check) | Build PASS (daw_vst_bridge.exe), round-trip PASS (save/load 2 inst: path Windows co space+backslash, presetBase64 co newline/quote/backslash; khong con .tmp; clear xoa file) | — (module moi, chua hook vao bridge) | — | ✅ (Win xong + Linux review OK) | +| G2.1 | main.cpp: globals g_stateDirty/g_transportPlaying/g_lastSnapMutex/g_lastSnap, snapshotsEqual (bo playhead); stateFile = SF_STATE_FILE fallback defaultPath; playheadSamples -> std::atomic; saveStateJob (UiThread, skip khi isReloading, chi save khi snapshot doi); TRANSPORT set g_transportPlaying + dirty; cuoi LOAD dirty=true; periodic 2s dirty + debounce 300ms. Vst3Instrument.h: khai bao bankOf/programOf. StateStore.cpp: ensureParentDir() (CreateDirectoryA chain / mkdir) goi dau save(). | Build PASS (daw_vst_bridge.exe). Deploy PASS (PID moi: sonicforge-daw 9876, daw_engine 24632, daw_vst_bridge 7352). Verify live PASS: LOAD Nexus ch5 + PLAY -> bridge_state.json co 7 instrument (ch0-6 frontend da load), playing=true, presetBase64 non-empty (Nexus 3468/3488, EZkeys 24764); STOP -> playing=false. State round-trip test_state_store.cpp PASS. | test script ban dau ky vong dung 1 instrument (frontend da load 7) -> FAIL gia, chuc nang PASS | assert dung so instrument | ✅ (Win xong + Linux review OK) | +| G2.2 | main.cpp: control type=5 DUMP_STATE (post saveStateJob len uiWorker ngay lap tuc). lib.rs: flush_bridge_state() — push_control(5,0,0,0) + sleep 300ms, goi dau kill_bridge() (cover restart_bridge_with_sample_rate + stall-restart) va truoc khi giet bridge trong window-close Destroyed | Build PASS (daw_vst_bridge.exe) + cargo check PASS (src-tauri) + deploy + verify live PASS: DUMP qua SHM -> log "DUMP_STATE - flushing state now", state file intact (7 inst), writeIndex advancing | Runtime Rust flush chua test duoc khi khong build app Tauri (cargo check xac nhan compile) | | ✅ (Linux review OK) | +| G2.3 | main.cpp: startup restore block (doc SF_STATE_FILE, guard channel trong, post LOAD job tung channel qua uiWorker, assign + loadSerializedState preset / selectProgram bank-program, g_loadMutex serialize, restore transport PLAY + playhead neu state playing). Vst3Instrument: base64Decode + loadSerializedState (parse [4B BE compLen][comp][4B BE ctrlLen][ctrl], goi restoreState). INativeInstrument.h: virtual loadSerializedState no-op. lib.rs: spawn_bridge set env SF_STATE_FILE = %APPDATA%/SonicForgeDAW/state/bridge_state.json. | Build PASS (bridge + cargo check). Deploy PASS. Verify live PASS: LOAD ch5 + PLAY (playhead 5000) -> kill bridge -> respawn -> log "restoring 7 instruments" + "restored ch=0..6" (Nexus + EZkeys) + "restoring transport PLAY playhead=5000" + ch5 preset 3488B giu nguyen; state file playing=true playhead=5000 inst=7; writeIndex advancing (14859->15234). | - | - | ✅ (Win xong + Linux review OK) | +| G2.4 | main.cpp: LOAD case double-load guard — skip khi channel da co cung path (lower_plugin_path(ch) == lower_path_str(path)), giu instance + preset da restore khi JS reload sau respawn/bootstrap | Verify live PASS: LOAD lai Nexus ch5 sau respawn -> log "LOAD skip ch=5 (already loaded)", preset ch5 3488B giu nguyen, writeIndex advancing; 15 LOAD skip trong log (JS reload 7ch x2 + test) | Khong can sua JS: loadVstToBridge/loadTrackInstrumentToBridge khong co guard __bridgePlaying (da chay khi PLAY); chi openVstGuiRetry giu guard OPEN_GUI (G0.2, dung) | | ✅ (Linux review OK) | +| G2.5 | native_bridge/debug/test_g2_stress.py (mới, khung test_g15_stress.py: ctypes SHM, write_control, wait_log, bridge_pid, bad_since) + bước kill daw_vst_bridge giữa vòng + chờ watchdog respawn; verify restore + preset giữ + LOAD-skip guard (G2.4) + writeIndex advancing; test_g15_stress.py: LOAD ch5 chấp nhận "LOAD skip" (guard G2.4 đổi log) | Chạy 5 + 10 iterations PASS (tổng 20 lần kill/respawn): LOAD skip pre-kill/post-respawn OK, restore ch5/ch6 OK, watchdog "[tauri] bridge restarted" OK, bridge alive; preset Nexus giữ nguyên (exact), EZkeys sim>=0.87; writeIndex advancing (delta 283-376) | Assert preset ch6 byte-exact FAIL giả: EZkeys nhúng PlayHeadPos vào state riêng (đổi cả giữa 2 save thường, ratio 0.872) | ch5 so exact, ch6 so ratio>=0.7 + |len chênh|<=64 | ✅ (Win xong + Linux review OK) | +| G2.6 | Chạy lại regression G1.5 (test_g15_stress.py) + G2.5 (5+10 iter) + probe G0.4 (bản cập nhật PID + guard-aware, OPEN_GUI full path) | G1.5 regression PASS 5/5 (LOAD/OPEN_GUI/PLAY/STOP/WM_CLOSE, writeIndex delta 307-551, bridge alive); G2.5 PASS 5+5+10 (20 lần kill/respawn, preset giữ, không crash); G0.4 probe PASS: LOAD skip -> OPEN_GUI hwnd -> WM_CLOSE -> capture 2628B -> reload -> restore 2628B -> OPEN_GUI lại attach OK, không restart/EXCEPTION/FAILED | Không có bug sản phẩm mới; 2 bug test-script: (1) probe G0.4 so sánh PID int vs list -> báo restart giả, (2) preset EZkeys byte-diff do PlayHeadPos runtime (đã phân tích ở G2.5) | Sửa test scripts (guard-aware wait, pid compare); không sửa code sản phẩm | ✅ (Win xong + Linux review OK) | + +### 5.1 Review + verify phía Linux (2026-08-16, Linux — chỉ review/ghi chú, KHÔNG sửa code) +Kết quả review toàn bộ diff G2 (`git diff 1fe2d26~1..b59e416`, 15 files; không có file JS trong diff). + +**Code review — nhất trí với báo cáo Windows, không tìm thấy bug sản phẩm:** +- `Vst3Instrument.cpp`: `base64Encode`/`base64Decode` đúng RFC 4648 (padding `=`, decode bỏ qua whitespace); `loadSerializedState` có bounds-check cả componentLen lẫn controllerLen trước khi copy — blob hỏng không gây OOB; gán buffer xong mới gọi `restoreState()` (đúng thứ tự). +- `NativeInstrumentEngine.cpp`: `snapshot()` giữ `mu_` suốt `captureState()` — audio loop cũng serialize bằng `mu_` per block nên không race capture với process; skip channel đang reload (`reloadingCh_`, instance cũ sắp chết); `bank_/program_` cập nhật đúng (reset khi `loadSoundFontFile`, set khi program_select/programChange). +- `main.cpp`: `saveStateJob` defer khi có channel reloading (đặt lại dirty → retry sau debounce) — không ghi state lúc instance nửa chết; `snapshotsEqual` loại playhead (không ghi file mỗi ~2s khi PLAY); periodic mark 2s + debounce 300ms; startup restore chỉ nhận channel trống (`instruments.get(ch)==nullptr`) và serialize bằng `g_loadMutex`; LOAD guard so sánh lower-path — chỉ skip khi CÙNG path, path khác vẫn replace bình thường; case 5 DUMP_STATE post `saveStateJob` trực tiếp (không chờ debounce). +- `lib.rs`: `flush_bridge_state` an toàn khi bridge treo (SHM do Rust sở hữu, control 5 không được consume — vô hại). +- `test_g2_stress.py` + `test_state_store.cpp` + sửa `test_g15_stress.py`: logic vững (wait_load chấp nhận real hoặc skip; SequenceMatcher ≥0.7 cho EZkeys vì PlayHeadPos runtime; bad_since watch EXCEPTION/FAILED; round-trip cover escape `\n \t \" \\`, atomic .tmp, missing file, clear). + +**Findings (minor, không chặn; Linux không sửa code — chờ Windows quyết định):** +1. **F1 (minor)**: window-close gọi `flush_bridge_state()` ở `run()` VÀ `kill_bridge()` giờ cũng tự gọi → DUMP_STATE 2 lần khi đóng app (+~300ms shutdown). Harmless; có thể bỏ lời gọi thừa trong `run()`. +2. **F2 (portability note)**: `lib.rs` luôn set `SF_STATE_FILE` sang đường dẫn Windows; nếu chạy bridge non-Windows, env thành `./SonicForgeDAW/state/bridge_state.json` (theo CWD) thay vì Linux fallback `$HOME/.sonicforge/state/` của `StateStore::defaultPath`. Không ảnh hưởng target Windows (cùng path APPDATA); ghi nhận để sửa khi cần bridge chạy Linux. +3. **F3 (cosmetic)**: `loadSerializedState` in cùng message "bad ctrlLen" cho 2 check khác nhau (thiếu header / độ dài vượt) — chỉ log, không ảnh hưởng hành vi. + +**Verify trên Linux (cú pháp/logic — máy Linux không build Windows):** +- `python3 -m py_compile native_bridge/debug/test_g2_stress.py`, `test_g15_stress.py`: PASS. +- `g++ -std=c++17 -Wall -Wextra debug/test_state_store.cpp + src/StateStore.cpp` (Linux): compile sạch (1 warning benign multi-line comment) + run **"state_store round-trip PASS"** → StateStore portable, atomic write + escape verified trên Linux. +- `node --check app/static/js/app.precompiled.js`: PASS (G2 không sửa JS — diff stat không có file JS). +- Rust: `rustc`/`cargo` chỉ là rustup proxy, chưa cài default toolchain → không chạy được `cargo check` trên Linux (khớp ghi chú big_tasks.md "máy Linux không có rustc toolchain"); Windows đã `cargo check` PASS ở G2.2/G2.3. + +**Kết luận Linux**: code G2 nhất quán với báo cáo Windows; không có bug sản phẩm; 2 findings minor (F1, F2) + 1 cosmetic (F3) không chặn merge. Trạng thái G2.0–G2.6: ✅ (Win xong + Linux review xác nhận). ### Nhật ký thực hiện - **G2.0** (2026-08-16, Windows): tao `native_bridge/include/StateStore.h` + `native_bridge/src/StateStore.cpp` (CRLF, JSON viet tay, atomic write .tmp→rename, escape \ " @@ -165,7 +188,8 @@ Lý do: G2.2 phụ thuộc G2.1 nhưng giá trị thấp hơn G2.3; làm G2.3 s - **G2.2** (2026-08-16, Windows): dump state truoc kill. main.cpp: them control type=5 DUMP_STATE sau case 4 — post saveStateJob len uiWorker ngay (khong cho debounce 300ms/2s), log "DUMP_STATE - flushing state now". lib.rs: them flush_bridge_state(app) — shm.push_control(5,0,0,0,"") roi sleep 300ms (du thoi gian UiThread ghi xong file); goi o dau kill_bridge() (cover ca restart_bridge_with_sample_rate va stall-restart) va trong on_window_event Destroyed truoc khi kill_process_tree(bridge). Build PASS (daw_vst_bridge.exe) + cargo check PASS (src-tauri). Deploy + verify live PASS: push DUMP qua SHM driver -> log "DUMP_STATE - flushing state now" xuat hien, state file intact (7 inst, playing=false), writeIndex advancing. Ghi chu: nhanh Rust flush chi cargo check (khong build app Tauri) — logic don gian, compile OK. Commit `G2.2: flush bridge state to disk before kill (control type=5 DUMP + Rust pre-kill)`. - **G2.4** (2026-08-16, Windows): go double-load khi JS reload sau respawn. main.cpp: them guard dau LOAD case (truoc setReloading) — neu channel da co cung path (so sanh lower_plugin_path(ch) vs lower_path_str(path)) thi log "LOAD skip ch=N (already loaded)" va return, khong assign lai (thay instance se mat preset da restore tu state file). Ly do: JS reconnect/bootstrap reload gui LOAD cung path sau respawn -> truoc day assign() thay instance -> mat preset. Guard PLAY cua G2.4 (cho restore chay khi PLAY) da co san o JS: loadVstToBridge/loadTrackInstrumentToBridge khong co guard __bridgePlaying, chi openVstGuiRetry giu guard OPEN_GUI (G0.2 — defer GUI khi PLAY van dung). Build PASS (daw_vst_bridge.exe) + deploy + verify live PASS: sau respawn, LOAD lai Nexus ch5 -> "LOAD skip ch=5 (already loaded: ...Nexus.vst3)", preset 3488B giu nguyen, writeIndex advancing; tong 15 LOAD skip (JS reload 7ch x2 + test ch5). Commit `G2.4: skip redundant LOAD of already-loaded instrument (keep restored preset)`. main.cpp: block restore truoc audio loop - doc state file, chi restore channel trong (guard instruments.get(ch)==nullptr), post LOAD job (assign + loadSerializedState / selectProgram) serialize bang g_loadMutex, neu state playing thi set playhead + g_transportPlaying=true + g_stateDirty. Vst3Instrument.cpp: base64Decode (b64val lookup) + loadSerializedState (parse blob, goi restoreState); Vst3Instrument.h + INativeInstrument.h: khai bao. lib.rs: spawn_bridge them env SF_STATE_FILE (fallback StateStore::defaultPath khi khong set). Build PASS (daw_vst_bridge.exe + cargo check). Deploy + verify live PASS: LOAD ch5 + PLAY playhead 5000 -> taskkill bridge -> respawn tu dong -> log restore 7 inst (Nexus ch0-5, EZkeys ch6) + restore transport PLAY playhead=5000 + ch5 preset 3488B giu nguyen; state file playing=true inst=7; writeIndex advancing. Commit `G2.3: auto-reload instruments from state file after bridge respawn`. - **G2.5** (2026-08-16, Windows): tao native_bridge/debug/test_g2_stress.py (khung test_g15_stress.py). N vong: LOAD Nexus ch5 + EZkeys ch6 (real hoac skip deu OK) -> PLAY -> taskkill daw_vst_bridge -> cho watchdog "[tauri] bridge restarted" + "restored ch=5/6" -> LOAD lai -> verify "LOAD skip" (guard G2.4) + preset giu (ch5 exact, ch6 sim>=0.7 — EZkeys nhung PlayHeadPos vao state, ratio 0.872 giua save thong thuong) + writeIndex advancing. Chay 5 + 10 iterations: PASS (20 lan kill/respawn, khong crash, bridge alive). Kem sua test_g15_stress.py: LOAD ch5 chap nhan "LOAD skip" (guard G2.4 doi log). Commit `G2.5: stress driver for persistence restart (N loop + kill/respawn)`. -- **G2.6** (2026-08-16, Windows): verify tong. G1.5 regression PASS 5/5 (LOAD/OPEN_GUI/PLAY/STOP/WM_CLOSE, writeIndex delta 307-551, bridge alive). G2.5 PASS 5+5+10 (20 lan kill/respawn, preset giu, khong EXCEPTION/FAILED). Probe G0.4 (ban cap nhat: PID dong, LOAD guard-aware, OPEN_GUI full path): PASS — LOAD skip -> OPEN_GUI hwnd -> WM_CLOSE -> "state captured ch=5 component=2628B" -> reload -> "state restored ch=5 component=2628B" -> OPEN_GUI lai attach OK, khong restart. Bug tim duoc: khong co bug san pham; 2 bug test-script (probe PID int vs list -> bao restart gia; preset EZkeys byte-diff do PlayHeadPos runtime). Commit `G2.6: verify — regression G1.5 + stress G2.5 + probe G0.4 all PASS`. +- **G2.6** (2026-08-16, Windows): verify tong. +- **G2.0–G2.6 review + verify** (2026-08-16, Linux — máy Linux chỉ review/ghi chú, KHÔNG sửa code): đọc toàn bộ diff G2 (`1fe2d26~1..b59e416`, 15 files) — Vst3Instrument base64 + loadSerializedState (bounds-check đủ), NativeInstrumentEngine snapshot (mu_ chặn race với audio loop, skip reloading), main.cpp saveStateJob/restore/LOAD-guard/DUMP/debounce, lib.rs flush_bridge_state + env SF_STATE_FILE, test_g2_stress.py + test_state_store.cpp + test_g15_stress.py. Verify Linux: py_compile 2 stress scripts PASS; g++ compile + run test_state_store PASS (StateStore portable); node --check app.precompiled.js PASS; rustc/cargo không có default toolchain → không cargo check (Windows đã check PASS). Findings: F1 double flush_bridge_state khi window-close (minor), F2 SF_STATE_FILE thiếu Linux fallback (portability, không ảnh hưởng target Windows), F3 log "bad ctrlLen" trùng cho 2 check (cosmetic). Không sửa code (đúng phân máy). Chi tiết ở mục 5.1. G1.5 regression PASS 5/5 (LOAD/OPEN_GUI/PLAY/STOP/WM_CLOSE, writeIndex delta 307-551, bridge alive). G2.5 PASS 5+5+10 (20 lan kill/respawn, preset giu, khong EXCEPTION/FAILED). Probe G0.4 (ban cap nhat: PID dong, LOAD guard-aware, OPEN_GUI full path): PASS — LOAD skip -> OPEN_GUI hwnd -> WM_CLOSE -> "state captured ch=5 component=2628B" -> reload -> "state restored ch=5 component=2628B" -> OPEN_GUI lai attach OK, khong restart. Bug tim duoc: khong co bug san pham; 2 bug test-script (probe PID int vs list -> bao restart gia; preset EZkeys byte-diff do PlayHeadPos runtime). Commit `G2.6: verify — regression G1.5 + stress G2.5 + probe G0.4 all PASS`. ---