v0.3: Large world 4096x3072 + momentum waves + zoom-scaled lighting

- World expanded to 4096x3072 (16x area, tiled 4x4 copies)
- Active region margin increased to 200px
- Liquid momentum system (FLAG_MOMENTUM) for wave sloshing
- Zoom-at-cursor support (camera.zoomAt)
- Smoother zoom steps (0.2 per scroll tick)
- Light propagation scaled by zoom (passes & falloff)
- AGENTS.md updated with all current features
This commit is contained in:
2026-07-09 12:15:52 +02:00
parent 1bce9da0c6
commit 2c23216152
7 changed files with 267 additions and 54 deletions
+9
View File
@@ -12,4 +12,13 @@ export class Camera {
setZoom(z: number) {
this.targetZoom = Math.max(0.1, Math.min(10, z));
}
zoomAt(z: number, worldX: number, worldY: number) {
if (this.zoom <= 0) return;
const oldZoom = this.zoom;
this.setZoom(z);
const newZoom = this.targetZoom;
this.x = worldX - (worldX - this.x) * (oldZoom / newZoom);
this.y = worldY - (worldY - this.y) * (oldZoom / newZoom);
}
}
+93 -23
View File
@@ -3,8 +3,8 @@ import { Renderer } from "./renderer";
import { Input } from "./input";
import { Camera } from "./camera";
const GRID_WIDTH = 1024;
const GRID_HEIGHT = 768;
const GRID_WIDTH = 4096;
const GRID_HEIGHT = 3072;
const PAINT_MATERIALS = [M.SAND, M.WATER, M.LAVA, M.WOOD, M.FIRE, M.ICE, M.OIL, M.ACID, M.STONE, M.DIRT, M.GLASS];
@@ -19,30 +19,93 @@ async function main() {
const engine = await createEngine(GRID_WIDTH, GRID_HEIGHT);
engine.fillRect(0, 740, 1024, 28, M.STONE);
engine.fillRect(440, 720, 144, 20, M.WOOD);
engine.fillRect(460, 700, 104, 20, M.DIRT);
engine.fillCircle(490, 560, 25, M.SAND);
engine.fillCircle(520, 550, 20, M.SAND);
engine.fillRect(430, 650, 40, 30, M.WATER);
engine.fillRect(550, 650, 40, 25, M.LAVA);
engine.fillRect(505, 640, 10, 16, M.ICE);
engine.fillCircle(620, 580, 20, M.OIL);
engine.fillRect(480, 620, 64, 30, M.WOOD);
engine.fillRect(480, 600, 64, 20, M.FIRE);
engine.fillRect(300, 720, 40, 20, M.GLASS);
engine.fillCircle(200, 680, 18, M.SAND);
engine.fillRect(100, 700, 30, 40, M.WOOD);
engine.fillRect(750, 680, 60, 60, M.STONE);
engine.fillCircle(780, 660, 15, M.LAVA);
engine.fillRect(700, 720, 30, 20, M.ICE);
// === EPIC WORLD: tiled 16 copies of the scene ===
function buildScene(ox: number, oy: number) {
engine.fillRect(ox, oy + 740, 1024, 28, M.STONE);
engine.fillRect(ox + 20, oy + 680, 80, 60, M.STONE);
engine.fillRect(ox + 30, oy + 640, 60, 40, M.STONE);
engine.fillRect(ox + 40, oy + 610, 40, 30, M.STONE);
engine.fillCircle(ox + 60, oy + 600, 15, M.DIRT);
engine.fillRect(ox + 50, oy + 660, 20, 20, M.DIRT);
engine.fillRect(ox, oy + 720, 120, 20, M.STONE);
engine.fillRect(ox + 85, oy + 705, 35, 15, M.STONE);
engine.fillRect(ox + 5, oy + 705, 60, 15, M.STONE);
engine.fillCircle(ox + 60, oy + 712, 12, M.LAVA);
engine.fillCircle(ox + 70, oy + 708, 6, M.LAVA);
engine.fillRect(ox + 60, oy + 695, 20, 10, M.DIRT);
engine.fillRect(ox + 350, oy + 680, 100, 60, M.STONE);
engine.fillRect(ox + 365, oy + 640, 70, 40, M.STONE);
engine.fillRect(ox + 380, oy + 610, 40, 30, M.STONE);
engine.fillRect(ox + 390, oy + 660, 20, 20, M.GLASS);
engine.fillRect(ox + 370, oy + 700, 60, 40, M.WOOD);
engine.fillCircle(ox + 400, oy + 690, 8, M.FIRE);
engine.fillRect(ox + 395, oy + 675, 10, 5, M.FIRE);
engine.fillRect(ox + 750, oy + 640, 50, 100, M.STONE);
engine.fillRect(ox + 760, oy + 610, 30, 30, M.STONE);
engine.fillRect(ox + 770, oy + 590, 10, 20, M.STONE);
engine.fillCircle(ox + 775, oy + 580, 8, M.FIRE);
engine.fillRect(ox + 760, oy + 650, 30, 40, M.WOOD);
engine.fillRect(ox + 180, oy + 420, 100, 20, M.STONE);
engine.fillRect(ox + 190, oy + 400, 80, 20, M.DIRT);
engine.fillCircle(ox + 230, oy + 390, 15, M.SAND);
engine.fillRect(ox + 200, oy + 380, 30, 20, M.WOOD);
engine.fillCircle(ox + 215, oy + 385, 6, M.FIRE);
engine.fillRect(ox + 240, oy + 410, 10, 10, M.GLASS);
engine.fillRect(ox + 620, oy + 350, 80, 18, M.STONE);
engine.fillRect(ox + 630, oy + 335, 60, 15, M.DIRT);
engine.fillCircle(ox + 660, oy + 325, 12, M.SAND);
engine.fillCircle(ox + 640, oy + 340, 8, M.ICE);
engine.fillRect(ox, oy + 700, 100, 40, M.WATER);
engine.fillRect(ox + 10, oy + 680, 80, 20, M.WATER);
engine.fillRect(ox + 20, oy + 660, 60, 20, M.WATER);
engine.fillRect(ox + 880, oy + 700, 80, 40, M.LAVA);
engine.fillRect(ox + 890, oy + 680, 60, 20, M.LAVA);
engine.fillRect(ox + 900, oy + 660, 40, 20, M.LAVA);
engine.fillCircle(ox + 420, oy + 280, 30, M.ICE);
engine.fillCircle(ox + 450, oy + 270, 20, M.ICE);
engine.fillCircle(ox + 390, oy + 290, 18, M.ICE);
engine.fillRect(ox + 420, oy + 300, 20, 15, M.ICE);
engine.fillRect(ox + 100, oy + 720, 180, 20, M.SAND);
engine.fillCircle(ox + 120, oy + 710, 25, M.SAND);
engine.fillCircle(ox + 180, oy + 708, 30, M.SAND);
engine.fillCircle(ox + 160, oy + 700, 18, M.SAND);
engine.fillCircle(ox + 220, oy + 715, 20, M.SAND);
engine.fillCircle(ox + 850, oy + 580, 22, M.OIL);
engine.fillCircle(ox + 870, oy + 590, 14, M.OIL);
engine.fillRect(ox + 840, oy + 600, 40, 20, M.OIL);
engine.fillRect(ox + 470, oy + 660, 280, 10, M.WOOD);
engine.fillRect(ox + 490, oy + 650, 40, 10, M.WOOD);
engine.fillRect(ox + 710, oy + 650, 40, 10, M.WOOD);
engine.fillRect(ox + 520, oy + 720, 180, 20, M.STONE);
engine.fillRect(ox + 540, oy + 710, 140, 10, M.STONE);
engine.fillCircle(ox + 600, oy + 718, 15, M.LAVA);
engine.fillCircle(ox + 620, oy + 716, 10, M.LAVA);
engine.fillRect(ox + 100, oy + 730, 40, 10, M.ACID);
engine.fillCircle(ox + 120, oy + 732, 8, M.ACID);
engine.fillRect(ox + 280, oy + 640, 40, 40, M.GLASS);
engine.fillRect(ox + 290, oy + 650, 20, 30, M.STONE);
engine.fillCircle(ox + 300, oy + 670, 5, M.FIRE);
engine.fillCircle(ox + 550, oy + 520, 20, M.DIRT);
engine.fillCircle(ox + 570, oy + 530, 14, M.DIRT);
engine.fillCircle(ox + 200, oy + 520, 30, M.STONE);
engine.fillCircle(ox + 220, oy + 500, 20, M.STONE);
engine.fillRect(ox + 300, oy + 580, 12, 20, M.WATER);
engine.fillRect(ox + 700, oy + 500, 10, 30, M.WATER);
}
for (let ty = 0; ty < 4; ty++) {
for (let tx = 0; tx < 4; tx++) {
buildScene(tx * 1024, ty * 768);
}
}
// === Spawn point: center on the castle ===
camera.x = 400;
camera.y = 630;
const rw = engine.renderWidth();
const rh = engine.renderHeight();
camera.x = 512;
camera.y = 600;
function resize() {
const dpr = window.devicePixelRatio || 1;
renderer.resize(window.innerWidth, window.innerHeight, dpr);
@@ -58,6 +121,13 @@ async function main() {
return { x: Math.floor(mx), y: Math.floor(my) };
}
function mouseWorld() {
return {
x: (input.mouseX / window.innerWidth - 0.5) * rw / camera.zoom + camera.x,
y: (input.mouseY / window.innerHeight - 0.5) * rh / camera.zoom + camera.y,
};
}
const panel = document.getElementById("panel")!;
function buildPanel() {
panel.innerHTML = "";
@@ -122,7 +192,7 @@ async function main() {
if (input.isDown("Equal")) camera.setZoom(camera.zoom + 2 * dt);
if (input.isDown("Minus")) camera.setZoom(camera.zoom - 2 * dt);
if (input.wheel !== 0) {
camera.setZoom(camera.zoom + input.wheel * 0.5);
camera.setZoom(camera.zoom + input.wheel * 0.2);
input.wheel = 0;
}