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.
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.
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).
- Host surface today:
lib/dlc_paths.py:43(_resolve_dlc_path), re-exported byserver.pyfor its existing call sites. - Difficulty Ladder — required.
routes.pydoesfrom dlc_paths import _resolve_dlc_pathat module top level. A host without the module fails the import, sosetup()never registers the generation routes at all; this is not a per-call degrade. - Lyrics Karaoke — optional.
routes.pytriesfrom dlc_paths import _resolve_dlc_path, and onImportErrorfalls back tosafepath.safe_join— the containment helper that was core's_resolve_dlc_pathin the alpha.1 shape. If neither imports, it returnsNone(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.
- Host surface today:
lib/sloppak.py:587. - Difficulty Ladder — required.
routes.pycallssloppak.read_member_bytes(pack_path, rel)while reading arrangement/manifest members; generation of a ladder from a.sloppakfails without it. - Feedpakr — required at call time, not at import. The import of the host's
sloppakmodule is guarded (sloppak_mod = NoneonImportError, and_read_memberhas a standalone zip reader behind it), so the plugin loads on a host withoutlib/sloppak.pyat all. But a host that hassloppakand predatesd876dedpasses theis not Nonecheck and then hits a missing attribute on the pack reuse/upgrade/dedup paths. The standalone fallback therefore covers "nosloppakmodule", not "an oldsloppakmodule" — do not read it as tolerance for an older host.
- Host surface today:
static/capabilities/chart-transform.js, registered in the domain table atstatic/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) readscontext["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, andhighway.getChordTemplates()returns no generated diagrams. Nothing throws.
- Host surface today:
lib/routers/ws_sync.py:196, mounted atserver.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 reachesonclose) 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.
- 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 (commit2301dd5, on Splitscreen's default branch) — and it is all-or-nothing:renderFrameAtreturnsfalseunless offline rendering is active, so a host withrenderFrameAtbut nobeginOfflineRendercan 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
(
_canDriveFramesrequiressetExternalFrameDriverandrenderFrame;renderFrameAtis checked separately). When it is absent the coordinated-frame path degrades silently: incapable panels keep their own rAF loop and nothing is logged. Thecoordinated-frameswarn-once fires only frombeginOfflineRender(), i.e. when an exporter such as Visual Export asks for an offline split frame. - What this commit does not introduce:
getSongInfo()andgetSections(), which Visual Export also calls, are long-standingstatic/highway.jsAPIs.
- Host surface today:
static/capabilities/player-identity.js:112and:117(fb.playerContexts). - Optional in every consumer. Splitscreen reads
window.feedBack && window.feedBack.playerContextsbehind a guard and warns that panels stay anonymous. Difficulty Ladder gates its per-player write onfb.capabilities.dispatchinstead, accepts only a literaltrueresult, and otherwise falls back to the context-owned highway and then to single-playerwindow.setMastery;playerContextsonly 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.
- Host surface today:
static/highway.js:74-102(tier resolution) and:2770-2783(top_difficultyingetPhrases()), withlib/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_difficultywhen present and falls back when it is not. On a pre-e5339c0host 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.
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.
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 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
minHoststill costs nothing at load time; a wrong one now warns instead of passing silently. - Do not put a plugin's version in a
minHostfield. That conflation is exactly what Visual Export removed (it once read1.14.8, a Splitscreen version — see #6).
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_difficultyFor 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)The audit behind this document confirmed #102's dependency mapping with two corrections, and one date correction:
- Lyrics Karaoke does not require
0dcc913. At5e9d586it imports the core helper opportunistically and falls back tosafepath.safe_joinonImportError. It is a preferred path, not a requirement. - Splitscreen's need for
f7c761cis 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. f7c761cis 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.
- 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, plustests/host-compat.test.jsandtools/verify-host-surface.js) and Difficulty Ladder (README.md→ host-requirements table,PLAYER_CONTEXT.md).
| 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.