@zakkster/lite-camera-pro 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,692 @@
1
+ # @zakkster/lite-camera-pro
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@zakkster/lite-camera-pro.svg?style=for-the-badge&color=latest)](https://www.npmjs.com/package/@zakkster/lite-camera-pro)
4
+ ![Zero-GC](https://img.shields.io/badge/Zero--GC-Hot%20path-00C853?style=for-the-badge&logo=leaf&logoColor=white)
5
+ [![sponsor](https://img.shields.io/badge/sponsor-PeshoVurtoleta-ea4aaa.svg?logo=github)](https://github.com/sponsors/PeshoVurtoleta)
6
+ [![npm bundle size](https://img.shields.io/bundlephobia/minzip/@zakkster/lite-camera-pro?style=for-the-badge)](https://bundlephobia.com/result?p=@zakkster/lite-camera-pro)
7
+ [![npm downloads](https://img.shields.io/npm/dm/@zakkster/lite-camera-pro?style=for-the-badge&color=blue)](https://www.npmjs.com/package/@zakkster/lite-camera-pro)
8
+ [![npm total downloads](https://img.shields.io/npm/dt/@zakkster/lite-camera-pro?style=for-the-badge&color=blue)](https://www.npmjs.com/package/@zakkster/lite-camera-pro)
9
+ ![TypeScript](https://img.shields.io/badge/TypeScript-Types-informational)
10
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](https://opensource.org/licenses/MIT)
11
+
12
+ ### Cinematic Camera System for Canvas2D Games
13
+
14
+ > Zero-GC. Zero external deps. Framework-agnostic.
15
+ > One import. Every camera feature a 2D game needs.
16
+
17
+ **2,916 lines of source · 10 modules · Full TypeScript · 105 unit tests**
18
+
19
+ ```
20
+ npm install @zakkster/lite-camera-pro
21
+ ```
22
+
23
+ ---
24
+
25
+ ## Why Pro?
26
+
27
+ You already ship games with `@zakkster/lite-camera`. It handles follow, deadzone, lookahead, and basic shake. That's enough to prototype.
28
+
29
+ **lite-camera-pro** is what you reach for when the prototype becomes a product:
30
+
31
+ - The boss reveal that zooms in, shakes, holds, then swoops back
32
+ - The co-op mode where the camera frames both players automatically
33
+ - The parallax layers that scroll at different depths
34
+ - The platformer camera that's smooth horizontally but pixel-locked vertically
35
+ - The explosion that layers three different shakes simultaneously
36
+
37
+ All of it runs at 60fps with **zero garbage collection** in the hot path.
38
+
39
+ ---
40
+
41
+ ## Architecture
42
+
43
+ ```mermaid
44
+ graph TB
45
+ subgraph "@zakkster/lite-camera-pro"
46
+ Core["CinematicCameraPro<br/><i>894 lines · main class</i>"]
47
+ Follow["FollowMode<br/><i>179 lines · 5 strategies</i>"]
48
+ Multi["MultiTarget<br/><i>125 lines · bbox framing</i>"]
49
+ Shake["ShakeEngine<br/><i>286 lines · 8-slot noise pool</i>"]
50
+ Presets["ShakePresets<br/><i>177 lines · 8 built-in profiles</i>"]
51
+ Seq["CameraSequence<br/><i>513 lines · timeline cinematics</i>"]
52
+ Parallax["ParallaxManager<br/><i>199 lines · 16 layers</i>"]
53
+ Bounds["BoundsSystem<br/><i>220 lines · per-edge behavior</i>"]
54
+ Debug["DebugHUD<br/><i>290 lines · toggleable overlay</i>"]
55
+ end
56
+
57
+ Core --> Follow
58
+ Core --> Multi
59
+ Core --> Shake
60
+ Core --> Seq
61
+ Core --> Parallax
62
+ Core --> Bounds
63
+ Core --> Debug
64
+ Shake --> Presets
65
+
66
+ style Core fill:#fbbf24,stroke:#92400e,color:#000
67
+ style Seq fill:#a78bfa,stroke:#5b21b6,color:#000
68
+ style Shake fill:#ef4444,stroke:#991b1b,color:#fff
69
+ style Follow fill:#22d3ee,stroke:#155e75,color:#000
70
+ style Multi fill:#34d399,stroke:#065f46,color:#000
71
+ style Parallax fill:#34d399,stroke:#065f46,color:#000
72
+ style Bounds fill:#22d3ee,stroke:#155e75,color:#000
73
+ style Presets fill:#f87171,stroke:#991b1b,color:#000
74
+ style Debug fill:#6b7280,stroke:#374151,color:#fff
75
+ ```
76
+
77
+ Every module is a separate file. Tree-shaking drops what you don't use.
78
+
79
+ ---
80
+
81
+ ## Dependency Graph
82
+
83
+ ```mermaid
84
+ graph LR
85
+ Pro["lite-camera-pro"]
86
+ Cam["lite-camera"]
87
+ Ease["lite-ease"]
88
+ Lerp["lite-lerp"]
89
+ Noise["lite-noise"]
90
+ TL["lite-timeline"]
91
+
92
+ Pro --> Cam
93
+ Pro --> Ease
94
+ Pro --> Lerp
95
+ Pro --> Noise
96
+ Pro --> TL
97
+
98
+ style Pro fill:#fbbf24,stroke:#92400e,color:#000,stroke-width:2px
99
+ style Cam fill:#1e1e2e,stroke:#fbbf24,color:#fbbf24
100
+ style Ease fill:#1e1e2e,stroke:#a78bfa,color:#a78bfa
101
+ style Lerp fill:#1e1e2e,stroke:#34d399,color:#34d399
102
+ style Noise fill:#1e1e2e,stroke:#ef4444,color:#ef4444
103
+ style TL fill:#1e1e2e,stroke:#a78bfa,color:#a78bfa
104
+ ```
105
+
106
+ All `@zakkster` packages. Zero third-party dependencies in the final bundle.
107
+
108
+ ---
109
+
110
+ ## lite-camera vs lite-camera-pro
111
+
112
+ | Feature | lite-camera | lite-camera-pro |
113
+ |:---|:---:|:---:|
114
+ | Smooth follow + deadzone + lookahead | ✓ | ✓ |
115
+ | Basic RNG shake | ✓ | — |
116
+ | Canvas transform apply | ✓ | ✓ |
117
+ | Debug rectangle | ✓ | ✓ |
118
+ | | | |
119
+ | **Zoom** (smooth, eased, anchor-point) | | ✓ |
120
+ | **Dynamic zoom-at-target** (tracks moving objects) | | ✓ |
121
+ | **5 follow modes** (smooth / lock / predictive / cut / hybrid) | | ✓ |
122
+ | **Multi-target auto-framing** (bounding box + auto-zoom) | | ✓ |
123
+ | **Noise-based shake** (simplex, 8 simultaneous layers) | | ✓ |
124
+ | **Directional shake** (recoil, landing, per-axis) | | ✓ |
125
+ | **8 shake presets** + custom registry | | ✓ |
126
+ | **Cinematic sequences** (timeline-driven camera moves) | | ✓ |
127
+ | **Fluent sequence builder** (moveTo · zoomTo · shake · wait · call) | | ✓ |
128
+ | **Sequence presets** (panTo · dramaticZoom · bossReveal) | | ✓ |
129
+ | **Parallax layer manager** (16 layers, per-layer speed) | | ✓ |
130
+ | **Smart bounds** (hard / soft / elastic / none — per-edge) | | ✓ |
131
+ | **Dynamic bounds** (room transitions, arenas) | | ✓ |
132
+ | **Pro debug HUD** (toggleable panels, trauma bars, sequence progress) | | ✓ |
133
+ | **Zero-alloc coordinate conversion** (screenToWorld / worldToScreen) | | ✓ |
134
+ | **Save / Load** (getState / setState) | | ✓ |
135
+ | **TypeScript declarations** | | ✓ |
136
+
137
+ ---
138
+
139
+ ## Quick Start
140
+
141
+ ```js
142
+ import { CinematicCameraPro, FollowMode } from '@zakkster/lite-camera-pro';
143
+
144
+ const cam = new CinematicCameraPro(
145
+ canvas.width, // viewport width
146
+ canvas.height, // viewport height
147
+ WORLD_W, // world width
148
+ WORLD_H // world height
149
+ );
150
+
151
+ // ── Game loop ──
152
+ function update(dt) {
153
+ cam.update(dt, player.x, player.y, player.vx, player.vy);
154
+ }
155
+
156
+ function render(ctx) {
157
+ ctx.save();
158
+ cam.apply(ctx); // transforms the canvas
159
+ drawWorld(ctx);
160
+ cam.debug(ctx); // world-space overlay
161
+ ctx.restore();
162
+ cam.debugHUD(ctx); // screen-space HUD
163
+ }
164
+ ```
165
+
166
+ ---
167
+
168
+ ## Feature Guide
169
+
170
+ ### Follow Modes
171
+
172
+ ```mermaid
173
+ stateDiagram-v2
174
+ direction LR
175
+ SMOOTH --> LOCK : setMode()
176
+ SMOOTH --> PREDICTIVE : setMode()
177
+ SMOOTH --> CUT : setMode()
178
+ SMOOTH --> HYBRID : setMode()
179
+ LOCK --> SMOOTH : setMode()
180
+ PREDICTIVE --> SMOOTH : setMode()
181
+ CUT --> SMOOTH : setMode()
182
+ HYBRID --> SMOOTH : setMode()
183
+
184
+ note right of SMOOTH : Deadzone + lookahead + lerp<br/>Default. Good for everything.
185
+ note right of LOCK : Instant snap. No interpolation.<br/>Top-down shooters.
186
+ note right of PREDICTIVE : Velocity extrapolation.<br/>Racing games, fast runners.
187
+ note right of CUT : Hard jump. Zero lerp.<br/>Cutscene transitions.
188
+ note right of HYBRID : Smooth-X, locked-Y.<br/>Platformer standard.
189
+ ```
190
+
191
+ Switch mid-gameplay. No position jumps (except CUT, which jumps by design).
192
+
193
+ ```js
194
+ cam.setMode(FollowMode.SMOOTH); // deadzone + lookahead + lerp
195
+ cam.setMode(FollowMode.LOCK); // instant snap, no interpolation
196
+ cam.setMode(FollowMode.PREDICTIVE); // velocity extrapolation
197
+ cam.setMode(FollowMode.CUT); // hard cut (cutscene transitions)
198
+ cam.setMode(FollowMode.HYBRID); // smooth horizontal, locked vertical
199
+
200
+ // Predictive tuning
201
+ cam.predictTime = 0.5; // seconds of velocity extrapolation
202
+
203
+ // Hybrid tuning
204
+ cam.hybridVerticalSnap = true; // instant vertical (default)
205
+ cam.hybridVerticalSnap = false; // fast-lerp vertical
206
+ ```
207
+
208
+ ---
209
+
210
+ ### Zoom System
211
+
212
+ ```js
213
+ // Smooth zoom with easing
214
+ cam.setZoom(2.0, 0.5, easeOutExpo); // zoom to 2× over 0.5s
215
+
216
+ // Zoom toward a static world point
217
+ cam.zoomAt(400, 300, 1.8, 0.8, easeOutExpo);
218
+
219
+ // Zoom toward a MOVING target — anchor follows the object each frame
220
+ cam.zoomAt(boss, 1.8, 0.8, easeOutExpo);
221
+
222
+ // Zoom limits
223
+ cam.minZoom = 0.25;
224
+ cam.maxZoom = 4.0;
225
+
226
+ // Read visible area (cached, zero-alloc — use for frustum culling)
227
+ const w = cam.visibleW; // viewW / zoom
228
+ const h = cam.visibleH; // viewH / zoom
229
+ ```
230
+
231
+ **Coordinate conversion** (zero-alloc, caller-owned `out` pattern):
232
+ ```js
233
+ const pt = { x: 0, y: 0 }; // allocate once at init
234
+ cam.screenToWorld(mouseX, mouseY, pt);
235
+ cam.worldToScreen(enemy.x, enemy.y, pt);
236
+ ```
237
+
238
+ ---
239
+
240
+ ### Multi-Target Framing
241
+
242
+ ```mermaid
243
+ graph LR
244
+ subgraph Viewport
245
+ direction TB
246
+ P1["Player 1"]
247
+ P2["Player 2"]
248
+ BB["Bounding Box<br/>+ padding"]
249
+ end
250
+
251
+ BB --> AutoZoom["Auto-Zoom<br/><i>fit bbox into viewport</i>"]
252
+ BB --> AutoCenter["Auto-Center<br/><i>track bbox midpoint</i>"]
253
+ AutoZoom --> Smooth["Exponential<br/>Damping"]
254
+ AutoCenter --> Smooth
255
+
256
+ style P1 fill:#fbbf24,stroke:#92400e,color:#000
257
+ style P2 fill:#22d3ee,stroke:#155e75,color:#000
258
+ style BB fill:none,stroke:#a78bfa,stroke-dasharray:5 5,color:#a78bfa
259
+ style AutoZoom fill:#a78bfa,stroke:#5b21b6,color:#000
260
+ style AutoCenter fill:#a78bfa,stroke:#5b21b6,color:#000
261
+ style Smooth fill:#34d399,stroke:#065f46,color:#000
262
+ ```
263
+
264
+ ```js
265
+ // Track two players — camera auto-zooms to keep both visible
266
+ cam.trackMultiple([player1, player2], {
267
+ padding: 120, // world-space padding around the bounding box
268
+ minZoom: 0.4,
269
+ maxZoom: 1.8,
270
+ zoomSpeed: 4.0, // zoom smoothing (higher = snappier)
271
+ followSpeed: 5.0, // position smoothing
272
+ });
273
+
274
+ // Add a third target dynamically
275
+ cam.trackMultiple([player1, player2, boss], { padding: 100 });
276
+
277
+ // Return to single-target follow (smooth transition)
278
+ cam.trackSingle();
279
+ ```
280
+
281
+ ---
282
+
283
+ ### Shake System
284
+
285
+ ```mermaid
286
+ graph TB
287
+ subgraph "Shake Engine — 8 simultaneous slots"
288
+ S1["Slot 1<br/>EXPLOSION<br/>trauma=0.8"]
289
+ S2["Slot 2<br/>RECOIL ↑<br/>trauma=0.5"]
290
+ S3["Slot 3<br/>RUMBLE<br/>trauma=0.2"]
291
+ S4["Slot 4–8<br/><i>available</i>"]
292
+ end
293
+
294
+ S1 --> Sum["Sum All Layers"]
295
+ S2 --> Sum
296
+ S3 --> Sum
297
+ Sum --> Noise["Simplex Noise<br/><i>smooth, organic</i>"]
298
+ Noise --> Out["offsetX · offsetY · angle"]
299
+ Out --> Canvas["ctx.translate() + ctx.rotate()"]
300
+
301
+ style S1 fill:#ef4444,stroke:#991b1b,color:#fff
302
+ style S2 fill:#a78bfa,stroke:#5b21b6,color:#000
303
+ style S3 fill:#f97316,stroke:#9a3412,color:#000
304
+ style S4 fill:#374151,stroke:#4b5563,color:#9ca3af
305
+ style Noise fill:#fbbf24,stroke:#92400e,color:#000
306
+ style Sum fill:#1e1e2e,stroke:#6b7280,color:#d1d5db
307
+ style Out fill:#1e1e2e,stroke:#6b7280,color:#d1d5db
308
+ style Canvas fill:#1e1e2e,stroke:#6b7280,color:#d1d5db
309
+ ```
310
+
311
+ Multiple shakes run simultaneously and sum together. Each slot has its own trauma, frequency, decay rate, and direction.
312
+
313
+ ```js
314
+ // Backward-compatible simple trauma
315
+ cam.addTrauma(0.5);
316
+
317
+ // Named presets
318
+ cam.shakePreset('explosion'); // big boom
319
+ cam.shakePreset('earthquake'); // sustained rumble
320
+ cam.shakePreset('recoil'); // directional upward kick
321
+ cam.shakePreset('impact'); // sharp snappy jolt
322
+ cam.shakePreset('landing'); // vertical downward push
323
+ cam.shakePreset('damage'); // quick pulse, no rotation
324
+ cam.shakePreset('rumble'); // continuous low vibration
325
+ cam.shakePreset('heavy_impact'); // maximum everything
326
+
327
+ // Custom profile
328
+ cam.shake({
329
+ trauma: 0.6,
330
+ freq: 18, // noise frequency (higher = jittery)
331
+ decay: 1.5, // trauma units lost per second
332
+ maxOffset: 20, // max pixel displacement
333
+ maxAngle: 0.03, // max rotation (radians)
334
+ dirX: 1, // directional X (0 = omnidirectional)
335
+ dirY: 0, // directional Y
336
+ });
337
+
338
+ // Layer multiple shakes for complex events
339
+ cam.shakePreset('explosion');
340
+ cam.shakePreset('recoil', 0.7); // half intensity
341
+ cam.shakePreset('rumble');
342
+
343
+ // Register custom presets
344
+ import { registerPreset } from '@zakkster/lite-camera-pro';
345
+
346
+ registerPreset('sword_clash', {
347
+ trauma: 0.3, freq: 28, decay: 3.0,
348
+ maxOffset: 8, maxAngle: 0.03,
349
+ dirX: 1, dirY: 0,
350
+ });
351
+
352
+ cam.shakePreset('sword_clash');
353
+
354
+ // Stop all shakes immediately
355
+ cam.clearShakes();
356
+ ```
357
+
358
+ ---
359
+
360
+ ### Cinematic Sequences
361
+
362
+ ```mermaid
363
+ sequenceDiagram
364
+ participant G as Gameplay
365
+ participant S as Sequence
366
+ participant C as Camera
367
+
368
+ G->>S: camera.playSequence(seq)
369
+ Note over G: Follow mode paused
370
+
371
+ S->>C: moveTo(boss.x, boss.y, 1200ms)
372
+ S->>C: zoomTo(1.8, 800ms)
373
+ S->>C: shake('explosion')
374
+ S->>C: wait(600ms)
375
+ S->>C: call(() => boss.startPhase2())
376
+ S->>C: moveAndZoom(player, 1.0, 1000ms)
377
+
378
+ S-->>G: onComplete callback
379
+ Note over G: Follow mode resumes<br/>smooth blend-back
380
+ ```
381
+
382
+ **The killer feature.** Chain camera moves with a fluent API. The sequence takes full control of position and zoom. When it ends, follow mode resumes with a smooth transition.
383
+
384
+ ```js
385
+ const seq = cam.createSequence({ onComplete: () => showUI() })
386
+ .moveTo(boss.x, boss.y, 1200) // pan to boss
387
+ .zoomTo(1.8, 800) // zoom in
388
+ .shake('explosion') // screen shake
389
+ .wait(600) // hold for drama
390
+ .call(() => boss.startPhase2()) // trigger game event
391
+ .moveAndZoom(player.x, player.y, 1.0, 1000); // return to player
392
+
393
+ cam.playSequence(seq);
394
+
395
+ // Playback control
396
+ cam.stopSequence(); // cancel + smooth return to follow
397
+ seq.pause(); // freeze
398
+ seq.resume(); // continue
399
+ seq.seek(2000); // jump to 2s mark
400
+ ```
401
+
402
+ **Sequence presets** for common patterns:
403
+ ```js
404
+ import { panTo, dramaticZoom, bossReveal, timedShake } from '@zakkster/lite-camera-pro';
405
+
406
+ // Simple pan
407
+ cam.playSequence(panTo(cam, 800, 400, 1500));
408
+
409
+ // Boss reveal: zoom in → shake → hold → return
410
+ cam.playSequence(bossReveal(cam, boss.x, boss.y, 3000));
411
+
412
+ // Dramatic zoom with overshoot easing
413
+ cam.playSequence(dramaticZoom(cam, boss.x, boss.y, 2.5, 1200));
414
+ ```
415
+
416
+ ---
417
+
418
+ ### Parallax Layers
419
+
420
+ ```mermaid
421
+ graph LR
422
+ subgraph "Scroll Speed"
423
+ Sky["☁ Sky<br/>speed: 0.1"]
424
+ Mountains["⛰ Mountains<br/>speed: 0.3"]
425
+ Trees["🌲 Trees<br/>speed: 0.7"]
426
+ Game["🎮 Game Layer<br/>speed: 1.0"]
427
+ Foreground["🌿 Foreground<br/>speed: 1.3"]
428
+ end
429
+
430
+ Sky ~~~ Mountains ~~~ Trees ~~~ Game ~~~ Foreground
431
+
432
+ style Sky fill:#1e3a5f,stroke:#2563eb,color:#93c5fd
433
+ style Mountains fill:#1e3a5f,stroke:#2563eb,color:#93c5fd
434
+ style Trees fill:#064e3b,stroke:#059669,color:#6ee7b7
435
+ style Game fill:#fbbf24,stroke:#92400e,color:#000
436
+ style Foreground fill:#064e3b,stroke:#059669,color:#6ee7b7
437
+ ```
438
+
439
+ Up to 16 layers. Each scrolls at its own speed relative to the camera.
440
+
441
+ ```js
442
+ cam.addParallaxLayer('sky', 0.1); // barely moves
443
+ cam.addParallaxLayer('mountains', 0.3); // slow
444
+ cam.addParallaxLayer('trees', 0.7); // medium
445
+ // game layer is the normal camera (1.0)
446
+ cam.addParallaxLayer('foreground', 1.3); // moves faster than camera
447
+
448
+ // Render each layer with its own transform
449
+ ctx.save();
450
+ cam.applyParallax('sky', ctx);
451
+ drawSky(ctx);
452
+ ctx.restore();
453
+
454
+ ctx.save();
455
+ cam.applyParallax('mountains', ctx);
456
+ drawMountains(ctx);
457
+ ctx.restore();
458
+
459
+ ctx.save();
460
+ cam.apply(ctx); // normal game layer
461
+ drawWorld(ctx);
462
+ ctx.restore();
463
+
464
+ ctx.save();
465
+ cam.applyParallax('foreground', ctx);
466
+ drawForeground(ctx);
467
+ ctx.restore();
468
+
469
+ // Update or remove layers
470
+ cam.addParallaxLayer('sky', 0.15); // update speed by re-adding same id
471
+ cam.removeParallaxLayer('foreground');
472
+ ```
473
+
474
+ ---
475
+
476
+ ### Smart Bounds
477
+
478
+ ```mermaid
479
+ graph LR
480
+ subgraph "Boundary Behavior"
481
+ H["HARD<br/><i>stops at edge</i>"]
482
+ S["SOFT<br/><i>decelerates smoothly</i>"]
483
+ E["ELASTIC<br/><i>overshoot + spring back</i>"]
484
+ N["NONE<br/><i>no enforcement</i>"]
485
+ end
486
+
487
+ style H fill:#ef4444,stroke:#991b1b,color:#fff
488
+ style S fill:#fbbf24,stroke:#92400e,color:#000
489
+ style E fill:#a78bfa,stroke:#5b21b6,color:#000
490
+ style N fill:#374151,stroke:#4b5563,color:#9ca3af
491
+ ```
492
+
493
+ Configure boundary behavior per-edge. Mix and match.
494
+
495
+ ```js
496
+ import { BoundsType } from '@zakkster/lite-camera-pro';
497
+
498
+ // All edges the same
499
+ cam.setBoundsType(BoundsType.SOFT);
500
+
501
+ // Per-edge configuration
502
+ cam.setBoundsEdges({
503
+ left: BoundsType.HARD,
504
+ right: BoundsType.SOFT,
505
+ top: BoundsType.ELASTIC,
506
+ bottom: BoundsType.HARD,
507
+ });
508
+
509
+ // Tuning
510
+ cam._bounds.softZone = 80; // deceleration zone width (pixels)
511
+ cam._bounds.elasticMax = 30; // max overshoot (pixels)
512
+ cam._bounds.elasticStrength = 8.0; // spring-back speed
513
+
514
+ // Dynamic bounds for rooms / arenas
515
+ cam.setBoundsRect(200, 200, 1200, 800); // constrain to rectangle
516
+ cam.clearBoundsRect(); // revert to full world
517
+ ```
518
+
519
+ ---
520
+
521
+ ### Debug HUD
522
+
523
+ The Pro debug overlay shows everything at a glance. Each panel is individually toggleable.
524
+
525
+ ```js
526
+ // Toggle panels on/off
527
+ cam.debugConfig.show.shake = false;
528
+ cam.debugConfig.show.parallax = false;
529
+ cam.debugConfig.show.bounds = true;
530
+
531
+ // Render
532
+ ctx.save();
533
+ cam.apply(ctx);
534
+ cam.debug(ctx); // world-space: deadzone rect, lookahead vector, world bounds
535
+ ctx.restore();
536
+ cam.debugHUD(ctx); // screen-space: position, zoom, mode, shake bars, sequence %
537
+ ```
538
+
539
+ **Panels:** position · zoom · follow mode · shake slots (per-slot trauma bars) · sequence progress · parallax layers · bounds type
540
+
541
+ The debug HUD uses **zero allocations per frame** — it draws directly to canvas with no intermediate objects.
542
+
543
+ ---
544
+
545
+ ### Save & Load
546
+
547
+ ```js
548
+ // Capture snapshot
549
+ const snapshot = cam.getState();
550
+ // → { posX, posY, targetX, targetY, zoom, mode }
551
+
552
+ // Restore
553
+ cam.setState(snapshot);
554
+ // Updates position, zoom, mode, and recalculates visible dimensions
555
+
556
+ // Serialize for save files
557
+ localStorage.setItem('camera', JSON.stringify(cam.getState()));
558
+ cam.setState(JSON.parse(localStorage.getItem('camera')));
559
+ ```
560
+
561
+ ---
562
+
563
+ ## Update Loop Integration
564
+
565
+ ```mermaid
566
+ flowchart TB
567
+ Start["camera.update(dt, px, py, vx, vy)"] --> SeqCheck{Sequence<br/>active?}
568
+
569
+ SeqCheck -->|Yes| SeqPath["Read sequence state<br/><i>position + zoom from timeline</i>"]
570
+ SeqCheck -->|No| MTCheck{Multi-target<br/>active?}
571
+
572
+ MTCheck -->|Yes| MTPath["Compute bounding box<br/>Auto-zoom + center"]
573
+ MTCheck -->|No| SinglePath["Follow strategy<br/><i>SMOOTH / LOCK / PREDICTIVE / CUT / HYBRID</i>"]
574
+
575
+ SinglePath --> ZoomAnim["Zoom animation<br/><i>lerp + easing</i>"]
576
+ ZoomAnim --> BoundsCalc["Update visible dims"]
577
+
578
+ SeqPath --> Bounds
579
+ MTPath --> Bounds
580
+ BoundsCalc --> Bounds
581
+
582
+ Bounds["Apply bounds<br/><i>HARD / SOFT / ELASTIC / NONE</i>"]
583
+ Bounds --> Lerp["Smooth follow<br/><i>pos += (target - pos) × speed × dt</i>"]
584
+ Lerp --> Parallax["Update parallax layers"]
585
+ Parallax --> Shake["Update shake decay"]
586
+ Shake --> Done["Frame complete"]
587
+
588
+ style Start fill:#fbbf24,stroke:#92400e,color:#000
589
+ style SeqPath fill:#a78bfa,stroke:#5b21b6,color:#000
590
+ style MTPath fill:#34d399,stroke:#065f46,color:#000
591
+ style SinglePath fill:#22d3ee,stroke:#155e75,color:#000
592
+ style Shake fill:#ef4444,stroke:#991b1b,color:#fff
593
+ style Done fill:#1e1e2e,stroke:#6b7280,color:#d1d5db
594
+ ```
595
+
596
+ One call to `update()` handles everything. The camera automatically dispatches to the right code path based on active state (sequence > multi-target > follow mode).
597
+
598
+ ---
599
+
600
+ ## Module Reference
601
+
602
+ | Module | Lines | Purpose |
603
+ |:---|---:|:---|
604
+ | `CinematicCameraPro.js` | 894 | Main class. Zoom, modes, multi-target, shake, sequences, parallax, bounds |
605
+ | `CameraSequence.js` | 513 | Fluent timeline builder + sequence presets |
606
+ | `DebugHUD.js` | 290 | Screen-space + world-space debug overlays |
607
+ | `ShakeEngine.js` | 286 | 8-slot noise-based shake pool |
608
+ | `BoundsSystem.js` | 220 | Per-edge boundary enforcement |
609
+ | `ParallaxManager.js` | 199 | 16-layer scroll manager |
610
+ | `FollowMode.js` | 179 | 5 pure follow strategies |
611
+ | `ShakePresets.js` | 177 | 8 frozen profiles + custom registry |
612
+ | `MultiTarget.js` | 125 | Bounding box framing + auto-zoom |
613
+ | `index.d.ts` | 228 | Full TypeScript declarations |
614
+ | `index.js` | 33 | Public exports (tree-shakeable) |
615
+ | **Total** | **3,144** | |
616
+
617
+ ---
618
+
619
+ ## Zero-GC Design
620
+
621
+ Every hot-path function in lite-camera-pro is allocation-free:
622
+
623
+ - **Coordinate conversion** uses caller-owned `out` objects (never returns `{ x, y }`)
624
+ - **Visible dimensions** are cached as `cam.visibleW` / `cam.visibleH` (no `getVisibleArea()`)
625
+ - **Shake slots** are pre-allocated in a fixed-size pool (8 slots, reused via steal)
626
+ - **Follow modes** are pure functions that mutate `cam.target[]` directly
627
+ - **Debug HUD** draws directly to canvas — no intermediate line objects
628
+ - **Parallax layers** are pre-allocated (16 slots, mutated in place)
629
+ - **Bounds state** is a single pre-allocated config object
630
+
631
+ The only allocations happen during **setup** (constructor, `createSequence()`, `trackMultiple()`) — never inside the 60fps update/render loop.
632
+
633
+ ---
634
+
635
+ ## TypeScript
636
+
637
+ Full declarations ship in `src/index.d.ts`:
638
+
639
+ ```ts
640
+ import {
641
+ CinematicCameraPro,
642
+ FollowMode,
643
+ BoundsType,
644
+ WrapMode,
645
+ createCameraSequence,
646
+ EXPLOSION,
647
+ registerPreset,
648
+ } from '@zakkster/lite-camera-pro';
649
+
650
+ const cam = new CinematicCameraPro(800, 600, 3200, 2400);
651
+ cam.setMode(FollowMode.PREDICTIVE);
652
+ cam.setBoundsType(BoundsType.ELASTIC);
653
+
654
+ const seq: CameraSequence = cam.createSequence()
655
+ .moveTo(400, 300, 1200)
656
+ .zoomTo(2.0, 800)
657
+ .shake('explosion');
658
+ ```
659
+
660
+ ---
661
+
662
+ ## Testing
663
+
664
+ ```bash
665
+ npm test # vitest run — 105 tests across 2 files
666
+ npm run test:watch # vitest watch mode
667
+ ```
668
+
669
+ `CinematicCameraPro.test.js` covers the facade: initialization, coordinate conversion, all 5 follow modes, multi-target framing (including overlapping-target edge cases), zoom animation, shake engine (slot stealing, directional normalization, decay), bounds enforcement, parallax management, sequences, save/load, and destruction. `subsystems.test.js` covers the directly-exported API: the DebugHUD draws (mock-context smoke tests), the functional shake / parallax / bounds helpers, the multi-target updater, the shake-preset registry, and the sequence preset helpers (panTo, dramaticZoom, bossReveal, timedShake).
670
+
671
+ ---
672
+
673
+ ## Migration from lite-camera
674
+
675
+ lite-camera-pro extends `CinematicCamera`. Drop-in replacement:
676
+
677
+ ```diff
678
+ - import { CinematicCamera } from '@zakkster/lite-camera';
679
+ + import { CinematicCameraPro as CinematicCamera } from '@zakkster/lite-camera-pro';
680
+
681
+ const cam = new CinematicCamera(800, 600, 3200, 2400);
682
+ // Everything from lite-camera still works.
683
+ // addTrauma(), update(), apply(), debug() — all backward-compatible.
684
+ ```
685
+
686
+ Then add Pro features incrementally. Nothing breaks.
687
+
688
+ ---
689
+
690
+ ## License
691
+
692
+ MIT © Zahary Shinikchiev. See [LICENSE](LICENSE).