Files
samplez/tunie/research/TUNING_GUIDE.md
uhryniuk 4aa5da53d2 Commit tune maps and research so tunes are reachable from the phone
Removes the *.hex/maps_cache gitignore rule (explicit user call, reversing
the earlier no-redistribution stance) so the official TuneECU catalogue
maps, derived SAI/O2-delete composites, and the checksum/composition
tooling are actually available to pull up on a phone browser when using
the real TuneECU app. Also folds in tonight's KWP2000 fixes (TesterPresent
keep-alive, connect-failure cleanup, slow-init StartCommunication fix) and
the accumulated research docs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FP2GaxS9HkUdL5sLBnjKje
2026-08-27 01:34:05 -05:00

532 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Tuning the 2010 T100 — master reference
One place that answers "how do we actually tune this bike, any way we
want" — indexes everything scattered across this project's research files
into the actual decision tree: what's possible, what's proven, what's
still open. Read this first; follow the links for the technical depth
behind each claim.
**The bike:** 2010 Triumph Bonneville T100, 865cc air-cooled twin, Keihin
ECU (Renesas SH7054), **mechanical odometer** — this generation detail
matters constantly (see "the one recurring trap" below).
**Current real-world state:** SAI plumbing physically removed, solenoid
left electrically connected (clicking, harmless — [resistor fix
below](#stop-the-sai-solenoid-clicking)). Exhaust: Arrow 2-in-1 in hand,
not yet fitted. Airbox: still stock, removal planned for later. Fuel:
Canadian premium, ≤10% ethanol (E10). ECU not yet connected to
(`tunie info` never run) — ["Phase 1" in the project's own
`../STATUS.md`](../STATUS.md), still blocked on the Triumph diagnostic
connector cable.
## Two goals, and they sometimes pull in different directions
**Primary:** tune my own bike correctly for whatever mods actually go on
it — exhaust now, airbox later, whatever comes after that. Accuracy and
correctness win when this goal is in tension with the one below.
**Secondary:** make the tuning process non-invasive and simple enough that
this gets open-sourced and other riders can use these tools without
cutting/drilling/welding anything on their bike they can't easily undo.
These aren't always aligned, and it's worth being explicit about it rather
than letting decisions quietly optimize for one without saying so:
- The bung-reuse / device-flag-decoupling approach two sections down is a
case where both goals point the same way — no permanent modification
*and* it doesn't compromise the primary tune's correctness. When that's
possible, it's the obvious choice.
- Elsewhere they can conflict: e.g. two wideband sensors (one per
cylinder, pre-collector) gives better tuning accuracy (goal 1) but costs
more hardware and setup complexity than one sensor post-collector (goal
2). No blanket rule — flag the trade-off explicitly at each decision
point rather than silently picking one goal's answer.
- The open-sourcing goal also means the eventual tooling (Phase 0/4 in
`TUNING_IMPL_PLAN.md`) should stay agnostic to *which* hardware path a
given rider picked, rather than hard-coding this build's specific
choices — already the design intent there, worth keeping true as it
gets built out.
---
## Is the no-drilling ECU-streamed plan actually achievable? (Aug 2026 status check)
Short answer: **architecturally yes, nothing found so far says it can't
work — but it is not yet proven end-to-end, and it all sits behind the
one blocker this whole project has had since before this plan existed.**
Broken down honestly, piece by piece:
| Piece | Status |
|---|---|
| No drilling — reuse stock O2 bungs for wideband | **Solid.** M18x1.5 thread standard confirmed for both sensor types and this Triumph twin family specifically. |
| Wideband sensor + controller hardware | **Solid.** Off-the-shelf, well-understood (Innovate/AEM), just needs buying and wiring — execution risk only, no research risk. |
| ECU exposes RPM/TPS/coolant live | **Mechanism confirmed**, specific record IDs **not yet confirmed for this bike**. Recovered one candidate record array from the decompile (the Keihin-family "default" case), but which `Oe()` switch case actually applies to a Bonneville twin specifically hasn't been traced through the ECU-type-selection logic — it's a good candidate, not a verified one. |
| Wideband AFR into the onboard logger | **Solid**, standard ADC-reads-a-voltage problem, no open questions. |
| TuneECU's F Trim / Commit trims workflow | **Confirmed from documentation** (`android.html`), never operated hands-on — no bike has been connected yet, project-wide, per `../STATUS.md`. |
| Mapping log points to fuel-table cells | **Solid** — RPM/throttle axes already extracted and validated (`reference-maps/table_map.py`). |
| Polling-loop + onboard-unit integration code | **Not written yet.** Phase 0 of `TUNING_IMPL_PLAN.md` — straightforward extension of `tunie`'s existing transport, but zero lines of it exist right now. |
**The one hard blocker underneath all of it:** first ECU contact
(`tunie info`) has never happened — still stuck on the Triumph diagnostic
connector adapter, the same blocker `../STATUS.md` has tracked since
before this tuning plan existed. Record labeling, live-capture validation,
and confirming the F Trim workflow hands-on all require that connection to
exist first. Nothing about the plan is contradicted or looks unworkable —
it's just genuinely unverified past the paper-architecture stage until
that cable problem gets solved.
**So: achievable, yes. Achieved, not yet.** The path from here to "working
system" is: solve the cable → `tunie info` (resolves current stock map ID
as a side effect) → label the live records empirically → write the
polling-loop code (can actually start before the cable's solved, against
mocked data) → wire the wideband ADC → do a real capture → build the
first tune. No step in that chain currently looks like it won't work — but
none of them past "the cable exists" have actually been tried.
---
## The five ways to get a tune, ranked by effort/reliability
### 1. Pick an existing official TuneECU calibration (done, free, safest)
For Arrow 2-in-1 + E10 + mechanical odo, TuneECU's own catalogue already
has the validated answer: **map `20262` or `20313`** (same tune, two
catalogue reissues — see
[`reference-maps/PERMUTATIONS.md`](reference-maps/PERMUTATIONS.md) for the
proof they're identical bar one trivial byte). This is real engineering
data from Triumph/TuneECU, dyno-validated by them, zero DIY risk. Covers
exhaust + fuel type. **Does not cover SAI/O2 delete or airbox removal** —
TuneECU never ships those combined with an aftermarket exhaust, confirmed
by an exhaustive catalogue-wide keyword search (zero hits across all 1811
entries, any model) — see
[`COMMUNITY_TUNING.md`](COMMUNITY_TUNING.md#existing-tunes-for-airbox-removal--2-1-exhaust-aug-2026).
### 2. Flip the SAI/O2 device flags on top of #1 (done, free, low risk)
`reference-maps/toggle_devices.py` flips the three device-enable bytes
(`0x53801` SAI, `0x53818`/`0x53819` O2×2 — found and independently
re-verified 3× across different reference maps, see
[`reference-maps/DEVICES.md`](reference-maps/DEVICES.md)) on any base map.
Already built: `reference-maps/derived/20262-noSAI-only.hex` (matches your
resistor-swap plan — see below) and `...-noSAI-noO2.hex`. Confirmed
byte-for-byte disjoint from the Arrow exhaust calibration, so this
composition is sound, not a guess
([`COMMUNITY_TUNING.md`](COMMUNITY_TUNING.md#what-the-real-device-flag-file-tells-us-about-composing-arrow--delete)).
**Important, established this session:** this flag most likely only
**suppresses the fault-code/CEL logic** for a missing device — it does
*not* reliably change fueling behavior. Multiple forum sources describe it
this way, and TuneECU's own current Android UI still separately exposes
"Idle Fuel Trim (CO)" as *the* parameter specifically for "Triumph without
O²-Sensor" — i.e. the flag and the fueling correction are two different
things you both need, not one flag that does both.
### 3. Hand-compose a "good enough" trim correction (done, updated Aug 2026)
`reference-maps/compose_arrow_delete.py` sets **both** idle-trim bytes now
(`0x536AB` and `0x5369C`, part of the 32-entry "Idle Fuel Trim (CO)" table,
[`reference-maps/TABLES.md`](reference-maps/TABLES.md#fuelignition-trim-array--rpm-indexed-32-entries-0x53690-0x536af)) —
`0x5369C` was originally excluded as "exhaust-family-dependent," but that
was an artifact of reading the byte as unsigned; the table is signed, and
in signed space both bytes compose the same way (one saturates at `+127`).
**This composition is still scoped to the SAI/O2-delete case only — it is
explicitly NOT an airbox-removal tune.** A real dyno reference on the same
ECU family
(`20184dynoTuneSteveO2-Disable.hex`) showed a real airbox+open-pipe mod
rewrites nearly the *entire* 32-cell trim table plus a further ~50-byte
region we haven't mapped — see
[`COMMUNITY_TUNING.md`](COMMUNITY_TUNING.md#major-find-a-real-dyno-tune-on-our-exact-ecu-generation-airbox-removed-aug-2026).
Output: `reference-maps/derived/20262-arrow-delete-composed.hex`.
### 4. Buy a matched commercial tune (real, currently blocked)
- **Triumph Twin Power (TTP)** had exact-match products (`Tune 11 2-1`:
airbox removal + 2-1 exhaust + O2/SAI removal) — but the storefront is
effectively wound down (founder retirement 2022, every SKU checked shows
"not in stock," hardware supply handed to a sister company). Don't count
on this.
- **DNK TuneWorks** ($275, mail-in or self-flash) — no airbox-specific
variant listed for the 865 air-cooled model, but they've built per-mod
variants off-catalogue before (per a customer review); worth emailing
directly. Watch for their *other*, wrong-model product page (liquid-cooled
2016+ T100) — easy to mix up.
- Full details: [`COMMUNITY_TUNING.md`](COMMUNITY_TUNING.md#existing-tunes-for-airbox-removal--2-1-exhaust-aug-2026).
### 5. Road-tune it yourself, no dyno (documented, not yet tried, most promising for airbox)
A complete, concrete methodology exists and is **confirmed still usable in
the current TuneECU Android app** (not a dead Windows-only technique):
wideband O2 sensor + datalogger, drive fixed throttle positions through the
RPM range, log target-vs-measured AFR, feed the discrepancy into the **F
Trim** table (swipe right from the F/I table screen), **Commit trims**
(bakes correction into the permanent fuel table, resets trim to zero),
repeat 3-4 passes. A dyno session afterward becomes optional fine-tuning,
not a requirement. Full writeup, sourced:
[`COMMUNITY_TUNING.md`](COMMUNITY_TUNING.md#major-find-a-documented-no-dyno-road-tuning-method-aug-2026).
**This is the actual answer to "how do we get airbox removal tuned
without paying a tuner."** It directly fixes what airbox removal breaks
(the fuel table no longer matching real airflow) using measured reality,
sidestepping the whole byte-composition problem in #3 entirely.
#### How it actually works, mechanically
The ECU's fuel table (`FuelMap`) stores intake air mass in mg for each
RPM/throttle-position cell. Hardware changes (exhaust, airbox) change the
real airflow at each cell without changing what's stored — the table goes
stale until someone re-measures it. A dyno does that by holding the bike at
load and reading a wideband sensor; this method does the same measurement
on the road.
1. **Get a truthful AFR reading.** The stock O2 sensor is narrowband — fine
for a 14.7:1 closed-loop target, useless for tuning richer WOT mixtures.
A wideband sensor + controller reads accurately across the whole range
actually being tuned (roughly 12.5-14.7:1).
2. **Flatten the baseline so it can't lie.** Before measuring, load a tune
with AF1/AF2 (target-AFR tables) both set flat to something safely rich
(13.0:1 in the original guide) and **F Trim** zeroed. This isolates "is
the fuel table wrong" from "was the target already weird here" — you
can't tell those apart if the target varies cell-to-cell too.
3. **Log real riding data.** Mark the throttle grip at the fuel table's
throttle breakpoints. On a quiet straight road, hold each mark steady,
sweep the RPM range, three passes per mark. Log RPM, table
row/cell, and measured-vs-target AFR at each point.
4. **Translate the log into corrections.** For each logged point, work out
the nearest fuel-table cell (interpolating between throttle
breakpoints) and how far off the AFR was — that error goes into the
**F Trim** table, a temporary zero-baseline overlay the same shape as
the main table.
5. **Commit and iterate.** **Commit trims** permanently bakes the F Trim
corrections into the real fuel table and zeroes F Trim again. Reflash,
repeat the same test. Converges in roughly 3-4 passes per the original
author.
6. **Finish it.** Once the base table is correct, replace the flat 13.0:1
placeholder with real AF1/AF2 targets (richer at full load for power,
leaner at part load for drivability/economy), one final commit.
7. **Dyno becomes optional** — at that point it's just fine-tuning via
F Trim on an already-correct base map, not the whole job.
#### Tools actually required — and why this isn't a simple process
Being honest about this rather than making it sound tidier than it is:
**Fabrication.** The wideband sensor needs a bung in the exhaust — either
weld one in (needs a welder, or a shop that will), or find/reuse an
existing bung (check whether Arrow's collector has a provision, or whether
the stock O2 bung is reusable). Not a bolt-on step.
**Electrical work.** Wideband controller needs switched 12V, ground, and
the sensor's connector wired in — same category of work as the SAI
resistor swap, just for a different, more sensitive sensor (wideband
sensors are also consumables that die if run rich/unheated wrong, adding a
"don't fry the $80 sensor" constraint the resistor job didn't have).
**A real integration gap, not just extra steps.** The German guide's slick
workflow — a single log row giving you table position *and* measured AFR
together, entered with one keypress — depended on a **TuneBoy-specific
cable with a built-in analog input**, a different product from TuneECU.
**It is not confirmed that TuneECU's cable has an equivalent.** Without it,
this becomes correlating *two separate logs* (the wideband's own
recording, and whatever position/RPM reference you have) after the fact,
by eye/timestamp — meaningfully messier and more error-prone than the
streamlined description implies. This is the single biggest unresolved
question before treating this as a smooth process: does TuneECU support
synced logging at all, or does every pass require manual correlation.
**A second software tool.** The wideband controller has its own logging
software/app (Innovate LogWorks, AEM's app, or onboard SD), separate from
TuneECU. Plus a spreadsheet to do the cell-correlation math by hand.
**Riding logistics, not just equipment.** Steady, repeatable throttle
positions at speed, on a quiet legal road, while a logger runs — solo,
that's watching a gauge/throttle marks/traffic/road all at once, which is
a real safety concern, not a paperwork one. A passenger to run the logger,
or a fully autonomous SD-logging setup that needs zero mid-ride
interaction, is the practical answer, not "just wear the gauge and go."
**Time.** Multiple ride-then-reflash cycles (3-4 minimum, per the guide),
each needing the bike, the logger, a suitable road, and a laptop/Android
device to reflash between passes. Realistically spread across more than
one outing, not an afternoon.
**Net honest assessment:** this is a real DIY-tuning project — fabrication
+ wiring + a second logging tool + an unresolved software-integration
question + genuine riding-safety logistics + several iteration cycles —
not a "flash a file and go" task. It's the most *correct* path (measures
your actual bike, not someone else's), but it's the highest-effort of the
five options in this document, not the easy one. Worth weighing again
against just paying DNK once/if they confirm an airbox variant (#4) —
that trades money for skipping all of the above.
---
## Better idea (Aug 2026): stream live data straight off the ECU with `tunie`
The messy "two separate logs correlated by hand" problem above assumed we
needed TuneECU's app/cable for the ECU side. We don't — `tunie` already has
verified, safe, read-only K-line access to this exact bike. Its own live
dashboard is built on the same mechanism, confirmed in the decompile:
- **Service `0x21` ReadDataByLocalIdentifier**, already implemented in
`tunie` (`src/tunie/kwp2000.py`, used today by `identify.py`'s one-shot
sweep) — same service TuneECU's live dashboard polls continuously via a
round-robin scheduler (`m.java`'s `se()`/`Oe()` functions).
- Records are **2-byte extended identifiers** (not the single-byte ones
`identify.py` currently sweeps), sent as `0x21 <hi> <lo>`. Recovered the
actual per-model record table for the Keihin-family default case (array
`ch` in `m.java:204`): decoded record values `0x0100`, `0x0001`,
`0x5103`, `0x0139`, `0x0007` (recurring across the 15 polled slots).
- **Not yet labeled** — which record is RPM vs. throttle position vs.
coolant temp isn't identified from the decompile alone (the field-name
mapping lives in UI code not yet fully traced). **Fastest way to finish
this isn't more static tracing — it's empirical, once the cable's
connected**: poll all 5-ish records in a loop with ignition on, rev the
engine / move the throttle by hand, and see which values respond to
what. RPM scales with engine speed, TPS jumps with throttle movement,
coolant temp changes slowly. Should take one bench session to label all
of them.
### What data we actually need, and where each piece comes from
| Data | Source | Status |
|---|---|---|
| RPM | ECU, live poll (`0x21`) | mechanism confirmed, record ID unlabeled |
| Throttle position (TPS) | ECU, live poll (`0x21`) | mechanism confirmed, record ID unlabeled |
| Coolant temp (exclude cold-engine data) | ECU, live poll (`0x21`) | mechanism confirmed, record ID unlabeled |
| Wideband AFR | **external hardware, no way around it** | ECU's own O2 is narrowband, useless for rich-mixture tuning regardless of any of the above |
Everything except the wideband AFR reading can come from the ECU itself,
via hardware already owned (the K-line/FTDI cable this project needs
anyway for `tunie info`) and code already 90% built (`tunie`'s existing
transport + safety layer, just needs a polling loop added instead of a
one-shot sweep, plus the 2-byte-record variant of `READ_DATA_BY_LOCAL_ID`).
**No new ECU-side hardware, no dependency on TuneECU's app, no uncertainty
about a proprietary analog-input cable** — that whole question from the
previous plan is moot.
### The "onboard live processing unit" idea — sound, and better than the alternatives
A small onboard computer (Pi Zero, ESP32-class board, etc.) running two
things in parallel, writing one synced timestamped log:
1. `tunie`'s KWP2000 poller hitting the labeled RPM/TPS/coolant records in
a tight loop over the K-line cable — safe by construction, same
read-only transport already verified against this bike's protocol.
2. An ADC channel reading the wideband controller's analog AFR output.
This **eliminates the correlation problem entirely** — one log, one
timestamp column, no post-ride guesswork matching a wideband recording
against hand-marked throttle positions. It also removes the riding-skill
burden from the original plan (no need to hit exact throttle marks by
feel — real TPS is captured directly, at whatever resolution the polling
loop achieves).
**Still required, unavoidably:** the wideband sensor + bung + controller
(external AFR hardware, see below) and someone actually riding the bike
through the RPM/throttle range while the logger runs — the bike still has
to physically experience the conditions being tuned for. What this
approach removes is the *ambiguity* in capturing that, not the ride itself.
**Concrete next steps, in order:**
1. Extend `tunie` with a 2-byte-record variant of `READ_DATA_BY_LOCAL_ID`
and a polling-loop mode (currently only does the one-shot `identify`
sweep). Software-only, doable now, no bike required.
2. First ECU contact (`tunie info`, per `../STATUS.md` — still blocked on
the connector cable) doubles as the session to empirically label which
record is which by watching values change.
3. Once labeled, wire in the wideband ADC channel alongside the KWP2000
poller on the same onboard unit.
4. Do the actual road/bench data capture, post-process off-bike into F
Trim corrections exactly as described above — just with a clean single
synced log instead of two to hand-correlate.
---
## Wideband O2 + datalogger setup plan (not yet acquired — hardware/shopping list)
The German guide's original hardware (Wayne Macdonald's proprietary
TuneBoy/TuneEdit cable with a built-in analog input) is **TuneBoy-specific,
not confirmed to exist for TuneECU** — this project uses TuneECU (Alain
Fontaine's tool), a different product built on the same underlying
Keihin/Sagem architecture. Don't assume TuneECU's cable has an equivalent
analog input; not verified either way.
**Practical path that works regardless:** use a self-contained wideband
controller with its own onboard/SD datalogging, rather than depending on
feeding the signal into TuneECU's cable directly. Correlate logs to fuel
table cells by throttle position + RPM (the German guide's own low-tech
method — marked throttle-grip positions, steady-state RPM sweeps) instead
of a live electrical link. Candidates, all with standalone logging:
- **Innovate LM-2** or **MTX-L** — two programmable analog outputs, widely
documented for this exact "log AFR, correlate to ECU table cells"
workflow on other platforms.
- **AEM X-Series UEGO** — 0-5V analog out + onboard SD logging, CAN option.
Both need: a bung in the exhaust for the sensor (weld-in, ideally before
any downpipe junction — check whether the Arrow 2-in-1's collector has a
provision, or where the stock O2 bung sits since it may be reusable), a
controller box (fits under the seat, same area the SAI solenoid used to
occupy), switched 12V power, and a way to read RPM alongside AFR (either
the wideband unit's own RPM input if it has one, or TuneECU's own live
Dashboard screen running in parallel, watched/logged manually).
**Setup steps, when the hardware is in hand** (this project can't do this
part — physical bung welding, sensor mounting, wiring):
1. Weld/fit the wideband bung, mount the sensor.
2. Mount the controller, wire switched 12V + ground + sensor.
3. Power on, let the sensor heat-soak per its instructions before trusting
readings.
4. Load a flat/rich baseline tune (per the German method: AF1/AF2 set to a
uniformly rich target, F Trim zeroed) so there's no lean risk during
logging.
5. Do the steady-state throttle sweeps, log AFR vs. target.
6. Enter corrections into TuneECU's **F Trim** table (swipe right from the
F table screen), **Commit trims**, reflash, repeat 3-4 passes.
7. Set real AF1/AF2 targets once the base fuel table is corrected.
This is genuinely the first step in this plan that requires physical
hardware acquisition and bike work — everything before it in this document
was achievable offline. Flag it to revisit once you're ready to buy parts.
## Stop the SAI solenoid clicking
Already physically removed the SAI plumbing; solenoid left wired, clicking
harmlessly (confirmed no DTC risk, no engine risk). Fix: swap the solenoid
for a **470 Ω / 0.5 W resistor** across the harness connector's two
solenoid pins (battery disconnected while wiring). Full procedure and
alternate 50 Ω/10 W option:
[`COMMUNITY_TUNING.md`](COMMUNITY_TUNING.md#practical-silencing-a-removed-sai-solenoid-with-a-resistor-aug-2026).
Then flash `derived/20262-noSAI-only.hex` to suppress the resulting DTC —
device flag toggle is confirmed clean/disjoint from the Arrow calibration
(#2 above).
---
## After tuning: the wideband hardware isn't permanent
Once the F Trim corrections are validated and Committed into the permanent
FuelMap, the wideband sensors were a measurement tool, not a runtime
requirement — pull them and **plug the bungs with standard M18x1.5
threaded plugs, not a weld**, so they stay reusable for a future re-tuning
pass without welding again. Optional, not required: leave one wideband
installed permanently as an ongoing AFR gauge — worth it specifically
because a fully SAI/O2-deleted bike has zero live feedback watching for a
lean condition afterward. Detail: `TUNING_IMPL_PLAN.md` Phase 5.
## Best version yet: reuse the stock O2 bungs, make the O2-delete decision reversible too
**Instead of drilling anything, or permanently deciding O2 delete now:**
temporarily unscrew the stock narrowband O2 sensors from their existing
bungs, thread wideband sensors into those same stock locations for the
tuning sessions (M18x1.5 is standard — no adapter expected, though worth
confirming on the actual bike), then **put the stock narrowband sensors
back afterward**. Zero new holes, zero permanent exhaust modification, and
it reuses hardware the bike already has.
**This surfaces a real tension worth being explicit about, though:** the
original reason for wanting O2 delete wasn't the road-tuning project — it
was `RESEARCH.md` §9's finding that O2 delete lets the ECU run a richer,
cooler idle/cruise mixture and fixes snatchy off-idle response. That's a
**closed-loop** behavior change, at idle/steady-cruise. The wideband/F Trim
tuning process only ever corrects the **open-loop** main fuel table
(WOT/acceleration) — it doesn't touch idle/cruise closed-loop behavior at
all. So if the stock sensors go back in *and* the final flashed tune
re-enables the O2 device flag, you get an accurately-tuned WOT table for
the new exhaust, but the original snatchy/hot-idle problem O2 delete was
meant to fix **comes back** — the two projects don't automatically bundle.
**The actual best move: decouple the physical sensor from the software
flag.** Put the stock sensors back in the bungs either way (no permanent
mod, always reversible) — but the *final* flashed tune's O2 device flag is
an independent choice from whether a sensor happens to be physically
present:
- Flag **off**: ECU ignores whatever's in the bung, runs open-loop-derived
richer idle/cruise — gets the original SAI/O2-delete benefit, with a
stock sensor sitting there doing nothing (harmless).
- Flag **on**: full closed loop restored, snatchy/hot-idle behavior
returns, but with the WOT table now correctly matched to the exhaust.
Since nothing physical differs between these two states, **switching
between them later is just reflashing a different device-flag setting —
no hardware change either way, forever.** That's a stronger "no permanent
mods" outcome than just avoiding drilled holes: the O2-delete decision
itself becomes fully reversible, not just the sensor mounting.
**One session-scoped exception:** during the actual tuning rides
themselves, closed loop needs to be bypassed regardless of the final
plan — a wideband sensor occupying the narrowband's physical spot can't
also feed the ECU a valid narrowband signal, so the ECU can't run real
closed loop during that window. This matches the original German guide's
approach and is normal for any tuning session (dyno tuners do the same) —
it's a temporary session state, not a permanent change, and doesn't affect
which final flag setting gets chosen afterward.
**Practical note:** repeatedly swapping sensors in and out of the same
bung across multiple tuning passes is real wear — use anti-seize on the
threads to avoid the bung seizing after heat cycles.
## The one recurring trap: ECU generation
**Every dead end and wrong turn in this research traced back to the same
mistake: mixing mechanical-odometer and LCD-odometer map/ECU generations.**
They use different `ecu` type codes in `maps.json`, different absolute
byte offsets for the same logical parameter (confirmed: our device-flag
offset reads garbage on an LCD-odo file), and are not interchangeable in
any way. **Before using any map ID or byte offset found anywhere — forum,
vendor site, or this project's own derived files — check it's the
mechanical-odo family (`ecu='0'`, or explicitly labeled "Mechanical
odometer").** Wrong-generation map IDs that came up and got ruled out:
`20500`, `20505`, `20507`, `20516`, `20498...LCD`, `20500MapAirboxBonnie_LCD`.
---
## Local research assets (what's actually on disk)
- `reference-maps/*.hex` — real downloaded reference maps: stock
production/aftermarket (`20187`-`20192`), Arrow 2-in-1/2-in-2 all fuel
variants (`20262`-`20316`), the real SAI/O2 delete map
(`20188Map2009AIRBOXBONNY.hex`), the America dyno tune
(`20184dynoTuneSteveO2-Disable.hex`) plus its stock baseline
(`20185`/`20186`). Gitignored (`*.hex` at the samplez repo root) — these
are working files, not committed, so they won't survive a fresh clone;
re-download commands are in each research doc's history if needed again.
- `reference-maps/derived/` — our own composed candidates (SAI-only,
SAI+O2, the trim-composed Arrow variant). **None are known-flashable**
— the ECU flash checksum isn't solved (`docs/ROADMAP.md` Phase 3),
separate from everything in this document.
- `reference-maps/{decode_map,reconstruct_rom,table_map,toggle_devices,
compose_arrow_delete}.py` — the working toolchain: decrypt → flat ROM →
named tables → device-flag edit → trim-byte compose.
- `tuneecu_site_mirror/` — full local mirror of tuneecu.net's
documentation (built by `tuneecu_crawl.py`), including the German
road-tuning primer. The live site says it won't be updated after
September 2024 — this mirror is insurance against link rot.
- `PERMUTATIONS.md`, `TABLES.md`, `DEVICES.md`, `COMMUNITY_TUNING.md` — the
detailed research trail; this file is the index, those are the evidence.
## Open questions, if this gets picked up again
1. ~~Byte→percent scaling formula for the "Idle Fuel Trim (CO)" table~~ —
**upgraded, Aug 2026** (`reference-maps/TABLES.md`): found TuneECU's
own CO-Trim edit dialog and a live-value dispatcher, both independently
confirming the raw byte is **signed two's-complement (-128 to 127),
used directly with no scaling factor** — not the unsigned 0-255
interpretation used everywhere earlier in this project. This also
retroactively explains away the "255 looks like a sentinel" puzzle from
`PERMUTATIONS.md`: unsigned 255 is just signed -1, an ordinary
near-neutral value. Encoding confidence: low → medium-high. Still open:
the exact real-world unit (percent, or an ECU-internal unit close to
it) isn't separately confirmed. Matters for both project goals per the
reasoning below, now on firmer ground than when this was first flagged.
2. Map the ~50-byte region (`0x50010`-`0x50C21`) the America dyno tune
also rewrote — likely ignition/limiter/other trim, unmapped.
3. Identify `0x53822` — probably traction control or instrument-cluster
deactivate per the Android UI's device list, unconfirmed for this ECU
family.
4. Actually try the road-tuning method (#5) once the airbox mod happens —
the real next step, not more offline byte analysis.
5. First ECU contact (`tunie info`) — still blocked on the physical cable
adapter, per `../STATUS.md`. Would resolve which exact stock map is
currently flashed, a prerequisite fact this whole document has been
assuming rather than confirming.