local World = { GAME_W = 1024, GAME_H = 768, FIELD_SIZE = 4000, visibleH = 768, scale = 1, offsetX = 0, offsetY = 0, screenW = 0, screenH = 0, -- Viewport regions (screen pixels) radarH = 0, viewY = 0, viewH = 0, -- Classic vector-arcade thresholds (defaults matching the most common DIP -- switch settings documented in arcade-preservation records). MISSILE_THRESHOLD = 10000, -- binary: below score, no missiles; at/above, missiles mix in SUPERTANK_MISSILE_COUNT = 6, -- super tanks replace slow tanks after this many missiles have launched SAUCER_THRESHOLD = 2000, -- saucer becomes eligible SAUCER_MIN_DELAY = 0, SAUCER_MAX_DELAY = 17, EVADE_TANK_TIME_MIN = 48, -- if current tank stays alive this long, swap it for a missile (arcade: 48-64s) EVADE_TANK_TIME_MAX = 64, RESPAWN_SPAWN_BEAT = 2.5, -- seconds between enemy death and next spawn FIRE_SUPPRESSION = 2.0, -- seconds after enemy spawn during which it cannot fire (arcade rule) -- State state = "title", score = 0, highScore = 0, lives = 3, nextExtraLife = 15000, enemiesDestroyed = 0, gameOverTimer = 0, spawnTimer = 0, saucerCooldown = 0, -- seconds until next saucer eligible missilesLaunched = 0, -- total missile spawns this game (gates super-tank transition) evadeDeadline = 0, -- RNG'd 48..64s lifetime limit for the current tank } function World.resize(w, h) if not w or not h then w, h = love.graphics.getDimensions() end World.screenW = w World.screenH = h World.scale = w / World.GAME_W World.visibleH = h / World.scale World.offsetX = 0 World.offsetY = 0 -- Viewport layout World.radarH = math.floor(h * 0.18) World.viewY = World.radarH World.viewH = h - World.radarH end function World.ensureScale() local w, h = love.graphics.getDimensions() if w ~= World.screenW or h ~= World.screenH then World.resize(w, h) local Fonts = require("rendering.fonts") Fonts.init(World.scale) local Camera = require("game.camera") Camera.initViewport() end end function World.wrapField(x, z) local S = World.FIELD_SIZE return (x % S + S) % S, (z % S + S) % S end function World.wrappedDelta(a, b, size) local d = b - a if d > size / 2 then d = d - size end if d < -size / 2 then d = d + size end return d end function World.wrappedDist(x1, z1, x2, z2) local S = World.FIELD_SIZE local dx = World.wrappedDelta(x1, x2, S) local dz = World.wrappedDelta(z1, z2, S) return dx, dz, math.sqrt(dx*dx + dz*dz) end function World.addScore(points) World.score = World.score + points local granted = false while World.score >= World.nextExtraLife do World.lives = World.lives + 1 granted = true if World.nextExtraLife < 100000 then World.nextExtraLife = 100000 else World.nextExtraLife = World.nextExtraLife + 100000 end end if granted then local ok, Sounds = pcall(require, "audio.sounds") if ok and Sounds.play then Sounds.play("extra_life") end end if World.score > World.highScore then World.highScore = World.score end end return World