fuzzyworld

nordic-skating / mistral-medium-35-littlejs / files / LITTLEJS_CONVERSION_PLAN.md

Branch mistral-medium-35-littlejs · default branch · all branches

LITTLEJS_CONVERSION_PLAN.md

Blame: the commit that last changed each line · file · history

185c6976 2026-10-19 Pavel Safronov1# LittleJS Conversion Plan for Nordic Skating
2
3## Overview
4
5This document outlines a comprehensive plan to migrate the Nordic Skating project from its current Three.js-based architecture to **LittleJS**, a tiny fast JavaScript game engine with 2D/3D capabilities.
6
7### Key Insight: LittleJS Three.js Plugin
8
9LittleJS has a **Three.js plugin** that allows using Three.js as an alternative renderer. This means:
10- We can **keep the existing Three.js rendering code** largely intact
11- We gain LittleJS's game loop, input system, scene management, audio, and other features
12- We get built-in mobile support, touch controls, and better performance management
13- The engine handles the animation loop, delta time, and frame rate independence
14
15### Why Migrate?
16
171. **Simplified Architecture**: LittleJS provides a unified framework for game development
182. **Better Input Handling**: Built-in support for keyboard, mouse, touch, gamepads, and device orientation
193. **Audio System**: Built-in audio with ZzFX sound generation (could supplement or replace current Web Audio)
204. **Scene Management**: Built-in scene system for menus, game states, etc.
215. **Mobile Optimization**: Designed for mobile devices with touch controls
226. **Performance**: Optimized game loop and rendering
237. **Tooling**: Built-in level editors, debug tools, particle systems
248. **Size**: Small footprint (~135KB minified, but bundlers can tree-shake unused features)
25
26---
27
28## Current Architecture Analysis
29
30### Current Tech Stack
31- **Rendering**: Three.js (WebGL-based 3D rendering)
32- **Build**: Vite with TypeScript
33- **Audio**: Web Audio API (custom ice sound synthesis)
34- **Input**: Custom implementation for keyboard, mouse, touch, gamepad, device orientation
35- **World Generation**: Procedural lake generation with noise functions
36- **Physics**: Custom skater physics and ice acoustics
37- **State Management**: Custom history/state handling for lake seeds
38
39### Core Systems
40
411. **World/Lake System** (`src/world/lake.ts`)
42 - Procedural channel generation
43 - Ice thickness calculation
44 - Islet and island generation
45 - Fork handling
46
472. **Skater System** (`src/skater.ts`)
48 - Position, direction, speed management
49 - Steering and turning physics
50 - Autopilot logic
51 - Stride and push mechanics
52
533. **Rendering System** (`src/render/`)
54 - Ice rendering with dynamic textures
55 - Shore/bank rendering
56 - Sky and weather effects
57 - Snow and drifts
58 - Marks (tracks, cracks)
59
604. **Audio System** (`src/audio/iceSound.ts`)
61 - Custom Web Audio synthesis
62 - Ice singing, cracking, striking sounds
63 - Wind and snow sounds
64 - Physics-based sound generation
65
665. **Input System** (`src/input.ts`)
67 - Keyboard, mouse, touch
68 - Gamepad support
69 - Device orientation (tilt steering)
70
716. **UI System**
72 - Settings dialog
73 - About dialog
74 - Sound, pace, tilt, randomize buttons
75 - Responsive design
76
77---
78
79## Conversion Strategy
80
81### Phase 1: Preparation and Setup (1-2 days)
82
83#### 1.1 Install LittleJS
84```bash
85npm install littlejsengine
86```
87
88#### 1.2 Update package.json
89```json
90{
91 "dependencies": {
92 "littlejsengine": "^latest",
93 "simplex-noise": "^4.0.3",
94 "three": "^0.180.0"
95 }
96}
97```
98
99#### 1.3 Create LittleJS Entry Point
100Create `src/littlejs-entry.ts` as the new main entry point:
101```typescript
102import { engineInit, gameInit, gameUpdate, gameRender } from 'littlejsengine'
103import { main } from './main-littlejs'
104
105// Initialize LittleJS with our game functions
106engineInit(
107 () => main.init(),
108 () => main.update(),
109 () => main.updatePost(),
110 () => main.render(),
111 () => main.renderPost()
112)
113```
114
115#### 1.4 Update HTML Entry Point
116Update `index.html` to use LittleJS:
117```html
118<!DOCTYPE html>
119<html>
120<head>
121 <meta charset="UTF-8">
122 <title>Nordic Skating</title>
123 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
124</head>
125<body>
126 <canvas id="littlejsCanvas"></canvas>
127 <script type="module" src="/src/littlejs-entry.ts"></script>
128</body>
129</html>
130```
131
132### Phase 2: Core Engine Integration (2-3 days)
133
134#### 2.1 Initialize LittleJS with Three.js Plugin
135```typescript
136// src/main-littlejs.ts
137import * as THREE from 'three'
138import { engine, setThreeRenderer } from 'littlejsengine'
139import { Lake } from './world/lake'
140import { Skater } from './skater'
141
142class NordicSkatingGame {
143 private lake: Lake
144 private skater: Skater
145 private scene: THREE.Scene
146 private camera: THREE.PerspectiveCamera
147 private renderer: THREE.WebGLRenderer
148
149 init(): void {
150 // Initialize LittleJS
151 engineInit(this.gameInit, this.gameUpdate, this.gameUpdatePost,
152 this.gameRender, this.gameRenderPost)
153
154 // Set up Three.js renderer for LittleJS
155 setThreeRenderer(THREE)
156
157 // Create Three.js scene
158 this.scene = new THREE.Scene()
159 this.camera = new THREE.PerspectiveCamera(70, 1, 0.1, 1500)
160 this.camera.rotation.order = 'YXZ'
161
162 // Initialize world
163 this.lake = createLake(12345, { impossibleIce: false })
164 this.skater = createSkater(this.lake, 0)
165
166 // Set up fog (matches current implementation)
167 this.scene.fog = new THREE.Fog(0, 30, 600)
168 this.scene.background = this.scene.fog.color
169 }
170
171 gameInit(): void {
172 // Called by LittleJS on initialization
173 }
174
175 gameUpdate(): void {
176 // Handle input, update skater, world, etc.
177 }
178
179 gameRender(): void {
180 // Render the scene
181 renderer.render(this.scene, this.camera)
182 }
183}
184
185export const main = new NordicSkatingGame()
186```
187
188#### 2.2 Map Input Systems
189
190**Current Input:**
191- Keyboard (Arrow keys, WASD, M, R)
192- Mouse/Touch drag for steering
193- Gamepad buttons and sticks
194- Device orientation (tilt steering)
195
196**LittleJS Input Mapping:**
197```typescript
198// In gameUpdate()
199const steer = inputGetState('arrowLeft') - inputGetState('arrowRight')
200 + inputGetState('a') - inputGetState('d')
201 + inputGetAxis('mouseX') * 0.1 // Mouse/touch
202 + inputGetAxis('gamepadLeftX') // Gamepad
203 + inputGetAxis('deviceOrientationGamma') * 0.05 // Tilt
204
205const muted = inputGetPressed('m')
206const randomize = inputGetPressed('r')
207```
208
209**Note:** LittleJS has built-in device orientation support that we can use instead of our custom implementation.
210
211#### 2.3 Integrate Audio System
212
213**Option A: Keep Current Web Audio (Recommended)**
214The current audio system is highly specialized for ice physics. We can keep it and just trigger it from LittleJS's game loop.
215
216**Option B: Migrate to LittleJS Audio**
217LittleJS has a simpler audio system. We'd need to port the ice acoustics synthesis.
218
219**Recommendation:** Start with Option A, as the current audio is complex and physics-based. Later consider if LittleJS's audio effects can supplement it.
220
221### Phase 3: Scene and Rendering Migration (3-5 days)
222
223#### 3.1 Camera System
224
225**Current:**
226- First-person camera following skater
227- Dynamic FOV based on speed
228- Pitch and heading calculations
229
230**LittleJS Integration:**
231```typescript
232gameRender(): void {
233 // Update camera position based on skater
234 const push = this.skater.under === 0 && sincePush(this.skater) < pushFor
235 ? Math.sin((Math.PI * sincePush(this.skater)) / pushFor) : 0
236
237 const hurry = (this.skater.speed - PACES.cruise.speed) /
238 (PACES.olympic.speed - PACES.cruise.speed)
239 const crouch = hurry > 0 ? PACES.olympic.crouch * hurry :
240 PACES.meander.crouch * Math.min(1, -hurry * 2.3)
241 const fov = baseFov + 12 * Math.max(-0.4, Math.min(1.1, hurry))
242
243 camera.fov = fov
244 camera.aspect = engine.canvas.width / engine.canvas.height
245 camera.updateProjectionMatrix()
246
247 const along = Math.atan(this.skater.channel.slope(this.skater.s))
248 const look = this.skater.heading - 0.25 * Math.sin(this.skater.heading - along)
249 camera.position.set(
250 this.skater.x,
251 EYE - crouch + 0.03 * push - 1.3 * sunk,
252 this.skater.s
253 )
254 camera.rotation.set(-pitch, -look, -0.02 * this.skater.turn * this.skater.speed)
255
256 renderer.render(scene, camera)
257}
258```
259
260#### 3.2 World Rendering Components
261
262**Ice Rendering** (`src/render/ice.ts`):
263- Keep as Three.js mesh
264- Integrate into LittleJS render loop
265- Update texture coordinates based on skater position
266
267**Shores Rendering** (`src/render/shores.ts`):
268- Keep current Three.js implementation
269- Update chunk management in gameUpdate()
270
271**Sky Rendering** (`src/render/sky.ts`):
272- Keep as Three.js mesh with shaders
273- Update time-based animations in gameUpdate()
274
275**Snow and Drifts** (`src/render/snow.ts`, `src/render/drifts.ts`):
276- Keep current particle systems
277- Integrate with LittleJS particle system if beneficial
278
279**Marks** (`src/render/marks.ts`):
280- Keep current implementation
281- Update based on skater position
282
283### Phase 4: Game Logic Integration (2-3 days)
284
285#### 4.1 Skater Physics
286
287Port the skater update logic to use LittleJS's delta time:
288```typescript
289gameUpdate(): void {
290 const dt = engine.timing.lastTimeStep
291
292 // Update skater
293 const steer = this.getSteeringInput()
294 stepSkater(this.skater, this.lake, steer, dt)
295
296 // Update marks
297 marks.follow(this.skater.x, this.skater.s, dt)
298 if (dt > 0) marks.track(this.skater.x, this.skater.s)
299
300 // Check for pushes
301 const pushed = Math.floor(this.skater.stride) !== strides
302 if (pushed) {
303 marks.push(this.skater.x, this.skater.s, this.skater.heading,
304 Math.floor(this.skater.stride) % 2 ? 1 : -1)
305 }
306}
307```
308
309#### 4.2 Lake Generation
310
311Keep the procedural lake generation as-is. It's already frame-independent and seed-based.
312
313#### 4.3 Weather and Time Systems
314
315Integrate with LittleJS's timing system:
316```typescript
317// In gameUpdate()
318const weather = this.getCurrentWeather()
319const timeOfDay = this.getCurrentTime()
320
321// Update sky based on time
322sky.update(engine.timing.time)
323
324// Update wind
325const blowing = weather.wind[0] +
326 (weather.wind[1] - weather.wind[0]) *
327 (0.5 + 0.5 * Math.sin(engine.timing.time * 0.23) *
328 Math.sin(engine.timing.time * 0.071 + 1))
329```
330
331### Phase 5: UI Migration (2-3 days)
332
333#### 5.1 Settings and About Dialogs
334
335**Option A: Use LittleJS UI System**
336LittleJS has a basic UI system with buttons, sliders, etc. We can use this for the settings dialog.
337
338**Option B: Keep Current HTML/CSS UI**
339Keep the existing dialog elements and just trigger them from LittleJS input.
340
341**Recommendation:** Start with Option B for faster migration, then consider Option A for future enhancements.
342
343#### 5.2 Buttons and Controls
344
345Map current buttons to LittleJS:
346- Sound button: `inputGetPressed('m')` or UI button
347- Randomize: `inputGetPressed('r')` or UI button
348- Pace: UI button or keyboard
349- Tilt: UI button
350- Settings: UI button
351- About: UI button
352
353#### 5.3 HUD Elements
354
355LittleJS has built-in text rendering:
356```typescript
357gameRenderPost(): void {
358 // Draw HUD elements
359 drawTextScreen(`Speed: ${Math.round(this.skater.speed * 3.6)} km/h`,
360 vec2(10, 10), 20, colorWhite)
361
362 // Draw hints
363 if (this.showHint) {
364 drawTextScreen(this.hintText, vec2(centerX, 100), 30, colorWhite)
365 }
366}
367```
368
369### Phase 6: State Management (1-2 days)
370
371#### 6.1 Lake Seed and History
372
373**Current:** Uses browser history API to manage lake seeds
374
375**LittleJS Integration:**
376- Keep history API usage
377- Or use LittleJS's scene system for different "levels"
378
379**Recommendation:** Keep current approach as it works well for sharing links.
380
381#### 6.2 Settings Persistence
382
383Keep current localStorage approach, it's working well.
384
385### Phase 7: Audio Integration (2-3 days)
386
387#### 7.1 Ice Sound System
388
389**Keep Current Implementation:**
390The ice sound synthesis is complex and physics-based. It should remain as Web Audio API code.
391
392**Integration:**
393```typescript
394// In gameInit()
395audio = new AudioContext()
396sound = new IceSound(audio)
397
398// In gameUpdate()
399if (sounding()) {
400 sound.wind(blowing)
401 sound.glide(thickness, pushAmount)
402 sound.strike(thickness)
403 // etc.
404}
405```
406
407**Note:** Need to handle audio context suspend/resume based on user interaction.
408
409#### 7.2 Sound Button
410
411```typescript
412// In gameUpdate()
413if (inputGetPressed('m')) {
414 setMuted(!muted)
415}
416```
417
418### Phase 8: Testing and Optimization (3-5 days)
419
420#### 8.1 Input Testing
421- Test all input methods (keyboard, mouse, touch, gamepad, tilt)
422- Verify steering feel is preserved
423- Test on mobile devices
424
425#### 8.2 Performance Testing
426- Verify frame rate on desktop and mobile
427- Check memory usage
428- Test with various lake complexities
429
430#### 8.3 Audio Testing
431- Verify all ice sounds work correctly
432- Test wind and weather sounds
433- Check audio on mobile devices
434
435#### 8.4 Visual Testing
436- Verify ice rendering at all distances
437- Check shore and island rendering
438- Test sky and weather effects
439- Verify marks (tracks, cracks) rendering
440
441### Phase 9: Cleanup and Documentation (1-2 days)
442
443#### 9.1 Code Cleanup
444- Remove deprecated code
445- Update comments
446- Ensure consistent code style
447
448#### 9.2 Update Documentation
449- Update README with new LittleJS-based instructions
450- Update build and run instructions
451- Document any changes in behavior
452
453#### 9.3 Final Testing
454- Full regression testing
455- Performance benchmarking
456- Mobile device testing
457
458---
459
460## Detailed File Migration Plan
461
462### Core Files to Create/Modify
463
4641. **`src/littlejs-entry.ts`** (New)
465 - LittleJS engine initialization
466 - Entry point for the application
467
4682. **`src/main-littlejs.ts`** (New, replaces `src/main.ts`)
469 - Main game class
470 - Initializes all systems
471 - Manages game loop
472
4733. **`src/game-config.ts`** (New)
474 - Game configuration constants
475 - LittleJS engine settings
476
477### Files to Adapt
478
4791. **`src/world/lake.ts`** (Minimal changes)
480 - Keep as-is, may need to adjust imports
481 - Ensure compatible with ESM
482
4832. **`src/skater.ts`** (Minimal changes)
484 - Keep physics logic
485 - May need to adjust timing to use LittleJS delta time
486
4873. **`src/render/*.ts`** (Minimal changes)
488 - Keep Three.js rendering code
489 - May need to adjust how they're instantiated
490
4914. **`src/audio/iceSound.ts`** (Minimal changes)
492 - Keep Web Audio implementation
493 - Adjust how it's triggered
494
4955. **`src/input.ts`** (Significant changes)
496 - Replace with LittleJS input system
497 - May keep some utility functions (tiltSteer, padSteer)
498
4996. **`src/settings.ts`** (Minimal changes)
500 - Keep as-is
501 - May adjust how settings are applied
502
503### Files to Deprecate
504
5051. **`src/main.ts`** (Replace with `main-littlejs.ts`)
506
507---
508
509## Migration Order Recommendation
510
5111. **Setup Phase** (Day 1)
512 - Install LittleJS
513 - Create basic entry point
514 - Get LittleJS running with empty game
515
5162. **Core Integration** (Days 2-3)
517 - Set up Three.js with LittleJS
518 - Port input system
519 - Get basic rendering working
520
5213. **Game Logic** (Days 4-5)
522 - Integrate skater physics
523 - Integrate lake generation
524 - Basic movement working
525
5264. **Rendering** (Days 6-7)
527 - Integrate ice rendering
528 - Integrate shores
529 - Integrate sky
530
5315. **Audio** (Days 8-9)
532 - Integrate ice sounds
533 - Test audio triggering
534
5356. **UI** (Days 10-11)
536 - Port settings dialog
537 - Port about dialog
538 - Add HUD elements
539
5407. **Polish** (Days 12-13)
541 - Weather system
542 - Snow and drifts
543 - Marks (tracks, cracks)
544
5458. **Testing** (Days 14-15)
546 - Full testing on all devices
547 - Performance optimization
548 - Bug fixes
549
550---
551
552## Risk Assessment
553
554### Low Risk Items
555- Lake generation (already modular)
556- Settings system (already modular)
557- Three.js rendering (can coexist with LittleJS)
558
559### Medium Risk Items
560- Input system migration (need to test feel)
561- Skater physics timing (delta time changes)
562- Audio system integration
563
564### High Risk Items
565- Mobile device testing (need physical devices)
566- Performance on low-end devices
567- Tilt steering on iOS (permission issues)
568
569---
570
571## Rollback Plan
572
573If the migration encounters significant issues:
574
5751. **Partial Migration**: Keep using Three.js for rendering while using LittleJS for input, audio, and game loop
5762. **Incremental Migration**: Migrate one system at a time, keeping the old code until new is proven
5773. **Fallback**: Can always revert to current implementation if needed
578
579---
580
581## Success Criteria
582
5831. All current features work as before
5842. Frame rate >= current implementation
5853. Input feel is preserved or improved
5864. Audio quality is preserved
5875. Visual quality is preserved
5886. Mobile experience is improved
5897. Code is more maintainable
590
591---
592
593## Estimated Timeline
594
595| Phase | Duration | Start Date | End Date |
596|-------|----------|------------|----------|
597| Phase 1: Setup | 1-2 days | TBD | TBD |
598| Phase 2: Core Integration | 2-3 days | TBD | TBD |
599| Phase 3: Scene Migration | 3-5 days | TBD | TBD |
600| Phase 4: Game Logic | 2-3 days | TBD | TBD |
601| Phase 5: UI Migration | 2-3 days | TBD | TBD |
602| Phase 6: State Management | 1-2 days | TBD | TBD |
603| Phase 7: Audio Integration | 2-3 days | TBD | TBD |
604| Phase 8: Testing | 3-5 days | TBD | TBD |
605| Phase 9: Cleanup | 1-2 days | TBD | TBD |
606| **Total** | **17-26 days** | | |
607
608**Note:** This is a part-time estimate. For full-time work, reduce by ~50%.
609
610---
611
612## Next Steps
613
6141. Review this plan and provide feedback
6152. Set up development environment with LittleJS
6163. Create proof-of-concept with basic movement
6174. Proceed with migration phase by phase
6185. Test thoroughly at each phase
619
620---
621
622## Additional Considerations
623
624### LittleJS Features to Leverage Later
625
6261. **Particle System**: Could enhance snow, drifts, and crack effects
6272. **Level Editor**: Could be used for testing specific lake configurations
6283. **Pathfinding**: Could be used for autopilot improvements
6294. **Networking**: Could enable multiplayer features
6305. **Cloud Saves**: Could save settings and favorite lakes
6316. **Medals/Leaderboards**: Could add competitive elements
632
633### Features That Might Be Simplified
634
6351. **Input Handling**: LittleJS handles many input complexities
6362. **Audio Management**: LittleJS can manage audio context
6373. **Delta Time**: Automatic handling of frame timing
6384. **Canvas Management**: LittleJS handles canvas sizing and pixel ratio
639
640### Features to Keep Custom
641
6421. **Lake Generation**: Too specialized for LittleJS's built-in systems
6432. **Ice Acoustics**: Too physics-specific
6443. **Procedural World**: Unique to Nordic Skating
645
646---
647
648## Conclusion
649
650The migration to LittleJS is **feasible and beneficial**, particularly because of the Three.js plugin. The migration can be done incrementally with minimal disruption to the existing codebase. The biggest benefits will be in input handling, game loop management, and mobile optimization.
651
652**Recommendation:** Proceed with the migration as outlined in this plan.