diff --git a/plugins/highway_3d/screen.js b/plugins/highway_3d/screen.js index 01cfee4..61daf21 100644 --- a/plugins/highway_3d/screen.js +++ b/plugins/highway_3d/screen.js @@ -9,7 +9,7 @@ import { geoFretX, dZ, slideTrailEnd, camBaseDistU, camLowFretPullbackU, computeBPM, _makeGaussTex, RENDER_ORDER_LAYER_STACK, RENDER_ORDER_LAYER_INDEX, RENDER_ORDER_AT_Z_ZERO, RENDER_ORDER_FAR_CLAMP, renderOrderForLayerAtZ, _noteKey, lowerBoundT, hwyFirstRelevantFrettedTime, geoFretMid } from './src/geometry.js'; // h3d-carve-1b import { loadThree, T } from './src/three-loader.js'; // h3d-carve-2 import { _h3dHexToInt, _clampByteI, _darkenInt, _lightenInt, resolveStringCount as _resolveStringCountBase, _NOTE_NAMES_SHARP, _BASE_OPEN_MIDI_BASS4, _BASE_OPEN_MIDI_BASS5, _BASE_OPEN_MIDI_GUITAR6, _BASE_OPEN_MIDI_GUITAR7, _BASE_OPEN_MIDI_GUITAR8, _baseOpenStringMidis, _midiToPitchLabel, _openStringPitchLabelsForTuning as _openStringPitchLabelsForTuningBase, _ssActive, _ssIsCanvasFocused } from './src/utils.js'; // h3d-carve-3 -import { _bcIsDesktop, _bcCreateController } from './src/bc-panel.js'; // h3d-carve-4 +import { _bcIsDesktop, _bcCreateController, _bcLoadSettings, _bcFfIdx } from './src/bc-panel.js'; // h3d-carve-4 (function () { 'use strict'; @@ -25,7 +25,7 @@ import { _bcIsDesktop, _bcCreateController } from './src/bc-panel.js'; // h3d-ca // THREE_URL / THREE_CDN — h3d-carve-4: dead code (three-loader.js owns its own URLs). Tombstoned. // ── B-section: Butterchurn control panel — h3d-carve-4 ────────────── - // Moved to src/bc-panel.js. Imports: _bcIsDesktop, _bcCreateController. + // Moved to src/bc-panel.js. Imports: _bcIsDesktop, _bcCreateController, _bcLoadSettings, _bcFfIdx. // window.h3dBcApplySettings assigned at bc-panel.js module scope (R5). // ──────────────────────────────────────────────────────────────────── // TOMBSTONE: BC_VENDOR, BC_FRAME, BC_WORKLET, _bcMeters, BC_BTN — h3d-carve-4: moved to src/bc-panel.js. diff --git a/plugins/highway_3d/src/bc-panel.js b/plugins/highway_3d/src/bc-panel.js index e742508..3bdb7aa 100644 --- a/plugins/highway_3d/src/bc-panel.js +++ b/plugins/highway_3d/src/bc-panel.js @@ -47,7 +47,7 @@ export function _bcIsDesktop() { // Position at the first entry whose time is >= ct (strict <), so an event // landing exactly on the seek/loop target time is still fired by the update // walkers (which consume `<= ct`) instead of being skipped past here. -function _bcFfIdx(arr, ct, key) { if (!arr) return 0; let i = 0; while (i < arr.length && (arr[i][key] || 0) < ct) i++; return i; } +export function _bcFfIdx(arr, ct, key) { if (!arr) return 0; let i = 0; while (i < arr.length && (arr[i][key] || 0) < ct) i++; return i; } // Force-free a canvas's WebGL context so the GPU resources are released // immediately instead of lingering until GC — repeated Butterchurn // mount/unmount cycles otherwise pile up live contexts toward the browser cap. @@ -164,7 +164,7 @@ function _bcGuitarFeed(actx, onReady) { const BC_LS = 'viz3d_settings'; const BC_DEFAULTS = { enabled: true, opacity: 1.0, laneDim: true, laneDimStrength: 0.45, chartAccents: true, colorTint: true, chartStrength: 1.0, tintStrength: 0.65, guitarGain: 6, songGain: 1.8, cyclePool: 'all', hold: false }; let _bcSettings = null; -function _bcLoadSettings() { +export function _bcLoadSettings() { if (_bcSettings) return _bcSettings; let saved = {}; try { saved = JSON.parse(localStorage.getItem(BC_LS) || '{}'); } catch (e) {} diff --git a/tests/js/highway_3d_bc_panel.test.js b/tests/js/highway_3d_bc_panel.test.js index 7c2f5ce..065e79f 100644 --- a/tests/js/highway_3d_bc_panel.test.js +++ b/tests/js/highway_3d_bc_panel.test.js @@ -178,6 +178,50 @@ test('screen.js imports _bcCreateController and _bcIsDesktop from src/bc-panel.j 'screen.js must import _bcIsDesktop from ./src/bc-panel.js'); }); +// ── 12. screen.js has no bare caller references to private bc-panel.js symbols ─ + +test('every bc-panel.js export referenced in screen.js IIFE is in the import statement', () => { + // Mutation: remove _bcLoadSettings (or any other export) from the screen.js import + // → symbol is exported by bc-panel.js, referenced in the IIFE, but not bound via + // import → ReferenceError at runtime. This is the class Creed HIGH found on + // cut 4 (screen.js:15396-15402: _bcLoadSettings + _bcFfIdx called but not imported). + // + // Method (generalised): + // exported = all _bc* symbols with `export` keyword in bc-panel.js + // imported = all symbols in screen.js's `from './src/bc-panel.js'` import clause + // leaked = exported symbols that appear as bare refs in screen.js IIFE + // but are NOT in imported + // Adding a new export and a new caller without updating the import → leaked is + // non-empty → test RED. + const bcSrc = src(); + const scrSrc = screenSrc(); + + // All exported _bc* symbols from bc-panel.js. + const exported = new Set( + [...bcSrc.matchAll(/^export\s+(?:const|let|var|function)\s+(_bc\w+)/mg)].map(m => m[1]) + ); + + // Symbols actually imported from bc-panel.js in screen.js. + const importMatch = scrSrc.match(/import\s+\{([^}]+)\}\s+from\s+['"]\.\/src\/bc-panel\.js['"]/); + const imported = new Set( + importMatch ? importMatch[1].split(',').map(s => s.trim()).filter(Boolean) : [] + ); + + // IIFE body: strip import lines and line comments to avoid false positives. + const noImports = scrSrc.replace(/^import\s+.*\n/gm, ''); + const noComments = noImports.replace(/\/\/[^\n]*/g, ''); + + // Exported symbols referenced in the IIFE body but absent from the import list. + const leaked = [...exported].filter( + sym => new RegExp('\\b' + sym + '\\b').test(noComments) && !imported.has(sym) + ); + + assert.deepStrictEqual(leaked, [], + 'screen.js references bc-panel.js exports that are not in its import clause: ' + + leaked.join(', ') + + ' — add them to the import { … } from \'./src/bc-panel.js\' line in screen.js'); +}); + // ── 11. screen.js IIFE no longer defines moved B-section symbols ────────────── test('screen.js IIFE does not redeclare _bcCreateController or _bcLoadLib', () => {