From c7cf971e540b9969a6ab818d30485bc90b02a72a Mon Sep 17 00:00:00 2001 From: tekki mariani Date: Mon, 23 Mar 2026 20:08:12 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=93=9D=20update=20CHANGELOG=20for=20d?= =?UTF-8?q?epth=20sorting=20(PR=20#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c672039..091fe50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Fixed +- **Y-based depth sorting** (Issue #31): trees, rocks, seedlings and buildings now use `tileY + 5` as depth instead of fixed values — objects lower on screen always render in front of objects above them, regardless of spawn order; build ghost moved to depth 1000 +- **Nisse always visible** (Issue #33): Nisse sprites fixed at depth 900, always rendered above world objects + ### Added - **Försterkreislauf** (Issue #25): - **Setzlinge beim Fällen**: Jeder gefällte Baum gibt 1–2 `tree_seed` in den Stockpile From d02ed33435e5ac6fdbe59805cc99870c94115f28 Mon Sep 17 00:00:00 2001 From: tekki mariani Date: Mon, 23 Mar 2026 20:18:00 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9A=A1=20fix=20GC=20ruckler=20=E2=80=94?= =?UTF-8?q?=20Object.values()=20einmal=20pro=20pickJob-Aufruf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #34. Alle Object.values()-Aufrufe werden einmal am Anfang von pickJob() extrahiert und in allen Branches wiederverwendet. Der Forester-Loop rief zuvor fuer jedes Zone-Tile 4x Object.values() auf. JOB_ICONS als Modul-Konstante, Math.min-spread durch Schleife ersetzt. --- src/systems/VillagerSystem.ts | 38 ++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/systems/VillagerSystem.ts b/src/systems/VillagerSystem.ts index 7e21b6c..f49cabd 100644 --- a/src/systems/VillagerSystem.ts +++ b/src/systems/VillagerSystem.ts @@ -13,6 +13,9 @@ const ARRIVAL_PX = 3 const WORK_LOG_MAX = 20 +/** Job-type → display icon mapping; defined once at module level to avoid per-frame allocation. */ +const JOB_ICONS: Record = { chop: '🪓', mine: '⛏', farm: '🌾', forester: '🌲', '': '' } + interface VillagerRuntime { sprite: Phaser.GameObjects.Image nameLabel: Phaser.GameObjects.Text @@ -126,8 +129,7 @@ export class VillagerSystem { this.drawEnergyBar(rt.energyBar, v.x, v.y, v.energy) // Job icon - const icons: Record = { chop: '🪓', mine: '⛏', farm: '🌾', forester: '🌲', '': '' } - const jobType = v.aiState === 'sleeping' ? '💤' : (v.job ? (icons[v.job.type] ?? '') : '') + const jobType = v.aiState === 'sleeping' ? '💤' : (v.job ? (JOB_ICONS[v.job.type] ?? '') : '') rt.jobIcon.setText(jobType).setPosition(v.x + 10, v.y - 18) } @@ -377,20 +379,27 @@ export class VillagerSystem { const vTY = Math.floor(v.y / TILE_SIZE) const dist = (tx: number, ty: number) => Math.abs(tx - vTX) + Math.abs(ty - vTY) + // Extract state collections once — avoids repeated Object.values() allocation per branch/loop. + const resources = Object.values(state.world.resources) + const buildings = Object.values(state.world.buildings) + const crops = Object.values(state.world.crops) + const seedlings = Object.values(state.world.treeSeedlings) + const zones = Object.values(state.world.foresterZones) + type C = { type: JobType; targetId: string; tileX: number; tileY: number; dist: number; pri: number } const candidates: C[] = [] if (p.chop > 0) { // Build the set of all tiles belonging to forester zones for chop priority const zoneTiles = new Set() - for (const zone of Object.values(state.world.foresterZones)) { + for (const zone of zones) { for (const key of zone.tiles) zoneTiles.add(key) } const zoneChop: C[] = [] const naturalChop: C[] = [] - for (const res of Object.values(state.world.resources)) { + for (const res of resources) { if (res.kind !== 'tree' || this.claimed.has(res.id)) continue // Skip trees with no reachable neighbour — A* cannot reach them. if (!this.hasAdjacentPassable(res.tileX, res.tileY)) continue @@ -406,7 +415,7 @@ export class VillagerSystem { } if (p.mine > 0) { - for (const res of Object.values(state.world.resources)) { + for (const res of resources) { if (res.kind !== 'rock' || this.claimed.has(res.id)) continue // Same reachability guard for rock tiles. if (!this.hasAdjacentPassable(res.tileX, res.tileY)) continue @@ -415,7 +424,7 @@ export class VillagerSystem { } if (p.farm > 0) { - for (const crop of Object.values(state.world.crops)) { + for (const crop of crops) { if (crop.stage < crop.maxStage || this.claimed.has(crop.id)) continue candidates.push({ type: 'farm', targetId: crop.id, tileX: crop.tileX, tileY: crop.tileY, dist: dist(crop.tileX, crop.tileY), pri: p.farm }) } @@ -423,7 +432,7 @@ export class VillagerSystem { if (p.forester > 0 && (state.world.stockpile.tree_seed ?? 0) > 0) { // Find empty plantable zone tiles to seed - for (const zone of Object.values(state.world.foresterZones)) { + for (const zone of zones) { for (const key of zone.tiles) { const [tx, ty] = key.split(',').map(Number) const targetId = `forester_tile_${tx}_${ty}` @@ -431,12 +440,12 @@ export class VillagerSystem { // Skip if tile is not plantable const tileType = state.world.tiles[ty * WORLD_TILES + tx] as TileType if (!PLANTABLE_TILES.has(tileType)) continue - // Skip if something occupies this tile + // Skip if something occupies this tile — reuse already-extracted arrays const occupied = - Object.values(state.world.resources).some(r => r.tileX === tx && r.tileY === ty) || - Object.values(state.world.buildings).some(b => b.tileX === tx && b.tileY === ty) || - Object.values(state.world.crops).some(c => c.tileX === tx && c.tileY === ty) || - Object.values(state.world.treeSeedlings).some(s => s.tileX === tx && s.tileY === ty) + resources.some(r => r.tileX === tx && r.tileY === ty) || + buildings.some(b => b.tileX === tx && b.tileY === ty) || + crops.some(c => c.tileX === tx && c.tileY === ty) || + seedlings.some(s => s.tileX === tx && s.tileY === ty) if (occupied) continue candidates.push({ type: 'forester', targetId, tileX: tx, tileY: ty, dist: dist(tx, ty), pri: p.forester }) } @@ -445,8 +454,9 @@ export class VillagerSystem { if (candidates.length === 0) return null - // Lowest priority number wins; ties broken by distance - const bestPri = Math.min(...candidates.map(c => c.pri)) + // Lowest priority number wins; ties broken by distance — avoid spread+map allocation + let bestPri = candidates[0].pri + for (let i = 1; i < candidates.length; i++) if (candidates[i].pri < bestPri) bestPri = candidates[i].pri return candidates .filter(c => c.pri === bestPri) .sort((a, b) => a.dist - b.dist)[0] ?? null