fix(frontend): camera shows sidelines and house above controls

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Jason Dekarske 2026-07-10 23:19:02 -07:00
parent 20c592d8ac
commit 2eb5760922

View File

@ -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))