The NO-SAI-NO-O2 reference map bundles SAI + O2 + airbox deletes, so it validates
the three flag bytes as a GROUP but doesn't isolate O2 individually; no single-mod
map exists and the x.a() device-layout branch for this ECU is too nested to trace.
- SAI @ 0x53801: confirmed (code lc() -> Devices[0] via fe[33], + the delete map).
- O2 @ 0x53818/0x53819: probable (2 O2 sensors declared off <-> 2 adjacent bytes
cleared; 865 has no air-flap so airbox removal is fuel-only).
Viewer now shows confirmed/probable badges and a warning that flags alone are not
a tune: O2 delete forces open-loop and needs fuel enrichment, so prefer flashing a
complete matching delete map over hand-toggling a stock one. See DEVICES.md.
Reversed the device-flag mechanism (l.java lc() + ee bit logic) and validated the
flag locations against a real reference map: 20188Map2009AIRBOXBONNY (explicitly
"NO SAI, NO O2 SENSORS"). Diffing its flat ROM vs stock 20188 isolated exactly
three byte-boolean flags that flip 1->0:
0x53801 SAI (= base + fe[33] + 0x00)
0x53818 O2 sensor (+ 0x17)
0x53819 O2 sensor (2) (+ 0x18)
1 = enabled, 0 = disabled. Neither stock map could reveal these (both have SAI+O2
on); the delete map was the key. See research/reference-maps/DEVICES.md.
Viewer: the Triumph-tables tab now has a Device flags panel — checkboxes reflect
the loaded map's real state (stock: all on; delete map: all off), toggling flips
the byte, and "Export edited .hex" re-encodes the distribution format. The
decode->edit->encode round-trip is byte-exact (verified). The caXX header bytes
are map-ID metadata, not a cal checksum; the ECU-flash checksum is applied at
write time.
Reversed the inner map format from l.java (Nc/Lb) and validated it against the
stock reference maps:
- reconstruct_rom.py: decoded map -> unpack directory -> 384KB flat ROM. In the
flat ROM, production (20187) vs aftermarket (20188) differ only 1.4% (vs 96%
packed), i.e. real fuel enrichment.
- table_map.py + TABLES.md: fe = c.a[Qd*48]; base = (fe[0]&0x2F0)<<12 = 0x50000;
table = base + fe[k]. Located 9 tables (main/low-throttle fuel per cylinder,
base/idle fuel, ignition by gear x4), 32 RPM rows x 20 throttle cols, with real
axes (RPM fe[8], throttle fe[27]) and the AFR curve (fe[2], 128=lambda 1.00).
Validated: main-fuel delta is uniformly richer in the aftermarket map.
Viewer now has a "Triumph tables" tab: drop a real .hex (or pick two) and it
decodes, unpacks, and renders the fuel/ignition tables as heatmaps with real
axes, plus an A->B difference view. build_viewer.py embeds the c.a/s.a directory
so any map resolves in-browser.
Catalogue dropdown: download_maps.py fetches map .hex files into maps_cache/;
serve.py serves the viewer over http so the dropdown can fetch them (drag-and-drop
still works on file://).
Proprietary map binaries (*.hex, *.dec.bin, maps_cache/) are gitignored — code and
docs only.