Commit Graph

641 Commits

Author SHA1 Message Date
brianfertig 858dc9535d Merge pull request 'Game-Polish' (#10) from Game-Polish into main
Reviewed-on: #10
2026-09-02 05:10:11 +00:00
Brian Fertig 52eaff8b96 Add word-steered board generation and level-scaled special tiles to Book
- Introduce BookworkSteering with context-aware letter placement: class
  steering, vowel-band control, and a real-word completion boost so boards
  stay playable and avoid dead/vowel-clumped states across refills.
- Make gold/diamond spawn chances configurable per level via a linear ramp
  (specialTiles in bookwork.json), wiring the values through clearAndRefill
  and refillSteered instead of hard-coded constants.
- Add verifyBookwormBoard harness comparing baseline vs steered boards on
  word availability, dead-board rate, and simulated session survival, and
  extend verifyBookwork with special-tile schedule checks.
2026-09-01 23:09:12 -06:00
Brian Fertig 44de2fb049 Rename Bookwork to Bookworm and add dictionary-steered grid generation
- Update display name in registry, scene title, README, and tooling references
- Load ENABLE word list at runtime; fall back to unsteered random on failure
- Introduce makeSteeredGrid/refillSteering helpers for class/vowel-aware letter placement
- Export LETTER_WEIGHTS from BookworkLogic for reuse by the steering module
- Add `steer` tuning block (wordBoostK, classThreshold/damp/boost, vowelBand) to bookwork.json
2026-09-01 22:57:07 -06:00
brianfertig e3d2fdee34 Merge pull request 'Game-Polish' (#9) from Game-Polish into main
Reviewed-on: #9
2026-09-02 02:51:15 +00:00
Brian Fertig 3c2cf462ed Polish Mahjong layout: reposition portraits and reference panel
- Rename card back themes (Cthulhu, Cyberpunk, Nautical, etc.) and update
  artwork assets to match
- Move player portraits just left of each seat's tiles so they read as
  belonging to that player without covering any tile
- Reposition the "Hands" reference button clear of seat 2's melds/bonus
  tiles, and hide seat 1's portrait video while the panel covers it
- Auto-close the open reference panel whenever claim or win/kong buttons
  appear so nothing clickable is hidden
2026-09-01 20:49:47 -06:00
Brian Fertig 93db9ef9dc Fix ghost button hover state to use full opacity and correct text color 2026-09-01 20:03:10 -06:00
brianfertig ae30c66a3a Merge pull request 'Polish' (#8) from Polish into main
Reviewed-on: #8
2026-09-01 04:46:09 +00:00
Brian Fertig 429102105b Persist last selected category across menu restarts
- Track the most recently chosen category in module scope so it survives scene.start() reusing the same instance.
- Fall back to that value when entering the menu without explicit category data, restoring the user's previous selection instead of defaulting.
2026-08-31 22:45:22 -06:00
Brian Fertig 1298648e97 Polish opponent select scene and restore category on return
- Replace flat title/subtitle pills with brass Plaque headers in OpponentSelectScene for visual consistency with the game menu
- Shift opponent scroll area down (OPP_SCROLL_TOP 155 -> 180, height 440 -> 415) to clear the subtitle plaque band
- Pass selected category back to GameMenu via scene.start data on Back so the active grid is restored instead of resetting to default
- Add GameMenuScene.init() to capture incoming category and reset stale instance props (_currentCategory, _gridAnim) since scene.start reuses the existing instance
- Guard fadeIn target list with .filter(Boolean) to skip any uncreated elements
2026-08-31 22:21:03 -06:00
Brian Fertig 74cd4df85b Polish title plates with rounded brass-trimmed plaque and size animation
- Replace flat black rectangles behind the game-menu and landing titles with a new Plaque component that draws a rounded, accent-bordered plate with a soft drop shadow and warm top highlight.
- Add letter-spacing and text shadow to the menu title so it reads as raised against the plate.
- When the tab label swaps (e.g. "Loading…" → "Choose a game"), tween the plaque to its new width via a size proxy that redraws each frame, and pop the title text with a Back.easeOut scale so the change feels intentional.
2026-08-31 20:55:29 -06:00
Brian Fertig cbec3b3a44 Polish game menu and landing scene transitions
- Add slide-in/out grid animations when switching categories in the game menu, replacing the instant rebuild
- Rename "Choose a game" to "Choose a Game Category" and update title dynamically on category change
- Fade in menu controls (tabs, hint, back button) on entry to complete the landing handoff
- Animate logo zoom-out and fade buttons out when transitioning from landing to game menu via goToGameMenu()
- Guard against double-taps and stale avatar loads during scene transitions
2026-08-31 19:53:22 -06:00
Brian Fertig f2108e6258 Remove default category selection in favor of a hint prompt
- Drop the `_lastCategory` module-level state and the logic that pre-selected a tab on scene entry.
- Show a "Select a category above to see its games" hint instead, destroyed once the user picks a category.
2026-08-31 19:39:09 -06:00
brianfertig 4ecfcaee50 Merge pull request 'jigsaw-ui-improvements' (#7) from jigsaw-ui-improvements into main
Reviewed-on: #7
2026-08-31 14:47:28 +00:00
Brian Fertig e319f41ce8 Fix jigsaw tray and win-camera to account for the fixed HUD band
- Tray top now clears the HUD at full zoom-out so an entire piece
  (base cell + tabs + shadow headroom) stays below the bar.
- Win camera centers the board in the area below the HUD rather than
  the whole screen, giving the framed picture room under the bar.
- Add AGENTS.md noting the user handles their own commits/pushes.
2026-08-31 08:44:51 -06:00
Brian Fertig 97adfa642d Fix win-screen counter guard and add Playwright browser smoke test
- piecesBig counter guard used .parent (always undefined on container
  children in this Phaser build — they get .parentContainer), so the
  'PIECES' count-up never rendered; drop the dead guard.
- buttonHit() had the same .parent assumption; use .parentContainer.
- Add tools/smokeJigsaw.mjs: headless-Chromium smoke test driving
  menu -> play -> win showcase (camera settle, counter, confetti,
  frame/shine) -> Play Again -> Menu -> play again, asserting zero JS
  errors. Verified pass in browser.
2026-08-31 08:26:32 -06:00
Brian Fertig 18d89d4553 Redesign jigsaw win screen with zoomed board view and celebration effect
- Frame the completed puzzle large on the right while parking stats/actions in a left-hand panel, easing the camera into place
- Add staggered slide-in choreography for title, time, stats (with piece counter), and action buttons
- Fire confetti burst + falling curtain, a shine sweep, and a pulsing gold frame when the win camera lands
- Introduce exitWin() so restart/menu pull the camera back and slide the panel out before rebuilding state
- Simplify pinHUD() now that HUD/win overlay render through the fixed hudCam
2026-08-31 08:11:27 -06:00
brianfertig 0c59a292a3 Merge pull request 'Rework jigsaw UI with stitched mat playfield and two-column menu' (#6) from jigsaw-ui-improvements into main
Reviewed-on: #6
2026-08-31 05:30:00 +00:00
Brian Fertig 243d2e143c Rework jigsaw UI with stitched mat playfield and two-column menu
- Replace the flat felt background with a canvas-generated beige fabric mat (border band, dashed stitching, subtle weave tile, vignette) plus a deeper "pocket" panel behind the board so pieces read as resting on a puzzle mat.
- Add soft drop shadows to jigsaw pieces via an extra shadow pass before the art fill, and pad the piece canvas so the shadow isn't clipped.
- Rebuild the menu into two columns (image picker left, difficulty + start right) with tighter vertical rhythm; make the thumbnail box size a `thumbBox` property so preview/border scale together.
- Consolidate panel background+frame pairs (menu, dropdown, win layer) into single graphics objects to simplify tweening and reduce node count.
- Use a separate invisible zone for table pointerdown so the mat image can be swapped without dropping input, and guard `onTableDown` against double-firing while a piece grab is in progress.
2026-08-30 23:11:58 -06:00
brianfertig c29f33e2a3 Merge pull request 'Puzzle' (#5) from Puzzle into main
Reviewed-on: #5
2026-08-31 03:25:35 +00:00
Brian Fertig a7ebb67413 Extend jigsaw initial-image test coverage
Test now also asserts the menu is back to its original shape (no random-start button), the initial preview and thumb name match the random pick, Start Puzzle begins exactly that image, and the original 🎲 Random button still randomises the menu preview.
2026-08-30 21:19:11 -06:00
Brian Fertig 25f5e05a96 Start the jigsaw puzzle picker on a random image
The menu always opened on the first artwork; now imageIndex is randomised right after loadArtwork(), so each visit to the picker shows a different initial puzzle. The existing Random/prev/next buttons are unchanged (previous random-start button reverted in 4f2d299). Test page + CDP driver verify 8 fresh page loads all land on a valid in-range image with its preview loaded, with variety across loads.
2026-08-30 21:14:37 -06:00
Brian Fertig 4f2d29942d Revert "Add one-click random start to the jigsaw menu"
This reverts commit 0740c12ffa.
2026-08-30 21:12:44 -06:00
Brian Fertig 0740c12ffa Add one-click random start to the jigsaw menu
New '🎲 Start Random' button picks a random picture (never the current one when there's a choice) and starts the puzzle immediately, keeping the selected difficulty. Menu panel grows 820→890 to fit it. Headless-chromium test page + CDP driver verify: button placement, 8/8 random starts reach playing state, image variety, single-image guard, and difficulty preservation.
2026-08-30 21:11:15 -06:00
Brian Fertig 19dff633ce Add jigsaw test harness and local phaser.esm.js bundle
- __jig_test.html boots a Phaser game that loads JigsawGame with stubbed artwork/music caches and exposes window.__* helpers for driving piece grab/drop, scene inspection, and coordinate conversion.
- phaser.esm.js is a vendored webpack build of Phaser used as the "phaser" import map target so the harness can run without a package manager.
2026-08-30 21:03:23 -06:00
Brian Fertig 8dee798ae2 Fix jigsaw piece grab when click lands inside custom hit area
- Add pointerdown handler on each piece to forward the event to the central onTableDown resolver; without it, Phaser's topOnly=true consumes the click and the background never sees it, leaving pieces un-grabbable.
- Anchor the hit-area rectangle to the image's displayOrigin instead of (0,0) so the custom hit box tracks the piece's actual rendered position.
2026-08-30 21:02:22 -06:00
Brian Fertig 85fc7c16bf Add piece joining, board locking, and site soundtrack to jigsaw game
- Introduce group-based table assembly: grid-adjacent pieces snap together on the table, drag as one rigid unit, and lock onto the board as a block
- Extract join/lock rules into pure `resolveDrop` in JigsawLogic.js with a headless verification suite (tools/verifyJigsaw.js) covering fit guarantee, joining, locking, and win counting
- Replace the flat top-bar buttons with a "Menu ▾" dropdown panel to free the top-right corner for the shared site music HUD
- Integrate MusicPlayer soundtrack via getGameSoundtrack() and pin it to the fixed HUD camera
2026-08-30 19:49:00 -06:00
Brian Fertig b6299234cf Fix jigsaw camera bounds and piece tray to avoid overlapping the HUD
- Offset main camera scroll/zoom bounds by HUD_H so the play field
  never slides beneath the fixed top bar
- Shift the scattered-piece tray down by HUD_H to keep pieces clear of
  the target image and HUD
- Assign board panel, frame, and reference image to the main camera only
  so the fixed HUD camera doesn't draw their corners into the screen
2026-08-30 19:03:28 -06:00
Brian Fertig dca1cbef4d Add difficulty-based reference image and ghost slot visibility to Jigsaw
- Hide the faint full-picture reference on Hard and Legendary levels
- Show the target-slot ghost preview only on Easy while dragging a piece
- Store the selected difficulty on the scene for these checks
2026-08-30 18:40:56 -06:00
Brian Fertig 22e6b68cef Add camera pan/zoom and fixed HUD for jigsaw puzzle
- Expand the play field to a 3840x2160 world so pieces can scatter widely and the player can explore
- Introduce MIN_ZOOM (0.5) so the entire field fits on screen, with zoom anchored to the cursor
- Add a second fixed HUD camera so the menu, stats bar, and win overlay stay in screen space while the board camera pans/zooms
- Route world objects and UI containers to their respective cameras via cameraFilter
- Update background, scatter tray, and camera clamping to use the new world dimensions
2026-08-30 18:24:36 -06:00
Brian Fertig a8b4d15e8c Add Jigsaw puzzle game with drag-and-drop pieces and pan/zoom
- New Phaser scene (JigsawGame) supporting 4 difficulty tiers (25–144 pieces),
  image selection, timer, hint overlay, and win screen.
- Pure geometry module (JigsawLogic) generates seeded tab/blank outlines so
  adjacent pieces mesh exactly; also provides deterministic scatter positions.
- Pieces are drawn to canvas textures with depth shading; drag-to-snap with a
  ghost target, camera pan on empty table, and wheel/button zoom (1x–4x).
- Registered in gamesRegistry, main.js scene list, and GameRoomScene slug map;
  game-icons.png updated for the new icon frame.
2026-08-30 17:33:31 -06:00
brianfertig cb4ac7bd85 Merge pull request 'Small update to tutorial' (#4) from Master-of-Vega-Tutorial into main
Reviewed-on: #4
2026-08-29 17:15:37 +00:00
Brian Fertig 26700acbab Small update to tutorial 2026-08-29 11:14:50 -06:00
brianfertig d19fd1681b Merge pull request 'Master-of-Vega-Tutorial' (#3) from Master-of-Vega-Tutorial into main
Reviewed-on: #3
2026-08-29 17:13:03 +00:00
Brian Fertig ce14631544 Expand Master of Vega tutorial to cover full gameplay loop
- Add ~25 new steps: nearby stars, pan/zoom lesson, star info, fleet movement, home star/system view, colony management (allocation, focus), end turn, and the full Empire menu tour (Research, Diplomacy, Leaders, Colonies)
- Introduce hotspotAction, starPick, advanceOn modes (starPick/fleetInFlight/external), and advanceAction fields in tutorial data schema with validation
- Add VegaStarMap.fitToWorldBounds and extend pickFitZoomIndex to support a narrowed viewport area so the reachable-stars frame avoids the docked panel
- Bypass openModal gating for System View, Colony View, and Empire screens by calling their open functions directly from VegaTutorial; wire onClose/onColonyOpen callbacks to advance()
- Add tutorialFreePan flag so pan/zoom work during the free-pan step while all other map interactions stay frozen
- Add multi-target resolution (nearbyStars returns an array), invisible-shape holes, and per-star click hotspots in the tutorial overlay
- Update verification script to allow buttonless modal steps that advance via external callbacks
2026-08-29 11:11:25 -06:00
Brian Fertig 4175da2e26 Add guided tutorial to Master of Vega
- New data-driven script (data/mastervega-tutorial.json) with intro modal and three callout steps over the fleet marker and side-panel ship rows/count controls.
- Split into Phaser-free VegaTutorialData.js (schema validation, interpolation, target-id registry) and Phaser VegaTutorial.js (darken overlay, accent rings, callouts, hotspot, skip confirm, state machine).
- Tutorial auto-opens on every brand-new game and is re-triggerable from the menu when the starting fleet is still in orbit; malformed JSON disables the feature with a warning instead of crashing.
- Add D.tutorial depth (just below detail) so an invited ship-detail window layers above the overlay, and expose VegaSidePanel.tutorialRegion() for screen-space panel regions.
- Register the tutorial asset in the manifest and add verifier section 12 covering schema, targets, voice clips, placeholders, and interpolation.
2026-08-28 22:27:04 -06:00
brianfertig 25d29c0f86 Merge pull request 'Add Tents sound effects and intro jingle' (#2) from Tents into main
Reviewed-on: #2
2026-08-29 03:12:30 +00:00
Brian Fertig 58902075e5 Add Tents sound effects and intro jingle
- Add dedicated place, remove, win, and puzzle-start intro audio assets
- Register the new cues in the asset manifest and SFX key map
- Replace generic UI sounds with Tents-specific cues for placement, removal, and win
- Play the intro jingle on startGame, ducking the soundtrack until it completes
- Update smoke tests to assert the new jingles and cues fire
2026-08-28 21:09:04 -06:00
brianfertig 9363fc3848 Merge pull request 'Add Tents & Trees logic puzzle game' (#1) from Tents into main
Reviewed-on: #1
2026-08-28 02:54:54 +00:00
Brian Fertig af6ea98528 Add Tents & Trees logic puzzle game
- Implement full Tents & Trees scene (TentsGame.js) with day-to-night
  sky transition, campfire/ember/firefly effects, hover hints, violation
  feedback, and a win panel
- Add pure board model (TentsLogic.js) with backtracking solver,
  unique-solution puzzle generation across four difficulties, toggle/
  diagnose/solved helpers — all unit-testable in Node
- Register the game in gamesRegistry, main scene list, and slug dispatch
- Update game-icons.png with a new icon frame for Tents & Trees
- Add tools/verifyTents.js (solver + play-state unit tests and generation
  soak) and tools/smokeTents.js (Phaser-stub smoke test driving the full
  select → play → solve → win → restart flow)
2026-08-27 20:51:26 -06:00
Brian Fertig 199bbc40fb Fix Gin Rummy stale-index drag bug and add Pipe Puzzle unhover regressio
- Resolve hand card index from the container at event time instead of
  capturing it when handlers are bound, preventing the "wrong card on
  second drag" bug after a reorder.
- Enable drag-to-arrange in both draw and discard phases; clicking a card
  during draw now shows guidance rather than discarding.
- Add discard-pile click target so a selected card can be discarded by
  clicking the pile, and extract shared `_setupPileClick` helper to reduce
  duplication.
- Add `tools/regressionPipePuzzleUnhover.cjs`, a Playwright regression
  test verifying that Phaser's `killTweensOf(target, 'prop')` no longer
  clobbers in-flight rotation tweens on pointerout/pointerover.
2026-08-26 22:38:46 -06:00
Brian Fertig 759b70733a Fixed issues with pipe rotation on mouse out 2026-08-25 18:36:46 -06:00
Brian Fertig a4efa5b68a Simplify pipe puzzle to pure spanning-tree boards
- Remove extra-edge generation and cross pieces; board is now a single random spanning tree (one unique route between every pair of cells, no loops)
- Update leak rendering to place droplets on exact open edges using DELTA/EDGE and render water/flash layers above tiles in the container
- Adjust mini preview tiles and difficulty config (drop `extra` param)
- Rework tutorial to explain tree structure, scattered dead ends, and branch-by-branch solving strategy
- Update verification tool: assert tree edge count, no 4-way cells, leaf distribution across rows, faucet row variety, and board randomness; replace cross-based fixture with a valid tree
2026-08-24 22:14:08 -06:00
Brian Fertig d27244ac60 Rework Pipe Puzzle to branching network with no-leak win condition
Replace the single Hamiltonian-path model with a spanning-tree + extra-edges
approach that produces boards full of T-pieces, crosses and dead ends. The
win condition is now simply "zero leaks" — every socket matched to a
neighbour that opens back — instead of requiring one continuous path from
faucet to drain.

Key changes:
- Logic: generate puzzles via randomized Kruskal spanning tree plus tunable
  extra edges per difficulty; add bitCount, countLeaks, and derived
  angleFor/canonical rotation table
- Art: new tile textures for stub (dead end), T-piece, and cross;
  tileKeyFor and angleFor now handle all five piece types
- Game: faucet and drain are fixed anchors (not rotatable); removed the
  CONNECTED counter in favour of a simpler LEAKS stat; updated difficulty
  tiers (6×6 through 10×10) with extra-edge counts; improved water
  rendering contrast
- Tests: rewrite verify script for the new generation model (spanning tree,
  connectivity, piece mix, no-leak solution); update smoke test to skip
  anchor tiles
- Tutorial: updated rules, board reading guide, and tips for the branching
  network variant
2026-08-24 21:04:27 -06:00
Brian Fertig 518818dc1b Add Pipe Puzzle game with Hamiltonian path generation and procedural tile art 2026-08-24 19:42:16 -06:00
Brian Fertig 3bc8b69f1f # Commit Message
## 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
2026-08-23 19:28:07 -06:00
Brian Fertig 9fa3896b5b feat(wolfenstein): add level-new map, improve HUD weapon icon contrast, and update e1m1 items
- 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
2026-08-22 21:08:45 -06:00
Brian Fertig 8b213ea887 feat(wolfenstein): add blue steel plate HUD bar with portrait and inventory
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
2026-08-22 20:45:39 -06:00
Brian Fertig 0940cd7255 feat(wolfenstein): add fists viewmodel, weapon cycling, ammo drops, and campaign carry-over
- 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
2026-08-22 19:07:37 -06:00
Brian Fertig 5d8f51ae49 Bunch of edits 2026-08-22 17:50:40 -06:00
Brian Fertig eab08eede3 Add multiple weapons, pooled ammo, and pickup system to Wolfenstein
- 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
2026-08-22 14:13:03 -06:00