Skip to content

gen3: maintain the channel slice in a stride - #77

Closed
ptn wants to merge 1 commit into
sKuhLight:mainfrom
ptn:main
Closed

gen3: maintain the channel slice in a stride#77
ptn wants to merge 1 commit into
sKuhLight:mainfrom
ptn:main

Conversation

@ptn

@ptn ptn commented Aug 24, 2026

Copy link
Copy Markdown

Axis was not detecting the amp type correctly on most presets on FM3, firmware 13.

The FM3's fn-0x1F body carries all four channels back to back — channel k's paramId p lives at k*stride + p — and #channelSlice takes that stride from the profile's rangeSections. A walk-built runtime profile reports a SHORT one there: forgefx-midi's buildDeviceCache sets stride: sec.size, the count of records the walk actually collected, not the section's width. The FM3 walk brings back 126 of DISTORT's 144 (and 62 of CABINET's 106), and runtimeProfileFrom merged the cache's section meta OVER the catalog's hardware-validated one, so 126 won.

Channel A (base 0) is unaffected, which is why this only surfaced on some presets. For channels B-D, base = channel*126 lands inside an EARLIER channel's slice, so every param read there — the amp model included — is a real, valid-looking value from the wrong param:

• preset 068 "USA MKIIC++ lead", amp on channel B: 1261+6 = channel A's
paramId 132 (VPLATEMON, 0) → "59 Bassguy Bright" instead of roster 248.
• preset 043 "Friedman BE metal" scene 4, amp on channel C: 126
2+6 = channel
B's paramId 114 (TRIODE1RATIO, raw 23330) → the type decode's raw > max
rescale launders it into 117, "CA3+ Clean", instead of roster 259. That
rescale is also why the wrong name SHIFTED (115 → 117) when the roster length
changed — the clue that misdirected the earlier channel-pick investigation.
The channel pick was right all along.

Two independent guards, either one sufficient for the cases above:

• runtimeProfileFrom takes the WIDER of catalog/cache stride. The cache value
is a lower bound, never a measurement; the catalog's is validated. Cache-only
sections are still adopted whole, and a genuinely wider cache stride still
wins if a firmware ever grows one.
• #channelSlice cross-checks the table stride against the wire: the body is
whole channel blocks (itemCount = channels * stride on every shipped
catalog), so a stride that does not divide the advertised itemCount cannot be
the real one → fall back to the width the body implies.

drivers/channel-slice.test.ts replays both presets field-exact over a mocked transport; without the fix it fails with literally "59 Bassguy Bright".

No cache rebuild needed — the stale short stride is now ignored at read time.

Release

Merging to main releases automatically — a single release:* label controls it:

  • no label → patch (a PR touching only docs/, .github/, or *.md → no release)
  • release:minor / release:major → bigger bump
  • release:none → adopt without releasing (rides the next release)
  • release:hold → merge now, release later
  • release:hold / release:none win over a co-present bump label
  • at most one bump label (the pr-labels check enforces this)

Versions come from tags — never bump server/package.json in a PR. This repo releases with the -beta suffix (X.Y.Z-beta).

Details: docs/RELEASING.md

Axis was not detecting the amp type correctly on most presets on FM3,
firmware 13.

The FM3's fn-0x1F body carries all four channels back to back — channel k's
paramId p lives at k*stride + p — and #channelSlice takes that stride from the
profile's rangeSections. A walk-built runtime profile reports a SHORT one there:
forgefx-midi's buildDeviceCache sets `stride: sec.size`, the count of records the
walk actually collected, not the section's width. The FM3 walk brings back 126 of
DISTORT's 144 (and 62 of CABINET's 106), and runtimeProfileFrom merged the
cache's section meta OVER the catalog's hardware-validated one, so 126 won.

Channel A (base 0) is unaffected, which is why this only surfaced on some
presets. For channels B-D, base = channel*126 lands inside an EARLIER channel's
slice, so every param read there — the amp model included — is a real,
valid-looking value from the wrong param:

  • preset 068 "USA MKIIC++ lead", amp on channel B: 126*1+6 = channel A's
    paramId 132 (VPLATEMON, 0) → "59 Bassguy Bright" instead of roster 248.
  • preset 043 "Friedman BE metal" scene 4, amp on channel C: 126*2+6 = channel
    B's paramId 114 (TRIODE1RATIO, raw 23330) → the type decode's `raw > max`
    rescale launders it into 117, "CA3+ Clean", instead of roster 259. That
    rescale is also why the wrong name SHIFTED (115 → 117) when the roster length
    changed — the clue that misdirected the earlier channel-pick investigation.
    The channel pick was right all along.

Two independent guards, either one sufficient for the cases above:

  • runtimeProfileFrom takes the WIDER of catalog/cache stride. The cache value
    is a lower bound, never a measurement; the catalog's is validated. Cache-only
    sections are still adopted whole, and a genuinely wider cache stride still
    wins if a firmware ever grows one.
  • #channelSlice cross-checks the table stride against the wire: the body is
    whole channel blocks (itemCount = channels * stride on every shipped
    catalog), so a stride that does not divide the advertised itemCount cannot be
    the real one → fall back to the width the body implies.

drivers/channel-slice.test.ts replays both presets field-exact over a mocked
transport; without the fix it fails with literally "59 Bassguy Bright".

No cache rebuild needed — the stale short stride is now ignored at read time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ptn

ptn commented Aug 29, 2026

Copy link
Copy Markdown
Author

Superseded by #78. This PR was opened from my fork's main branch and accidentally accumulated unrelated commits (color-labels, amp Ideal page, block-library, etc.) as I kept committing to main. The channel-slice fix now lives on a dedicated branch and is re-opened cleanly as #78 with a single commit. Closing this one.

@ptn

ptn commented Aug 29, 2026

Copy link
Copy Markdown
Author

Superseded by #83 — same single commit, now from a dedicated branch () instead of , per the branch/PR workflow.

@ptn ptn closed this Aug 29, 2026
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.

1 participant