feat(detector): Bosch EDC16CP31 (Mercedes OM642/OM646) family - #13
feat(detector): Bosch EDC16CP31 (Mercedes OM642/OM646) family#13COSSART-FR wants to merge 18 commits into
Conversation
Adds the Bosch EDC16CP31 family as an uncalibrated skeleton, following the
walkthrough in CONTRIBUTING.md.
What works out of the box:
- generic axis-first scan, scoring and axis validation (family-agnostic)
- big-endian MPC5xx decoding
- EDC16 checksum correction and frontend endianness, inherited via the
existing includes("EDC16") routing - no code change needed
- Mercedes identification gate placed BEFORE the VAG variant logic, so a
Mercedes 2MB dump can no longer be labelled EDC16U31
What is deliberately empty (needs a corpus of real CP31 dumps):
- CP31_SIGNATURES / CP31_MARKERS
- CP31_ZONES address windows
- every MAP_TEMPLATE carries calibrated: false
Safety contract: detect() returns no maps while uncalibrated and never falls
back to the VAG detectors. Locked by uncalibrated_detector_reports_no_maps.
Frontend stays disabled (ecus.json enabled: false, SUPPORTED_ECUS commented).
See docs/PORTING-EDC16CP31.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EXgAH9nQKiN7FwQAmneXXb
Validated against one KESS V2 read of an OM642 3.0 CDI 165 kW (SW 1037393817): - dump is exactly 2MB and everything below 0x190000 is 0xFF (the tool returns the calibration area only), so CP31_ZONES.calibration moves from the copied VAG guess (0x180000) to the real (0x190000, 0x1FD000) - the single EDC16 checksum descriptor sits at 0x19003C and declares 0x190000..0x1FCFFB; the BE dword sum over that region is exactly 0xD01FE500, so edc16-checksum.ts reads this file unmodified - identification gains two confirmed markers: the Bosch 1037 SW number at the fixed offset 0x190010 (positional, not a substring search) and the Daimler engine descriptor CR<n>-<group>- Adds identifier tests built from synthetic fixtures reproducing that layout (no .bin committed), including the required negative test and a regression test proving a CP31 dump is never labelled EDC16U31. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EXgAH9nQKiN7FwQAmneXXb
…ilies
The skeleton guessed grids the way the VAG detectors do. That is unnecessary
on CP31: every 2D map uses the Bosch Kf_Xs16_Ys16_Ws16 record layout, which
carries its own dimensions -
+0x00 u16 nx | +0x02 u16 ny | i16[nx] X | i16[ny] Y | i16[nx*ny] Z
with Z in COLUMN direction (Y is the fast index). So the detector now reads
the grid instead of guessing it, which removes the worst failure mode of the
axis-first engine: a wrong grid can no longer produce a plausible-looking
map. Presentation follows the file: rows = nx (engine speed), cols = ny
(load), so nothing is transposed and y_axis_address points at the first axis
in the block.
Calibration source: one real dump (OM642 3.0 CDI 165 kW, Bosch SW
1037393817, KESS V2 read) cross-referenced with the Daimler ASAP2 description
of the same project (B209 / V080000 / CR4-642). The A2L describes a DIFFERENT
calibration build - 55% of the calibration bytes differ and no constant offset
exists - so the labels were transferred structurally: exact axis-vector match
plus a longest-increasing-subsequence alignment on block order (238 maps),
then piecewise-constant delta interpolation validated against the A2L physical
limits (374 of 392). Every family below was then re-read from the dump and
checked by hand. The damos and the .bin are not committed; only the derived
signature data is.
Eleven families, all calibrated:
Rail_pSetPointBase / Rail_pSetPointLimN / PCR_pDesBas /
PCR_pBDesMaxAPGear{Hi,Lo} / PCR_rCtlBas / FlMng_qSmk / AccPed_trqEng{,2} /
FlMng_qLimBstPres / FMTC_trq2qBas / InjCrv_phiMI1Bas1..3+Max1..2 /
AirCtl_mDesBas.
Two hypotheses inherited from the VAG templates were wrong and are fixed:
- the CR4 smoke limiter is a function of engine speed and CORRECTED BOOST
PRESSURE (PCR_pBPSCor), not of air mass;
- pedal and duty-cycle axes are 1/8192 per bit (8192 == 100%), not the VAG
0.01 %/bit. AxisType::Pedal is replaced by AxisType::Percent; the old
factor would have scaled every pedal map by 1.22.
CP31_ZONES now carries a window per category, and each template a tighter one
of its own. CP31_SIGNATURES and CP31_MARKERS are populated with markers cut
from the dump at the exact block start and extended until unique inside their
window; detect_by_signatures is implemented as a real marker walk. A marker
hit stays a hint - the block is still decoded, both axes validated and the
data range-checked before anything is emitted.
Tests: 14 unit tests, no binary in the repo (synthetic Kf blocks), including
rejection of an out-of-range block and of a block planted outside its zone.
The fixture test on a real dump is separate and #[ignore]d behind
ZEDSUITE_CP31_DUMP; on the reference dump it finds 25 blocks, all of them
real members of the eleven families, zero false positives.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HMdNS7jKKmsNwuh49CHag8
The detector reports real maps now, so EDC16CP31 goes into SUPPORTED_ECUS and ecus.json flips to status "beta", enabled: true. Mappack export stays off: the corpus is one software build, and an export prices a confidence the data does not support yet. docs/PORTING-EDC16CP31.md is rewritten around what was actually found: the self-describing record layout, the method used to transfer a damos whose addresses do not line up, the table of the eleven confirmed families, and what is still missing (a second software build, OM646, checksum CORRECTION on a stock/modified pair, and the 1D Kl curves). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HMdNS7jKKmsNwuh49CHag8
The editor showed "Checksum non supporté" on a Mercedes EDC16CP31 dump whose checksum is perfectly readable. Routing was never the problem - `isChecksumSupported` matches on the EDC16 substring and returns true - but `findEdc16Regions` rejected the region, `correctEDC16Checksum` returned null, and the caller reads null as "unsupported". The cause is an assumption inherited from the original six VAG pairs: the region had to be 0x100-aligned at BOTH ends. That is a property of that corpus, not of the format. VAG U31/U34 start 0x100000 (%0x100 = 0) end+1 0x200000 (%0x100 = 0) CP31 OM642 start 0x190000 (%0x100 = 0) end+1 0x1FCFFC (%0x100 = 0xFC) The end only needs to be dword-aligned, since the checksum IS the last dword of the region. REGION_END_ALIGNMENT drops to 4. To keep the guard against a stray occurrence of the magic just as tight, two constraints are added in the same place: a 4 KB minimum region length, and the requirement that the signature lives inside the region it announces. The start alignment stays at 0x100 - that is the constraint that actually pins the signature's position. Verified on the real CP31 dump (OM642, SW 1037393817): single descriptor at 0x19003C, region 0x190000..0x1FCFFB, 111615 dwords summing to exactly 0xD01FE500 with the stored word 0x0375D4D9 at 0x1FCFF8. Stock file -> OK, found 1, fixed 0. One byte changed in the Boost Target -> FAIL, fixed 1, and the region revalidates after correction. So the CORRECTION path is now confirmed, not just verification. VAG non-regression checked on synthetic regions: a 0x100-aligned end is still accepted, while a non-dword end, a non-0x100 start, a sub-4KB region and a lone magic in noise are all still rejected. docs/PORTING-EDC16CP31.md said this worked out of the box. It did not, and the section now says so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HMdNS7jKKmsNwuh49CHag8
… VAG The editor showed "3%" on a healthy Mercedes dump. build_expected_report routes anything containing EDC16 to the VAG rule set - Duration 00..05, Duration Selector, Gearbox Torque Limiter, SVRL, Smoke Limiter by MAF/MAP/Lambda, N75 duty cycle. None of those families exist under those names on a CR4 Mercedes, so the score measured the file against the wrong rulebook and told the user their original file looked heavily modified. CP31 now gets its own report over the eleven calibrated families. Expected counts are 1 each, deliberately: the corpus is one software build, and turning its per-family counts (3 EGR, 7 SOI, 3 boost...) into expectations would flag a legitimate different build as modified. Presence is the only invariant a corpus of one supports. Tighten it when a second software joins the corpus. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HMdNS7jKKmsNwuh49CHag8
Three TDI-shaped assumptions got in the way of the CLK 320 CDI. 1. Brand list. project-creator only offered Audi/Seat/Skoda/Volkswagen, so a Mercedes project could not record its own brand. Mercedes-Benz is added, and one ecu-brand-db entry (Bosch SW 1037393817 -> Mercedes-Benz CLK 320 CDI W209, OM642) makes the brand auto-fill on import like it does for VAG. 2. Engine presets. Every preset was a VAG TDI, and guessEnginePreset matched "edc16" before it matched "3.0", so a CP31 file was estimated as a 1968 cc four-cylinder. Adds "3.0 CDI V6 - OM642" (6 cyl, 2987 cc - a different displacement from the VAG 3.0 V6) and matches EDC16CP31/OM642 first. 3. The air bound. This is the part that mattered. MIN_AFR = 17 is a BLACK-SMOKE bound: it describes what a modified engine swallows before it sooties, and it fits the tuned VAG PD files of the original corpus, which really do fuel to that limit. A stock Euro 4 common-rail V6 with a DPF runs far leaner. It bound the curve because CP31 has no Nm torque limiter for the model to use - CP31 limits in QUANTITY - so nothing else shapes the full-load line and the constant decides everything. At 17 the reference stock dump came out at 299 hp for an engine rated 224. CP31_MIN_AFR = 21 puts peak power at 229 hp against 224 announced (+2 %) and peak torque at 448 Nm. This is a one-unknown fit, on one file, against one published figure - not a bench calibration, and the code says so. Published torque for this car does not agree with itself either: Wikipedia gives 510 Nm for the 165 kW OM642, two spec databases give 415 Nm for this body. 448 sits between them, which is consistent but proves nothing. Also wires the CP31 quantity ceilings (FlMng_qSmk_MAP, FlMng_qLimT3BPS_MAP) into the EDC16 fuel path. The existing comment says EDC16 smoke maps are unusable because their rows are stored mirrored - that is true of the VAG detectors, not of the family: the CP31 detector reads the self-describing Kf header and emits rows in file order. They do not bind on the stock file (air is tighter) but they will on a tuned one, which is the point. FlMng_qLimBstPres_MAP is deliberately NOT used: it is indexed by a pressure DIFFERENCE, and feeding it absolute boost collapses the curve. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HMdNS7jKKmsNwuh49CHag8
Adresses de bloc, géométrie, axes et facteurs des onze familles confirmées sur le dump de référence (OM642 165 kW, SW Bosch 1037393817), plus les vecteurs d'axes lus dans le fichier. Données dérivées du damos, pas le damos : ni le .bin ni l'A2L ne sont dans le dépôt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HMdNS7jKKmsNwuh49CHag8
Three things the ground rules asked for and I had not done. "Comments in the Rust engine are English" - my build_expected_report_edc16cp31 doc block and its inline comments were French. Translated. (commands.rs is already largely French upstream; that is not a reason to add more.) "Add tests for anything you fix or add" - the CP31 report shipped with none. Four now: every calibrated family is matched by its prefix, every prefix actually exists in MAP_TEMPLATES (the two tables live in different files and drift silently), CP31 does not fall through to the VAG rules, and the VAG families still get theirs. And a mistake of mine: inserting the CP31 function directly above build_expected_report orphaned that function's doc comment onto mine. The original comment is back where it belongs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HMdNS7jKKmsNwuh49CHag8
The Solutions button was gated to EDC15 - getSolutionsForECU returned null for anything else - so a Mercedes project had none. CP31 now gets one. What it writes is not invented: it is a byte-for-byte replay of an EGR delete done by a professional tuner, verified against a REAL pair - the stock read of the ECU and a read of the same ECU after flashing. 21 ranges, 60 bytes, all inside the EGR block; nothing touches boost, rail, timing or the limiters. Applying it to the stock file reproduces the car's calibration exactly, save the programming signature and checksum word the flashing tool regenerates. Core: AirCtl_qLo_CUR (EGR shutdown threshold by injected quantity) to zero everywhere, EGRCD_swtEGROnVal_C 1 to 0, the first row of the duty-cycle conversion 95 % to 5 %, EGRCD_rInit_C 12 % to 5 %. Diagnostics: the EGRCD debounce and tiDia timers to 0x0000/0xFFFF, plus four DSM bytes whose damos identification stays ambiguous - two candidates each. Those four are replayed because they are part of the verified change, not because their role is established, and the module says so. The corpus is ONE software build, so no address is hardcoded. Each group is anchored on a byte signature checked to be UNIQUE in the dump, and every write carries the original bytes it expects. Anchor missing, anchor ambiguous, bytes already different, solution already applied - any of those and it returns zero patches rather than writing blind. A partial patch would be worse than none. Verified: 21 patches / 60 bytes, result identical to the car's calibration, re-applying emits 0, a file with the EGR area wiped emits 0, and the checksum module reports the file as needing correction afterwards (the editor does it). Not covered, and the module documents it: the EGR air-mass setpoint maps stay untouched - the tuner killed the actuator rather than rewriting the targets - so nothing here guarantees the control-deviation fault path is silenced. i18n already carried an `emissions` category and an `egr_off` entry; the new solution gets its own key in all five languages with wording that describes what it actually does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HMdNS7jKKmsNwuh49CHag8
commands.rs states the rule: increment whenever a change alters the results the detector produces, so projects detected by an older engine are re-scanned on open. The CP31 calibration went in without a bump - my oversight - and the family now returns 25 maps where it returned none. Upstream took 38 for the EDC15VM work in 1.1.6, so this branch takes 39. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HMdNS7jKKmsNwuh49CHag8
… address The CP31 detector located families by walking a per-family address window. That works on the one software build the port was calibrated on and nowhere else: on another build every zone is wrong, and the detector silently finds nothing. Adds a third anchor that does not depend on an address. A MapTemplate can now carry the exact `[nx][ny] + X + Y` bytes of its block(s); the detector searches for those bytes over the whole calibration area and hands each hit to the same `try_block` validation as the other phases. Breakpoint grids are calibration data a remap has no reason to touch, so a key that matched a stock file still matches the tuned one. The keys are not a guess. The ASAP2 package ships the reference binary its addresses describe, which makes every characteristic readable at its declared address. Searching the reference axis vectors in the corpus dump resolves 9 of the 13 calibrated blocks to exactly ONE place each, and that place is the address the previous structural alignment had produced. The other 4 are families whose members legitimately share a grid; they were resolved by the local offset of a uniquely-matched neighbour, which agreed. Zero contradictions - the damos labels are now verified rather than inferred. Behaviour on the corpus is unchanged: the same 25 maps at the same addresses on both the stock and the EGR-off file. Only the confidence of key-confirmed blocks moves, which is the point - an exact grid match used to score below a zone-walk guess. `ignores_a_boost_target_planted_outside_its_zone` planted the REAL Boost Target grid outside its window and asserted it was ignored. Phase 0 now finds it, and correctly so: 28 exact breakpoints identify a family far better than an address range does. The test keeps its invariant - a block that merely looks like a family is still rejected outside its zone - by planting axes close to but not equal to the real grid, and the exact-grid case gets its own test. Not committed: the reference binary and the ASAP2 file are third-party licensed material. Only byte patterns derived from the corpus dump are stored. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HMdNS7jKKmsNwuh49CHag8
…lings Two problems, both found by opening a modified file in the editor. 1. A remapped map disappeared. `score()` range-checks against `z_range_stock` unless the caller asks for tuned mode - and nothing in the UI ever asks: all three `detectMaps` call sites leave `tuned_mode` at false. So on a file whose limiters had been raised, the very maps that were raised fell out of the stock range and were not reported. The editor hid exactly what the user opened it to look at. Fixed where the evidence is strongest rather than by widening the ranges: the axis-key phase now validates against the tuned range. Once the whole breakpoint grid has matched byte for byte, identity is settled, and the stock range no longer serves to identify anything - it only rejects the family's own map for having been modified. The zone walk keeps the stock range, so nothing else gets looser. 2. `FlMng_qSmk_MAP` and `FlMng_qLimT3BPS_MAP` were reported under one name. Same unit, same axes, adjacent addresses - but one is the visible-smoke ceiling and the other is exhaust-temperature protection. Reporting them as one family means a user raising "the smoke limiter" cannot tell which of the two they are editing, and one of those two choices removes a thermal protection. They are now separate families with their own grid, zone and axis key. On the corpus: 25 maps on the stock file, 25 on the EGR-off file, and 25 on a file with the quantity ceilings raised (22 before this change). DETECTOR_VERSION 40 -> 41: map names change, so CP31 projects need a re-scan. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HMdNS7jKKmsNwuh49CHag8
…limit The port only handled 2D blocks, so one-dimensional calibration was invisible in the editor. That is not a cosmetic gap: `FlMng_qLimN_CUR` is the ceiling that ends the power band - flat at 86 mm3/stroke from 1800 to 4000 rpm on the corpus dump, then collapsing to 1.5 by 4550 - and it is one of three limiters that have to move together for anything to change at full load. A user could edit two of them in the editor and never see the third. Adds `CurveTemplate` / `CalCurve` / `read_curve` for the Bosch `[nx][X][Z]` layout, and two calibrated families: `FlMng_qLimN_CUR` and `Rail_pMaxSetSubst_CUR`. Curves are located by axis key ONLY - there is no zone walk for them, and that is deliberate. A 2D block is recognised by two axes plus a grid; a curve has a single axis, so a blind walk would report any ascending vector as calibration data. `a_curve_without_a_matching_key_is_never_reported` pins this down: a curve whose rpm axis is perfectly plausible but differs from the key by ONE breakpoint must be ignored, even in exploratory mode. Only 2 of the 47 curves that could be uniquely identified against the reference build are shipped, because only those have an unambiguous ASAP2 conversion. `FlMng_facFullLd_CUR` is left out on purpose and the reason is in the code: its COMPU_METHOD gives 1/128 while its declared upper limit is 1.0 and the stored values reach 2.0. Showing a number that may be twice the truth is worse than showing nothing. `cp31_report_matches_every_detector_family` carried a hard-coded copy of the family names - the very duplication it exists to catch - and went red on the new families. It now derives the list from MAP_TEMPLATES and CURVE_TEMPLATES instead of restating it, so adding, splitting or renaming a family can no longer break it. The prefix test reads both tables too. On the corpus: 27 elements instead of 25 (25 maps + 2 curves), identical on the stock file, the EGR-off file and a file with the quantity ceilings raised. 52 tests pass. DETECTOR_VERSION 41 -> 42: CP31 projects need a re-scan to pick up the curves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HMdNS7jKKmsNwuh49CHag8
DETECTOR_VERSION goes to 43: upstream used 39–40 for the EDC15P display scaling rename and the EDC15VM MAP/MAF switch, this branch used 39–42 for CP31, so every project scanned with either lineage is re-scanned. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xs3ohatqVeW78mfeSCpb7D
|
Thanks for this, and for the care that went into it. I read the whole thing and ran it against my bench before answering: the detector and the identifier give byte-identical results to master on every VAG file I have (EDC15P, EDC15VM, EDC16), and the checksum still works exactly as before. So nothing in this PR hurts the existing families, cargo test and tsc are clean, and the way you read the self-describing Kf blocks is honestly better than what the VAG detectors have to do. The porting doc is the kind of thing I wish I had written myself. I am not going to merge it yet though, and I want to explain why properly, because it is not about the code quality, it is about how I decide a detector is finished. New ECU families are the contribution I want the most, so it is worth being precise. How the existing detectors were built Every family in the app went through the same loop. I locate the maps by hand on real dumps with the references I have (WinOLS packs, damos/A2L when there is one, the reference tool's map lists), I extract the structural signatures that identify each family (dimension headers, axis markers and layouts, selector blocks, spacing between maps), then I run the detector on a bench of real files, stock AND tuned, and I compare its output file by file with the references until they match. Then I chase the false positives, category by category, until the output is quiet. A detector is not "done" when it finds the maps on the file it was written on; it is done when it behaves on files it has never seen. Some numbers, so you see what "finished" means here: EDC15 and EDC16 VAG: more than 350 dumps all families together, mostly stock, with a share of tuned files, some modified on purpose and some at random, to see how the detector behaves on files that are not stock. On EDC15P the completeness check is 35 rules per codeblock written from tuner knowledge, not from statistics on the files: one EGR map, one driver wish plus two start-quantity maps, one torque limiter, two idle maps, the six injector durations 00 to 05 always with their selector, ten SOI maps, and so on. Every codeblock of the bench is at 100 %, and the gaps against the reference tool are documented and deliberate (hidden families). On EDC16 (U1, U31, U34) the invariants come from experience with these engines: every VAG EDC16 has exactly six main durations 00 to 05 with their selector, every one has the gearbox torque limiter slot, the smoke limiters come by MAF/MAP/lambda, the driver wish is 12 to 16 rpm points by 8 or 9 pedal points with the ninth being kickdown. Those rules are what let the app say "this file has been modified" instead of counting whatever it found. Where the CP31 stands against that bar It is calibrated on one dump, one software, one engine. Every axis key is the axis vector of that file, so on another software the key phase finds nothing and the detector falls back to the zone walk, whose false-positive rate nobody has measured yet. And this ECU is not an OM642 ECU: the same CP31 sits on the 4-cylinder OM646 of the same years with a different calibration, and the app works per ECU type, not per engine, exactly like a VAG EDC16 that serves a 1.9, a 2.0 and a 2.5 TDI. A CP31 detector has to behave on every engine the CP31 was fitted to; today the OM646 is declared but was never seen. There are no invariants yet, only "at least one of each family", because with one file you cannot know what a CP31 always carries. I have no Mercedes dump at all, so I cannot even run the bench myself. And the EGR OFF solution replays a tune from that same single file; beyond that, I am not integrating solutions of that kind for now, for several reasons I would rather not go into here. We can maybe talk about it in the future, but that part I will not take regardless of the corpus. What would get this merged A corpus. For a family like this I want in the range of 50 files minimum, stock and tuned, across the software versions and the engines this ECU is fitted to, OM642 and OM646 at least, power levels and years. Mercedes dumps are less common than VAG ones, so if you get to 30 real files with both engines represented, talk to me. Forum threads and the usual sources have them; people will also send files once the family is announced as being worked on. Two small things while you are in there: the "PORTING SKELETON" comment in bosch/mod.rs is outdated, and the package-lock version bump should go away, the version lives in the release. I would leave the PR open and mark it as a draft in the meantime, so it stays the place where this lands. And if you want to talk about all this directly, the corpus, the invariants, how I run the benches, write to me on Instagram or wherever suits you (links on linktr.ee/zedperf), it is easier than a comment thread. Thanks again, really. This is the best contribution the project has received, and I want it in. Just not at 70 %. Whatever happens next, you officially stay the first contributor on GitHub :) |
The estimate reads the boost TARGET map as the air available, and caps fuel at the family's AFR floor (21:1 on CP31). Both assumptions broke on the OM642 logs: the turbo never reaches the +100 hPa target (2568 hPa measured against 2633 asked, 2430 at 3800 rpm), and a build that accepts smoke runs richer than 21:1. Two optional inputs in the power modal: a smoke floor (family default / 19 / 17) and the measured absolute boost, which clamps the air model at every point. Unset, nothing changes.
DETECTOR_VERSION 43 (local) et 47 (amont) reconcilies en 48 : les deux lignees avaient attribue les numeros 41-47 independamment, tous les projets sont a re-scanner.
7edde57 to
95cd102
Compare
What this adds
Support for the Bosch EDC16CP31, the common-rail ECU of the Mercedes CDI
engines (OM642 V6, OM646 4-cyl). Follows the layout described in
CONTRIBUTING.md: new detector module, strict identifier, routing, frontend
declaration.
Calibrated and verified on a real OM642 dump (CLK 320 CDI W209, Daimler
project CR4-642, SW 1037393817, 2 MB). No dump is committed — the tests that
need one read it from
ZEDSUITE_CP31_DUMPand are#[ignore]d otherwise.Detection engine (Rust)
detector/ecu/bosch/edc16cp31/—EDC16CP31Detectorwithsignatures.rs. 16 families (14 maps + 2 curves): rail pressureset-point and limiter, boost set-point and limiters, smoke limiter,
thermal quantity limiter, quantity limit by boost and by rpm, driver wish
×2, torque→quantity, main-injection timing, air-mass set-point, boost
controller base duty.
(
Kf_Xs16_Ys16_Ws16:nx, ny, X[], Y[], Z[]column-major;Kl_Xs16_Ws16for curves). The detector reads them instead of assumingdimensions, so a 12×12 map in a 16×16 allocation is read correctly.
[nx][ny] + X + Y, byte forbyte), independent of address. A map whose Z values were remapped is still
found, and a software not in the corpus can still yield maps. Byte
markers and a bounded zone sweep are the fallbacks, in that order. Curves
are keyed only — a blind sweep would match any increasing vector.
Identifier
ecu_identifier.rs: positive evidence only — Bosch software number at thecalibration start (positional, not a substring search), Daimler engine tag,
Daimler part-number format. A VAG part number vetoes the Mercedes gate.
Tests include
foreign_2mb_file_is_not_cp31andcp31_dump_is_never_labelled_vag.Frontend (TypeScript)
ecus.json,SUPPORTED_ECUS, ECU list, i18n strings.dword-aligned. The 0x100 end alignment was a property of the six VAG pairs
in the original corpus, not of the format — the CP31 region is
0x190000..0x1FCFFBand was rejected as "unsupported checksum" althoughits sum is exactly reproducible. Start alignment (which constrains the
signature position) is unchanged, and
MIN_REGION_LENGTHcompensates.The six VAG pairs still reproduce.
edc16cp31_egr_off: replayed from a verified stock/EGR-offpair (21 ranges, 60 bytes, all inside the EGR block). Anchored by
signature with a guard on the original bytes; returns nothing on any
mismatch, no hard-coded address.
(the model has no torque limiter in Nm on this family, so treat CP31
figures as an order of magnitude).
Docs
docs/PORTING-EDC16CP31.md— how the port was done, reusable for otherKf/Kl-based EDC16 variants.
docs/EDC16CP31-OM642-maps.md— reference sheet of the located maps(addresses on SW 1037393817, grids, factors, axes, pitfalls).
Validation
cargo testgreen (52 tests added: identifier, block reader, everyfamily,
build_expected_report_edc16cp31consistency).tsc --noEmitclean,tauri buildOK on Windows x64.report the same 27 elements; 2 MB dumps of other ECUs are rejected.
DETECTOR_VERSIONbumped to 43 (39–42 were used byboth this branch and 1.1.7, so every EDC15/CP31 project is re-scanned).
Known limits
is declared but has not been verified on a dump.
tuned_modeis never requested by the UI (the threedetectMapscallspass
false); worked around for keyed families, not fixed at the root.FlMng_facFullLd_CURdeliberately not shipped: its declaredCOMPU_METHOD (1/128) contradicts the values found (up to 2.0).
permanent test. Happy to add Vitest in a follow-up if you want it.
Unrelated:
cargo clippyon 1.98 fails on three pre-existingifs_same_condinedc15p/mod.rs(4048/4515, 4075/4539, 4340/5038). Nottouched here — I'll open a separate issue.