mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-12 03:41:40 +00:00
Aspect-aware framing for ultra-wide 3D highway panes (#652)
* Add aspect-aware framing for ultra-wide highway panes On a top/bottom 2-player split each 3D highway pane is full-width / half-height (~32:9). The camera's vertical FOV was locked at a single value, so at that aspect the horizontal cone ballooned past 130deg and squeezed the fixed-width neck into a thin central sliver with large dead margins on either side. Add a "horizontal-FOV-hold" path: past a configurable start aspect the effective vertical FOV is lowered so the horizontal cone stays roughly constant, letting the neck fill a wide pane. At/under the start aspect it is an exact no-op, so normal ~16:9 single-player and most 2x2 panes are unchanged. Optional pose nudges (height / dolly / pitch / look-depth) further flatten the view toward a low, immersive angle. Everything is driven by a runtime bridge (window.__h3dAspectTune) with a live tuner panel (Shift+A in the player) exposing every knob plus a live aspect/FOV readout, localStorage persistence, and a Copy button. Toggling the feature off restores the exact prior framing, so it doubles as an A/B control. Shipped on by default for wide panes for testing feedback. Source-pinned by tests/js/highway_3d_wide_fov.test.js. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: topkoa <topkoa@gmail.com> * fix(highway_3d): ship wide-pane framing default-OFF with a coherent config Review fixes for the aspect-aware framing. The first cut shipped _ASPECT_DEFAULTS = { enabled:true, baseVfov:30, blend:0, minVfovDeg:36 }, which contradicted the PR's own "default off → byte-for-byte prior behaviour" claim: - enabled:true made the tune active for everyone, and baseVfov:30 forced every pane's vertical fov from 70° to 30° (normal single-player/2x2 panes included — a drastic global zoom, not the advertised no-op). - blend:0 collapsed the Hor+ math back to base, so the actual horizontal-FOV- hold did nothing even on wide panes — the only net effect was the zoom. - minVfovDeg:36 > baseVfov:30 was an inverted floor (clamped wide panes UP to 36° rather than flooring a real reduction). New defaults: { enabled:false, baseVfov:BASE_VFOV(70), blend:1, minVfovDeg:HORPLUS_MIN_VFOV(28) }. Now: - OFF by default → camUpdate passes a null tune → effectiveVfov returns BASE_VFOV → exact no-op on every pane (verified: 70° at 16:9 and 32:9). - When a tester enables it (Shift+A), baseVfov==BASE_VFOV keeps normal/≤start panes at 70° (still a no-op there) and blend:1 makes the hold actually engage on genuinely wide panes (47.7° at 32:9, flooring toward 28° as aspect grows). - minVfovDeg < baseVfov is a real floor. Also bumps the localStorage key (h3d_aspect_tune → h3d_aspect_tune2) so a machine that persisted the old broken default gets the corrected one, and adds source-pin tests guarding default-off + the coherent base/blend/floor so this can't silently regress to default-on again. The pose-nudge values are left as the author's in-progress wide-pane look (dormant until enabled). 110/110 tests pass; node --check clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Signed-off-by: topkoa <topkoa@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: byrongamatos <xasiklas@gmail.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
byrongamatos
parent
db81d7dafb
commit
3e3a98a0d0
@@ -0,0 +1,174 @@
|
||||
// Pins the wide-pane horizontal-FOV-hold ("Hor+") framing in
|
||||
// plugins/highway_3d/screen.js.
|
||||
//
|
||||
// What it guards: ultra-wide panes (top/bottom 2-player split → full-width /
|
||||
// half-height → ~32:9) used to render the neck as a thin central sliver because
|
||||
// THREE's PerspectiveCamera fov is VERTICAL and was locked at 70°, ballooning
|
||||
// the horizontal cone past 130°. The fix lets camUpdate lower the effective
|
||||
// vertical fov as the pane widens (holding the horizontal cone ~constant) so the
|
||||
// neck fills the pane. It is gated behind window.__h3dAspectTune (default off →
|
||||
// byte-for-byte the prior behaviour) for live A/B comparison.
|
||||
//
|
||||
// A refactor that re-hardcodes the camera fov, drops the change-guarded cam.fov
|
||||
// write, stops caching the pane aspect, or removes the no-op-at-startAspect
|
||||
// guarantee would silently regress the feature (or worse, change normal-pane
|
||||
// framing). These are source-level pins — same strategy as the other
|
||||
// tests/js/ files (no DOM / WebGL in CI).
|
||||
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const SCREEN_JS = path.join(__dirname, '..', '..', 'plugins', 'highway_3d', 'screen.js');
|
||||
const src = fs.readFileSync(SCREEN_JS, 'utf8');
|
||||
|
||||
// ── Constants ────────────────────────────────────────────────────────────────
|
||||
|
||||
test('BASE_VFOV is a named constant (not a literal in the camera ctor)', () => {
|
||||
assert.match(
|
||||
src,
|
||||
/const\s+BASE_VFOV\s*=\s*70\s*;/,
|
||||
'BASE_VFOV must be declared as a constant',
|
||||
);
|
||||
});
|
||||
|
||||
test('the camera is constructed with BASE_VFOV, not a bare 70', () => {
|
||||
assert.match(
|
||||
src,
|
||||
/new\s+T\.PerspectiveCamera\(\s*BASE_VFOV\s*,/,
|
||||
'PerspectiveCamera must take BASE_VFOV as its vertical fov',
|
||||
);
|
||||
});
|
||||
|
||||
test('the Hor+ start-aspect and min-vfov defaults exist', () => {
|
||||
assert.match(src, /const\s+HORPLUS_START_ASPECT\s*=\s*16\s*\/\s*9\s*;/,
|
||||
'HORPLUS_START_ASPECT must default to 16/9 (no-op at/under the reference aspect)');
|
||||
assert.match(src, /const\s+HORPLUS_MIN_VFOV\s*=\s*\d+\s*;/,
|
||||
'HORPLUS_MIN_VFOV floor must be declared');
|
||||
});
|
||||
|
||||
// ── effectiveVfov: no-op guarantees ──────────────────────────────────────────
|
||||
|
||||
test('effectiveVfov returns the base fov when the bridge is off/absent', () => {
|
||||
// The disabled / malformed-input guard returns `base` before any Hor+ math,
|
||||
// so normal panes are unaffected when __h3dAspectTune is missing or off.
|
||||
assert.match(
|
||||
src,
|
||||
/function\s+effectiveVfov\s*\(\s*aspect\s*,\s*tune\s*\)\s*\{[\s\S]*?if\s*\(\s*!tune\s*\|\|\s*!tune\.enabled[\s\S]*?return\s+base\s*;/,
|
||||
'effectiveVfov must short-circuit to the base fov when disabled',
|
||||
);
|
||||
});
|
||||
|
||||
test('effectiveVfov is a no-op at/under the start aspect', () => {
|
||||
assert.match(
|
||||
src,
|
||||
/if\s*\(\s*aspect\s*<=\s*start\s*\)\s*return\s+base\s*;/,
|
||||
'effectiveVfov must return base when aspect <= start (no-op for normal/2x2 panes)',
|
||||
);
|
||||
});
|
||||
|
||||
// ── shipped defaults: off + coherent ─────────────────────────────────────────
|
||||
// The "default off → byte-for-byte prior behaviour" contract only holds if the
|
||||
// shipped _ASPECT_DEFAULTS actually ship disabled with a base that matches the
|
||||
// camera's constructed fov. A previous revision shipped enabled:true with
|
||||
// baseVfov:30 (and blend:0), which forced every pane's fov to 30/36 and
|
||||
// silently re-framed normal single-player panes. These pin against that.
|
||||
|
||||
test('_ASPECT_DEFAULTS ships disabled (no-op out of the box)', () => {
|
||||
assert.match(
|
||||
src,
|
||||
/const\s+_ASPECT_DEFAULTS\s*=\s*\{[\s\S]*?\benabled\s*:\s*false\b/,
|
||||
'_ASPECT_DEFAULTS.enabled must default to false so the feature is opt-in',
|
||||
);
|
||||
});
|
||||
|
||||
test('the default base fov matches BASE_VFOV (enabling is still a no-op on normal panes)', () => {
|
||||
// baseVfov === BASE_VFOV means even with the feature ON, a <=startAspect pane
|
||||
// returns the unchanged 70° — the effect is confined to genuinely wide panes.
|
||||
assert.match(
|
||||
src,
|
||||
/const\s+_ASPECT_DEFAULTS\s*=\s*\{[\s\S]*?\bbaseVfov\s*:\s*BASE_VFOV\b/,
|
||||
'_ASPECT_DEFAULTS.baseVfov must default to BASE_VFOV, not a divergent literal',
|
||||
);
|
||||
});
|
||||
|
||||
test('the default blend engages the hold and the floor sits below the base', () => {
|
||||
// blend:1 means turning the feature on actually holds the horizontal cone
|
||||
// (blend:0 would collapse effectiveVfov back to base = feature inert), and
|
||||
// minVfovDeg:HORPLUS_MIN_VFOV keeps the floor below baseVfov (a real floor,
|
||||
// not one that clamps the base upward).
|
||||
assert.match(
|
||||
src,
|
||||
/const\s+_ASPECT_DEFAULTS\s*=\s*\{[\s\S]*?\bblend\s*:\s*1\b/,
|
||||
'_ASPECT_DEFAULTS.blend must default to 1 so the Hor+ hold actually applies when enabled',
|
||||
);
|
||||
assert.match(
|
||||
src,
|
||||
/const\s+_ASPECT_DEFAULTS\s*=\s*\{[\s\S]*?\bminVfovDeg\s*:\s*HORPLUS_MIN_VFOV\b/,
|
||||
'_ASPECT_DEFAULTS.minVfovDeg must default to HORPLUS_MIN_VFOV (a floor below baseVfov)',
|
||||
);
|
||||
});
|
||||
|
||||
// ── camUpdate: change-guarded fov write + cached aspect ───────────────────────
|
||||
|
||||
test('applySize caches the pane aspect for camUpdate', () => {
|
||||
assert.match(
|
||||
src,
|
||||
/_paneAspect\s*=\s*cam\.aspect\s*;/,
|
||||
'applySize must cache cam.aspect into _paneAspect',
|
||||
);
|
||||
});
|
||||
|
||||
test('camUpdate reads the live tune bridge and respects splitOnly', () => {
|
||||
assert.match(
|
||||
src,
|
||||
/const\s+_aspTune\s*=\s*_aspectTune\(\)\s*;[\s\S]*?_aspTune\.splitOnly\s*&&\s*!_ssActive\(\)/,
|
||||
'camUpdate must read the bridge via _aspectTune() and gate splitOnly on _ssActive()',
|
||||
);
|
||||
});
|
||||
|
||||
test('the tune bridge seeds from localStorage (persisted sessions apply on load)', () => {
|
||||
assert.match(
|
||||
src,
|
||||
/function\s+_aspectTune\s*\(\)[\s\S]*?localStorage\.getItem\(\s*_ASPECT_LS\s*\)/,
|
||||
'_aspectTune() must seed the bridge from localStorage',
|
||||
);
|
||||
});
|
||||
|
||||
test('a floating tuner panel is built and toggled with the A/B state', () => {
|
||||
assert.match(src, /function\s+_ensureAspectPanel\s*\(\)/,
|
||||
'_ensureAspectPanel() must exist to build the live panel');
|
||||
assert.match(src, /function\s+_setAspectPanelVisible\s*\(/,
|
||||
'_setAspectPanelVisible() must show/hide the panel with the feature');
|
||||
});
|
||||
|
||||
test('camUpdate only writes cam.fov when it actually changes', () => {
|
||||
// Guarding the write avoids a per-frame updateProjectionMatrix on a steady
|
||||
// pane and keeps the disabled path free.
|
||||
assert.match(
|
||||
src,
|
||||
/Math\.abs\(\s*_vfov\s*-\s*cam\.fov\s*\)\s*>\s*1e-4[\s\S]*?cam\.fov\s*=\s*_vfov\s*;[\s\S]*?cam\.updateProjectionMatrix\(\)/,
|
||||
'camUpdate must guard the cam.fov write behind a change check',
|
||||
);
|
||||
});
|
||||
|
||||
// ── A/B toggle + lifecycle reset ──────────────────────────────────────────────
|
||||
|
||||
test('an A/B toggle shortcut flips the tune enabled flag', () => {
|
||||
assert.match(
|
||||
src,
|
||||
/registerShortcut\(\{[\s\S]*?const\s+t\s*=\s*_aspectTune\(\)\s*;[\s\S]*?t\.enabled\s*=\s*!\s*t\.enabled/,
|
||||
'a registerShortcut handler must toggle the bridge enabled flag',
|
||||
);
|
||||
});
|
||||
|
||||
test('destroy() resets the pane aspect and restores the base fov', () => {
|
||||
assert.match(src, /_paneAspect\s*=\s*0\s*;/,
|
||||
'destroy() must reset _paneAspect to 0');
|
||||
assert.match(
|
||||
src,
|
||||
/cam\.fov\s*!==\s*BASE_VFOV[\s\S]*?cam\.fov\s*=\s*BASE_VFOV\s*;\s*cam\.updateProjectionMatrix\(\)/,
|
||||
'destroy() must restore cam.fov to BASE_VFOV for instance reuse',
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user