## Wolfenstein: Expand E1M2 level and persist per-mission weapon loadouts
### Level Data (assets/gamedata/wolfenstein/level-e1m2.json)
- Increase map dimensions from 33×24 to 39×29 cells, adding new corridors, rooms, and wall structures
- Add tile type `5` (likely a new floor or decorative element) throughout the expanded areas
- Reposition existing doors, guards, items, objects, and wall art to match the new layout
- Add new entities: additional guards with patrol routes, machine gun pickups, ammo, health kits, yellow key, and secret door in the expanded sections
- Update player start position and exit location for the larger map
### Game Logic (src/games/wolfenstein/WolfensteinGame.js)
- Introduce `LOADOUTS_KEY` localStorage persistence to record which weapons the player owned when each mission was unlocked
- Replace `_freshCampaignCarry()` with `_carryForUnlockedStart()`, which loads the recorded weapon loadout for a given mission (falling back to fists-only for mission 0 or unrecorded missions) and tops up ammo to full
- Add `_fullAmmoFor()` helper that computes max ammo reserves for all ammo-consuming weapons in a loadout
- Update `_recordMissionCleared()` to capture the player's current weapon list at clear-time and store it as the unlock loadout for the next mission (only advancing monotonically)
- Change "Next Mission" flow to rely on the recorded loadout rather than extracting live state, ensuring consistent full-ammo/full-health starts
- Update `_retry()` to restore the original attempt's weapons but top up ammo to full per the "die → come back with full ammo" spec
- Added new "level-new" level data (33x24 grid) with doors, enemies, items, and objects
- Removed one ammo-clip from e1m1 and added two health-large pickups
- Introduced shared HUD row anchor constants for consistent positioning
- Updated HUD to display level name alongside episode/mission info
- Added gray rounded-rect background behind weapon icon in HUD for better contrast against steel-blue bar
- Repositioned weapon icon to dynamically sit left of ammo text based on rendered width
- Updated WolfensteinArt to generate the new HUD icon background texture
Replace the flat dark rectangle status bar with a baked riveted-metal "blue steel plate" background styled after Wolfenstein 3D's original HUD:
- Add `paintHudBar` to WolfensteinArt.js generating a 1920x140 texture with vertical steel gradient, bevel edges, recessed readout panels, divider strips with rivets, and a centered portrait socket sized around the new `HUD_PORTRAIT_SIZE` constant
- Add `paintProfile` fallback face and wire up `profile.png` (128x128, 3 health frames) in wolfenstein-artwork.json
- Rebuild `_buildHud` in WolfensteinGame.js: left panel shows episode/mission label + HEALTH value, center shows health-reactive portrait (frame swaps at 66/33 HP thresholds), right panel shows INVENTORY key icons + AMMO count with equipped weapon icon
- Derive weapon/key icon frames from `rules.items` data instead of hardcoded values so HUD stays in sync with wolfenstein-rules.json
- Add `_hudEpisodeMissionText()` helper for static per-level label text (campaign episode/mission or test level name)
- Apply subdued CRT overlay (`applyArcadeCRTOverlay`) tuned for a tactical-display feel, destroyed on teardown
- Switch HUD fonts to shared `m6x11` pixel font with white/ice-blue lettering; remove old KEY_COLORS constant and rectangular key indicators
- Add fists weapon viewmodel with idle/hit animation states (220ms hit pose)
- Implement mouse-wheel weapon cycling that skips unowned weapons
- Add 33% chance for killed guards to drop ammo-clip pickups at death location
- Introduce campaign carry-over system preserving weapons/ammo/health between missions
- Fix stale 'attack' state clearing when enemies lose sight of player
- Extend save/load serialization to persist nextPickupId counter
- Update level-e1m1 with pistol pickup at spawn point
- Add comprehensive test coverage for all new features in verifyWolfenstein.js
- Add shotgun, machine gun, gatling gun, and plasma rifle weapons with
distinct fire modes (semi-auto, auto, burst) and damage profiles
- Refactor ammo from per-weapon pools to shared pools keyed by ammo type
(9mm, shells, plasma), allowing multiple weapons to share the same pool
- Add pickup system with 11 item types: 5 weapons, 4 ammo variants, and
2 health packs, all with spritesheet frames for rendering
- Update level editor with dynamic pickup dropdown populated from rules.json
- Generalize weapon viewmodel rendering to support multiple weapon images
with per-weapon bob/sway animation
- Add cosmetic pickup animations: vertical bobbing and orbiting sparkle
effects drawn in screen space around each pickup billboard
- Bump save version to v3 to invalidate old saves with incompatible ammo
structure
- Update keyboard bindings for weapon switching (keys 1-6)
- Add comprehensive test coverage for new weapons, burst fire mechanics,
ammo pooling, and pickup behavior
- Rename weapon_gattling.png to weapon_gatling.png to fix spelling
Introduces a new "Object" entity type that blocks movement but not sight/bullets, distinct from walls and pickups:
- **Data**: New `objects` array in level JSON (`{x, y, frame}`), with `data/wolfenstein-objects.json` as the frame registry (Tall Bush, Gold Eagle)
- **Artwork**: New `objects.png` spritesheet registered in `wolfenstein-artwork.json`; placeholder texture via `paintObject()` in WolfensteinArt.js
- **Logic**: Objects stored in `state.objects`, with a derived `map.objectBlocked` Set checked only by movement collision (`isWallCell`) — raycaster/sight/bullets never read it, so objects are see-through and shoot-through by construction
- **View**: Rendered as billboard sprites in `_drawSprites` with ground-anchored positioning (bottom edge on floor) and asymptotic depth sorting that stays below the weapon viewmodel
- **Editor**: New "Object" tool category with dynamic dropdown; click open floor to place/remove (toggle), click wall is a no-op; teal square indicator on board
- **Validation**: `validateLevel` checks objects sit on open floor; save/load round-trips with graceful degradation for pre-existing saves
- **Tests**: Verifies object blocks player movement but bullets pass through
- Add new `wallArt` level data array with per-cell decal frames
- Integrate wall art rendering into WolfensteinView using same texture sampling as walls
- Add Wall Art tool to WolfensteinEditor with dynamic dropdown from JSON registry
- Create wolfenstein-wallart.json frame registry for easy decal management
- Add validation ensuring wall art sits on actual wall cells
- Support wall art in save/load system with backward compatibility
- Alert idle enemies when shot, even outside vision cone
Replace all-or-nothing per-sprite depth checks with per-column occlusion
testing. This allows walls to correctly hide only the parts of sprites
(like dead guards) that are behind them, rather than hiding the entire
sprite if any part is occluded.
Implementation details:
- Walk outward from the sprite's center column to determine the visible
span based on the wall depth buffer.
- Use Phaser's `setCrop` to clip the sprite to the visible columns, which
is more performant than redrawing per-column like walls.
- Add `OCCLUSION_EPS` tolerance to prevent flickering when sprites are
positioned exactly against walls due to float precision issues.
This refactors weapon/enemy combat systems to support per-weapon cooldowns,
fire modes (auto/semi), and data-driven damage rolls via ammo types. Guards
now use the pistol's stats for ranged attacks and can be stunned by non-lethal
hits. The level editor now includes a directional facing picker for newly placed
enemies and displays enemy orientation as arrows instead of dots.
Key changes:
- Logic: Replaced single weaponCooldownMs with per-weapon cooldowns map; added
prevFireHeld for semi-auto trigger-edge detection; guards now reference real
weapon definitions via rangedWeapon; projectiles carry ammoType and resolve
damage at hit time using rollDamage(); non-lethal ranged hits apply stunMs to
enemies, freezing their AI until it expires.
- Rules: Added ammoTypes array with damageMin/damageMax; weapons now specify
fireMode and ammoType (projectiles) or damage (melee); enemy definitions
include stunMs and reference weapon IDs instead of hardcoded projectile stats.
- Editor: Added FACING_DIRS constants and drawFacingPicker() overlay that lets
users click N/S/E/W neighbor cells to set a just-placed enemy's facing;
enemies now render as directional arrows showing their spawn orientation;
pendingFacingEnemy state tracks the in-progress pick across tool switches,
undo/redo, and level resets.
- View: Added GUARD_FRAME.stunned (frame 9) which takes visual priority over
all other states when e.stunMs > 0, ensuring the flinch pose displays even if
the enemy's internal state still reads 'attack' or 'chase'.
- Data: Expanded level-e1m1 to 19x21 grid with repositioned doors/enemies/exit;
bumped SAVE_VERSION to 2 since player.cooldowns and prevFireHeld are new
required fields absent from v1 saves.
- Tests: Added coverage for semi-auto trigger-edge behavior, stun application
and duration, melee non-stun behavior, damage roll range bounds, and guard
projectile ammo type/cooldown spacing; updated existing tests to use new
cooldowns/ammoType fields and toggle fireHeld for repeated shots.
Add computeRooms + alertEnemiesInPlayerRoom: a gunshot now alerts every
idle guard sharing the player's room regardless of range/LOS/cone. A
door cell is always treated as a room boundary for this purpose, even
when fully open — two rooms joined only by a doorway stay distinct
("gunfire carries through the room, not through doorways").
Fix _guardFacing side-walk/side-idle flip: was `rel > 0`, read
backwards in-game, now `rel < 0`.
Cover with three verifyWolfenstein cases: same-room alert fires,
behind-closed-door does not, through-open-door does not (door forced
slide=1 / wall zeroed to exercise the slide-independent path).
- Implement 90° field-of-view check for idle/patrolling guards (only alerts when player is within cone, in range, and has clear line of sight)
- Change patrol behavior: 0-1 waypoints ping-pong, 2+ waypoints form a one-way loop (home → nodes → home → ...)
- Reduce patrol speed to 50% of chase speed for natural movement
- Update editor to display loop closing line when 2+ patrol nodes exist
- Add new weapon sprite images (gattling, machinegun, plasma, rocket, shotgun)
- Extend map parser to support patrol route definitions via `opts.patrols`
- Add patrol routes to e1m1 (guard at 2.5,18.5) and e1m2 (guard at 9.5,8.5)
- Add comprehensive tests for vision cone (inside/outside edge cases) and patrol loop/ping-pong behavior
- Replaced the single-circle bullet texture with a three-circle design
(brass/copper body, bright tracer core, and off-center highlight) for
better readability against various backgrounds
- Reduced bullet texture size from 16×16 to 12×12 to match the more detailed
design
- Decreased bullet render scale from 0.18 to 0.1 in the view to make bullets
appear as small rounds rather than floating dots
- Updated documentation in sprites.md to reflect the new bullet design, size,
and scale changes
Introduce a first-person weapon viewmodel for the pistol that peeks up
from behind the HUD, completing the classic FPS look. The viewmodel
features a subtle, smoothed bob and sway animation that responds to
player movement (forward/strafe), ramping in and out to avoid
snapping.
- Add `paintWeaponPistol` to `WolfensteinArt` as a small procedural
placeholder (360x260) for when real art is absent.
- Update `WolfensteinView` to handle both real and procedural weapon art
with appropriate origins and base positions.
- Implement `_drawWeapon` with bob/sway logic and smooth strength
transitions.
- Register the new sprite key in `wolfenstein-artwork.json`.
- Document the new `wolfenstein-weapon-pistol` asset in `sprites.md`,
including its size, depth, and behavior.
- Add directional billboarding for guards: front/side/back idle & walk
frames selected by relative angle to camera, with flipX for left side
- Add shooting pose (always shown when guard is in attack state)
- Add 2-frame walk cycle (300ms) for moving guards (chase/alert/patrol)
- Add death sequence: fall (350ms) → ground (2s) → fade (800ms),
view-only state in WolfensteinView, pooled sprite destroyed on completion
- Wire guard sheet texture from artwork JSON (enemies.png, 9×7 grid,
frames 0-8 used)
- Add E key as alternate interact key for opening doors
- Update door hint text to show [SPACE/E]
- Update sprites.md with full animation/facing/death documentation
The raycaster's door geometry now takes the live `doors[]` state instead of a boolean flag, so partially-open door cells let render rays pass through the already-slid portion to show what's genuinely beyond, rather than painting a flat "socket" fill. `intersectDoorMidplane` returns a slide-shifted `textureX` so the door texture visibly translates into the wall as it opens.
`WolfensteinView` samples the new `wolfenstein-doors` sheet the same way it does walls (source-texel column, vertical crop, multiply shading), falling back to the flat tan placeholder. Added the doors.png/.psd assets and a `sheets.doors` entry in the art manifest. Updated sprites.md to document the new sheet, the see-through behavior, and that this stays render-only (gameplay raycasts are unaffected).
Editor (WolfensteinEditor.js):
- Decouple the board from grid size: fixed 900px viewport with independent
pan (right-drag / WASD / arrows) and zoom (wheel / +/- / F to fit)
instead of squeezing the whole grid in; add a whole-level map overview
minimap with click-to-jump.
- Grids up to 1000 cells/side (was 48); painting past the edge auto-grows
the grid (preserving content, re-sealing borders) instead of being refused.
- Rebuild the toolbar as a categorized DOM radio + dropdown panel (Wall /
Door / Pickup / Enemy / Patrol / Zone / Erase) instead of a flat list.
- Add a Patrol tool: select a guard, then click tiles to add/remove
waypoints; routes are drawn (dim for all, highlighted for the selection).
- Throttle the reachability validate during paint-drag; flush it before
Test Play / Export; queue game assets in preload() like ZumaEditor.
Doors (WolfensteinLogic.js, WolfensteinRaycaster.js, WolfensteinView.js):
- Player now opens doors by pressing Space (HUD "[SPACE] Open" prompt when
one is in range); enemies still shove them open. Doors animate open/closed
via a slide value (400ms) instead of popping, and stay solid for
gameplay until fully open.
- Render doors as recessed mid-cell geometry (real depth, producing the "H"
doorway shape) via a door-aware render-only raycast — collision/LOS are
unchanged; the door visibly slides sideways into a dark socket as it opens.
Enemy AI (WolfensteinLogic.js):
- Idle enemies with an authored patrol route ping-pong along
[home, ...nodes] until they spot the player; validate patrol nodes.
Rendering (WolfensteinView.js, assets, data):
- Add textured wall rendering: per-column 1px source-texel sampling from
the new walls.png spritesheet, darkened with a 'multiply' grey to match
the existing side/fog shading, with per-wall-type flat-color fallback.
- Wire in assets/images/wolfenstein/walls.png and point the art manifest at it.
Misc:
- level-e1m1.json: reworked to a taller (20-row) map exercising the new
wall/door types; playerRadius 0.25 -> 0.35.
- sprites.md: document exact wall-sheet size, the sliding/recessed door
behavior, guard sheet usage, and standalone image sizes/proportions.