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.
2.9 KiB
Keihin SH7054 (Triumph 865 twin, mechanical odo) — table map
Reversed from Nc()/Lb() in l.java and validated against the stock reference
maps. All offsets are in the reconstructed flat ROM (0x60000). Compute per map:
fe = c.a[Qd*48] # Qd from the s.a directory lookup on the map signature
base = (fe[0] & 0x2F0) << 12 # = 0x50000 for the 865 twin family
offset(table) = base + fe[<field>]
Main tables are 32 rows (RPM) × 20 cols (throttle), 16-bit big-endian.
Axes
| Axis | fe field | Entries | Values (20187) | Meaning |
|---|---|---|---|---|
| RPM (rows) | fe[8] |
32 | 0, 500, 900, 1000 … 10000 | engine speed, direct RPM |
| Throttle (cols) | fe[27] |
20 | 0, 10, 20 … 780, 1000 | throttle %, ÷10 (0–100.0%) |
Tables (validated by production 20187 vs aftermarket 20188 diff)
| Table | fe | Offset (20187) | Value range | Notes |
|---|---|---|---|---|
| Main fuel — cyl 1 | 11 | 0x56990 | 951–10480 | strong: 75% diff, smooth VE, aftermarket richer |
| Main fuel — cyl 2 | 12 | 0x56E90 | 951–10680 | strong: 67% diff |
| Low-throttle fuel — cyl 1 | 15 | 0x55590 | 0–10760 | 70% diff; starts at 0 (closed throttle) |
| Low-throttle fuel — cyl 2 | 16 | 0x55A90 | 0–10810 | 68% diff; cyl-1 pair |
| Fuel — base/idle | 9 | 0x55500 | 0–10760 | 61% diff; lower values |
| Ignition advance — gear 1 | 19 | 0x587D0 | 13–600 | 4 evenly-spaced (0x500) 20×32 tables |
| Ignition advance — gears 2–5 | 20 | 0x58CD0 | 13–600 | main operating map |
| Ignition advance — gear 6 | 21 | 0x591D0 | 13–600 | overdrive |
| Ignition advance — neutral | 22 | 0x596D0 | 60–600 | idle/free-rev |
AFR / target lambda
fe[2] @ 0x52260 is an interleaved (value, breakpoint) curve where 128 = λ1.00
(stoich, 14.7 AFR) — e.g. … 128, 500, 128, 400, 128, 300 …. The 128 cells are
the closed-loop lambda targets; disabling closed loop (O2 delete) lets you set
these richer. Exact dimensions still being confirmed.
Scaling (confidence varies)
- Fuel values are VE/fuel-mass in the ECU's internal units (≈ 0–10800). Real units (injector µs / VE %) need the ECU's fuel constant; relative changes are exact, absolute scaling TBD.
- Ignition 13–600 is advance in an internal unit (candidate: value ÷ 10 = °BTDC, giving ~1.3–60°; the low-RPM row 90→14→20 fits a retard-then-advance curve). TBD.
- AFR 128 = λ1.00 is solid (standard Keihin convention).
SAI / O2 / lambda flags
Now findable via the localised flat-ROM diff (20187 vs 20188), which isolates small
single-byte changes (e.g. 0x50C13, 0x534AD, 0x59759) as prime toggle candidates.
Confirming which is SAI vs O2 needs either bench testing or the finer Nc device
logic — a follow-up.
Status
Fuel and ignition table locations are validated end-to-end. Remaining: exact scaling constants, AFR dimensions, and confirming the individual device flags.