@zakkster/lite-camera-pro 1.0.0 → 1.0.1
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/CHANGELOG.md +75 -0
- package/llms.txt +2 -0
- package/package.json +11 -6
- package/src/BoundsSystem.js +1 -1
- package/src/CinematicCameraPro.js +42 -8
- package/src/MultiTarget.js +1 -1
- package/src/ParallaxManager.js +4 -1
- package/src/ShakeEngine.js +19 -1
- package/src/index.js +13 -11
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@zakkster/lite-camera-pro` are documented here. The
|
|
4
|
+
format follows Keep a Changelog, and this project adheres to Semantic
|
|
5
|
+
Versioning. Version lives in three places at once -- `package.json`, the
|
|
6
|
+
`VERSION` const in `src/index.js`, and the `Version:` header in `llms.txt` --
|
|
7
|
+
bumped together or not at all.
|
|
8
|
+
|
|
9
|
+
## [1.0.1] -- 2026-08-25
|
|
10
|
+
|
|
11
|
+
Foundation release: make the suite run, gate it, and land the fixes that cost
|
|
12
|
+
nothing at the public surface. No behavior change to any valid, finite,
|
|
13
|
+
in-bounds call -- CP-13 and CP-14 change only what defective input does.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **CP-1 -- the standalone functional API is now reachable from the package
|
|
18
|
+
entry.** `src/index.js` re-exports `createShakeState` (Shake Engine) and
|
|
19
|
+
`createMultiTargetState` (Multi-Target); before, every state-taking function
|
|
20
|
+
in the documented tree-shakeable layer took a state no consumer could
|
|
21
|
+
construct. Reported in `BRIEF.md` by the Las Vegas scratch-card consumer.
|
|
22
|
+
- **CP-8 -- use-after-destroy fails closed.** After
|
|
23
|
+
`CinematicCameraPro.destroy()`, EVERY public method throws an error with code
|
|
24
|
+
`ERR_CAMERA_DESTROYED` (fail closed) instead of a raw null deref. destroy()
|
|
25
|
+
calls `super.destroy()` for base parity, then rebinds the whole public surface
|
|
26
|
+
to the named-error sentinel, so the guarantee cannot drift as methods are
|
|
27
|
+
added. A double `destroy()` throws the same named error.
|
|
28
|
+
- **CP-13 -- uniform floor snap.** `apply()` and `applyParallaxLayer()` now
|
|
29
|
+
snap the world/layer scroll with `Math.floor` instead of `| 0`, matching the
|
|
30
|
+
base camera. `| 0` truncated toward zero, disagreeing with the base by a full
|
|
31
|
+
pixel at negative fractional positions and taking a double-length integer step
|
|
32
|
+
about the origin.
|
|
33
|
+
- **CP-14 + H-F -- trauma default fail-closed.** `addShake`/`addTraumaSimple`
|
|
34
|
+
replaced the falsy `profile.trauma || 0.5` default: `undefined` still means
|
|
35
|
+
0.5, but a non-finite trauma/intensity now activates NO slot (fail closed) and
|
|
36
|
+
an explicit `0` fires nothing. The old `||` laundered `NaN` into 0.5 -- the
|
|
37
|
+
only reason a NaN could not poison the shake sum from the trauma side (H-F).
|
|
38
|
+
The laundering is removed and the poison door closed in one change. Guards live
|
|
39
|
+
in the cold entry functions only; the per-frame `updateShake`/`computeShake`
|
|
40
|
+
loops are unchanged.
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
- **node:test suite.** Ported both vitest suites to `node:test` +
|
|
45
|
+
`node:assert/strict` (facade + standalone subsystems), added
|
|
46
|
+
`test/consumer.test.js` (the feel-freeze pinning the consumer's three-tier
|
|
47
|
+
shake ramp), `test/regressions.test.js` (one named test per finding above),
|
|
48
|
+
and `test/metadata.test.js` (three-place version law, packaging, CP-1 export
|
|
49
|
+
guard). Removed vitest and its config.
|
|
50
|
+
- **Torture gate.** `test/torture.mjs` plus a tiered harness
|
|
51
|
+
(`@zakkster/lite-gc-profiler` + `@zakkster/lite-leak` + `@zakkster/lite-signal`
|
|
52
|
+
dev-only): metamorphic laws (T0), degenerate known-bad pins (T1), lifecycle
|
|
53
|
+
abuse (T4), the zero-alloc budget gate (T6: 200k `update`+`apply` under active
|
|
54
|
+
shake, `maxMajor:0`/`maxPauseMs:4`, pool-not-reallocated), retention soak (T7),
|
|
55
|
+
and the BREAK control (T9). `CAMPRO_TORTURE_BREAK=1` exits non-zero.
|
|
56
|
+
- **Version law.** `export const VERSION = "1.0.1"` from `src/index.js`, this
|
|
57
|
+
`CHANGELOG.md`, and a `Version:` header in `llms.txt`.
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
|
|
61
|
+
- Corrected three stale dependency header comments: `src/index.js` no longer
|
|
62
|
+
credits `lite-random` (not a dependency); `src/BoundsSystem.js` and
|
|
63
|
+
`src/MultiTarget.js` no longer claim a `@zakkster/lite-lerp` dependency they
|
|
64
|
+
do not import (their clamps are inline).
|
|
65
|
+
|
|
66
|
+
## [1.0.0] -- 2025-06-28
|
|
67
|
+
|
|
68
|
+
Initial release. Cinematic Canvas2D camera system extending
|
|
69
|
+
`@zakkster/lite-camera`'s `CinematicCamera`: smooth/eased zoom, five follow
|
|
70
|
+
modes, multi-target auto-framing, an 8-slot simplex-noise shake engine with
|
|
71
|
+
presets, fluent timeline sequences, a 16-layer parallax manager, per-edge
|
|
72
|
+
bounds, a debug HUD, and zero-alloc coordinate conversion.
|
|
73
|
+
|
|
74
|
+
[1.0.1]: https://github.com/PeshoVurtoleta/lite-camera-pro/releases/tag/v1.0.1
|
|
75
|
+
[1.0.0]: https://github.com/PeshoVurtoleta/lite-camera-pro/releases/tag/v1.0.0
|
package/llms.txt
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
> with presets, fluent timeline sequences, a 16-layer parallax manager, per-edge
|
|
7
7
|
> bounds, a debug HUD, and zero-alloc coordinate conversion.
|
|
8
8
|
|
|
9
|
+
Version: 1.0.1
|
|
10
|
+
|
|
9
11
|
ESM only. `import` from `@zakkster/lite-camera-pro`. Default export is the
|
|
10
12
|
CinematicCameraPro class. All hot-path APIs avoid allocation; setup-time calls
|
|
11
13
|
(constructor, createSequence, trackMultiple) may allocate.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zakkster/lite-camera-pro",
|
|
3
3
|
"author": "Zahary Shinikchiev <shinikchiev@yahoo.com>",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.1",
|
|
5
5
|
"description": "Cinematic camera system for Canvas2D games. Zero-GC, multi-target framing, noise-based shake, and timeline sequences.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": false,
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"src/",
|
|
20
20
|
"README.md",
|
|
21
|
+
"CHANGELOG.md",
|
|
21
22
|
"llms.txt",
|
|
22
23
|
"LICENSE"
|
|
23
24
|
],
|
|
@@ -46,14 +47,18 @@
|
|
|
46
47
|
"@zakkster/lite-timeline": "^1.0.0"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
49
|
-
"
|
|
50
|
-
"
|
|
50
|
+
"@zakkster/lite-gc-profiler": "^1.16.0",
|
|
51
|
+
"@zakkster/lite-leak": "^1.10.0",
|
|
52
|
+
"@zakkster/lite-signal": "^1.5.0",
|
|
53
|
+
"esbuild": "^0.25.0"
|
|
51
54
|
},
|
|
52
55
|
"scripts": {
|
|
53
|
-
"test": "
|
|
54
|
-
"test:
|
|
56
|
+
"test": "node --test test/*.test.js",
|
|
57
|
+
"test:gc": "node --expose-gc --test test/*.test.js",
|
|
58
|
+
"torture": "node --expose-gc test/torture.mjs",
|
|
59
|
+
"verify": "npm run test:gc && npm run torture",
|
|
55
60
|
"bundle-check": "esbuild ./src/index.js --bundle --format=esm --outfile=test-bundle.js",
|
|
56
|
-
"prepublishOnly": "npm run
|
|
61
|
+
"prepublishOnly": "npm run verify && npm run bundle-check"
|
|
57
62
|
},
|
|
58
63
|
"keywords": [
|
|
59
64
|
"camera",
|
package/src/BoundsSystem.js
CHANGED
|
@@ -46,6 +46,16 @@ import {
|
|
|
46
46
|
} from './BoundsSystem.js';
|
|
47
47
|
import {createDebugHUDConfig, drawDebugHUD, drawDebugWorld} from './DebugHUD.js';
|
|
48
48
|
|
|
49
|
+
// Cold-path sentinel: after destroy() every method that reads nulled internal
|
|
50
|
+
// state is rebound to this so a use-after-destroy fails closed with a named
|
|
51
|
+
// error instead of a raw null deref. Rebinding (not an in-body guard) keeps
|
|
52
|
+
// update()/apply() at zero per-frame cost. Matches base CinematicCamera (CP-8).
|
|
53
|
+
const _dead = () => {
|
|
54
|
+
const e = new Error("CinematicCameraPro: use after destroy()");
|
|
55
|
+
e.code = "ERR_CAMERA_DESTROYED";
|
|
56
|
+
throw e;
|
|
57
|
+
};
|
|
58
|
+
|
|
49
59
|
export class CinematicCameraPro extends CinematicCamera {
|
|
50
60
|
|
|
51
61
|
/**
|
|
@@ -798,8 +808,10 @@ export class CinematicCameraPro extends CinematicCamera {
|
|
|
798
808
|
ctx.scale(this.zoom, this.zoom);
|
|
799
809
|
ctx.translate(-this._halfW / this.zoom, -this._halfH / this.zoom);
|
|
800
810
|
|
|
801
|
-
// 6. Scroll to camera world position
|
|
802
|
-
|
|
811
|
+
// 6. Scroll to camera world position.
|
|
812
|
+
// int snap is deliberate: kills texture shimmer. floor (not | 0) so the
|
|
813
|
+
// snap is uniform about the origin -- | 0 truncates toward zero (CP-13).
|
|
814
|
+
ctx.translate(-Math.floor(this.pos[0]), -Math.floor(this.pos[1]));
|
|
803
815
|
}
|
|
804
816
|
|
|
805
817
|
// ─────────────────────────────────────────────────────
|
|
@@ -868,9 +880,10 @@ export class CinematicCameraPro extends CinematicCamera {
|
|
|
868
880
|
}
|
|
869
881
|
|
|
870
882
|
/**
|
|
871
|
-
* Destroy the camera. Releases sequences, clears shake state, and
|
|
872
|
-
*
|
|
873
|
-
*
|
|
883
|
+
* Destroy the camera. Releases sequences, clears shake state, and nulls all
|
|
884
|
+
* nested allocations so the GC can reclaim them. After destroy() the camera
|
|
885
|
+
* is unusable: EVERY public method throws an Error with code
|
|
886
|
+
* "ERR_CAMERA_DESTROYED" (fail closed) rather than a raw null deref.
|
|
874
887
|
*/
|
|
875
888
|
destroy() {
|
|
876
889
|
if (this._seq) {
|
|
@@ -878,16 +891,37 @@ export class CinematicCameraPro extends CinematicCamera {
|
|
|
878
891
|
this._seq = null;
|
|
879
892
|
}
|
|
880
893
|
|
|
894
|
+
// clearShakeState needs _shake still live -- run it before the null.
|
|
881
895
|
clearShakeState(this._shake);
|
|
882
896
|
|
|
883
|
-
// Release nested state so the slot pools / layer arrays can be GC'd
|
|
897
|
+
// Release nested Pro state so the slot pools / layer arrays can be GC'd.
|
|
884
898
|
this._shake = null;
|
|
885
899
|
this._mt = null;
|
|
886
900
|
this._parallax = null;
|
|
887
901
|
this._bounds = null;
|
|
888
902
|
this.debugConfig = null;
|
|
889
|
-
|
|
890
|
-
|
|
903
|
+
|
|
904
|
+
// Null the base typed arrays + rng and rebind the base methods
|
|
905
|
+
// (update/apply/debug/addTrauma/resize/destroy) to the base sentinel.
|
|
906
|
+
// Ordered after the Pro teardown so nothing above reads a nulled base
|
|
907
|
+
// field. T8 asserts parity with the base's destroy contract.
|
|
908
|
+
super.destroy();
|
|
909
|
+
|
|
910
|
+
// CP-8: a destroyed camera fails closed on EVERY public method. Any call
|
|
911
|
+
// throws ERR_CAMERA_DESTROYED (Pro's sentinel -- consistent message)
|
|
912
|
+
// instead of a raw null deref. Rebinding the WHOLE public surface, not a
|
|
913
|
+
// curated subset, means the guarantee cannot silently drift as methods
|
|
914
|
+
// are added -- and re-stamps the base rebinds from super.destroy() with
|
|
915
|
+
// Pro's message for parity. Getters (sequencePlaying) are already
|
|
916
|
+
// null-safe. A double destroy() throws the same named error.
|
|
917
|
+
this.update = this.apply = this.debug = this.debugHUD =
|
|
918
|
+
this.addTrauma = this.shake = this.shakePreset = this.clearShakes =
|
|
919
|
+
this.setMode = this.trackMultiple = this.trackSingle = this.setTargetCount =
|
|
920
|
+
this.createSequence = this.playSequence = this.stopSequence =
|
|
921
|
+
this.addParallaxLayer = this.removeParallaxLayer = this.applyParallax =
|
|
922
|
+
this.setBoundsType = this.setBoundsEdges = this.setBoundsRect = this.clearBoundsRect =
|
|
923
|
+
this.setZoom = this.zoomAt = this.screenToWorld = this.worldToScreen =
|
|
924
|
+
this.getState = this.setState = this.resize = this.destroy = _dead;
|
|
891
925
|
}
|
|
892
926
|
}
|
|
893
927
|
|
package/src/MultiTarget.js
CHANGED
package/src/ParallaxManager.js
CHANGED
|
@@ -191,7 +191,10 @@ export function applyParallaxLayer(state, id, ctx) {
|
|
|
191
191
|
for (let i = 0; i < state.layerCount; i++) {
|
|
192
192
|
const layer = state.layers[i];
|
|
193
193
|
if (layer.active && layer.id === id) {
|
|
194
|
-
|
|
194
|
+
// int snap is deliberate: kills texture shimmer. floor (not | 0) so
|
|
195
|
+
// the snap is uniform about the origin -- | 0 truncates toward zero
|
|
196
|
+
// (CP-13), matching the base camera and CinematicCameraPro.apply().
|
|
197
|
+
ctx.translate(-Math.floor(layer.scrollX), -Math.floor(layer.scrollY));
|
|
195
198
|
return true;
|
|
196
199
|
}
|
|
197
200
|
}
|
package/src/ShakeEngine.js
CHANGED
|
@@ -129,11 +129,24 @@ function acquireSlot(state) {
|
|
|
129
129
|
* @param {number} [profile.intensity=1] Scale multiplier for the profile
|
|
130
130
|
*/
|
|
131
131
|
export function addShake(state, profile, intensity = 1) {
|
|
132
|
+
// CP-14 + H-F (fail closed): validate trauma/intensity in this COLD entry so
|
|
133
|
+
// the per-frame updateShake/computeShake loops gain zero new branches.
|
|
134
|
+
// - trauma undefined -> 0.5 (documented default)
|
|
135
|
+
// - trauma or intensity non-finite (NaN/Inf) -> activate NOTHING, return
|
|
136
|
+
// early. The old `profile.trauma || 0.5` laundered NaN to 0.5, opening a
|
|
137
|
+
// poison door: a single NaN shake would drive every later frame to NaN.
|
|
138
|
+
// null is not zero; an unverified number does not get a default.
|
|
139
|
+
// - resulting trauma <= 0 -> inert (a zero-trauma shake fires nothing).
|
|
140
|
+
const rawTrauma = profile.trauma === undefined ? 0.5 : profile.trauma;
|
|
141
|
+
if (!Number.isFinite(rawTrauma) || !Number.isFinite(intensity)) return;
|
|
142
|
+
const trauma = Math.min(1, rawTrauma * intensity);
|
|
143
|
+
if (trauma <= 0) return;
|
|
144
|
+
|
|
132
145
|
const slot = acquireSlot(state);
|
|
133
146
|
|
|
134
147
|
slot.active = true;
|
|
135
148
|
slot.isDefault = false;
|
|
136
|
-
slot.trauma =
|
|
149
|
+
slot.trauma = trauma;
|
|
137
150
|
slot.decay = profile.decay !== undefined ? profile.decay : 1.0;
|
|
138
151
|
slot.freq = profile.freq !== undefined ? profile.freq : 15;
|
|
139
152
|
slot.maxOffset = profile.maxOffset !== undefined ? profile.maxOffset : 15;
|
|
@@ -166,6 +179,11 @@ export function addShake(state, profile, intensity = 1) {
|
|
|
166
179
|
* @param {number} amount Trauma to add [0, 1]
|
|
167
180
|
*/
|
|
168
181
|
export function addTraumaSimple(state, amount) {
|
|
182
|
+
// CP-14 + H-F (fail closed): a non-finite amount activates NOTHING and an
|
|
183
|
+
// amount <= 0 is inert. Same policy as addShake, in this cold entry only so
|
|
184
|
+
// the hot per-frame loops stay branch-for-branch unchanged.
|
|
185
|
+
if (!Number.isFinite(amount) || amount <= 0) return;
|
|
186
|
+
|
|
169
187
|
// Try to find an existing default omni slot to stack onto.
|
|
170
188
|
// Preset/profile slots are NEVER stacked onto — they have parameters
|
|
171
189
|
// (freq, decay, etc.) that addTrauma's generic shake wouldn't match.
|
package/src/index.js
CHANGED
|
@@ -2,32 +2,34 @@
|
|
|
2
2
|
* @zakkster/lite-camera-pro
|
|
3
3
|
* Commercial cinematic camera system for Canvas2D games.
|
|
4
4
|
*
|
|
5
|
-
* Built on: lite-camera, lite-lerp, lite-ease, lite-
|
|
5
|
+
* Built on: lite-camera, lite-lerp, lite-ease, lite-noise, lite-timeline
|
|
6
6
|
* Zero external dependencies outside the @zakkster ecosystem.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
export const VERSION = "1.0.1";
|
|
10
|
+
|
|
11
|
+
// -- Core --
|
|
10
12
|
export { CinematicCameraPro } from './CinematicCameraPro.js';
|
|
11
13
|
export { default } from './CinematicCameraPro.js';
|
|
12
14
|
|
|
13
|
-
//
|
|
15
|
+
// -- Follow Modes --
|
|
14
16
|
export { FollowMode, FOLLOW_STRATEGIES } from './FollowMode.js';
|
|
15
17
|
|
|
16
|
-
//
|
|
17
|
-
export { updateMultiTarget } from './MultiTarget.js';
|
|
18
|
+
// -- Multi-Target --
|
|
19
|
+
export { createMultiTargetState, updateMultiTarget } from './MultiTarget.js';
|
|
18
20
|
|
|
19
|
-
//
|
|
20
|
-
export { addShake, addTraumaSimple, updateShake, computeShake, clearShakes } from './ShakeEngine.js';
|
|
21
|
+
// -- Shake Engine --
|
|
22
|
+
export { createShakeState, addShake, addTraumaSimple, updateShake, computeShake, clearShakes } from './ShakeEngine.js';
|
|
21
23
|
export { EXPLOSION, EARTHQUAKE, RECOIL, IMPACT, LANDING, DAMAGE, RUMBLE, HEAVY_IMPACT, getPreset, registerPreset, listPresets } from './ShakePresets.js';
|
|
22
24
|
|
|
23
|
-
//
|
|
25
|
+
// -- Sequences --
|
|
24
26
|
export { createCameraSequence, panTo, dramaticZoom, bossReveal, timedShake } from './CameraSequence.js';
|
|
25
27
|
|
|
26
|
-
//
|
|
28
|
+
// -- Parallax --
|
|
27
29
|
export { WrapMode, createParallaxState, addParallaxLayer, removeParallaxLayer, updateParallax, getLayerScroll, applyParallaxLayer } from './ParallaxManager.js';
|
|
28
30
|
|
|
29
|
-
//
|
|
31
|
+
// -- Bounds --
|
|
30
32
|
export { BoundsType, createBoundsState, setBoundsAll, setBoundsEdges, setBoundsRect, clearBoundsRect, applyBounds } from './BoundsSystem.js';
|
|
31
33
|
|
|
32
|
-
//
|
|
34
|
+
// -- Debug HUD --
|
|
33
35
|
export { createDebugHUDConfig, drawDebugHUD, drawDebugWorld } from './DebugHUD.js';
|