From 2eb576092286f109ec21ee8e9959e8cc143543d7 Mon Sep 17 00:00:00 2001 From: Jason Dekarske Date: Fri, 10 Jul 2026 23:19:02 -0700 Subject: [PATCH] fix(frontend): camera shows sidelines and house above controls Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- frontend/src/renderer.ts | 41 ++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/frontend/src/renderer.ts b/frontend/src/renderer.ts index fcf64cb..b107dde 100644 --- a/frontend/src/renderer.ts +++ b/frontend/src/renderer.ts @@ -31,11 +31,19 @@ export interface Renderer { export function createRenderer(canvas: HTMLCanvasElement): Renderer { const ctx = canvas.getContext('2d')! - // Default viewport: roughly 14 ft (≈ 4.27 m) wide, centered on the house. - // Height is derived from the screen aspect ratio so the whole sheet is visible. - const viewportWidth = 14.0 / 3.28084 + // Fit full sheet width (5 m) with a little side padding so both sidelines show. + // Scale is primarily width-based, but also capped so house + backline fit above the HUD. + const SIDE_PADDING_M = 0.25 + const viewportWidth = SHEET_WIDTH + SIDE_PADDING_M * 2 + // Min world height: top of 12ft ring through backline + small pad. + const MIN_VIEW_HEIGHT_M = + BACK_LINE_Y - (HOUSE_CENTER.y - TWELVE_FT_RADIUS) + SIDE_PADDING_M + // Throw button (80) + hud padding (12×2) + small gap above controls. + const BOTTOM_UI_PX = 120 const viewportCenter = { x: 0, y: HOUSE_CENTER.y } - let viewportYOffset = 0 + // Prefer backline at the bottom of the usable ice area (just above HUD). + // First setSize()/setViewYOffset clamps this to the valid minOffset. + let viewportYOffset = -(BACK_LINE_Y - HOUSE_CENTER.y) const setSize = () => { const dpr = window.devicePixelRatio || 1 @@ -46,14 +54,29 @@ export function createRenderer(canvas: HTMLCanvasElement): Renderer { canvas.style.width = `${width}px` canvas.style.height = `${height}px` ctx.setTransform(dpr, 0, 0, dpr, 0, 0) + // Re-clamp so default framing keeps the backline just above the HUD. + setViewYOffset(viewportYOffset) + } + + /** Screen height available for ice (above bottom controls). */ + const usableHeightPx = (): number => { + return Math.max(1, window.innerHeight - BOTTOM_UI_PX) } const scale = (): number => { - return window.innerWidth / viewportWidth + const byWidth = window.innerWidth / viewportWidth + const byHeight = usableHeightPx() / MIN_VIEW_HEIGHT_M + // Width fit for sidelines; height cap so house/backline stay on-screen on wide displays. + return Math.min(byWidth, byHeight) } const viewportHeight = (): number => { - return window.innerHeight / scale() + return usableHeightPx() / scale() + } + + /** Vertical center of the usable ice area (not the full window). */ + const viewCenterY = (): number => { + return usableHeightPx() / 2 } const effectiveViewportCenter = () => ({ @@ -64,7 +87,7 @@ export function createRenderer(canvas: HTMLCanvasElement): Renderer { const worldToScreen = (x: number, y: number) => { const s = scale() const cx = window.innerWidth / 2 - const cy = window.innerHeight / 2 + const cy = viewCenterY() const center = effectiveViewportCenter() return { x: cx + (x - center.x) * s, @@ -75,7 +98,7 @@ export function createRenderer(canvas: HTMLCanvasElement): Renderer { const screenToWorld = (sx: number, sy: number) => { const s = scale() const cx = window.innerWidth / 2 - const cy = window.innerHeight / 2 + const cy = viewCenterY() const center = effectiveViewportCenter() return { x: (sx - cx) / s + center.x, @@ -85,6 +108,8 @@ export function createRenderer(canvas: HTMLCanvasElement): Renderer { const clampViewYOffset = (): number => { // Positive offset pans the view upward (toward the hog line). + // When the usable viewport is tall enough, minOffset pins BACK_LINE_Y to the + // bottom of the usable area (just above the HUD), not under the controls. const halfHeight = viewportHeight() / 2 const maxOffset = HOUSE_CENTER.y - halfHeight - HOG_LINE_Y const minOffset = -(BACK_LINE_Y - (HOUSE_CENTER.y + halfHeight))