Files
feedBack/static/v3/venue-scene-3d.js
T
af2949677a rename: slopsmith → feedBack, byron → got-feedBack (#537)
* Update GitHub repo references from feedback* to feedBack*

* rename: slopsmith -> feedBack, byron -> got-feedBack

Renames across the entire codebase:
- slopsmith/Slopsmith/SLOPSMITH/SlopSmith -> feedBack/FeedBack/FEEDBACK/FeedBack
- byron/Byron/Byrongamatos -> got-feedBack/got-feedBack/got-feedBack
- /home/byron/ -> /opt/got-feedBack/
- byron@ougsoft.com -> hi@got-feedBack.org
- github.com/byrongamatos/ -> github.com/got-feedback/
- com.byron. -> com.got-feedback.
- SLOPSMITH_ env vars -> FEEDBACK_ with backward-compat fallback
- Protocol/storage strings migrated with read-old/write-new pattern
- window.slopsmith JS API -> window.feedBack (canonical) + backward-compat alias

Refs: #rename-slopsmith

* rename: complete regen against current main + fix backward-compat alias

Regenerated the slopsmith->feedBack / byron->got-feedBack rename on top of
current main (3 commits had landed since the branch: #572/#554/#574),
resolving the four content conflicts in favour of main's newer content
(autoplay/auto-exit, accuracy-badge, Virtuoso re-home, feedpak badge).

Completion fixes on top of the mechanical rename:
- Re-apply rename to post-branch content the original rename never saw:
  window.slopsmith(.Tour) consumers in lessons.js / notifications.js /
  onboarding-tour.js, and the matching JS + python tests (autoplay_exit,
  progression_*, test_feedpak_extension FEEDBACK_* env vars). The test env
  vars now match server.py (which reads FEEDBACK_SYNC_STARTUP /
  FEEDBACK_SKIP_STARTUP_TASKS), so the sync-startup test exercises the real
  path again.
- Restore the window.slopsmith backward-compat alias dropped during conflict
  resolution, and move the bus aliases to AFTER the _feedBackExisting merge
  block so they reference the fully-assembled object (also fixes the
  loop_api.test.js API-surface regex, which the original PR latently broke).
- Drop the stray empty data/web_library.db (runtime DB lives in CONFIG_DIR)
  and gitignore it.
- Fix stale tone-source test: feed[dB]ack -> fee[dB]ack to match shipped
  source labels.

Verified locally (org CI billing-blocked): JS 819/819 pass; pytest 1669
passed / 1683 collected with 0 import errors; zero residual slopsmith/byron
except the two intentional window.slopsmith aliases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* rename: implement advertised backward-compat + prune dead community plugins

Address gaps where PR #537's "Backward compatibility" section was advertised
but not implemented, and clean up the community plugin list.

Env vars (FEEDBACK_* canonical, legacy SLOPSMITH_* honoured):
- New lib/env_compat.py (getenv_compat / env_flag_compat) + tests. server.py
  (_env_flag + all FEEDBACK_* reads), diagnostics_hardware, gp2midi and
  tailwind_rebuild now resolve the legacy alias, so existing SLOPSMITH_UI /
  SLOPSMITH_PLUGINS_DIR / etc. deployments keep working.
- Fix the rename collapsing plugins/__init__.py and minigames/routes.py from
  `FEEDBACK_PLUGINS_DIR or SLOPSMITH_PLUGINS_DIR` into a redundant
  `FEEDBACK_ or FEEDBACK_` (the fallback was silently lost).

Storage (app.js update-channel):
- Read feedBack-update-channel, fall back to legacy slopsmith-update-channel,
  and clear the legacy key on write — so a user's update-channel preference
  survives the rename instead of resetting to "stable".

Community plugin list (README): the rename rewrote third-party repo URLs we
don't own. Probed every one; their owners never renamed, so:
- Restore the 13 live community plugins to their real slopsmith-* names.
- Prune 6 that are 404 to the public (topkoa splitscreen/stems, OmikronApex
  tuner, Jafz2001 nam-rig-builder, DeathlySin song-preview, Erikcb91 shuffle).
- Fix a pre-existing Guitar Theory clone-command typo (nam-tone -> guitar-theory).

Verified: env_compat 7/7, JS 819/819, pytest 1690 collected / 0 import errors,
rename-sensitive + startup suites green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: byrongamatos <xasiklas@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 11:03:01 +02:00

252 lines
8.0 KiB
JavaScript

/*
* fee[dB]ack v0.3.0 — Venue 3D scene bridge.
*
* Activates the highway_3d `venue` background style when Visualization = Venue.
* Reacts to v3:live-performance-state for lighting mood (read-only).
*/
(function (root) {
'use strict';
const THEME_ID = 'small-club';
const ASSET_BASE = '/static/assets/venue/themes/small-club/';
const BG_PLATE = 'bg-plate.png';
const BG_PLATE_WEBP = 'bg-plate.webp';
let _active = false;
let _assetsLoaded = false;
let _loadFailed = false;
let _lastMood = 'idle';
let _bound = false;
function isVenueViz() {
if (root && root.v3VenueViz && typeof root.v3VenueViz.isVenueVisualization === 'function') {
const sel = root.v3VenueViz.getSelectedVizId
? root.v3VenueViz.getSelectedVizId()
: root.v3VenueViz.readVizSelection();
return root.v3VenueViz.isVenueVisualization(sel);
}
try {
const sel = document.getElementById('viz-picker');
if (sel && sel.value) return String(sel.value) === 'venue';
return localStorage.getItem('vizSelection') === 'venue';
} catch (_) {
return false;
}
}
function h3dApi(name) {
return root && typeof root[name] === 'function' ? root[name] : null;
}
function setH3dActive(on) {
const fn = h3dApi('h3dVenueSceneSetActive');
if (fn) fn(!!on);
}
function setH3dMood(state) {
const fn = h3dApi('h3dVenueSceneSetMood');
if (fn) fn(state);
}
function readH3dState() {
const fn = h3dApi('h3dVenueSceneGetState');
return fn ? fn() : null;
}
function syncPlaceholderVisibility() {
try {
if (root && root.v3VenueViz && typeof root.v3VenueViz.syncPlayerVizClass === 'function') {
const id = root.v3VenueViz.getSelectedVizId
? root.v3VenueViz.getSelectedVizId()
: root.v3VenueViz.readVizSelection();
root.v3VenueViz.syncPlayerVizClass(id);
}
} catch (_) { /* visual-only */ }
try {
if (root && root.v3VenueMoodFx && typeof root.v3VenueMoodFx.onVenueVisualizationSelected === 'function' &&
isVenueViz()) {
root.v3VenueMoodFx.onVenueVisualizationSelected();
}
} catch (_) { /* visual-only */ }
}
function readArrangementSignal() {
// Intentional karaoke/vocals signal: active arrangement name from the
// highway WS (user selected Vocals in #arr-select). Do NOT use
// highway.getLyricsVisible() — lyrics overlay stays on during normal
// guitar practice and must not force vocals POV.
try {
const si = root.highway && typeof root.highway.getSongInfo === 'function'
? root.highway.getSongInfo()
: null;
if (si && si.arrangement) return si.arrangement;
const cs = root.feedBack && root.feedBack.currentSong;
if (cs && cs.arrangement) return cs.arrangement;
if (cs && cs.arrangementSmartName) return cs.arrangementSmartName;
} catch (_) { /* visual-only */ }
return '';
}
function syncInstrumentPov() {
const fn = h3dApi('h3dVenueSceneSetInstrumentPov');
if (fn) fn(readArrangementSignal());
}
function activate() {
if (_active) {
syncInstrumentPov();
syncVenueMotion();
return;
}
_active = true;
_assetsLoaded = false;
_loadFailed = false;
setH3dActive(true);
setH3dMood(_lastMood);
syncInstrumentPov();
syncVenueMotion();
}
function syncVenueMotion() {
const motionApi = root && root.v3VenueMoodFx;
const mode = motionApi && typeof motionApi.getMotion === 'function'
? motionApi.getMotion()
: 'subtle';
const fn = h3dApi('h3dVenueSceneSetMotionMode');
if (fn) fn(mode);
else if (motionApi && typeof motionApi.syncMotionToRenderer === 'function') {
motionApi.syncMotionToRenderer(mode);
}
}
function deactivate() {
if (!_active) {
setH3dActive(false);
return;
}
_active = false;
_assetsLoaded = false;
_loadFailed = false;
setH3dActive(false);
syncPlaceholderVisibility();
}
function syncViz(vizId) {
const id = String(vizId || '');
if (id === 'venue') {
activate();
} else {
deactivate();
}
}
function onPerformanceState(e) {
if (!_active) return;
const d = (e && e.detail) || {};
const state = String(d.state || 'idle').toLowerCase();
// v3:live-performance-state fires per note hit/miss; skip the renderer
// push when the mood is unchanged (e.g. a run of hits all in 'fire').
if (state === _lastMood) return;
_lastMood = state;
setH3dMood(state);
}
function onAssetsLoaded() {
_assetsLoaded = true;
_loadFailed = false;
syncPlaceholderVisibility();
}
function onAssetsFailed() {
_loadFailed = true;
_assetsLoaded = false;
syncPlaceholderVisibility();
}
function bindRuntime() {
if (_bound) return;
_bound = true;
const sm = root && root.feedBack;
if (sm && typeof sm.on === 'function') {
sm.on('v3:live-performance-state', onPerformanceState);
sm.on('song:loaded', () => {
if (_active) syncInstrumentPov();
});
sm.on('arrangement:changed', () => {
if (_active) syncInstrumentPov();
});
sm.on('song:arrangement-changed', () => {
if (_active) syncInstrumentPov();
});
sm.on('viz:renderer:ready', () => {
if (isVenueViz()) activate();
else deactivate();
});
sm.on('viz:reverted', () => deactivate());
}
if (isVenueViz()) activate();
}
function getState() {
const h3d = readH3dState();
const povApi = root && root.v3VenueInstrumentPov;
const arrangement = readArrangementSignal();
const instrumentPov = povApi && typeof povApi.resolveVenueInstrumentPov === 'function'
? povApi.resolveVenueInstrumentPov(arrangement)
: 'guitar';
return {
active: _active,
themeId: THEME_ID,
assetBase: ASSET_BASE,
arrangement,
instrumentPov,
assetsLoaded: _assetsLoaded || !!(h3d && h3d.assetsLoaded),
loadFailed: _loadFailed || !!(h3d && h3d.loadFailed),
mood: _lastMood,
h3dVenueState: h3d,
isVenueViz: isVenueViz(),
};
}
function shouldShowDomPlaceholder() {
// V2: no on-screen construction badge during Venue playback.
return false;
}
const api = {
THEME_ID,
ASSET_BASE,
BG_PLATE,
BG_PLATE_WEBP,
activate,
deactivate,
syncViz,
onAssetsLoaded,
onAssetsFailed,
onPerformanceState,
bindRuntime,
getState,
syncInstrumentPov,
syncVenueMotion,
readArrangementSignal,
shouldShowDomPlaceholder,
isSceneLoaded: () => {
if (_assetsLoaded) return true;
const h3d = readH3dState();
return !!(h3d && h3d.assetsLoaded);
},
};
if (root) root.v3VenueScene3d = api;
if (typeof module !== 'undefined' && module.exports) module.exports = api;
if (typeof document !== 'undefined') {
const boot = () => bindRuntime();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
}
}(typeof window !== 'undefined' ? window : (typeof globalThis !== 'undefined' ? globalThis : null)));