Skip to content

Extract rendering controllers, add static obstacles + skyscrapers#1

Open
holoduke wants to merge 1 commit into
mainfrom
feature/skyscrapers-and-refactor
Open

Extract rendering controllers, add static obstacles + skyscrapers#1
holoduke wants to merge 1 commit into
mainfrom
feature/skyscrapers-and-refactor

Conversation

@holoduke

Copy link
Copy Markdown
Owner

Summary

Structural refactor of app.js (2334 → 1272 LOC, −45%) extracting three rendering controllers and a sun state owner, plus two new gameplay/scenery systems built on top of the cleaner architecture.

  • Controllers: HeightmapPipeline, ShadowController, PostProcessingController, SunController — each owns its state + public API; TerrainApp holds the composition root and exposes getter/setter proxies so ControlPanel continues to work unchanged.
  • Static obstacles: new StaticObstacleIndex 2D spatial hash with AABB slab sweep. CollisionDetector wires it into the laser-march (tunnel-proof) and into per-frame player-position checks. Player crash into an obstacle triggers the existing death-explosion pipeline.
  • Skyscrapers: procedural window-grid shader (local-space UVs, yaw-invariant), mirrored sky reflection via reflect(-viewDir, normal) + fresnel mix, specular sun glint on glass, lit-window emission (~18%, per-building seed). setBuildingDensity(0..25) slider in ControlPanel; placement scans the real heightmap for flat sites (Δh ≤ 28 over a 180-unit footprint, 260-unit spacing) and drops the rest.

Perf / correctness fixes along the way

  • Pre-allocated ~30 Vector3 in ShadowController.updateCascadeCamera (previously new Vector3 per cascade per frame).
  • Pre-allocated 4 Vector3 in Player flight/camera/laser/bomb hot paths.
  • Pre-allocated testPoint in CollisionDetector.
  • Fixed death-explosion rAF leak — handle stored + cancelled in Player.dispose.
  • Removed window.game global (18 reach-throughs) → constructor injection.
  • Removed late-set inputManager.game = this back-pointer.
  • Moved InputManager.toggleMenu's game.app.controlPanel.panel deep reach into Game.toggleMenu() / Game.isMenuVisible().
  • Race fix: InputManager.toggleMenu now clears intentionalUnlock and cancels any pending re-lock setTimeout on rapid M-key toggling.
  • PostProcessingController.setup is idempotent (calls dispose first).
  • Reconciled ShadowController resolution default (4192) with its setter clamp (was 2048; now both 4096; slider range matches).
  • Removed dead updateEngineGlow (mesh.traverse every frame for a flag that was never set).
  • Unified AudioManager fadeIn/fadeOut behind _fadeTo() helper.
  • Extracted FLIGHT_CONSTANTS (speeds, turn rates, weapon timings, camera tunables).
  • Collapsed duplicated effectParameters initialisation in app.js.
  • Stripped 4 hot-path console.logs from CollisionDetector's laser march.

Cleanup

  • Added .gitignore for node_modules/, dist/, .DS_Store, .vite/, *.log; untracked 1375 previously-committed build files.
  • Removed unused assets: 5 skybox PNGs, 3 standalone textures, JetstreamArcade.mp3, shift.glsl, src/assets/screenshots/.
  • Removed dead noise/index exports, geometry.cube, orphaned HeightmapPipeline methods with no callers, toggleRenderer/showRendererNotification (only referenced from a disabled KeyG handler).
  • Derived-state proxy setters on TerrainApp throw when written, to catch accidental external mutation of moved fields.

File structure

src/
├── app.js                               (1272, was 2334)
├── world/
│   ├── HeightmapPipeline.js             (new, 293)
│   ├── SunController.js                 (new, 39)
│   └── StaticObstacleIndex.js           (new, 230)
├── rendering/
│   ├── ShadowController.js              (new, 498)
│   └── PostProcessingController.js      (new, 203)
└── assets/shaders/
    └── skyscraper.{vert,frag}           (new)

Test plan

  • npm run build clean (verified: 2.69s, no warnings)
  • Page loads; canvas initialised; menu renders
  • ENGAGE → play mode: 🎮 Game system initialized✅ Player loaded🎮 Input enabled✅ Game started in play mode
  • Fire lasers (Space) / bombs (KeyB) without errors
  • M-key toggles menu without pausing
  • Shadow / Bloom / Post-FX toggles round-trip through controllers
  • "Generate Ridged Terrain" exercises HeightmapPipeline end-to-end: 📋 Initialized base & master heightmap🏔️ Generating ridged✅ Rebuilt master heightmap🔄 Updated terrain from master
  • Flying the player plane into a skyscraper/obelisk → crash → death explosion
  • Laser fired at a skyscraper → collision returns kind: "obstacle"
  • Building density slider 0 → 25 cleanly adds/removes buildings; obstacle-count tracking matches at each step
  • Windows show mirrored sky reflection + sun glint on glass; walls pick up diffuse sun; lit windows glow warm amber

Net diff

26 source files modified/added/deleted. −577 source LOC after cleanup.

…skyscrapers

Structural refactor of the 2334-line god-object in src/app.js (now 1272).
Extracted three focused modules, an obstacle system, and a skyscraper city.

## Architecture

