Skip to content

resizeable VE table - #791

Open
mck1117 wants to merge 7 commits into
masterfrom
variable-ve-table
Open

resizeable VE table#791
mck1117 wants to merge 7 commits into
masterfrom
variable-ve-table

Conversation

@mck1117

@mck1117 mck1117 commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

You can now resize the VE table in TS to use up to 576 cells, with axis sizes of 8-32. That means 24x24, 32x18, 27x21, etc, anything that meets those constraints.

@smclt30p

smclt30p commented Aug 6, 2026

Copy link
Copy Markdown

Here is Opus 5 PR review:

Overall: design is right and the generator work is clean. Two real safety holes on the firmware side.

Blocking

  1. airmass.cpp:78 — no bounds clamp on the read path.

Allocation is veLoadBins[32], veRpmBins[32], veTable[576]. But maxRows * maxCols = 1024 > 576, so the axis maxima and the cell budget cannot both be satisfied by construction — the product must be enforced at read time. getVeImpl passes the raw config bytes straight through.

Consequences with rows=32, cols=32: interpolate3dDynamic indexes up to table[1023] — 448 cells (896 bytes) past the array. A garbage count byte (rows=200) also walks bins[199] past a 32-entry array. Result is a uint16_t of adjacent config reinterpreted as VE, up to 6553% — commanded on a running engine.

Clamp once, in interpolate3dDynamic or a shared helper:
colCount = minI(colCount, binCountCols);
rowCount = minI(rowCount, binCountRows);
if (rowCount * colCount > cellBudget) rowCount = cellBudget / colCount;

  1. engine_controller.cpp:333-335 — validation runs in the wrong order, and is incomplete.

ensureArrayIsAscendingDynamic(..., config->veTableRows) runs before the size check, so a corrupt count reads out of bounds inside the validator itself. Move the size check first.

The size check is also insufficient: it tests the product only. rows=200, cols=2 → 400 ≤ 576, passes, and veLoadBins[199] is still out of bounds. Each axis needs checking against its own bin array too.

Worth fixing before merge

  1. validateConfig only runs at boot — rusefi.cpp:260 is the sole caller. No burn-time or chunk-write validation exists in this tree. So a bad shape is undetected for the whole drive, then bricks startup next key-on (validateConfig() false → initEngineController() skipped → car won't start over one drifted config byte). Since the shape is trivially repairable, clamping to a legal shape + a non-fatal warning beats firmwareError.

  2. Live resize while the engine runs. handleWriteChunkCommand (tunerstudio.cpp:128) memcpys into live config with no staging. When TS changes the counts, the stride changes before the cells land — the running engine reads the entire table from the wrong offsets until the resize finishes. Either refuse a chunk write that touches a shape byte while rpmCalculator.isRunning(), or at minimum say "resize with the engine off" in the changelog.

  3. resizable_table.h:142,166 — copyTableDynamic and copyBinsDynamic both write the counts, and call sites disagree on order. ford_festiva.cpp:78-80 does bins→table; mazda_miata_1_6.cpp:48-50 and both other miatas do table→bins. Harmless today because every source is 16×16, but the first non-square source silently corrupts one or the other (stride ≠ shape, or axis ≠ table) with no error. Make copyBinsDynamic take the count by value and assert count == N, so the table is the only shape setter.

Related: those bin arrays are still declared [FUEL_RPM_COUNT] / [FUEL_LOAD_COUNT], defines that no longer have anything to do with the VE table. Bumping FUEL_RPM_COUNT would silently reshape those engines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants