Skip to content

Latest commit

 

History

History
353 lines (300 loc) · 22.6 KB

compatibility.md

File metadata and controls

353 lines (300 loc) · 22.6 KB

Compatibility matrix — host APIs vs. plugin features

Which host (feedBack) build a plugin feature needs, expressed as exact commits instead of version strings. This is sub-issue 1/5 of #102; it is documentation only. Nothing here changes what the loader enforces, adds a capability check, or blocks a plugin from loading.

Nothing in this document is runtime-certified. Every row is a source audit: the API was located in the host's history at the commit named, and the dependent plugin's source was read to see how it uses it. No row was verified by running a plugin against a historical host build. Treat each row as "the code path you need arrived in this commit", not as "this build was tested end to end". Where a plugin ships executable host-compat fixtures (Piano) those tests are noted, because they are stronger evidence than this document — but they still run against fake hosts, not real builds.

Why not version strings

VERSION has been reused across incompatible builds. Its whole history in this repository:

Commit Date (UTC) VERSION reads
6c11039 2026-06-16 0.2.9 (file created)
8f800e0 2026-06-20 0.3.0
803bd0c 2026-07-03 0.3.0-alpha.1
ec1157a 2026-08-10 0.3.0-alpha.2 (still current)

0.3.0-alpha.1 therefore covers the range 803bd0c..ec1157a^ (278 commits) and 0.3.0-alpha.2 covers ec1157a..d1980ff (83 commits as of this audit), so one version string spans three of the seven APIs in this document. 0.3.0-alpha.2 is a truthful answer for the four upstream rows below (they all predate ec1157a) and an uninformative one for the three fork rows (they all postdate it). Each matrix row now also names the distinct release that first contains its commit (see "Releases" below), so a version comparison against those tags answers the question without resolving a hash.

This fork publishes per-commit prereleases for rows 1–5 and 7, plus the upstream b6169af baseline and the 605dbdf drum-parts loader — eight releases in total, with row 6 covered only via the e5339c0 baseline — under get-flashbacks/feedBack → Releases. A bare feedBack checkout without one of those tags is still identified by git rev-parse HEAD. The upstream tag v0.3.0-alpha.1 = b6169af6 (2026-07-03) is 14 commits past 803bd0c and contains none of the seven APIs below; the fork's own v0.3.0-alpha.1-b6169af release points at the same commit.

The matrix

Host revision audited: d1980ff (fork main, 2026-10-02). Plugin revisions audited are listed per row. "Upstream?" is the answer to "does this commit exist on got-feedback/feedBack:main?", not merely "does GitHub know the SHA".

# Commit Date (UTC) Provides Dependent plugin(s) / feature(s) Required? Upstream? Plugin revision audited
1 0dcc913 2026-07-10 lib/dlc_paths.py — _resolve_dlc_path containment helper Difficulty Ladder ladder generation (routes.py); Lyrics Karaoke backend (preferred path, has a fallback) Required for Difficulty Ladder's routes.py; optional for Lyrics Karaoke Upstream (main) difficulty_ladder 9ac046a; lyrics-karaoke 5e9d586
2 d876ded 2026-07-13 sloppak.read_member_bytes() — read one member without unpacking the pack Difficulty Ladder generation; Feedpakr pack reuse / upgrade / dedup Required for both (Feedpakr: at call time, not at import) Upstream (main) difficulty_ladder 9ac046a; feedpakr 29b0396
3 05be9eb 2026-07-19 chart-transform capability domain (static/capabilities/chart-transform.js) Chordr automatic chart/diagram enrichment Optional — Chordr's analysis tier works without it; only the enrichment tier needs it Upstream (main) chordr bcf49fa
4 03e1c1d 2026-07-22 /ws/sync/{session_id} relay WebSocket (lib/routers/ws_sync.py) Splitscreen LAN sharing (cross-device followers) Optional — local split layouts work without it; LAN sharing is the only dependent feature Upstream (main) splitscreen 1600e8c
5 f7c761c 2026-09-17 Highway.renderFrame(), renderFrameAt(time), setExternalFrameDriver(bool) + shared frame timestamp/ID in the render bundle Visual Export (deterministic offline frames); Splitscreen's coordinated/bridge frame path Required for Visual Export; optional for Splitscreen (feature-detected, degrades silently) Fork-only — not on upstream main visual_export a7bad12; splitscreen 1600e8c
6 7633211 2026-09-17 window.feedBack.playerContexts + the player-identity / player-difficulty.v1 capability owners Full per-player integration: Splitscreen panel identity, Difficulty Ladder per-player difficulty, Lyrics Karaoke per-player wiring Optional everywhere — every consumer guards and degrades to a single-player / player-unaware path Fork-only — not on upstream main splitscreen 1600e8c; difficulty_ladder 9ac046a
7 e5339c0 2026-09-23 Mastery selection by phrase tier number + getPhrases().top_difficulty Difficulty Ladder's presentation agreeing with the tiers the host actually renders Optional but load-bearing for correctness — the plugin falls back when top_difficulty is absent, and the fallback does not prove agreement Fork-only — not on upstream main difficulty_ladder 9ac046a

Two plugins have no row because their required APIs predate every commit above and are present in v0.3.0-alpha.1 (b6169af6): Piano (records 0.3.0-alpha.1 as its floor in its README.md — not in plugin.json, see the minHost section below — and pins that floor as executable fixtures in its own tests/host-compat.test.js) and basic Split Screen (local layouts; only its LAN-sharing and coordinated-render paths need rows 4 and 5).

Row detail and evidence

Row 1 — 0dcc913: lib/dlc_paths.py

  • Host surface today: lib/dlc_paths.py:43 (_resolve_dlc_path), re-exported by server.py for its existing call sites.
  • Difficulty Ladder — required. routes.py does from dlc_paths import _resolve_dlc_path at module top level. A host without the module fails the import, so setup() never registers the generation routes at all; this is not a per-call degrade.
  • Lyrics Karaoke — optional. routes.py tries from dlc_paths import _resolve_dlc_path, and on ImportError falls back to safepath.safe_join — the containment helper that was core's _resolve_dlc_path in the alpha.1 shape. If neither imports, it returns None (a 404 the caller already handles) rather than joining unguarded. Note the two helpers are not otherwise ordered by strictness: the fallback is stricter on symlinked song entries, and looser on drive-absolute and NUL-bearing names. Containment never weakens either way.

Row 2 — d876ded: sloppak.read_member_bytes()

  • Host surface today: lib/sloppak.py:587.
  • Difficulty Ladder — required. routes.py calls sloppak.read_member_bytes(pack_path, rel) while reading arrangement/manifest members; generation of a ladder from a .sloppak fails without it.
  • Feedpakr — required at call time, not at import. The import of the host's sloppak module is guarded (sloppak_mod = None on ImportError, and _read_member has a standalone zip reader behind it), so the plugin loads on a host without lib/sloppak.py at all. But a host that has sloppak and predates d876ded passes the is not None check and then hits a missing attribute on the pack reuse/upgrade/dedup paths. The standalone fallback therefore covers "no sloppak module", not "an old sloppak module" — do not read it as tolerance for an older host.

Row 3 — 05be9eb: chart-transform capability

  • Host surface today: static/capabilities/chart-transform.js, registered in the domain table at static/capabilities.js:131.
  • Chordr — optional. The plugin registers a transform provider so highway.getChordTemplates() picks up generated diagrams with no per-plugin integration. Everything else Chordr does (helpers, chord/lyrics view, audio-based detection, the server-side analysis callable) reads context["load_sibling"], the event bus and long-standing highway getters, which predate this commit.
  • Failure mode without it: window.chordr.getChartTransformStatus() reports a no-owner state, and highway.getChordTemplates() returns no generated diagrams. Nothing throws.

Row 4 — 03e1c1d: /ws/sync/{session_id} relay

  • Host surface today: lib/routers/ws_sync.py:196, mounted at server.py:1664.
  • Splitscreen — optional. LAN sharing is the only dependent feature; the plugin builds the URL at screen.js:519. A WebSocket endpoint cannot be feature-detected from a page, so the plugin detects the gap by counting connect failures (including a constructor that throws outright, which never reaches onclose) and, past a threshold, warns once and toasts — naming the missing relay and the commit that introduced it. Everything else keeps working; viewers can simply never join a share.
  • The relay is deliberately stateless (no schema, no history, no persistence): rooms appear on first join and are collected when the last socket leaves.

Row 5 — f7c761c: coordinated / deterministic frame rendering

  • Host surface today: static/highway.js:3037 (setExternalFrameDriver), :3057 (renderFrame), :3071 (renderFrameAt).
  • Visual Export — required. The whole export is driven by explicit song timestamps instead of the playback clock. The plugin detects the gap in frameDriverProblem() and reports the core requirement and the Splitscreen requirement separately rather than failing opaquely.
  • Visual Export also needs Splitscreen's beginOfflineRender() / renderFrameAt() / endOfflineRender() bridge for split-layout export. That bridge is a Splitscreen revision, not a core one — cite Splitscreen 1.14.8 (commit 2301dd5, on Splitscreen's default branch) — and it is all-or-nothing: renderFrameAt returns false unless offline rendering is active, so a host with renderFrameAt but no beginOfflineRender can never paint a frame. (87e3622a, which Visual Export's own error text also cites, is a side-branch commit that diverged from Splitscreen's default branch and is not reachable from it; do not use it as a floor.)
  • Splitscreen — optional. Panel highways feature-detect the frame API (_canDriveFrames requires setExternalFrameDriver and renderFrame; renderFrameAt is checked separately). When it is absent the coordinated-frame path degrades silently: incapable panels keep their own rAF loop and nothing is logged. The coordinated-frames warn-once fires only from beginOfflineRender(), i.e. when an exporter such as Visual Export asks for an offline split frame.
  • What this commit does not introduce: getSongInfo() and getSections(), which Visual Export also calls, are long-standing static/highway.js APIs.

Row 6 — 7633211: playerContexts and player-difficulty.v1

  • Host surface today: static/capabilities/player-identity.js:112 and :117 (fb.playerContexts).
  • Optional in every consumer. Splitscreen reads window.feedBack && window.feedBack.playerContexts behind a guard and warns that panels stay anonymous. Difficulty Ladder gates its per-player write on fb.capabilities.dispatch instead, accepts only a literal true result, and otherwise falls back to the context-owned highway and then to single-player window.setMastery; playerContexts only gates its main-context resolution. Declaring a capability the host does not own is not a load error — the loader validates a declaration's shape, not whether the host implements the domain.
  • What is lost without it: per-player difficulty routing reaches only the main player, and panels cannot be addressed individually by plugins.

Row 7 — e5339c0: phrase-tier mastery mapping and top_difficulty

  • Host surface today: static/highway.js:74-102 (tier resolution) and :2770-2783 (top_difficulty in getPhrases()), with lib/song.py:715 (collapse_arrangement_phrases) keeping collapsed ladders on their original tier numbers.
  • Difficulty Ladder — optional, but a fallback here is not evidence. The plugin reads phrase.top_difficulty when present and falls back when it is not. On a pre-e5339c0 host the fallback computes from the phrase's own levels, while the host is selecting the rendered level by positional index across a possibly collapsed ladder — so the plugin's HUD and attempt records can describe a different tier than the highway is showing. Treat the commit as required for the feature to be correct, and as optional only in the sense that nothing throws without it.
  • Fully authored ladders (levels 0..n-1) map identically before and after, so the disagreement only appears where duplicate tiers were collapsed or a generated phrase completes early.

Candidate baseline

e5339c0 (2026-09-23) is the earliest commit in this repository that is a descendant of all seven rows — verified with git merge-base --is-ancestor <row> e5339c0 for each. It is therefore the cheapest single identity to name as "has everything in this table", which is why #102 proposes it as a candidate baseline.

It is published as release v0.3.0-alpha.2-e5339c0 (2026-10-12), so a user can identify a compatible build from its release or commit. It remains not certified: nothing here has been exercised against a historical build (sub-issue 4, #134), it carries no minHost signal of its own (sub-issue 3, #133), and an incompatible feature must still explain its missing requirement at the call site (the required-vs-optional convention, docs/plugin-host-compat.md). If a plugin needs to name a host to its users today, name the row it needs (f7c761c for Visual Export, 0dcc913 for Difficulty Ladder's backend) — a row is a floor; the baseline release is the cheapest single build that clears every floor.

Releases

Eight of the nine commits named in this document are published as distinct prerelease tags (VERSION + short SHA, chronological order) — eight releases total, covering every named commit except row 6 (7633211, playerContexts), which was not in the release list and so has no dedicated tag; its first containing release is the e5339c0 baseline (it sits inside the 16-commit f7c761c..e5339c0 window). All eight are prereleases under get-flashbacks/feedBack → Releases (2026-10-12); each tag triggers a release.yml Docker build that strips the leading v, so tag v0.3.0-alpha.2-e5339c0 publishes image ghcr.io/get-flashbacks/feedback:0.3.0-alpha.2-e5339c0 (drop the v when pulling).

Release Commit Contains
v0.3.0-alpha.1-b6169af b6169af6 (2026-07-03) Upstream v0.3.0-alpha.1 baseline — floor for Piano and basic Split Screen (no row needed)
v0.3.0-alpha.1-0dcc913 0dcc9136 (2026-07-10) Row 1 (dlc_paths)
v0.3.0-alpha.1-d876ded d876ded0 (2026-07-13) Rows 1–2 (read_member_bytes)
v0.3.0-alpha.1-05be9eb 05be9ebd (2026-07-19) Rows 1–3 (chart-transform)
v0.3.0-alpha.1-605dbdf 605dbdfd (2026-07-21) Multi-drum-parts loader (no matrix row — feedpak 1.17.0 feature, same VERSION line)
v0.3.0-alpha.1-03e1c1d 03e1c1d5 (2026-07-22) Rows 1–4 (/ws/sync relay)
v0.3.0-alpha.2-f7c761c f7c761cf (2026-09-17) Row 5 (coordinated frames). Does NOT include row 6 (playerContexts)
v0.3.0-alpha.2-e5339c0 e5339c04 (2026-09-23) Baseline: all seven rows (rolls up the 16-commit f7c761c..e5339c0 window incl. row 6 + row 7)

minHost is a diagnostic, not enforcement

minHost is read at load time and evaluated against the running host, but it never blocks, skips, or degrades a plugin — every plugin loads regardless (feedBack#133). Each /api/plugins row carries the raw floor (min_host) plus min_host_status, one of:

Status Meaning
unset No minHost in the manifest — behaves exactly as before.
placeholder The 1.0.0 values existing manifests carry, which match no real dependency — labelled, never enforced.
invalid Not a bare version (ranges like >=1.2, typos) — ignored with a one-time startup warning so it can't silently become a floor.
ok A real floor at or below the running host.
unmet A real floor above the running host.

Comparison is prerelease-aware (lib/host_versions.py; mirrored in static/js/host-version.js): numeric prerelease identifiers compare numerically, so 0.3.0-alpha.2 < 0.3.0-alpha.10 < 0.3.0 — plain string comparison gets the middle pair backwards. An unreadable host version fails open to ok.

An unmet floor produces two diagnostics naming the gap (what plugin, what it needs, what the host is): a one-time log.warning at startup and a one-time console.warn when the plugin list loads (which lands in the console ring buffer, so it ships in the diagnostics bundle). system/plugins.json in the bundle records min_host + min_host_status per plugin. Prefer feature detection over a declared floor (docs/plugin-host-compat.md) — minHost names the gap for humans; it is not a substitute for probing the API at the call site.

Practical consequences:

  • A wrong or absent minHost still costs nothing at load time; a wrong one now warns instead of passing silently.
  • Do not put a plugin's version in a minHost field. That conflation is exactly what Visual Export removed (it once read 1.14.8, a Splitscreen version — see #6).

How to check a build against a row

Given a host checkout and a row's commit:

# Does this build contain the API? Exit 0 = yes.
git merge-base --is-ancestor <commit> HEAD && echo present || echo absent

# Quickest confirmation for a running install, per row:
#   1  PYTHONPATH=lib python -c "from dlc_paths import _resolve_dlc_path"
#   2  PYTHONPATH=lib python -c "from sloppak import read_member_bytes"
#      (run from the repo root; without PYTHONPATH=lib, lib/ is not on sys.path
#      outside pytest and a capable build reads as absent)
#   3  browser console: window.feedBack.chartTransformDomain?.version === 1
#   4  the /ws/sync/{id} socket accepts a connection
#   5  browser console: typeof window.highway.renderFrameAt
#   6  browser console: typeof window.feedBack.playerContexts
#   7  browser console: window.highway.getPhrases()?.[0]?.top_difficulty

For the upstream/fork column, from a clone with an upstream remote:

gh api repos/got-feedback/feedBack/compare/<commit>...main --jq .status
# "ahead"   -> the commit is on upstream main (upstream API)
# "behind"  -> upstream knows the SHA but main does not contain it (fork-only)

Corrections to #102's list

The audit behind this document confirmed #102's dependency mapping with two corrections, and one date correction:

  1. Lyrics Karaoke does not require 0dcc913. At 5e9d586 it imports the core helper opportunistically and falls back to safepath.safe_join on ImportError. It is a preferred path, not a requirement.
  2. Splitscreen's need for f7c761c is optional, not required, and the dependent feature is deterministic/coordinated frame rendering — not LAN sharing, which depends on row 4. Splitscreen feature-detects all three methods and degrades to its per-panel self-scheduled loop.
  3. f7c761c is dated 2026-09-16 in #102; its committer date in UTC is 2026-09-17 (2026-09-17T02:36:07Z). Dates in this document are UTC committer dates.

Related reports

  • Visual Export's own history of this problem: #6 — Declare the correct minimum host version and #7 — No working minHost/splitscreen version to record. #7's first two rounds concluded (correctly, at the time) that no Splitscreen version implemented the offline-render bridge; the bridge first shipped in Splitscreen 1.14.8.
  • Plugins that already document their own host floors the same way, and whose tables this document does not duplicate: Piano (README.md → Host: feedBack core v0.3.0-alpha.1 or newer, plus tests/host-compat.test.js and tools/verify-host-surface.js) and Difficulty Ladder (README.md → host-requirements table, PLAYER_CONTEXT.md).

Audit metadata

Host audited get-flashbacks/feedBack d1980ff (2026-10-02)
Upstream reference got-feedback/feedBack main = eef58c8 (read 2026-10-02)
Upstream tag reference v0.3.0-alpha.1 = b6169af6 (2026-07-03)
Plugins audited difficulty_ladder 9ac046a, lyrics-karaoke 5e9d586, feedpakr 29b0396, chordr bcf49fa, splitscreen 1600e8c, visual_export a7bad12, piano fef98a2 (all default-branch HEADs, 2026-10-01/02)
Method git log/git show on each commit, git merge-base --is-ancestor against e5339c0; gh api repos/got-feedback/feedBack/compare/<sha>...main for the upstream/fork column; read of each plugin's routes.py / screen.js / plugin.json on its default branch
Releases published 8 prereleases (2026-10-12, #135): rows 1–5 + row 7 (baseline v0.3.0-alpha.2-e5339c0) + drum-parts v0.3.0-alpha.1-605dbdf + upstream baseline v0.3.0-alpha.1-b6169af (row 6 has no dedicated tag — first contained in the baseline) — see "Releases" above
Not done Running any plugin against any historical host build; any prerelease-aware version comparison; any enforcement change

Re-audit when any listed commit is superseded, when a plugin's default branch moves past the audited revision, or when the baseline is certified (#133/#134) — at which point this document should collapse into a version-to-feature table and the minHost discussion should move to sub-issue 3.