---
name: Town Rendering & Equipment Slots
description: Much bigger town buildings rendered via PixiJS Graphics on map, NPC rendering/interaction, wandering NPC encounters, extended equipment slots UI, daily tasks, achievements, nearby heroes, collision detection
type: project
---

Updated on 2026-03-27:

**Towns on Map (enlarged in March 2026 update):**
- `GameRenderer.drawTowns()` draws large ground planes (tan/brown dirt ellipses), then clusters of 5-14 houses spread over ~100px radius
- Houses have 3 roof styles (pointed, flat, chimney), 2-3 windows, doors with knobs, optional fences and market stalls
- `_drawHouse()` accepts roofStyle parameter (0=pointed, 1=flat, 2=pointed+chimney)
- `_drawFence()` and `_drawTownStall()` add variety between houses
- Town border circle: `radius * TILE_WIDTH * 0.7` (was 0.35), scale factor: `radius / 8` (was /12)
- Town name label: 18px font (was 13px), positioned above house cluster
- House positions: deterministic pseudo-random from town.id for consistent layout
- `GameEngine.setTowns()` stores `TownData[]` and checks hero proximity each tick for enter/exit events
- Town data comes from `/towns` API, converted via `townToTownData()` in App.tsx
- `TownData` interface: id (number), centerX/centerY/radius/size, optional npcs: NPCData[]

**NPC Rendering and Interaction (March 2026 update):**
- `NPCData` interface: id, name, type ('quest_giver'|'merchant'|'healer'), worldX, worldY
- `GameRenderer.drawNPCs()` renders type-specific colored diamonds with floating icons (!/$/ +) and name labels
- Quest givers: gold body, "!" icon; Merchants: green body, "$" icon; Healers: white body, "+" icon
- Idle sway animation based on NPC id
- `GameEngine.setNPCs()` stores all NPCs, `_checkNPCProximity()` fires `onNearestNPCChange` within 2 tiles
- `NPCInteraction.tsx`: floating panel appears when hero near NPC, type-specific action button
- App.tsx wires nearest NPC state, dismiss tracking, and opens NPCDialog for quest givers
- Town NPC fetching: `getTownNPCs()` for each town, NPCs positioned at town.worldX + npc.offsetX

**Wandering NPC Encounter (March 2026 update):**
- `NPCEncounterEvent` type in types.ts: type, npcName, message, cost
- Encounter provider checks for `type: "npc_event"` response from `requestEncounter`
- `WanderingNPCPopup.tsx`: modal with accept/decline, calls `POST /api/v1/hero/npc-alms`
- `giveNPCAlms()` API returns hero + received item details

**Extended Equipment (spec 6.3):**
- `EquipmentItem` interface with id/slot/formId/name/rarity/ilvl/primaryStat/statType
- `HeroState.equipment?: Record<string, EquipmentItem>` optional map
- `HeroResponse.equipment` added to API types
- `HeroPanel` dynamically renders 5 display slots (main_hand, chest, head, feet, neck) with fallback to legacy weapon/armor fields
- `EquipmentPanel.tsx` -- compact 5-column grid with tap-to-expand detail, rarity-colored borders

**Daily Tasks (spec 10.1) -- now backend-driven:**
- `DailyTasks.tsx` -- expandable top-right panel with progress bars
- Fetches from `GET /api/v1/hero/daily-tasks` via `getDailyTasks()` API
- Claim button on completed unclaimed tasks calls `POST /api/v1/hero/daily-tasks/{taskId}/claim`
- Refreshes after victories, buff activations, and claims
- `DailyTaskResponse` interface in api.ts

**Achievements (spec 10.3):**
- `AchievementsPanel.tsx` -- expandable panel (trophy icon, top-right at x=240)
- Fetches from `GET /api/v1/hero/achievements` via `getAchievements()` API
- Unlocked: gold border, checkmark, unlock date; Locked: grey with progress bar
- After each victory, compares new achievements with previous state; shows toast for newly unlocked

**Nearby Heroes (spec 2.3 shared world):**
- `GET /api/v1/hero/nearby` via `getNearbyHeroes()` API; polled every 5 seconds
- `GameEngine.setNearbyHeroes()` stores `NearbyHeroData[]`
- `GameRenderer.drawNearbyHeroes()` draws semi-transparent green diamonds with name+level labels and idle sway
- `NearbyHeroData` interface in types.ts

**Collision Detection (spec 2.2):**
- Procedural terrain/object generation extracted to `src/game/procedural.ts` (shared by renderer and engine)
- `isProcedurallyBlocked(wx, wy)` checks if a tile has a blocking object (tree, bush, rock, ruin, stall, well)
- Engine `_isBlocked()` checks server obstacle set first, falls back to procedural
- In `_simulateWalking`: before applying position delta, checks next tile; if blocked, tries 90-degree turn, then reverses
- `GameEngine.populateObstacles()` accepts server map objects for non-procedural maps

**Why:** MVP feature expansion per spec sections 2.2, 2.3, 2.4, 2.5, 6.3, 10.1, 10.3.

**How to apply:** Town rendering is in the ground layer (drawn after tiles, before entities). NPCs are in the entity layer for depth sorting. NPC proximity is checked every engine tick. Equipment UI extends legacy weapon/armor pattern. Daily tasks and achievements are now backend-driven. Collision uses procedural generation for the endless world.
