From 36f82712ab4b948d8980f7f13475c8b3a1da86fc Mon Sep 17 00:00:00 2001 From: 44r0n7 <44r0n7+gitea@pm.me> Date: Sat, 24 Oct 2026 01:15:28 -0400 Subject: [PATCH] Setting to stop the camera drifting toward the pointer (t7) Co-Authored-By: Claude Opus 5.5 --- docs/PROJECT_MAP.md | 2 +- docs/agent-log.md | 9 +++++++++ src/ui/loop.js | 4 ++-- src/ui/settings.js | 1 + src/ui/state.js | 2 +- test/play.py | 25 +++++++++++++++++++++++++ 6 files changed, 39 insertions(+), 4 deletions(-) diff --git a/docs/PROJECT_MAP.md b/docs/PROJECT_MAP.md index 0101c07..c9d038e 100644 --- a/docs/PROJECT_MAP.md +++ b/docs/PROJECT_MAP.md @@ -379,7 +379,7 @@ Saves in IndexedDB, world files (export / import) and the blueprint library. Starting, loading and quitting a game; modal dialogs; the pause menu. - functions: showLoading, enterGame, startNewWorld, startFromSave, quitToTitle, refreshContinue, anyModal, openModal, closeModal, closeAllModals, topModal, dialog, openPause, renderWorlds -### src/ui/settings.js (122 lines) +### src/ui/settings.js (123 lines) The settings screen. - functions: openSettings, renderSettings, applySettings, toggleFullscreen, confirmWipe - variables: SET_TABS, setTab diff --git a/docs/agent-log.md b/docs/agent-log.md index d901521..55fe21b 100644 --- a/docs/agent-log.md +++ b/docs/agent-log.md @@ -4,6 +4,15 @@ What agents have done, newest first. Add an entry for every piece of work: the d note or request it was for, what changed, the files touched, the tests run, and anything left open or not verified. Keep each entry short; the git history has the details. +## 2026-10-24: t7 Option to turn off smooth pan following the mouse (Claude Code, Codex) + +- New setting `cameraLead` (Display: "Camera looks toward the pointer", on by default): + `src/ui/state.js`, `src/ui/settings.js`; `src/ui/loop.js` gives the camera no look-ahead when + it is off (mouse and gamepad). +- `test/play.py`: the toggle saves; with it off the camera stays on the bot with the mouse in a + corner; with it on the camera moves toward the corner. +- Tests: headless suites and `play.py` pass; `t_smooth` half-resolution flake as before. + ## 2026-10-24: t9 Script to run the local server for testing (Claude Code, Codex) - New `tools/serve.py`: builds, serves `dist/` on 127.0.0.1 (port 8000, or the next free one up diff --git a/src/ui/loop.js b/src/ui/loop.js index 9ae0e31..bfa2565 100644 --- a/src/ui/loop.js +++ b/src/ui/loop.js @@ -45,8 +45,8 @@ function frame(now) { } else acc = 0; drainEvents(); let lookX = 0, lookY = 0; - if (GAME.usingPad) { lookX = GAME.padAim.x * 0.5; lookY = GAME.padAim.y * 0.5; } - else if (GAME.mouse.in && toolId() !== 'build' && !GAME.paste && !GAME.selDrag && !selModeHeld()) { const dpr = R.canvas.width / window.innerWidth; lookX = (GAME.mouse.x - window.innerWidth / 2) * dpr / R.zp; lookY = (GAME.mouse.y - window.innerHeight / 2) * dpr / R.zp; } + if (SET.cameraLead && GAME.usingPad) { lookX = GAME.padAim.x * 0.5; lookY = GAME.padAim.y * 0.5; } + else if (SET.cameraLead && GAME.mouse.in && toolId() !== 'build' && !GAME.paste && !GAME.selDrag && !selModeHeld()) { const dpr = R.canvas.width / window.innerWidth; lookX = (GAME.mouse.x - window.innerWidth / 2) * dpr / R.zp; lookY = (GAME.mouse.y - window.innerHeight / 2) * dpr / R.zp; } // hold the camera's look-ahead still while dragging out a line, box or blueprint // While dragging, the camera stays put unless the bot itself is flying, so the // grid under the cursor does not slide around mid-line. diff --git a/src/ui/settings.js b/src/ui/settings.js index bc8de32..4f5fc86 100644 --- a/src/ui/settings.js +++ b/src/ui/settings.js @@ -11,6 +11,7 @@ const SET_TABS = { { k: 'cursorLabel', label: 'Name next to the cursor', d: 'Shows what you are pointing at right beside the cursor.', type: 'toggle' }, { k: 'showPerf', label: 'Performance stats', d: 'Frame time, simulation time and active areas.', type: 'toggle' }, { k: 'cameraSmooth', label: 'Camera smoothing', type: 'range', min: 0, max: 0.95, step: 0.05, fmt: v => Math.round(v * 100) + '%' }, + { k: 'cameraLead', label: 'Camera looks toward the pointer', d: 'The view drifts toward the mouse or right stick. Turn off to keep the bot centred.', type: 'toggle' }, { k: 'shake', label: 'Screen shake', type: 'toggle' }, ], Graphics: [ diff --git a/src/ui/state.js b/src/ui/state.js index d080e0d..d0c69fe 100644 --- a/src/ui/state.js +++ b/src/ui/state.js @@ -3,7 +3,7 @@ // ---------- Settings ---------- const SET_DEFAULT = { - zoom: 12, integerScale: true, renderScale: 1, uiScale: 1, fpsCap: 0, showPerf: false, shake: true, cameraSmooth: 0.7, + zoom: 12, integerScale: true, renderScale: 1, uiScale: 1, fpsCap: 0, showPerf: false, shake: true, cameraSmooth: 0.7, cameraLead: true, look: 'smooth', lighting: 'full', lightEvery: 1, ambient: 0.12, daylight: 1, glow: 'low', grain: 1, incandescence: true, particles: true, thermalPalette: 'inferno', simSpeed: 1, maxSteps: 3, volMaster: 0.8, volMusic: 0.5, volSfx: 0.8, muted: false, muteUnfocused: true, diff --git a/test/play.py b/test/play.py index b5075ac..f4e7986 100644 --- a/test/play.py +++ b/test/play.py @@ -121,6 +121,31 @@ with sync_playwright() as p: ev("SET.zoom = 4; resizeRender(); R.snapNext = true;") # the rest of this play-through was laid out at zoom 4 ev(f"SET.look = '{look}'"); pg.wait_for_timeout(200) check(f'the {look} look is drawing', ev('R.smoothOn') == (look == 'smooth'), ev('[SET.look, R.smoothOn, SM.ok]')) + # Camera lead changes the view, not the bot or its aim. + mouse = ev('[GAME.mouse.x, GAME.mouse.y]') + ev("openSettings('Display')") + pg.screenshot(path=str(out / '00-camera-settings.png')) + pg.click('#set-cameraLead') + check('camera lead toggle saves its value', ev("!SET.cameraLead && JSON.parse(localStorage.getItem('deepgrain.settings')).cameraLead === false")) + pg.keyboard.press('Escape') + paused = ev('GAME.paused') + ev("GAME.paused = true; SET.cameraLead = false; $('cards').replaceChildren()") # cards cover the corner + pg.mouse.move(1240, 740); pg.wait_for_timeout(1000) + check('camera test pointer reaches the world', ev('GAME.mouse.in && GAME.mouse.x === 1240 && GAME.mouse.y === 740')) + camera_offset = """(() => { + const v = viewCells(), x = bot.x + bot.w / 2 - v.w / 2, y = bot.y + bot.h / 2 - v.h / 2; + const tx = v.w >= W ? (W - v.w) / 2 : Math.max(0, Math.min(W - v.w, x)); + const ty = v.h >= H ? (H - v.h) / 2 : Math.max(0, Math.min(H - v.h, y)); + return [R.camX - tx, R.camY - ty]; + })()""" + centred = ev(camera_offset) + check('camera stays centred with pointer lead off', all(abs(d) < 2 for d in centred), centred) + ev('SET.cameraLead = true') + pg.wait_for_timeout(1000) + led = ev(camera_offset) + check('camera leads toward the pointer when enabled', all(b - a > 2 for a, b in zip(centred, led)), led) + pg.mouse.move(*mouse) + ev(f'GAME.paused = {json.dumps(paused)}; saveSettings()') def cell_to_screen(x, y): return ev(f'(() => {{ const dpr = R.canvas.width / innerWidth; const s = worldToScreenPx({x}+0.5, {y}+0.5); return [s.x/dpr, s.y/dpr]; }})()') def blk(bx, by): return cell_to_screen(bx * 4 + 2, by * 4 + 2)