"""Compose an Arrow 2-in-1 + SAI/O2-delete candidate map. Builds on `toggle_devices.py`'s clean, conflict-free device-flag toggle (0x53801/0x53818/0x53819 -- verified disjoint from the Arrow exhaust delta, see COMMUNITY_TUNING.md) and additionally resolves the two "Idle Fuel Trim (CO)" bytes that conflict between the Arrow calibration and the real 20188Map2009AIRBOXBONNY.hex delete map. **Aug 2026 correction:** both bytes are now composed. The original version of this script left 0x5369C untouched, reasoning it was "exhaust-family dependent" (stock aftermarket-silencer maps held unsigned 255, which looked like a sentinel distinct from the ~104 held by production/Arrow maps). That reasoning was built on an **unsigned** byte interpretation. `TABLES.md` later established this whole table is **signed** two's-complement -128..127, confirmed from two independent code paths in the TuneECU app. Redone in signed space: unsigned 255 is simply signed -1 -- an entirely ordinary near-neutral value, not a sentinel at all. There is no regime conflict; the byte composes the same way 0x536AB always did, it just saturates. 0x5369C -- composed additively, signed. stock188=-1, delete=+117, delta = +118. arrow=+69 + 118 = +187, clamped to the signed max +127. The correction genuinely wants this cell pushed to its richest possible value at this RPM point -- clamping to +127 is the correct outcome, not a sign the byte is unusable, and it's a real, meaningfully different result from the prior version's "leave at Arrow's +69" default. 0x536AB -- composed additively, signed, same as before (the arithmetic is invariant to signed vs. unsigned interpretation as long as nothing overflows the representable range, which this one doesn't): stock188 =-128, delete=-118, delta=+10, arrow=-123 + 10 = -113 -> byte 0x8F (143). Usage: python3 compose_arrow_delete.py 20262Map.hex 20188Map.hex \\ 20188Map2009AIRBOXBONNY.hex out/20262-arrow-delete-composed.hex """ from __future__ import annotations import sys import checksum from decode_map import decode, encode from toggle_devices import SAI, O2_1, O2_2, repack, unpack TRIM_BYTES = (0x536AB, 0x5369C) # both now composed, signed arithmetic def _s8(v: int) -> int: return v - 256 if v >= 128 else v def compose(arrow_raw: bytes, stock_after_raw: bytes, delete_raw: bytes) -> tuple[bytes, list[str]]: arrow_dec = decode(arrow_raw) arrow_rom, positions = unpack(arrow_dec) arrow_rom = bytearray(arrow_rom) stock_rom, _ = unpack(decode(stock_after_raw)) delete_rom, _ = unpack(decode(delete_raw)) notes = [] for addr, label in ((SAI, "SAI"), (O2_1, "O2 sensor 1"), (O2_2, "O2 sensor 2")): before = arrow_rom[addr] arrow_rom[addr] = 0 notes.append(f"0x{addr:05X} {label:<14} {before} -> 0 (device flag, conflict-free)") for addr in TRIM_BYTES: a_stock = _s8(stock_rom[addr]) a_delete = _s8(delete_rom[addr]) a_arrow = _s8(arrow_rom[addr]) delta = a_delete - a_stock synth = max(-128, min(127, a_arrow + delta)) before = arrow_rom[addr] after = synth & 0xFF arrow_rom[addr] = after notes.append( f"0x{addr:05X} trim (signed) {a_arrow:+d} + delta {delta:+d} = {synth:+d}" f" (byte {before} -> {after})" ) before_sum = checksum.stored(bytes(arrow_rom)) checksum.patch(arrow_rom) after_sum = checksum.compute(bytes(arrow_rom)) notes.append(f"checksum: stored {before_sum:04x} (stale) -> patched {after_sum:04x}") repacked = repack(arrow_dec, bytes(arrow_rom), positions) out = encode(repacked) assert decode(out) == repacked, "round-trip failed" return out, notes def main() -> int: if len(sys.argv) != 5: print( "usage: compose_arrow_delete.py " " ", file=sys.stderr, ) return 2 arrow_path, stock_path, delete_path, out_path = sys.argv[1:5] out, notes = compose( open(arrow_path, "rb").read(), open(stock_path, "rb").read(), open(delete_path, "rb").read(), ) open(out_path, "wb").write(out) for n in notes: print(" ", n) print(f"wrote {out_path} ({len(out)} bytes)") print("NOTE: main VE fuel-table overlap (~1340 bytes) still unresolved -- see COMMUNITY_TUNING.md.") print("NOTE: only device flags + 2 idle-CO trim bytes are composed -- the main") print(" running-range VE fuel tables are untouched stock-Arrow calibration.") return 0 if __name__ == "__main__": raise SystemExit(main())