Files
ZXCLI 0bfb926892 完成官方管线全量逆向与 demo3 v5 复刻,清理仓库
- 逆向:Ghidra/IDA 全量反编译 CoreSDKLib.dll/ThermalSDK.dll/libthermalSDK.so/
  libcoresdk.so(ARM64)/libmagcore.so,导出 analysis/ida/export/
- 解码官方渲染管线:DDT 校准表加载->快门端点选择->Q12 插值->ref(4x type1 帧
  均值)->NUC 查表->盲元补偿->窗口->LUT1024 重建->2x 升采样->调色板
- 逐像素验证:NUC+盲元 0/19200、插值 0 误差、2x 0/76800、窗口一致
- demo3 v5:完整复刻官方管线(含 DDT 解析、FFC 状态机、快门温度驱动),
  修复 load_ddt 表错位导致的零像素问题
- 鬼影根因分析写入 analysis/reverse_20260813_full.md
- 心跳/恢复机制:analysis/session_state.md + tools/resume_rev.ps1
- 新增 tsdk_pair3 增强采集工具;历史工具归档 csdk/tools/legacy/;
  根目录抓帧残留删除,历史文档归档 analysis/history/
- csdk/README.md 完整使用文档;.gitignore/.gitattributes 补 LFS 规则
2026-08-13 23:16:12 +08:00

23 KiB

MAG160C Reverse Engineering Progress

2026-07-12

  • Started task.
  • Read relevant process skills: using-superpowers, brainstorming, planning-with-files, test-driven-development, systematic-debugging, verification-before-completion.
  • Confirmed current directory is not a git repository.
  • Ran initial file inventory with rg --files; found Windows C# examples, native SDK headers/DLLs, Android demo/AARs, native libraries, and libuvc/libusb source.
  • Created persistent planning files.
  • Extracted SDK PDF text with Python/pdfplumber.
  • Read Windows headers and C# wrappers.
  • Read Android demo source and AAR/class signatures using javap.
  • Extracted thermallib-release.aar to .reverse/thermallib_aar for analysis.
  • Confirmed IR camera VID/PID 0x833C/0x0001 from Hardware Guide.
  • Confirmed Android native libcoresdk.so exports MAG_* APIs and uses libusb.
  • Attempted to install capstone/pyelftools into .tools/python-revlibs; failed because network/proxy is unavailable. No new reverse-engineering software installed.
  • Tried PowerShell Get-PnpDevice for attached hardware; access denied in current environment.
  • Tried MinGW objdump on ARM64 libcoresdk.so; unsupported architecture.
  • Resumed from planning files and continued Android bytecode inspection with javap.
  • Found detailed FTDI/TCM command framing in FTDICommand and confirmed Android TCM bulk communication auto-detects an interface with bulk IN + bulk OUT endpoints.
  • Expanded TCM command table for motor, proximity, light, power, audio, schedule task, and firmware update commands.
  • Extracted MagDevice JNI method surface and core native data structures from coresdk.jar.
  • Inspected Windows ThermalSDK.dll, CameraSDK.dll, and CoreSDKLib.dll exports/imports/strings.
  • Confirmed ThermalSDK.dll wraps CoreSDKLib.dll MAG_* APIs, CoreSDKLib.dll imports libusb0 bulk read/write APIs, and Windows TCM logs match the Android TCM command frame model.
  • Marked reverse-engineering evidence phases 3 and 4 complete; moved into design approval phase.
  • User selected Linux deliverables: C/C++ library plus CLI sample, and Python-callable tooling/bindings.
  • User approved implementation approach 1: C++ core library + stable C ABI + CLI + Python ctypes/cffi-style wrapper.
  • Wrote design spec at docs/superpowers/specs/2026-07-12-mag160c-linux-sdk-design.md and ran a placeholder scan/self-review.
  • Did not commit the design spec because the workspace is not a git repository; progress remains tracked in markdown files.
  • User approved the design/spec and requested the implementation plan.
  • Wrote implementation plan at docs/superpowers/plans/2026-07-12-mag160c-linux-sdk.md.
  • Ran implementation-plan self-review/placeholder scan; no red-flag placeholders were found.
  • Implemented Task 1 scaffold: CMake, public C ABI lifecycle, error names, and lifecycle tests.
  • Task 1 verification note: cmake is unavailable in PATH, so substituted direct MinGW g++ compile/run verification with MAG160C_STATIC and MAG160C_HAS_LIBUSB=0; test executable passed with exit code 0.
  • Implemented Task 2 TCM frame codec with byte-exact encode/decode tests and C ABI utility functions.
  • Task 2 verification note: cmake remains unavailable in PATH, so substituted direct MinGW g++ compile/run verification for test_c_api and test_tcm_frame; both passed with exit code 0.
  • Implemented Task 3 TCM command builder, dry-run C ABI helpers, and command payload tests.
  • Task 3 verification note: cmake remains unavailable in PATH, so substituted direct MinGW g++ compile/run verification for test_c_api, test_tcm_frame, and test_tcm_device; all passed with exit code 0.
  • Implemented Task 4 USB device model, endpoint-pair selection, and C ABI device listing/freeing.
  • Task 4 verification note: cmake remains unavailable in PATH, so substituted direct MinGW g++ compile/run verification for test_c_api and test_device_model; both passed with exit code 0.

