New research/ROAD_TEST_LOG.md: the 2026-09-15 flash of 20262-arrow-delete-lowthrottle-composed.hex was a major real-world success (stalling fully resolved, decel backfire much reduced but not eliminated). Captures the live diagnostic reasoning that followed: correcting an overly-lean-leaning framing mid-session (residual decel pop is more likely a rich-unburned-mixture-with-no-SAI-assist problem, not a lean one), a structural hypothesis that the Idle Fuel Trim (CO) curve likely applies whenever the throttle is closed at any RPM rather than only at a literal stop (only 2 of its 32 points are currently corrected, and the untouched middle range lines up with the residual pop's RPM window), and the reasoning for rejecting airbox-baffle removal as a fix for decel popping specifically (wrong mechanism -- closed throttle isn't airbox-limited -- and risks worsening the separately-flagged, still unaddressed main-VE-table leanness gap). Also updates TABLES.md with the CO-trim applicability open question, and brings TUNING_GUIDE.md's "local research assets" section up to date (it still said maps were gitignored/unflashable, both stale). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FP2GaxS9HkUdL5sLBnjKje
166 lines
9.1 KiB
Markdown
166 lines
9.1 KiB
Markdown
# 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).
|
||
|
||
## Fuel/ignition trim array — RPM-indexed, 32 entries, `0x53690`-`0x536AF`
|
||
|
||
**Located and axis-mapped (Aug 2026).** A 32-byte array sitting just before
|
||
the device-flags region (`0x53801`, `TABLES.md` above / `DEVICES.md`), one
|
||
byte per **RPM breakpoint on the same 32-point RPM axis** as the main
|
||
tables (`fe[8]`: 0, 500, 900, 1000, ..., 10000). Not addressed via a `fe[]`
|
||
pointer for the maps checked (no `fe` field in `c.a[Qd*48]` equals `0x3690`
|
||
for `20187`'s `Qd=32`) — likely a fixed offset relative to the device-flags
|
||
struct rather than an independently relocatable table, unconfirmed.
|
||
|
||
Evidence for the RPM-axis mapping: diffing `20188Map2009AIRBOXBONNY.hex`
|
||
(the Bonneville SAI/O2 delete map) against stock `20188` shows **exactly 2
|
||
of the 32 cells changed — at RPM=2400 and RPM=7000** — with all 30 other
|
||
RPM-indexed cells byte-identical. That's `0x5369C` (index 12, RPM 2400) and
|
||
`0x536AB` (index 27, RPM 7000) from earlier sessions, now understood as two
|
||
specific points on this curve rather than free-floating bytes.
|
||
|
||
Cross-checked against `20184dynoTuneSteveO2-Disable.hex` (America,
|
||
open-pipe + airbox-snorkel-removed + O2-disable, real dyno tune) vs. stock
|
||
`20186`: **nearly every one of the 32 cells changed**, by large,
|
||
non-monotonic swings (e.g. RPM=1000: 231→63, RPM=4400: 235→5, RPM=5200:
|
||
25→204). Not a smooth curve shift — looks like cell-by-cell dyno-session
|
||
adjustment (consistent with a human tuner nudging individual RPM points
|
||
after successive pulls) rather than a single global correction. Confirms
|
||
this is a real, independently-tunable-per-cell array, not a scalar.
|
||
|
||
**Identity: confirmed as "Idle Fuel Trim (CO)".** Crawled tuneecu.net's full
|
||
docs site (`../tuneecu_site_mirror/`) and found the exact match in
|
||
`TuneECU_En/tests.html`, TuneECU's live-diagnostics parameter list:
|
||
|
||
> "Idle Fuel Trim (CO): lets you adjust the fuel richness at idle."
|
||
> Available for "**Triumph without O²-Sensor only**."
|
||
> (Counterpart: "Long Term Fuel Trim... Triumph models **with** O²-Sensor
|
||
> only" — the closed-loop equivalent for bikes that still have the sensor.)
|
||
|
||
This is a direct, official match to what we observed empirically: the table
|
||
is specifically the O2-delete-relevant trim, exactly why it's what the real
|
||
delete/dyno maps touch and the stock maps don't need to. `co_trim`/`ift_co`
|
||
string resources (`strings.xml`: "CO Trim" / "Idle Fuel Trim (CO)") back
|
||
this — "CO" = idle mixture richness, the classic idle-adjustment convention
|
||
carried over from carbureted-era tuning terminology.
|
||
|
||
**Encoding, upgraded confidence (Aug 2026):** found TuneECU's actual "CO
|
||
Trim" edit dialog (`MainActivity.java`'s `La()`) and its value-scaling
|
||
logic. Two independent code paths converge on the same answer:
|
||
|
||
1. The dialog's clamp/display logic (`j8()`) treats the value as a plain
|
||
signed range: `if (v < -128) v = -128; else if (v > 127) v = 127;`,
|
||
displayed as `Integer.toString(v)` — no fractional scaling.
|
||
2. A separate live-value dispatcher (`n.java`, a large switch handling
|
||
various PID-style codes) has a case doing manual two's-complement
|
||
conversion — `if (raw > 127) raw -= 256;` — immediately before storing
|
||
into the same field (`MainActivity.Mc`) the CO Trim dialog reads.
|
||
|
||
**Conclusion: the raw byte is signed two's-complement (-128 to 127), used
|
||
directly with no multiply/divide scaling factor** — not the unsigned 0-255
|
||
interpretation this project used everywhere earlier. **This retroactively
|
||
resolves the "255 looks like a sentinel" puzzle** from the exhaust-family
|
||
composability analysis (`COMMUNITY_TUNING.md`/`PERMUTATIONS.md`): unsigned
|
||
`255` is simply signed `-1` — an entirely ordinary, near-neutral value, not
|
||
a special case. Re-reading the earlier data with correct signs: the stock
|
||
aftermarket-silencer family sits near `-1` (neutral), production/Arrow
|
||
sits around `+69` to `+125` (already noticeably rich), and the real
|
||
SAI/O2 delete map pushes to `+117` — a large, deliberate enrichment,
|
||
exactly the physically-sensible story this parameter's name would predict.
|
||
The composability conclusion (can't cleanly merge Arrow's delta with the
|
||
delete map's delta) still holds — redone with correct signs, the combined
|
||
correction still saturates past the `+127` clamp — but now for an
|
||
intuitive reason (the needed correction is genuinely large) instead of an
|
||
unexplained sentinel.
|
||
|
||
**Not fully closed:** the *encoding* (signed int8, no scaling) is now
|
||
well-supported by two independent sites; the exact real-world *unit* (is
|
||
`+1` precisely "+1% CO", or a nearby-but-not-identical ECU-internal unit)
|
||
isn't separately confirmed by either site. Raised from low to medium-high
|
||
confidence — upgraded, not fully resolved.
|
||
|
||
**Practical implication for composing tunes:** don't treat this array as
|
||
"2 conflicting bytes" the way `compose_arrow_delete.py` originally did —
|
||
it's a full 32-cell curve. A conservative mod (mufflers only) may touch 2
|
||
cells; a more aggressive one (open pipes + airbox) touches nearly all 32.
|
||
Composing two independent tunes' curves here needs the same per-cell
|
||
arithmetic treatment as the main VE tables (`PERMUTATIONS.md`), not a
|
||
handful of spot fixes.
|
||
|
||
**Open question, raised from real-world testing (2026-09-15/16, see
|
||
`../ROAD_TEST_LOG.md`): does "idle" in this table's name mean literal
|
||
engine-stopped idle, or does it apply whenever the throttle is
|
||
closed/near-closed regardless of RPM?** TuneECU's own docs say "at idle,"
|
||
but the table has real, non-trivial entries all the way out to 10,000 RPM
|
||
— hard to explain if it only ever applied near a stop. Circumstantial
|
||
support for "applies whenever throttle is closed": a real-world decel
|
||
sweep through the *uncorrected* middle of this curve (RPM 2600-6500,
|
||
composed only touched points 2400 and 7000) produced residual popping in
|
||
exactly that RPM range, while the low-throttle table fix (a genuinely
|
||
different, confirmed-idle-adjacent table) independently and completely
|
||
fixed literal stopped-idle stalling. That's consistent with this curve
|
||
being live during the whole closed-throttle deceleration, not just at a
|
||
stop — but it's inference from one road test, not confirmed in the
|
||
decompile. Worth designing a deliberate test for if this gets revisited:
|
||
does the CO Trim value visibly change on TuneECU's live-diagnostics screen
|
||
while decelerating at 4000 RPM with the throttle shut, or only once RPM
|
||
actually reaches idle?
|
||
|
||
## 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.
|