zx-kit 0.22.0 → 0.24.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 +245 -9
- package/dist/__tests__/collision.test.d.ts +25 -0
- package/dist/__tests__/collision.test.d.ts.map +1 -0
- package/dist/__tests__/collision.test.js +198 -0
- package/dist/__tests__/collision.test.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/particles.d.ts +132 -0
- package/dist/particles.d.ts.map +1 -0
- package/dist/particles.js +138 -0
- package/dist/particles.js.map +1 -0
- package/dist/renderer.d.ts +21 -0
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +53 -0
- package/dist/renderer.js.map +1 -1
- package/dist/rng.d.ts +70 -0
- package/dist/rng.d.ts.map +1 -0
- package/dist/rng.js +120 -0
- package/dist/rng.js.map +1 -0
- package/dist/tilescroll.d.ts +27 -0
- package/dist/tilescroll.d.ts.map +1 -0
- package/dist/tilescroll.js +74 -0
- package/dist/tilescroll.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,6 +31,7 @@ The goal is simple: **it should look and sound like a Spectrum, but run like a m
|
|
|
31
31
|
- **Keyboard input** — configurable key-repeat, single-consume action flags, instant state reset on phase transitions
|
|
32
32
|
- **ZX-style UI widgets** — progress bars with managed lifetime, boxes, frames, panel titles
|
|
33
33
|
- **Typed save / load** — persistent saves via `localStorage` with schema versioning, migrations, slot enumeration, in-memory throttling, and discriminated Result types for every failure mode
|
|
34
|
+
- **Runtime locale switching** — type-safe string-pack selection via `pickLocale()`, so a game can switch language while running — unimaginable on the original Spectrum, natural in the browser
|
|
34
35
|
- **Zero dependencies** — only Web platform APIs: `Canvas`, `Web Audio`, `KeyboardEvent`
|
|
35
36
|
- **Tree-shakeable** — `sideEffects: false`, so unused modules are dropped from your production bundle
|
|
36
37
|
- **TypeScript-first** — strict mode, full `.d.ts` declarations, no `any`
|
|
@@ -545,8 +546,12 @@ requestAnimationFrame(loop)
|
|
|
545
546
|
| [`scene.ts`](#scenets--scene-manager) | Stack-based scene manager with onEnter/onExit/onPause/onResume hooks |
|
|
546
547
|
| [`save.ts`](#savets--typed-save--load) | Typed save/load via callbacks, versioning + migrations, slot enumeration, throttling, Result types |
|
|
547
548
|
| [`tilemap.ts`](#tilemapts--tile-map-engine) | Scrollable maps, solid tiles, O(1) id-index, background swap |
|
|
549
|
+
| [`tilescroll.ts`](#tilescrollts--pixel-smooth-scrolling) | Pixel-smooth tile-map rendering at any camera position (sub-tile scroll) |
|
|
550
|
+
| [`particles.ts`](#particlests--particle-pool) | Allocation-free particle pool for pixel effects: sparks, dust, puffs |
|
|
551
|
+
| [`rng.ts`](#rngts--seeded-rng) | Seeded deterministic PRNG (mulberry32): int/range/float/chance/pick/shuffle/fork |
|
|
548
552
|
| [`palette.ts`](#palettets--color-constants) | 15 Spectrum colors, `SpectrumColor` type, `CELL`, `SCALE` |
|
|
549
553
|
| [`font.ts`](#fontts--rom-bitmap-font) | 96-character ROM font, raw bitmap access |
|
|
554
|
+
| [`i18n.ts`](#i18nts--runtime-locale-selection) | Type-safe runtime locale selection for translated string packs |
|
|
550
555
|
|
|
551
556
|
---
|
|
552
557
|
|
|
@@ -902,6 +907,36 @@ curveDisplay(ctx) // default strength
|
|
|
902
907
|
curveDisplay(ctx, 256, 208, 6) // stronger warp
|
|
903
908
|
```
|
|
904
909
|
|
|
910
|
+
### `createBitmapFromRows(rows): Bitmap`
|
|
911
|
+
|
|
912
|
+
Builds an arbitrary-size `Bitmap` from readable pixel-art rows instead of
|
|
913
|
+
hand-packed bytes. This is useful for sprites larger than 8x8 where hex arrays
|
|
914
|
+
become hard to review.
|
|
915
|
+
|
|
916
|
+
- `X` or `#` = solid pixel
|
|
917
|
+
- `.` or space = transparent pixel
|
|
918
|
+
- every row must have the same width
|
|
919
|
+
- width must be a positive multiple of 8
|
|
920
|
+
|
|
921
|
+
```ts
|
|
922
|
+
const TRUCK = createBitmapFromRows([
|
|
923
|
+
'....XXXXXXXX....',
|
|
924
|
+
'..XXXXXXXXXXXX..',
|
|
925
|
+
'.XXXX......XXXX.',
|
|
926
|
+
'XXXXXXXXXXXXXXXX',
|
|
927
|
+
'XX..XXXXXXXX..XX',
|
|
928
|
+
'XX............XX',
|
|
929
|
+
'..XXX......XXX..',
|
|
930
|
+
'................',
|
|
931
|
+
])
|
|
932
|
+
|
|
933
|
+
drawBitmap(ctx, TRUCK, x, y, C.B_WHITE)
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
The returned object is the same `Bitmap` shape produced by `createBitmap()`, so
|
|
937
|
+
it works with `drawBitmap`, `drawBitmapAttrs`, `mirrorBitmap`, and collision
|
|
938
|
+
helpers such as `bitmapPixelMask`.
|
|
939
|
+
|
|
905
940
|
---
|
|
906
941
|
|
|
907
942
|
## `audio.ts` — Beeper Audio
|
|
@@ -1637,6 +1672,8 @@ When `groundContact` is 0, a circle-shaped hero hanging over a tile edge won't t
|
|
|
1637
1672
|
| Bullet vs. irregular boss sprite | `masksOverlap` — pixel-precise, returns overlap count for damage |
|
|
1638
1673
|
| Off-road detection for a truck with a bumpy silhouette | `pixelSolidCount` / custom mask loop — checks each opaque pixel against road boundary |
|
|
1639
1674
|
|
|
1675
|
+
For a step-by-step walkthrough of both tiers — including how to combine AABB and pixel-precise in one loop and how to handle non-tile boundaries — see **[docs/collision.md](docs/collision.md)**.
|
|
1676
|
+
|
|
1640
1677
|
---
|
|
1641
1678
|
|
|
1642
1679
|
## `animation.ts` — Frame Timer & Tween
|
|
@@ -2192,6 +2229,201 @@ for (let row = 0; row < 8; row++) {
|
|
|
2192
2229
|
|
|
2193
2230
|
---
|
|
2194
2231
|
|
|
2232
|
+
## `i18n.ts` — Runtime Locale Selection
|
|
2233
|
+
|
|
2234
|
+
A tiny helper for choosing a translated string pack at runtime. The original ZX Spectrum could not realistically swap whole languages while a game was running; zx-kit keeps the Spectrum presentation, but lets browser games offer modern language switching without a framework or dependency.
|
|
2235
|
+
|
|
2236
|
+
### `pickLocale(defaultLocale, locales, code): T`
|
|
2237
|
+
|
|
2238
|
+
Selects a locale object by language code and falls back to the default locale when the code is missing, empty, or unknown. Matching is case-insensitive.
|
|
2239
|
+
|
|
2240
|
+
```ts
|
|
2241
|
+
import { pickLocale } from 'zx-kit'
|
|
2242
|
+
import * as en from './strings'
|
|
2243
|
+
import * as sk from './strings.sk'
|
|
2244
|
+
import * as ru from './strings.ru'
|
|
2245
|
+
|
|
2246
|
+
let languageCode = localStorage.getItem('language') // e.g. 'sk'
|
|
2247
|
+
let L = pickLocale(en, { sk, ru }, languageCode)
|
|
2248
|
+
|
|
2249
|
+
drawText(ctx, L.STR_PRESS_START, 32, 88, C.B_WHITE, C.BLACK)
|
|
2250
|
+
|
|
2251
|
+
function setLanguage(code: string): void {
|
|
2252
|
+
languageCode = code
|
|
2253
|
+
localStorage.setItem('language', code)
|
|
2254
|
+
L = pickLocale(en, { sk, ru }, languageCode)
|
|
2255
|
+
}
|
|
2256
|
+
```
|
|
2257
|
+
|
|
2258
|
+
Every locale must have the same shape as the default locale. Because `pickLocale` is generic over that shape, missing keys and wrong function signatures are caught by TypeScript:
|
|
2259
|
+
|
|
2260
|
+
```ts
|
|
2261
|
+
// strings.ts
|
|
2262
|
+
export const STR_PRESS_START = 'PRESS START'
|
|
2263
|
+
export const STR_DEPTH = (m: number) => `D:${m}M`
|
|
2264
|
+
|
|
2265
|
+
// strings.sk.ts
|
|
2266
|
+
export const STR_PRESS_START = 'STLAC START'
|
|
2267
|
+
export const STR_DEPTH = (m: number) => `H:${m}M`
|
|
2268
|
+
```
|
|
2269
|
+
|
|
2270
|
+
Selection rules:
|
|
2271
|
+
|
|
2272
|
+
| Code | Result |
|
|
2273
|
+
|------|--------|
|
|
2274
|
+
| `null`, `undefined`, `''` | `defaultLocale` |
|
|
2275
|
+
| `'sk'`, `'SK'`, `'Sk'` | `locales.sk` |
|
|
2276
|
+
| unknown code | `defaultLocale` |
|
|
2277
|
+
| `'en'` when `en` is not in `locales` | `defaultLocale` |
|
|
2278
|
+
|
|
2279
|
+
The default language does not need its own `strings.en.ts` file. Keep the source language at `strings.ts`, put only additional translations in the locale map, and call `pickLocale` again whenever the player changes language.
|
|
2280
|
+
|
|
2281
|
+
---
|
|
2282
|
+
|
|
2283
|
+
## `tilescroll.ts` — Pixel-Smooth Scrolling
|
|
2284
|
+
|
|
2285
|
+
[`tilemap.ts`](#tilemapts--tile-map-engine)'s `render(viewport)` takes a viewport in **whole tiles**, so a camera can only move in 8-pixel steps. That is perfect for grid games, but visibly steppy in a platformer where the player moves sub-pixel-smooth while jumping. `tilescroll.ts` renders the map at an **arbitrary pixel camera position** by drawing one overscan row/column and offsetting every tile by the camera remainder.
|
|
2286
|
+
|
|
2287
|
+
Pair it with [`camera.ts`](#camerats--scrolling-camera): use `tileMapWorldSize` for the camera's `worldW` / `worldH`, then feed `cam.x` / `cam.y` straight into `drawTileMapAt`.
|
|
2288
|
+
|
|
2289
|
+
### `tileMapWorldSize(map): { width, height }`
|
|
2290
|
+
|
|
2291
|
+
Returns the map's full size in pixels (`cols × CELL`, `rows × CELL`) — handy for the camera's world bounds.
|
|
2292
|
+
|
|
2293
|
+
```ts
|
|
2294
|
+
import { createCamera } from 'zx-kit'
|
|
2295
|
+
const cam = createCamera({ viewW: 256, viewH: 192, ...tileMapWorldSize(map) })
|
|
2296
|
+
```
|
|
2297
|
+
|
|
2298
|
+
### `drawTileMapAt(ctx, map, camX, camY, viewW?, viewH?): void`
|
|
2299
|
+
|
|
2300
|
+
Renders `map` with the viewport's top-left at world pixel `(camX, camY)`. The camera position is rounded to whole pixels for crisp output; off-screen and empty cells are skipped. `viewW` (default `256`) and `viewH` (default `192`) must be positive — throws otherwise. The leading/trailing partial tiles are drawn and naturally clipped by the canvas bounds, so keep the play area at the canvas origin (or render the map first) to avoid spilling under a status bar.
|
|
2301
|
+
|
|
2302
|
+
```ts
|
|
2303
|
+
// game loop
|
|
2304
|
+
setCameraTarget(cam, player.x, player.y)
|
|
2305
|
+
tickCamera(cam, dt)
|
|
2306
|
+
drawTileMapAt(ctx, map, cam.x, cam.y) // smooth background
|
|
2307
|
+
renderSprite(ctx, /* player drawn at (x - cam.x, y - cam.y) */)
|
|
2308
|
+
```
|
|
2309
|
+
|
|
2310
|
+
---
|
|
2311
|
+
|
|
2312
|
+
## `particles.ts` — Particle Pool
|
|
2313
|
+
|
|
2314
|
+
An **allocation-free** particle pool for ZX-style pixel effects: carrot-shot sparks, landing dust, an enemy curling into a puff, glowing motes around a crystal. The pool is created once at startup with a fixed capacity; emitting and ticking never allocate, so it is safe to run every frame. Particles are plain coloured squares drawn in the Spectrum palette. Pass an `rng` for deterministic effects (replays, seeded worlds); otherwise it uses `Math.random`.
|
|
2315
|
+
|
|
2316
|
+
### `Particle` / `ParticleSystem` interfaces
|
|
2317
|
+
|
|
2318
|
+
```ts
|
|
2319
|
+
interface Particle {
|
|
2320
|
+
x: number; y: number // world pixels
|
|
2321
|
+
vx: number; vy: number // px per ms
|
|
2322
|
+
life: number; maxLife: number // ms (fade with life / maxLife)
|
|
2323
|
+
color: SpectrumColor
|
|
2324
|
+
size: number // square side in px
|
|
2325
|
+
active: boolean // false slots are free for reuse
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
interface ParticleSystem {
|
|
2329
|
+
readonly particles: Particle[] // length === capacity
|
|
2330
|
+
readonly capacity: number
|
|
2331
|
+
activeCount: number
|
|
2332
|
+
}
|
|
2333
|
+
```
|
|
2334
|
+
|
|
2335
|
+
### `createParticleSystem(capacity): ParticleSystem`
|
|
2336
|
+
|
|
2337
|
+
Creates a pool of `capacity` particles, all inactive. Throws when `capacity` is not a positive integer.
|
|
2338
|
+
|
|
2339
|
+
### `emitParticles(ps, opts): number`
|
|
2340
|
+
|
|
2341
|
+
Emits up to `opts.count` particles and returns the number actually emitted (fewer when the pool is full). Throws when `count` is negative or non-integer.
|
|
2342
|
+
|
|
2343
|
+
| Option | Type | Default | Meaning |
|
|
2344
|
+
|--------|------|---------|---------|
|
|
2345
|
+
| `x`, `y` | `number` | — | spawn position (world px) |
|
|
2346
|
+
| `count` | `number` | — | how many to emit |
|
|
2347
|
+
| `color` | `SpectrumColor \| SpectrumColor[]` | — | single colour, or palette to pick per particle |
|
|
2348
|
+
| `speed` | `number \| [min, max]` | `0.03` | px/ms |
|
|
2349
|
+
| `angle` | `number` | `0` | base direction in radians (`-π/2` = up) |
|
|
2350
|
+
| `spread` | `number` | `0` | angular jitter centred on `angle` |
|
|
2351
|
+
| `life` | `number \| [min, max]` | `300` | lifetime in ms |
|
|
2352
|
+
| `size` | `number` | `1` | square side in px |
|
|
2353
|
+
| `rng` | `() => number` | `Math.random` | deterministic source |
|
|
2354
|
+
|
|
2355
|
+
```ts
|
|
2356
|
+
const sparks = createParticleSystem(128)
|
|
2357
|
+
|
|
2358
|
+
// on carrot impact — a fan of yellow/white sparks shooting upward
|
|
2359
|
+
emitParticles(sparks, {
|
|
2360
|
+
x: hit.x, y: hit.y, count: 12,
|
|
2361
|
+
color: [C.B_YELLOW, C.B_WHITE],
|
|
2362
|
+
speed: [0.02, 0.06], angle: -Math.PI / 2, spread: Math.PI,
|
|
2363
|
+
life: [200, 400],
|
|
2364
|
+
})
|
|
2365
|
+
```
|
|
2366
|
+
|
|
2367
|
+
### `tickParticles(ps, dtMs, gravity?): void`
|
|
2368
|
+
|
|
2369
|
+
Advances every active particle by `dtMs`, applying optional `gravity` (px/ms², default `0`) to vertical velocity. Expired particles are deactivated and returned to the pool.
|
|
2370
|
+
|
|
2371
|
+
### `renderParticles(ctx, ps, offsetX?, offsetY?): void`
|
|
2372
|
+
|
|
2373
|
+
Draws every active particle as a filled square, rounding world coordinates to whole pixels. Subtract the camera world position via `offsetX` / `offsetY` to convert world → screen.
|
|
2374
|
+
|
|
2375
|
+
### `clearParticles(ps): void`
|
|
2376
|
+
|
|
2377
|
+
Deactivates all particles immediately (e.g. on room change).
|
|
2378
|
+
|
|
2379
|
+
```ts
|
|
2380
|
+
// game loop
|
|
2381
|
+
tickParticles(sparks, dt, 0.0004) // gentle gravity
|
|
2382
|
+
renderParticles(ctx, sparks, cam.x, cam.y) // scrolled world
|
|
2383
|
+
```
|
|
2384
|
+
|
|
2385
|
+
---
|
|
2386
|
+
|
|
2387
|
+
## `rng.ts` — Seeded RNG
|
|
2388
|
+
|
|
2389
|
+
A **seeded deterministic** pseudo-random generator: the same seed produces the same sequence on every machine and every run — exactly what procedural worlds need. Built on **mulberry32** (fast, allocation-free, good statistical quality for games). It is **not** cryptographically secure.
|
|
2390
|
+
|
|
2391
|
+
The call order is part of the determinism contract: call the methods in the same order to reproduce a world.
|
|
2392
|
+
|
|
2393
|
+
### `createRng(seed): Rng`
|
|
2394
|
+
|
|
2395
|
+
Creates a generator from a `string` (hashed via `hashSeed`) or a finite `number` (coerced to uint32). Throws on a non-finite numeric seed.
|
|
2396
|
+
|
|
2397
|
+
### `Rng` methods
|
|
2398
|
+
|
|
2399
|
+
| Method | Returns | Throws when |
|
|
2400
|
+
|--------|---------|-------------|
|
|
2401
|
+
| `next()` | float `[0, 1)` | — |
|
|
2402
|
+
| `int(maxExclusive)` | int `[0, max)` | `max` not a positive integer |
|
|
2403
|
+
| `range(min, max)` | int `[min, max)` | bounds non-integer, or `max <= min` |
|
|
2404
|
+
| `float(min, max)` | float `[min, max)` | `max < min` |
|
|
2405
|
+
| `chance(p)` | boolean (`true` ~`p`) | `p` outside `[0, 1]` |
|
|
2406
|
+
| `pick(items)` | random element | `items` empty |
|
|
2407
|
+
| `shuffle(items)` | same array, shuffled in place | — |
|
|
2408
|
+
| `fork()` | independent `Rng` (advances parent one step) | — |
|
|
2409
|
+
|
|
2410
|
+
```ts
|
|
2411
|
+
const rng = createRng('cave-level-7')
|
|
2412
|
+
const roomCount = rng.range(3, 7)
|
|
2413
|
+
const theme = rng.pick(['spider', 'centipede', 'crystal'])
|
|
2414
|
+
if (rng.chance(0.15)) placeSecret()
|
|
2415
|
+
|
|
2416
|
+
// independent sub-streams so adding enemies doesn't shift terrain layout
|
|
2417
|
+
const terrainRng = rng.fork()
|
|
2418
|
+
const enemyRng = rng.fork()
|
|
2419
|
+
```
|
|
2420
|
+
|
|
2421
|
+
### `hashSeed(seed): number`
|
|
2422
|
+
|
|
2423
|
+
Hashes a string to an unsigned 32-bit integer (FNV-1a). Deterministic; exported for keying sub-streams by name.
|
|
2424
|
+
|
|
2425
|
+
---
|
|
2426
|
+
|
|
2195
2427
|
## Architecture
|
|
2196
2428
|
|
|
2197
2429
|
### Module structure
|
|
@@ -2205,9 +2437,8 @@ zx-kit/
|
|
|
2205
2437
|
│ ├── index.ts # barrel — re-exports everything
|
|
2206
2438
|
│ ├── palette.ts # SCALE, CELL, C, SpectrumColor
|
|
2207
2439
|
│ ├── font.ts # FONT, getCharRow
|
|
2208
|
-
│ ├── renderer.ts #
|
|
2209
|
-
│ │ #
|
|
2210
|
-
│ │ # drawScanlines, curveDisplay
|
|
2440
|
+
│ ├── renderer.ts # canvas setup, 8×8 sprites, arbitrary-size Bitmap,
|
|
2441
|
+
│ │ # AttrMap colour attributes, text, scanlines, border flash
|
|
2211
2442
|
│ ├── audio.ts # initAudio, resumeAudio, beep, playPattern,
|
|
2212
2443
|
│ │ # getAudioContext, getMasterGain,
|
|
2213
2444
|
│ │ # getMasterVolume, setMasterVolume,
|
|
@@ -2218,15 +2449,20 @@ zx-kit/
|
|
|
2218
2449
|
│ │ # consumePause, consumeDebug, consumeAnyKey,
|
|
2219
2450
|
│ │ # isHeld, resetInput, Direction
|
|
2220
2451
|
│ ├── ui.ts # drawBox, drawFrame, drawPanelTitle,
|
|
2221
|
-
│ │ #
|
|
2222
|
-
│ │ # BorderOptions, DrawProgressBarOptions
|
|
2452
|
+
│ │ # instrumentation widgets, progress bars
|
|
2223
2453
|
│ ├── tilemap.ts # createTileMap, Tile, Viewport, TileMap
|
|
2454
|
+
│ ├── tilescroll.ts # drawTileMapAt, tileMapWorldSize (sub-pixel scroll)
|
|
2224
2455
|
│ ├── sprite.ts # createSprite, moveSprite, applyGravity,
|
|
2225
2456
|
│ │ # renderSprite, Sprite
|
|
2226
|
-
│
|
|
2227
|
-
│
|
|
2228
|
-
│
|
|
2229
|
-
│
|
|
2457
|
+
│ ├── collision.ts # AABB, rect-vs-tile, pixel-precise masks
|
|
2458
|
+
│ ├── particles.ts # createParticleSystem, emitParticles,
|
|
2459
|
+
│ │ # tickParticles, renderParticles, clearParticles
|
|
2460
|
+
│ ├── rng.ts # createRng, hashSeed (seeded mulberry32)
|
|
2461
|
+
│ ├── animation.ts # frame timers, tweens, blinkers
|
|
2462
|
+
│ ├── camera.ts # scrolling viewport, lerp, deadzone, bounds
|
|
2463
|
+
│ ├── scene.ts # stack-based scene manager
|
|
2464
|
+
│ ├── save.ts # typed localStorage save/load with migrations
|
|
2465
|
+
│ └── i18n.ts # pickLocale runtime locale selection
|
|
2230
2466
|
└── dist/ # compiled output (npm run build)
|
|
2231
2467
|
├── index.js
|
|
2232
2468
|
├── index.d.ts
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pixel-perfect collision — visual ground truth test.
|
|
3
|
+
*
|
|
4
|
+
* Two bitmaps: 8×8 px each, but only a 2×2 px region is lit
|
|
5
|
+
* (rows 0–1, cols 0–1). The effective sprite shape is 2×2 — a size that
|
|
6
|
+
* is intentionally NOT a multiple of the ZX tile size (8 px).
|
|
7
|
+
*
|
|
8
|
+
* col: 01234567
|
|
9
|
+
* row 0: ██...... (0xC0)
|
|
10
|
+
* row 1: ██...... (0xC0)
|
|
11
|
+
* rows 2–7: empty
|
|
12
|
+
*
|
|
13
|
+
* Ground truth: a brute-force Set-of-pixel-coords intersection.
|
|
14
|
+
* Both sprites are projected to absolute (x,y) coordinates, all lit pixels
|
|
15
|
+
* are collected into a flat Set, and any shared coordinate counts as overlap.
|
|
16
|
+
* No formulas, no assumptions — just "which pixels are painted and do any
|
|
17
|
+
* coordinates match?"
|
|
18
|
+
*
|
|
19
|
+
* Every assertion compares masksOverlap against this ground truth, sweeping
|
|
20
|
+
* sprite B toward sprite A one pixel at a time in all four directions and
|
|
21
|
+
* diagonally. A separate block shows that AABB (8×8 bounding-box) fires
|
|
22
|
+
* 6 pixels earlier than pixel-perfect — the gap the ground truth exposes.
|
|
23
|
+
*/
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=collision.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collision.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/collision.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pixel-perfect collision — visual ground truth test.
|
|
3
|
+
*
|
|
4
|
+
* Two bitmaps: 8×8 px each, but only a 2×2 px region is lit
|
|
5
|
+
* (rows 0–1, cols 0–1). The effective sprite shape is 2×2 — a size that
|
|
6
|
+
* is intentionally NOT a multiple of the ZX tile size (8 px).
|
|
7
|
+
*
|
|
8
|
+
* col: 01234567
|
|
9
|
+
* row 0: ██...... (0xC0)
|
|
10
|
+
* row 1: ██...... (0xC0)
|
|
11
|
+
* rows 2–7: empty
|
|
12
|
+
*
|
|
13
|
+
* Ground truth: a brute-force Set-of-pixel-coords intersection.
|
|
14
|
+
* Both sprites are projected to absolute (x,y) coordinates, all lit pixels
|
|
15
|
+
* are collected into a flat Set, and any shared coordinate counts as overlap.
|
|
16
|
+
* No formulas, no assumptions — just "which pixels are painted and do any
|
|
17
|
+
* coordinates match?"
|
|
18
|
+
*
|
|
19
|
+
* Every assertion compares masksOverlap against this ground truth, sweeping
|
|
20
|
+
* sprite B toward sprite A one pixel at a time in all four directions and
|
|
21
|
+
* diagonally. A separate block shows that AABB (8×8 bounding-box) fires
|
|
22
|
+
* 6 pixels earlier than pixel-perfect — the gap the ground truth exposes.
|
|
23
|
+
*/
|
|
24
|
+
import { describe, it, expect } from 'vitest';
|
|
25
|
+
import { bitmapPixelMask, masksOverlap, rectsOverlap } from '../collision.js';
|
|
26
|
+
import { createBitmap } from '../renderer.js';
|
|
27
|
+
// ── Fixture ───────────────────────────────────────────────────────────────────
|
|
28
|
+
// width=8 (minimum for Bitmap: 1 byte per row). height=8.
|
|
29
|
+
// Lit region: rows 0–1, cols 0–1. All other bytes 0x00.
|
|
30
|
+
const BMP = createBitmap(new Uint8Array([
|
|
31
|
+
0xC0, // row 0: bits 7,6 set → cols 0,1
|
|
32
|
+
0xC0, // row 1: bits 7,6 set → cols 0,1
|
|
33
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // rows 2–7: empty
|
|
34
|
+
]), 8, 8);
|
|
35
|
+
const MASK = bitmapPixelMask(BMP);
|
|
36
|
+
// ── Brute-force ground truth ──────────────────────────────────────────────────
|
|
37
|
+
// Project every lit pixel of a mask to world (x,y). Put them in a Set.
|
|
38
|
+
// Check if any pixel of B appears in the Set of A. Completely explicit —
|
|
39
|
+
// no clever algorithm, just coordinate comparison.
|
|
40
|
+
function litPixels(mask, ox, oy) {
|
|
41
|
+
const s = new Set();
|
|
42
|
+
for (let row = 0; row < mask.height; row++) {
|
|
43
|
+
for (const col of mask.rows[row]) {
|
|
44
|
+
s.add(`${ox + col},${oy + row}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return s;
|
|
48
|
+
}
|
|
49
|
+
function bruteForce(maskA, ax, ay, maskB, bx, by) {
|
|
50
|
+
const setA = litPixels(maskA, ax, ay);
|
|
51
|
+
for (let row = 0; row < maskB.height; row++) {
|
|
52
|
+
for (const col of maskB.rows[row]) {
|
|
53
|
+
if (setA.has(`${bx + col},${by + row}`))
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
60
|
+
describe('masksOverlap — 2×2 lit region, visual ground truth', () => {
|
|
61
|
+
// ── Sanity-check the mask ─────────────────────────────────────────────────
|
|
62
|
+
describe('mask shape', () => {
|
|
63
|
+
it('has exactly 4 lit pixels (the 2×2 region)', () => {
|
|
64
|
+
expect(MASK.totalPixels).toBe(4);
|
|
65
|
+
});
|
|
66
|
+
it('rows 0–1 contain columns [0, 1]; rows 2–7 are empty', () => {
|
|
67
|
+
expect(MASK.rows[0]).toEqual([0, 1]);
|
|
68
|
+
expect(MASK.rows[1]).toEqual([0, 1]);
|
|
69
|
+
for (let r = 2; r < MASK.height; r++) {
|
|
70
|
+
expect(MASK.rows[r]).toEqual([]);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
// ── Horizontal sweep ──────────────────────────────────────────────────────
|
|
75
|
+
// A is fixed at (20,20). B sweeps from right (x=30) to left (x=17), same y.
|
|
76
|
+
// A's lit pixels: x∈[20,21], y∈[20,21].
|
|
77
|
+
// First contact: bx=21 — B column 0 (x=21) meets A column 1 (x=21).
|
|
78
|
+
// AABB first contact: bx=27 — 8×8 boxes share one column. Gap = 6 px.
|
|
79
|
+
describe('horizontal sweep: B approaches A from the right', () => {
|
|
80
|
+
const AX = 20, AY = 20;
|
|
81
|
+
it('masksOverlap matches brute-force at every pixel step', () => {
|
|
82
|
+
for (let bx = AX + 10; bx >= AX - 3; bx--) {
|
|
83
|
+
const expected = bruteForce(MASK, AX, AY, MASK, bx, AY);
|
|
84
|
+
const actual = masksOverlap(MASK, AX, AY, MASK, bx, AY) > 0;
|
|
85
|
+
if (actual !== expected) {
|
|
86
|
+
throw new Error(`bx=${bx}: masksOverlap=${actual} but brute-force says ${expected}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
it('no collision at bx = AX+2 — 1-pixel gap between lit edges', () => {
|
|
91
|
+
// A's rightmost lit column is AX+1=21. B's leftmost at bx=AX+2=22. Gap 1 px.
|
|
92
|
+
expect(masksOverlap(MASK, AX, AY, MASK, AX + 2, AY)).toBe(0);
|
|
93
|
+
expect(bruteForce(MASK, AX, AY, MASK, AX + 2, AY)).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
it('collision fires at bx = AX+1 — lit edges share column AX+1', () => {
|
|
96
|
+
expect(masksOverlap(MASK, AX, AY, MASK, AX + 1, AY)).toBeGreaterThan(0);
|
|
97
|
+
expect(bruteForce(MASK, AX, AY, MASK, AX + 1, AY)).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
it('AABB (8×8 box) fires 6 pixels before pixel-perfect', () => {
|
|
100
|
+
const boxA = { x: AX, y: AY, w: 8, h: 8 };
|
|
101
|
+
// AABB says: first overlap at bx=AX+7 (right edge of A box = left edge of B box)
|
|
102
|
+
expect(rectsOverlap(boxA, { x: AX + 8, y: AY, w: 8, h: 8 })).toBe(false);
|
|
103
|
+
expect(rectsOverlap(boxA, { x: AX + 7, y: AY, w: 8, h: 8 })).toBe(true);
|
|
104
|
+
// Pixel-perfect: no lit pixels near each other at bx=AX+7 — false positive
|
|
105
|
+
expect(masksOverlap(MASK, AX, AY, MASK, AX + 7, AY)).toBe(0);
|
|
106
|
+
expect(bruteForce(MASK, AX, AY, MASK, AX + 7, AY)).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
// ── Vertical sweep ────────────────────────────────────────────────────────
|
|
110
|
+
// A fixed at (20,20). B sweeps from below (y=30) upward (y=17), same x.
|
|
111
|
+
// First contact: by=21 — B row 0 (y=21) meets A row 1 (y=21).
|
|
112
|
+
// AABB first contact: by=27. Gap = 6 px.
|
|
113
|
+
describe('vertical sweep: B approaches A from below', () => {
|
|
114
|
+
const AX = 20, AY = 20;
|
|
115
|
+
it('masksOverlap matches brute-force at every pixel step', () => {
|
|
116
|
+
for (let by = AY + 10; by >= AY - 3; by--) {
|
|
117
|
+
const expected = bruteForce(MASK, AX, AY, MASK, AX, by);
|
|
118
|
+
const actual = masksOverlap(MASK, AX, AY, MASK, AX, by) > 0;
|
|
119
|
+
if (actual !== expected) {
|
|
120
|
+
throw new Error(`by=${by}: masksOverlap=${actual} but brute-force says ${expected}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
it('no collision at by = AY+2 — 1-pixel gap between lit rows', () => {
|
|
125
|
+
expect(masksOverlap(MASK, AX, AY, MASK, AX, AY + 2)).toBe(0);
|
|
126
|
+
expect(bruteForce(MASK, AX, AY, MASK, AX, AY + 2)).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
it('collision fires at by = AY+1 — lit rows share row AY+1', () => {
|
|
129
|
+
expect(masksOverlap(MASK, AX, AY, MASK, AX, AY + 1)).toBeGreaterThan(0);
|
|
130
|
+
expect(bruteForce(MASK, AX, AY, MASK, AX, AY + 1)).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
it('AABB fires 6 pixels before pixel-perfect', () => {
|
|
133
|
+
const boxA = { x: AX, y: AY, w: 8, h: 8 };
|
|
134
|
+
expect(rectsOverlap(boxA, { x: AX, y: AY + 8, w: 8, h: 8 })).toBe(false);
|
|
135
|
+
expect(rectsOverlap(boxA, { x: AX, y: AY + 7, w: 8, h: 8 })).toBe(true);
|
|
136
|
+
expect(masksOverlap(MASK, AX, AY, MASK, AX, AY + 7)).toBe(0);
|
|
137
|
+
expect(bruteForce(MASK, AX, AY, MASK, AX, AY + 7)).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
// ── Diagonal sweep ────────────────────────────────────────────────────────
|
|
141
|
+
// A fixed at (20,20). B sweeps along the main diagonal: bx=by=AX+d, d=10→-3.
|
|
142
|
+
// First contact: d=1 — corner pixel (AX+1, AY+1) is shared by both sprites.
|
|
143
|
+
// At d=2: B's nearest pixel is (AX+2, AY+2) — corner of A is (AX+1, AY+1). Gap.
|
|
144
|
+
describe('diagonal sweep: B approaches A from bottom-right', () => {
|
|
145
|
+
const AX = 20, AY = 20;
|
|
146
|
+
it('masksOverlap matches brute-force at every diagonal step', () => {
|
|
147
|
+
for (let d = 10; d >= -3; d--) {
|
|
148
|
+
const bx = AX + d, by = AY + d;
|
|
149
|
+
const expected = bruteForce(MASK, AX, AY, MASK, bx, by);
|
|
150
|
+
const actual = masksOverlap(MASK, AX, AY, MASK, bx, by) > 0;
|
|
151
|
+
if (actual !== expected) {
|
|
152
|
+
throw new Error(`d=${d} (bx=${bx}, by=${by}): masksOverlap=${actual} but brute-force says ${expected}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
it('no collision at d=2 — corner pixels are 1 diagonal step apart', () => {
|
|
157
|
+
expect(masksOverlap(MASK, AX, AY, MASK, AX + 2, AY + 2)).toBe(0);
|
|
158
|
+
expect(bruteForce(MASK, AX, AY, MASK, AX + 2, AY + 2)).toBe(false);
|
|
159
|
+
});
|
|
160
|
+
it('collision fires at d=1 — single corner pixel (AX+1, AY+1) coincides', () => {
|
|
161
|
+
expect(masksOverlap(MASK, AX, AY, MASK, AX + 1, AY + 1)).toBeGreaterThan(0);
|
|
162
|
+
expect(bruteForce(MASK, AX, AY, MASK, AX + 1, AY + 1)).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
// ── Overlap count ─────────────────────────────────────────────────────────
|
|
166
|
+
// masksOverlap returns a count, not just a boolean. Verify it grows as the
|
|
167
|
+
// sprites move deeper into each other.
|
|
168
|
+
describe('overlap pixel count increases with penetration depth', () => {
|
|
169
|
+
const AX = 20, AY = 20;
|
|
170
|
+
it('1 overlapping pixel at first horizontal contact (bx = AX+1)', () => {
|
|
171
|
+
// Only column AX+1 is shared (both rows): 2 pixels
|
|
172
|
+
expect(masksOverlap(MASK, AX, AY, MASK, AX + 1, AY)).toBe(2);
|
|
173
|
+
});
|
|
174
|
+
it('4 overlapping pixels when fully aligned (bx = AX)', () => {
|
|
175
|
+
// Complete overlap of the 2×2 regions
|
|
176
|
+
expect(masksOverlap(MASK, AX, AY, MASK, AX, AY)).toBe(4);
|
|
177
|
+
});
|
|
178
|
+
it('single corner pixel at diagonal first contact (d=1)', () => {
|
|
179
|
+
// Only (AX+1, AY+1) is shared
|
|
180
|
+
expect(masksOverlap(MASK, AX, AY, MASK, AX + 1, AY + 1)).toBe(1);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
// ── Symmetry ──────────────────────────────────────────────────────────────
|
|
184
|
+
// masksOverlap(A, B) must equal masksOverlap(B, A) — no directional bias.
|
|
185
|
+
describe('symmetry: collision count is order-independent', () => {
|
|
186
|
+
it('A→B count equals B→A count for all horizontal positions', () => {
|
|
187
|
+
const AX = 20, AY = 20;
|
|
188
|
+
for (let bx = AX + 5; bx >= AX - 3; bx--) {
|
|
189
|
+
const ab = masksOverlap(MASK, AX, AY, MASK, bx, AY);
|
|
190
|
+
const ba = masksOverlap(MASK, bx, AY, MASK, AX, AY);
|
|
191
|
+
if (ab !== ba) {
|
|
192
|
+
throw new Error(`Asymmetry at bx=${bx}: A→B=${ab}, B→A=${ba}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
//# sourceMappingURL=collision.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collision.test.js","sourceRoot":"","sources":["../../src/__tests__/collision.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,8 +6,11 @@ export * from './audio.js';
|
|
|
6
6
|
export * from './input.js';
|
|
7
7
|
export * from './ui.js';
|
|
8
8
|
export * from './tilemap.js';
|
|
9
|
+
export * from './tilescroll.js';
|
|
9
10
|
export * from './sprite.js';
|
|
10
11
|
export * from './collision.js';
|
|
12
|
+
export * from './particles.js';
|
|
13
|
+
export * from './rng.js';
|
|
11
14
|
export * from './animation.js';
|
|
12
15
|
export * from './camera.js';
|
|
13
16
|
export * from './scene.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -6,8 +6,11 @@ export * from './audio.js';
|
|
|
6
6
|
export * from './input.js';
|
|
7
7
|
export * from './ui.js';
|
|
8
8
|
export * from './tilemap.js';
|
|
9
|
+
export * from './tilescroll.js';
|
|
9
10
|
export * from './sprite.js';
|
|
10
11
|
export * from './collision.js';
|
|
12
|
+
export * from './particles.js';
|
|
13
|
+
export * from './rng.js';
|
|
11
14
|
export * from './animation.js';
|
|
12
15
|
export * from './camera.js';
|
|
13
16
|
export * from './scene.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA"}
|