2026-07-14 Handoff

  • User asked to stop and then requested a compact context handoff for a new conversation.
  • Updated task_plan.md, findings.md, and progress.md with a short resume-ready summary.
  • Current implementation state to remember:
    • design and plan are already approved
    • Tasks 1-4 are implemented and verified in the no-libusb fallback path
    • remaining work is the live Linux runtime path, CLI sample, and Python wrapper integration
  • Important stop condition:
    • do not continue execution unless the user explicitly asks to resume work

2026-07-14 Resume

  • User explicitly resumed the task and added a Linux SDK under IR_Camera_SDK-1.0.1/linux.
  • Restored context from task_plan.md, findings.md, and progress.md.
  • Attempted planning-with-files session catchup via .claude skill path; it failed because C:\Users\ZXC\.claude\skills\planning-with-files\scripts\session-catchup.py does not exist in this Codex install.
  • Verified the workspace is still not a git repository, so implementation continues in-place with markdown progress tracking.
  • Began Linux SDK inventory. Found versioned Linux packages for 1.0.0, 1.0.1, 1.2.1, and 1.2.2; 1.2.2 is extracted with Qt demo sources, headers, Linux shared libraries, and firmware.
  • Read Linux 1.2.2 CHANGELOG, Coding Tips, ThermalSDK.h, CameraSDK.h, Def.h, and Qt project file.
  • Inspected Linux libthermalSDK.so and libmagcore.so.2.1.1 ELF symbols/strings. Confirmed libmagcore exports MAG_* plus bundled libusb_*; confirmed Linux TCM implementation uses SerialPort/TCMDevice::open(std::string) classes around the same FTDI command concept.
  • Extracted text from Linux SDK Guide.pdf and Quick Start.pdf after two PowerShell quoting failures; root cause was Bash-style heredoc/Python newline quoting in PowerShell, not PDF tooling.
  • Read Linux Qt demo basewidget.cpp, testwidget.cpp, expertwidget.cpp, and smartwidget.cpp; recorded startup/callback/calibration usage in findings.
  • Disassembled small Linux x86-64 slices of MAG_LinkCamera, MAG_LinkCameraEx, MAG_StartProcessImage, MAG_TriggerFFC, MAG_EnumCameras, MAG_GetCamInfo, MAG_SetFFCMode, EloMotorDevice::rotate, and TCMVirtualDevice::sendData to extract interoperability facts only.
  • New low-level Linux evidence recorded: IR endpoints 0x03 OUT, 0x81/0x82/0x84 IN; raw stream markers 0x1bb1b11b/0x1bb1b11c; MAG_LinkCamera default VID 0x833c; Linux rotate clamp [-250,250] while Android evidence had [-128,128].
  • Implemented Task 5 IR API skeleton with public mag160c_ir_info_t, mag160c_ir_open_first, mag160c_ir_close, mag160c_ir_get_info, mag160c_ir_trigger_ffc, and mag160c_ir_read_raw_once.
  • Task 5 RED verification: direct MinGW compile of tests/cpp/test_ir_skeleton.cpp failed as expected because mag160c_ir_* and mag160c_ir_info_t were not declared.
  • Task 5 GREEN verification: direct MinGW compile and run of build_task5_ir_skeleton_test.exe passed with exit code 0.
  • CMake is still unavailable in PATH (cmake --version failed), so full Task 5 verification used direct MinGW no-libusb builds.
  • Full no-libusb direct verification after Task 5 passed: test_c_api, test_tcm_frame, test_tcm_device, test_device_model, and test_ir_skeleton all compiled and ran; output ended with ALL_DIRECT_TESTS_PASSED.
  • Implemented Task 6 mag160c-cli with probe, tcm-rotate --dry-run, tcm-light --dry-run, and ir-info.
  • Task 6 RED verification: direct MinGW compile of tools/mag160c_cli.cpp failed as expected because the CLI source file did not exist.
  • Task 6 CLI verification: build_task6_mag160c_cli.exe no-args printed usage and exited 1; tcm-rotate --dry-run 5 printed 7e 00 07 85 02 77 00 01 00 05 7f; tcm-light --dry-run green blink printed 7e 00 09 87 02 32 00 01 01 00 ff 00 35; ir-info printed MAG160C skeleton info and known Linux endpoint evidence.
  • Full no-libusb direct verification after Task 6 passed again for test_c_api, test_tcm_frame, test_tcm_device, test_device_model, and test_ir_skeleton; output ended with ALL_DIRECT_TESTS_PASSED.

2026-08-10 Deep Disassembly: Full Protocol Recovery

  • Network became available; pip-installed capstone 5.0.7 + pyelftools 0.33 into .tools/python-revlibs (verified: r2pipe/angr/keystone/unicorn also installed). Built reusable analysis/revtools.py (ELF loader, ARM64 PLT/GOT resolver, lite C++ demangler, xref scan) and used MinGW objdump/readelf on the x86-64 Linux libmagcore.so.2.1.1 and Windows CoreSDKLib.dll.
  • Recovered the complete vendor USB protocol from libmagcore (was the #1 open question):
    • Link: VID 0x833C, set_configuration(2), claim_interface(0), auto-detach on, optional pid/bus/addr filters.
    • Command channel: EP OUT 0x03 + EP IN 0x82 (0x1000, 2000ms); 8-byte packets {u32 magic, u32 param}; 0x3c-byte packets for DDT.
    • Command magic table: 0x6bb6b66b/66c prepare, 66d/66e DDT read/write (0x38-byte payload), 66f camera info, 670 version, 671 (Windows-only), 672 FFC, 673 start (after 50ms + thread setup), 674 stop, 676/677 set params (Linux-only).
    • Response magic table: 0x5bb5b55b/55c/55d/55f = 0x38-byte info blocks (55b: pid@0, width@0x10, height@0x14), 55e = 16-byte qword pair, 57b = saved-file header.
    • Frame stream EP 0x81: marker 0x1bb1b11b@0, frame counter@4, data length@8, type@0xc (0/1), shutter@0x10, pixel data@0x1c (uint16 LE), trailing 0x1bb1b11c@0x1c+len, total 0x38+len, width 160 or 384. Reader thread 0x170a4 + dispatcher 0x16fb5.
    • Windows CoreSDKLib.dll cross-validates: same magic codes (66b/66c/66f/670/671/672/673/674), same frame markers with 0x1c offset, same 50ms-then-start flow, same 0xe4ae error domain, same response dispatch shape (0x5bb5b55b..0x5bb5b560).
  • Recovered temperature conversion math from ARM64 libcoresdk.so exported CFunctions:
    • Calibration (0x6a41c): per-pixel PWL - diff=(raw-baseline)>>1, band search on per-pixel threshold table (stride bands-1), v=off[i][p]+(diff*coeff[i][p]>>12), clamp [0,0xffff].
    • ConvertResponse2Temperature (0x73124): base=(50000-gain)>>shift + diff*coeff, auto-gain decrement when >0x101 pixels saturate.
    • CAccumulator::PushFrame (0x62548): baseline copy/avg accumulation with restart state machine.
    • GetTemperature (0x7252c): window average (radius 1..3) -> ReviseTemperature (0x6cdb8, T2E PWL 0x112 entries + emissivity float mixing + inverse-map binary search) -> CorrectTemperature (0x6d6b0, offset segmentation k=(v-15000)/100 clamped 0..6, float gains +0xc4/+0xcc/+0xd0).
    • T2E table symbol exported (T2E @ 0x402010), monotonic 1000..737212, dumped to analysis/t2e_table.json; float constants 1000.0/0.01/0.99; units consistent with millidegree-C scale.
    • DDT saved file: 0x5bb5b57b header + aligned payload + 0x5bb5b55b/55c/55d/55e/55f typed blocks.
  • Wrote complete spec to analysis/protocol_spec.md; updated findings.md with the 2026-08-10 session summary.
  • Implementation updates (all passing direct MinGW builds, same method as before):
    • include/mag160c/mag160c.h: frame header struct, all magic constants, temp-conversion constants.
    • src/core/ir_frame.{hpp,cpp}: recovered frame parser (parse_ir_frame) + Calibration piecewise-linear converter (calibrate_frame).
    • tests/cpp/test_ir_frame.cpp: 7 tests (frame parse valid/bad marker/bad type/bad trailer/truncated, calibration band select/interpolation+clamp/negative diff).
    • ir_device.cpp + CLI ir-info now report the recovered protocol; skeleton errors changed from PROTOCOL_UNKNOWN to UNSUPPORTED; CMakeLists updated.
    • Full suite green: test_c_api, test_tcm_frame, test_tcm_device, test_device_model, test_ir_skeleton, test_ir_frame all EXIT=0; mag160c-cli ir-info shows the recovered protocol line.

2026-08-10 Windows Test Prep + USB Capture Tooling

  • Organized workspace: disassembly outputs moved to analysis/disasm/, legacy C++ exe artifacts moved to analysis/legacy-cpp/build/, temp files removed.
  • Hardware check: no VID 0x833C device currently attached (present USB devices: FFFF/FFFF, 0CF2/7750, 258A/010C, Logitech receiver). User will plug the camera in for Windows testing.
  • Installed USB capture tooling via winget:
    • USBPcap 1.5.4.0 (desowin.USBPcap) - driver + USBPcapCMD.exe
    • Wireshark 4.6.7 (WiresharkFoundation.Wireshark)
    • Reboot needed for the USBPcap driver to load.
  • Windows libusb-1.0 build support:
    • Found a 64-bit libusb-1.0.dll in TI CCS (C:\ti\ccs2050\ccs\ccs_base\common\bin\); openocd's copy is 32-bit (pei-i386), which crashed with 0xC000007B on a 64-bit exe.
    • Generated MinGW import library with gendef + dlltool -k (kill-at so symbols match the undecorated x64 calls) into csdk/third_party/libusb/win64/ (libusb.h + libusb-1.0.dll + libusb-1.0.x64.a + original .def).
    • CMakeLists now falls back to this bundled libusb on Windows when pkg-config finds nothing, and copies the DLL next to the CLI.
  • Verified full libusb+threads build on Windows:
    • csdk_cli_libusb.exe runs; tcm/frame/temp tests pass; ir-info reports "no device with VID 0x833c" (libusb enumeration works); ffc returns NOT_OPEN cleanly.
  • Wrote csdk/WINDOWS_TESTING.md: build commands, hardware test flow, USBPcap capture procedure, and the pcap verification checklist against the recovered protocol (magic bytes per command/response/frame).

2026-08-10 Pure-C SDK Rewrite (csdk/)

  • User requested a C SDK faithful to the reverse-engineered protocol; the previous C++ implementation (fake ir_open, dynamic endpoint discovery, missing temperature math) was replaced. Old tree archived to analysis/legacy-cpp/.
  • New pure-C tree in csdk/:
    • include/mag160c/mag160c.h - C ABI: vendor error domain, all magic command/response codes, frame format constants, temperature pipeline API, TCM framing API.
    • src/mag160c_error.c - TLS last-error.
    • src/mag160c_frame.c - 0x1bb1b11b frame parse + streaming assembler (marker align, length check, trailing marker, 0x38+len consumption).
    • src/mag160c_temp.c - Calibration (per-pixel PWL w/ baseline), ConvertResponse2Temperature (auto-gain + saturation counting), T2E Q13 interpolation using the dumped tables.
    • src/mag160c_tcm.c - FTDICommand 0x7e framing; NOTE: checksum is additive mod-256 (0x7e+0x07=0x85), byte-exact vs. the old C++ tests.
    • src/mag160c_ir.c - libusb session: VID 0x833C, config 2, iface 0, EP 0x03/0x82 command channel, EP 0x81 frame reader thread, 50 ms then START, STOP, FFC; compiles in libusb/threads/no-libusb modes.
    • src/mag160c_tables.h - T2E + E2TAccQ10 tables generated from the ARM64 binary dump.
    • tools/mag160c_cli.c - ir-info / ffc / start / stop / tcm-rotate / tcm-light / frame-test / temp-test.
    • tests/ - test_frame, test_temp, test_tcm, test_api.
  • Verification (mingw gcc, no-libusb mode): all 4 tests pass (ALL_CSDK_TESTS_PASSED); threads mode passes; libusb+threads mode compiles clean (no libusb .lib on this machine to link).
  • CLI output verified: tcm-rotate 5 -> 7e 00 07 85 02 77 00 01 00 05 7f (byte-exact), tcm-light green blink -> 7e 00 09 87 02 32 00 01 01 00 ff 00 35, frame-test/temp-test pass, ir-info prints the recovered protocol.

2026-07-14 Workspace Organization

  • Organized generated and analysis files without deleting source or vendor SDK material.
  • Moved root build_*.exe binaries into build-artifacts/.
  • Moved .reverse/ to analysis/reverse-cache/.
  • Moved reverse_tools/ to analysis/reverse_tools/.
  • Moved root 【模块文档】MAG160CORE.pdf to vendor-docs/【模块文档】MAG160CORE.pdf.
  • Left vendor SDK/app directories and active source directories in place: IR_Camera_SDK-1.0.1/, app/, src/, include/, tests/, tools/, and docs/.

2026-08-10 Live Hardware Protocol Verification (WinUSB/libusb-win32)

User connected the actual device (Magnity Thermal Camera, VID 0x833C PID 0x0001, serial 160043865). Full protocol verified live against the official Windows demo (EloThermal.exe, which shows a real thermal image).

Driver / libusb matrix (important)

  • Device ships without a driver; SDK's ThermoCube_stream.inf fails signature validation on Win10/11 (2012 cat). Zadig installed WinUSB, then libusb-win32 for the official demo (demo links libusb0.dll directly).
  • Working combo: TI libusb-1.0.dll + libusb-win32 driver (all commands/stream work; harmless "monotonic clock" warnings). Official libusb-1.0.27 MinGW build crashes on bulk transfer with the libusb-win32 driver; official DLL + WinUSB crashes in libusb_control_transfer; TI DLL + WinUSB fails FFC writes.

Protocol verified on hardware (differs from Linux-SDK assumptions)

  • Commands are 4-byte packets (magic only); only FFC (0x6bb6b672) uses the 8-byte {magic, param} form.
  • Init: 66b(4B) -> 66c(4B) -> 66f(4B). Response 0x5bb5b55b from 66b carries the camera info (width@+0x10=160, height@+0x14=120, fps@+0x18=15). The 0x5bb5b55c block (from 66c) is a DIFFERENT layout and must not be parsed as info.
  • START: FFC(0) x2, sleep 300ms, then 73(4B). This switches the unit into live imaging mode (frames with type field 0/1). FFC(param=1) toggles type 1->0, FFC(param=0) toggles 0->1; the official demo alternates FFC every ~10 frames.
  • STOP: clear_halt(0x03/0x82) then 74(4B) works.
  • Frame stream: 28-byte header (marker 0x1bb1b11b, counter, len=0x9600, type, shutter) + 38400-byte payload + 28-byte tail (0x1bb1b11c trailer), at 15 fps. Verified: 4 s capture = 60 frames.
  • Thermal response: hand vs background delta ~1322 counts (whole-FOV hand) => roughly 147 counts/C with a plausible 34C vs 25C assumption.

csdk fixes made from live testing

  • send_cmd4 for 66b/66c/66f/73/74; FFC stays 8-byte with param.
  • ir_open sequence 66b,66c,66f (was sending 670 in third slot).
  • FFC(0) x2 before START in ir_start.
  • Keep libusb_context alive for handle lifetime (was libusb_exit'ing it).
  • Only 0x5bb5b55b parsed as info (0x5bb5b55c corrupted width/height).
  • GUI live thermal viewer (thermal_viewer.c) shows real-time pseudo-color imagery; single-threaded read/render loop required (TI libusb DLL is unreliable with a reader thread + GUI thread).

Artifacts

  • official demo traffic capture: analysis/captures/libusb0_trace.txt (26k lines: init, periodic FFC, 10k+ frames) via a libusb0.dll API shim.
  • ETW capture experiments (USB-UCX has no payload data; etl2pcapng is network-only) - kept as notes in csdk/WINDOWS_TESTING.md.

2026-08-10 Post-reboot USBPcap Verification

  • System rebooted; USBPcap driver now bound to the root hubs. Device is on \\.\USBPcap4 (AMD USB 3.x hub on this machine).
  • Captured the csdk's own live traffic with USBPcap (no shim needed anymore): analysis/captures/csdk_verified.pcap.
  • Sequence verified identical to the official demo: 66b -> 66c -> 66f -> FFC(0) x2 -> START -> 61 frames @ 15 fps -> STOP.
  • csdk frame callback test: 60 frames in 4 s (type=1). All tests green.

2026-08-10 (evening)

  • Task 1: researched open-source thermal display pipelines (OpenThermal/libseek-thermal SeekCam.cpp, MLX90640 API, FLIR Lepton AGC/LUT). Key learnings: bad pixel = histogram peak-deviation threshold (thr = peak - (max - peak)) + temporal min/max, topological 4-neighbour fill; AGC = percentile stretch + ironbow LUT; diff AGC = adaptive mean-based span.
  • Task 2: rewrote tools/mag160c_demo2.c v3: Seek-style bad pixel detection + topological fill order applied to live frame and reference; adaptive diff AGC (span=2*mean|d|, deadband); ironbow LUT; two-point calibration buttons (Cal Cold 0C / Cal Hot 90C / Reset).
  • Task 3: decoded official FFC cadence from libusb0_trace.txt (26092 lines): FFC(1) after ~frame 10, then FFC(0) -> 9 frames -> FFC(1) -> variable gap (34..2680). Built mag160c_ffc_test.c with period 400 + gap 9: PASS 1400 type=0 frames / 95s / 15.1 fps / 0 stalls (twice). 44-frame period failed (fps drop + 17.7% type=1).
  • Task 4: added two-point linear calibration (mag160c_temp_calibrate_linear) to csdk + demo2 GUI buttons; physical measurement (ice water / boiling water) still TODO by user.
  • Task 5: new csdk module src/mag160c_display.c + include/mag160c/mag160c_display.h (badmap, AGC range/diff span, ironbow, FFC scheduler, linear cal); mag160c_ir.c now supports mag160c_ir_set_ffc_scheduler() ticking in the reader thread. test_display.c added (20/20 pass); all old tests still pass; CLI gained display-test/ffc-test/calibrate.
  • Hardware validation: csdk_stream_test.exe (threaded reader + scheduler) PASS 1030 type=0 frames / 14.7 fps; csdk_cli_libusb.exe ffc-test 1000 PASS. demo2 GUI verified rendering type=0 with bad-pixel red/blue down to ~0.02% and no saturated defects.
  • Evidence: analysis/demo2_v3_verified_20260810.png (GUI screenshot).

2026-08-10 (late night, third fix round)

  • User reported: FFC flash fixed but (1) startup noise image (red/yellow dots) persists and overlays the normal view, (2) ghosting still present.
  • Root cause: MOG permanent-freeze defect - reference collected before the first auto-FFC cycle baked startup noise in; frozen pixels (|d|>=60) could never heal, so noise + wrong reference persisted forever.
  • Fixes in mag160c_demo2.c:
    • first reference collection moved to right after startup FFC(1) settling (nread>=40, not >=150)
    • reference re-collected after EVERY FFC(1) (quiet-frame median, keeps bad map) instead of one-shot median rebase - any stale reference self-heals within one FFC cycle (~27s)
    • MOG self-healing: isolated foreground pixels (8-neighbor fg < 2) stuck for >=30 frames are reference errors (startup noise/bad pixel), reset to live value; contiguous objects never healed/absorbed
  • csdk: new mag160c_display_ref_track_heal() (w,h variant with fg counters); demo2 uses it directly. test_display now 30/30.
  • Verified on hardware: startup noise 0.12% -> 0.10% over 2157 frames (5+ FFC cycles), no growth, no overlay; red content all real scene blobs; no FFC flash (red stable, blue 0.01%); stream never stalls.
  • Evidence: analysis/demo2_v4_selfheal_verified_20260810.png

2026-08-10 (late night, fourth fix round - root cause found)

  • User reported startup noise image and ghosting STILL present after the MOG self-heal fix. Started a data-driven investigation instead of more reference-model tweaks.
  • Captured 60 real type=0 frames (tools/mag160c_frame_dump.c) and analyzed:
    • temporal noise std ~248 counts per pixel (99.9% of pixels) -> diff mode with adaptive span ~400 amplifies this into visible red/blue speckle = the "startup noise image"
    • fixed sensor mura: row profile span 15594 (row1 constant +5000), col span 8714, FFT shows ~10% energy on row/col axes
    • mura drifts ~350 counts per session and jumps after FFC -> reference diff shows old pattern = ghosting
    • flat-field correction live-(ref-mean(ref)) reduces mura std 3557->379
  • Decided to abandon the scene-diff approach (which caused both problems) and adopt the official pipeline: raw -> NUC flat-field correction -> absolute temperature display (median-centered window + ironbow) + 3x3 median smoothing (cuts temporal noise ~3x).
  • csdk: new mag160c_display_nuc(); reference capture now rejects frames where >5% pixels deviate from the running average (persistent objects never enter the flat field) - the anti-ghost guarantee. diff mode kept as optional.
  • Verified on hardware: speckle residual 2.2-2.7 (smooth), pure blue 0%, red content all real scene blobs (1-1.4%), 5+ FFC cycles with no flash and no pattern growth, stream 2268+ frames. csdk tests 32/32.
  • Evidence: analysis/demo2_v5_nuc_verified_20260810.png