- `src/world/HeightmapPipeline.js` owns the procedural heightmap state and
  erosion/effect pipeline (thermal, hydraulic, smooth, terracing, ridged,
  cellular, billow, warped). TerrainApp delegates via `applyTerrainEffect`.
- `src/rendering/ShadowController.js` owns CSM state: 3 cascade cameras,
  render targets, light-space bounds, terrain uniform sync. Per-frame
  Vector3 allocations pre-allocated as class fields (~30 alloc/frame
  eliminated from updateCascadeCamera).
- `src/rendering/PostProcessingController.js` owns bloom pipeline, FXAA,
  color grading. `setup()` is idempotent; `dispose()` removes its resize
  listener.
- `src/world/SunController.js` owns sun/ambient/sky-tint state previously
  scattered across TerrainApp fields.
- TerrainApp exposes getter/setter proxies for all controller state so
  ControlPanel's existing `app.shadowsEnabled`, `app.bloomStrength` etc.
  continue to work unchanged. Derived-state setters throw to catch
  accidental mutation.

## Decoupling

- Removed `window.game` global (18 reach-throughs in Player/CollisionDetector/
  InputManager); now passed via constructor injection.
- Removed late-set `inputManager.game = this` back-pointer.
- Moved InputManager's `game.app.controlPanel.panel` deep reach into
  Game.toggleMenu() / Game.isMenuVisible().

## Correctness / perf fixes

- Pre-allocate 4 per-frame Vector3 in Player.updateFlightDynamics/Camera
- Pre-allocate testPoint in CollisionDetector
- Replace velocity.clone().multiplyScalar() in laser/bomb update loops
- Fix death-explosion rAF leak: store handle, cancel in Player.dispose
- Strip 4 hot-path console.logs from CollisionDetector's laser march
- Collapse duplicated effectParameters init in app.js
- Fresh `_fadeTo()` helper unifies AudioManager fadeIn/fadeOut
- Extract FLIGHT_CONSTANTS (speeds, turn rates, weapon timings, camera)
- Fix InputManager.toggleMenu race: clear intentionalUnlock + cancel pending
  re-lock timeout on repeated M-key presses
- Reconcile ShadowController resolution default (4192) with setter clamp
  (was 2048; now both 4096)
- Kill dead updateEngineGlow (per-frame mesh.traverse looking for a flag
  that was never set)

## Static obstacles

- New `src/world/StaticObstacleIndex.js` — 2D spatial hash with AABB slab
  sweep for laser/bomb segments and AABB containment for player/bomb
  position checks. Register once, query O(1).
- CollisionDetector wires obstacles into the existing laser-march so a
  fast laser can't tunnel through thin obstacles between simulation steps.
- Player crash detection calls checkPointObstacle each frame while alive;
  hit triggers existing takeDamage → death-explosion pipeline.

## Skyscrapers on flat terrain

- New `src/assets/shaders/skyscraper.{vert,frag}` — procedural window grid
  in local space (yaw-invariant), per-window lit emission (~18%, seeded
  per building), mirrored sky reflection using `reflect(-viewDir, normal)`
  plus a fresnel mix factor, and a specular sun glint on glass panes.
  Minimum-light floor prevents pitch-black shadows.
- `setBuildingDensity(0..25)` control: disposes existing buildings +
  obstacle entries, regenerates templates, re-runs flat-terrain placement.
- Flat-terrain placement scans a 35×35 grid of candidate positions,
  measures height variation across each 180-unit footprint, sorts by
  flatness, and picks the top N with a 260-unit minimum spacing.
- Procedural template pool: 25 skyscrapers with varied sizes (55-99 wide,
  160-399 tall) and a 10-color wall/glass palette cycle, each with a
  stable index-based seed.
- ControlPanel adds a "Scenery" section with a density slider.

## Cleanup

- Added .gitignore (node_modules/, dist/, .DS_Store, .vite/, *.log); untracked
  1,375 previously-committed node_modules + dist files.
- Removed unused assets: 5 Skybox PNGs, 3 standalone textures,
  JetstreamArcade.mp3, shift.glsl, src/assets/screenshots/.
- Removed dead exports from noise/index.js (noise snapshot, setTerrainTexture,
  getNoiseConfig, HeightmapGenerator + TerrainTexture re-exports).
- Removed geometry.cube (unused).
- Removed orphaned HeightmapPipeline methods (storeOriginalHeightmap,
  storeRawHeightmap, reapplyTerrainSettings — no callers).
- Removed TerrainApp.toggleRenderer/showRendererNotification (only referenced
  from a disabled KeyG handler).

Net src diff: 26 files, -577 lines. app.js: 2334 -> 1272 (-45%).

Verified build clean; runtime-verified via Chrome:
- Menu loads, canvas 1708x898, WebGL active
- ENGAGE -> play mode: Collision detector initialized, Player loaded, Input
  enabled, Game started in play mode; no errors from refactored paths
- Shadow/bloom/post-fx toggles round-trip correctly through controller proxies
- Generate Ridged Terrain exercises HeightmapPipeline end-to-end
- Player teleport into obelisk/building triggers crash -> takeDamage ->
  death explosion
- Laser segment-sweep returns kind:"obstacle" at correct hit point
- Building density slider transitions 10 -> 20 -> 3 -> 0 -> 10 cleanly, with
  correct obstacle-count tracking at each step
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant