// Follows the prince horizontally with a dead zone across the middle third. // No vertical movement. No easing, no overshoot. Spec 2.2: "It should be // boring." // // This is the only place world coordinates become screen coordinates. import { TUNING } from './tuning.js'; export function createCamera(viewportW, worldMin, worldMax) { const halfDead = (viewportW * TUNING.CAMERA_DEAD_ZONE) / 2; // Mutable, because the village changes them exactly once. On the road the // camera follows forever; at the mouth it stops following and starts being // bounded, and that one switch IS the transition between the two modes. let minX = worldMin; let maxX = Math.max(worldMin, worldMax - viewportW); return { x: worldMin, prevX: worldMin, snapshot() { this.prevX = this.x; }, reset(targetX) { this.x = this.prevX = this.clamp(targetX - viewportW / 2); }, /** * Re-bound the view. Takes CAMERA positions, not world edges — the mouth of * the village fixes where the view may go, not where the world stops, and * passing world edges through the viewport subtraction twice is exactly the * off-by-a-half-screen this file has already produced once. */ setBounds(minCamX, maxCamX) { minX = minCamX; maxX = Math.max(minCamX, maxCamX); this.x = this.clamp(this.x); }, clamp(x) { return Math.min(maxX, Math.max(minX, x)); }, /** Move only when the target leaves the middle third. */ follow(targetX) { const centre = this.x + viewportW / 2; const offset = targetX - centre; if (offset > halfDead) this.x += offset - halfDead; else if (offset < -halfDead) this.x += offset + halfDead; this.x = this.clamp(this.x); }, /** World x -> screen x at a given parallax rate. 1.0 is the actor layer. */ toScreen(worldX, rate = 1) { return worldX - this.x * rate; }, }; }