@chris.araneo/cassette-canvas 0.1.0 → 0.2.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 +115 -38
- package/dist/lib/index.js +170 -47
- package/dist/lib/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<img src="https://raw.githubusercontent.com/ChrisAraneo/cassette-canvas/master/cassette.png" width="512px" height="256px"/>
|
|
5
5
|
<br>
|
|
6
|
-
<a href="https://github.com/ChrisAraneo/cassette-canvas/blob/master/package.json"><img src="https://img.shields.io/badge/version-v0.
|
|
6
|
+
<a href="https://github.com/ChrisAraneo/cassette-canvas/blob/master/package.json"><img src="https://img.shields.io/badge/version-v0.2.0-blue" alt="version"></a>
|
|
7
7
|
<a href="https://github.com/ChrisAraneo/cassette-canvas/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="@chris.araneo/cassette-canvas is released under the MIT license."></a>
|
|
8
8
|
<a href="https://github.com/ChrisAraneo/cassette-canvas/actions/workflows/ci.yml"><img alt="GitHub CI Status" src="https://img.shields.io/github/actions/workflow/status/ChrisAraneo/cassette-canvas/ci.yml?label=CI&logo=GitHub"></a>
|
|
9
9
|
<br>
|
|
@@ -29,20 +29,21 @@ browser and needs WebGL.
|
|
|
29
29
|
|
|
30
30
|
## Concepts
|
|
31
31
|
|
|
32
|
-
| Concept | Meaning
|
|
33
|
-
| ------------------ |
|
|
34
|
-
| **Base colors** | The 16 colors you provide, as `#rrggbb`.
|
|
35
|
-
| **Shade** | Sprites can use 8 shades of each color (0 to 7). Shade 4 (`BASE_SHADE`) is the base color. Shades 0 to 3 are darker and 5 to 7 are lighter.
|
|
36
|
-
| **Extended shade** | Lighting uses 32 shades of each color (0 to 31). 4 extended shades = 1 shade. The palette holds all 16 × 32 = 512 colors.
|
|
37
|
-
| **Pixel** | A number with the color in the high hex digit and the shade in the low hex digit. `0x35` is color 3, shade 5. `TRANSPARENT` (`-1`) draws nothing.
|
|
38
|
-
| **Pixel size** | The size of one logical pixel in canvas pixels, for example `4`. The screen size in logical pixels is the canvas size divided by the pixel size.
|
|
39
|
-
| **Sprite** | A flat array of `spriteSize × spriteSize` pixels, row by row from the top left.
|
|
40
|
-
| **Screen** | Immutable data: the background, sprites, lights and ambient light. Later sprites are drawn over earlier ones.
|
|
41
|
-
| **Display** | The canvas, its WebGL context, the monitor shader and the palette. You create it once.
|
|
42
|
-
| **Ambient light** | Shifts every pixel by a number of extended shades. `0` keeps the authored colors. `-16` (4 shades darker) looks like night.
|
|
43
|
-
| **Light** | A point at `row`, `column`.
|
|
44
|
-
| **
|
|
45
|
-
| **
|
|
32
|
+
| Concept | Meaning |
|
|
33
|
+
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
34
|
+
| **Base colors** | The 16 colors you provide, as `#rrggbb`. |
|
|
35
|
+
| **Shade** | Sprites can use 8 shades of each color (0 to 7). Shade 4 (`BASE_SHADE`) is the base color. Shades 0 to 3 are darker and 5 to 7 are lighter. |
|
|
36
|
+
| **Extended shade** | Lighting uses 32 shades of each color (0 to 31). 4 extended shades = 1 shade. The palette holds all 16 × 32 = 512 colors. |
|
|
37
|
+
| **Pixel** | A number with the color in the high hex digit and the shade in the low hex digit. `0x35` is color 3, shade 5. `TRANSPARENT` (`-1`) draws nothing. |
|
|
38
|
+
| **Pixel size** | The size of one logical pixel in canvas pixels, for example `4`. The screen size in logical pixels is the canvas size divided by the pixel size. |
|
|
39
|
+
| **Sprite** | A flat array of `spriteSize × spriteSize` pixels, row by row from the top left. |
|
|
40
|
+
| **Screen** | Immutable data: the background, sprites, lights and ambient light. Later sprites are drawn over earlier ones. |
|
|
41
|
+
| **Display** | The canvas, its WebGL context, the monitor shader and the palette. You create it once. |
|
|
42
|
+
| **Ambient light** | Shifts every pixel by a number of extended shades. `0` keeps the authored colors. `-16` (4 shades darker) looks like night. |
|
|
43
|
+
| **Light** | A point at `row`, `column`. At its center it lifts a pixel that daylight already lights by `intensity` extended shades, fading to nothing at `radius`. |
|
|
44
|
+
| **Stop** | One doubling of light, worth `EXTENDED_SHADES_PER_STOP` (4) extended shades. Lights are added as light and then read off this scale, the way the eye does. |
|
|
45
|
+
| **Shadow** | A sprite with `castsShadow: true` blocks light. Pixels behind it get no light from that light, and the shadow's edge ramps rather than steps. Its side that faces the light is still lit. |
|
|
46
|
+
| **Monitor effect** | A shader that adds a 2-pixel glow, scanlines, film grain and a surface texture. It uses full RGB color, not the palette. |
|
|
46
47
|
|
|
47
48
|
## Usage
|
|
48
49
|
|
|
@@ -143,11 +144,45 @@ shade 0 1 4 7
|
|
|
143
144
|
↑ darkest ↑ lightest a sprite can use
|
|
144
145
|
```
|
|
145
146
|
|
|
146
|
-
Light never changes a pixel's color, only its shade.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
147
|
+
Light never changes a pixel's color, only its shade. Ambient light and light
|
|
148
|
+
intensities are written in extended shades
|
|
149
|
+
(`EXTENDED_SHADES_PER_SHADE` = 4 per shade), because that is what you see on
|
|
150
|
+
screen. With ambient 0 and no lights, every pixel shows its authored color.
|
|
151
|
+
|
|
152
|
+
**Lights add up as light, not as shade numbers.** Shade numbers are a
|
|
153
|
+
brightness scale, and brightness scales do not add: a room with two lamps is
|
|
154
|
+
not twice as bright to look at as a room with one. So the engine turns the
|
|
155
|
+
ambient light and each light back into the amount of light behind it, adds
|
|
156
|
+
those amounts, and reads the shade off the total. Every doubling of light is
|
|
157
|
+
worth the same step on the scale, `EXTENDED_SHADES_PER_STOP` = 4 extended
|
|
158
|
+
shades, which is how the eye reads brightness.
|
|
159
|
+
|
|
160
|
+
What that gives you:
|
|
161
|
+
|
|
162
|
+
- One light on an unlit scene moves a pixel by exactly its `intensity`, as it
|
|
163
|
+
always did.
|
|
164
|
+
- A second light of the same intensity on the same spot adds one step, not
|
|
165
|
+
another full intensity. Ten lamps add a little over three steps.
|
|
166
|
+
- A lamp in daylight hardly tells, and the same lamp at night carries the
|
|
167
|
+
whole scene. Nothing piles up into flat white, so sprites and tiles keep
|
|
168
|
+
their shading however many lights reach them.
|
|
169
|
+
|
|
170
|
+
An `intensity` is therefore best read as _how many extended shades this light
|
|
171
|
+
lifts a pixel that daylight already lights_. A lamp that is only there to
|
|
172
|
+
light the night wants a small number, around 4 to 6; a flash or an explosion
|
|
173
|
+
wants a large one. Ambient and intensity can be fractions; the total is
|
|
174
|
+
rounded for each pixel and kept between extended shade 0 and 31.
|
|
175
|
+
|
|
176
|
+
A light also thins out with distance the way light does in the world, so it
|
|
177
|
+
leaves a bright core and a long dim halo rather than a flat disc, and the rim
|
|
178
|
+
at `radius` fades out smoothly instead of ending in a visible circle.
|
|
179
|
+
|
|
180
|
+
Shadow edges are soft. Every shadow carries a penumbra: its edge ramps from
|
|
181
|
+
full light to full shadow over two pixels, the way an edge looks under a
|
|
182
|
+
light that is not a single point. Only the edge softens, so the heart of a
|
|
183
|
+
shadow stays as dark as it ever was and a shadow never fades out as it
|
|
184
|
+
lengthens. `SHADOW_SOFTNESS` in `libs/engine/src/lighting/consts.ts` sets how
|
|
185
|
+
far the ramp goes, and `0` brings back the hard edges of a point source.
|
|
151
186
|
|
|
152
187
|
```ts
|
|
153
188
|
const WALL = toSprite(times(8, constant('..oooo..')), { o: toPixel(0, 3) });
|
|
@@ -161,19 +196,22 @@ const night = pipe(
|
|
|
161
196
|
row: 44,
|
|
162
197
|
column: 30,
|
|
163
198
|
radius: 40,
|
|
164
|
-
intensity:
|
|
199
|
+
intensity: 5,
|
|
165
200
|
}),
|
|
166
201
|
);
|
|
167
202
|
|
|
168
203
|
const torchMoved = patchLightByOffset(night, 'torch', 0, 2);
|
|
169
|
-
const flickered = patchLight(torchMoved, 'torch', { intensity:
|
|
204
|
+
const flickered = patchLight(torchMoved, 'torch', { intensity: 4 });
|
|
170
205
|
const dawn = patchAmbient(flickered, -3);
|
|
171
206
|
```
|
|
172
207
|
|
|
173
|
-
With ambient `-16`
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
208
|
+
With ambient `-16` (night), that torch of intensity `5` lifts its core about
|
|
209
|
+
18 extended shades above the night around it, a little past the authored
|
|
210
|
+
colors, and fades back to night through many small steps. The same torch at
|
|
211
|
+
noon lifts a pixel by 5. Flickering it down to `4` costs about 2 shades at
|
|
212
|
+
the core, not 1, because near the center the light is strong and the eye
|
|
213
|
+
notices less of it. A big number such as `16` is a flash or an explosion: it
|
|
214
|
+
burns the core out to the lightest shade.
|
|
177
215
|
|
|
178
216
|
A lit frame holds extended pixels:
|
|
179
217
|
`toExtendedPixel(color, extendedShade)` = `color × 32 + extendedShade`. The
|
|
@@ -222,13 +260,21 @@ screen
|
|
|
222
260
|
|
|
223
261
|
**`computeFrame`** fills the frame with the background and draws each sprite
|
|
224
262
|
in order. It also records which pixels belong to sprites that cast shadows.
|
|
225
|
-
Then it
|
|
226
|
-
matching extended shade.
|
|
263
|
+
Then it works out how much light reaches every pixel and moves the pixel to
|
|
264
|
+
the matching extended shade.
|
|
265
|
+
|
|
266
|
+
**Light.** Each light is turned into the amount of light it adds to every
|
|
267
|
+
pixel it reaches. Those amounts are added to the ambient light, pixel by
|
|
268
|
+
pixel, and the shade comes off the total at 4 extended shades per doubling.
|
|
269
|
+
Pixels no light reaches skip that step and keep the ambient light exactly.
|
|
227
270
|
|
|
228
271
|
**Shadows.** For each light, the engine visits pixels in rings, moving
|
|
229
272
|
outward from the light. A pixel gets light if the next pixel toward the light
|
|
230
273
|
gets light and does not cast a shadow. This costs one lookup per pixel
|
|
231
|
-
instead of tracing a full line back to the light.
|
|
274
|
+
instead of tracing a full line back to the light. For the penumbra, a pixel
|
|
275
|
+
takes a quarter of its light from each side of that pixel, along the ring it
|
|
276
|
+
sits on, which softens the edge of a shadow without touching what the shadow
|
|
277
|
+
covers.
|
|
232
278
|
|
|
233
279
|
**The shader** runs once for every canvas pixel. It:
|
|
234
280
|
|
|
@@ -253,24 +299,55 @@ libs/
|
|
|
253
299
|
├── index.ts the public API
|
|
254
300
|
├── palette/ colors, shades and pixel values
|
|
255
301
|
├── screen/ sprites, lights and ambient light
|
|
256
|
-
├── lighting/ light
|
|
302
|
+
├── lighting/ light amounts, the shade scale and shadows
|
|
257
303
|
├── frame/ turns a screen into lit pixels
|
|
258
304
|
├── crt/ the WebGL monitor shader
|
|
259
305
|
└── display/ draws to the canvas
|
|
260
306
|
apps/
|
|
261
|
-
└── demo/
|
|
262
|
-
|
|
307
|
+
└── demo/src/
|
|
308
|
+
├── main.ts the animation loop for both canvases
|
|
309
|
+
├── art/ text art: sky, buildings, street, people, cars, font
|
|
310
|
+
├── tiles/ cuts large art into sprites and places it
|
|
311
|
+
├── day/ the time of day and how dark it is
|
|
312
|
+
├── street/ the city street, composed from scratch every frame
|
|
313
|
+
├── hud/ the palette swatches, the frame rate and the clock
|
|
314
|
+
└── fps/ measures the frame rate
|
|
263
315
|
```
|
|
264
316
|
|
|
317
|
+
## Demo
|
|
318
|
+
|
|
319
|
+
Run `npm run dev` and open the page Vite prints. The demo draws two canvases,
|
|
320
|
+
each with its own display:
|
|
321
|
+
|
|
322
|
+
- **The street.** A city street with houses, trees, street lamps, people,
|
|
323
|
+
cars, clouds and a distant skyline. A day and night pass every 48 seconds.
|
|
324
|
+
The ambient light darkens at dusk, and the sun and then the moon cross the
|
|
325
|
+
sky behind the buildings. At night the stars come out, the lamps and
|
|
326
|
+
headlights light up, and people and cars cast shadows. Windows light up one
|
|
327
|
+
by one and go dark again later in the night.
|
|
328
|
+
- **The HUD.** A swatch for each of the 128 sprite colors (16 colors × 8
|
|
329
|
+
shades), the frame rate, and the time of day on the street. It has no
|
|
330
|
+
ambient light, so the swatches always show their exact colors, even when it
|
|
331
|
+
is night on the street.
|
|
332
|
+
|
|
333
|
+
The street does not keep sprites from one frame to the next. Every frame,
|
|
334
|
+
`composeStreetScreen` builds a new screen for that moment from the scenery
|
|
335
|
+
that never moves (built once) and from everything that does.
|
|
336
|
+
|
|
265
337
|
## Performance
|
|
266
338
|
|
|
267
|
-
Measured on the demo
|
|
268
|
-
|
|
339
|
+
Measured on the demo street at 1280×720 (320×180 logical pixels), in a
|
|
340
|
+
Rolldown bundle run with Node 24. By day the street has 856 sprites and 1
|
|
341
|
+
light; at night it has 1,013 sprites and 11 lights:
|
|
342
|
+
|
|
343
|
+
| Step | By day | At night |
|
|
344
|
+
| -------------- | ------- | -------- |
|
|
345
|
+
| `computeFrame` | ~2.1 ms | ~4.8 ms |
|
|
346
|
+
| `toFrameBytes` | ~0.4 ms | ~0.4 ms |
|
|
269
347
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
| `toFrameBytes` | ~0.3 ms |
|
|
348
|
+
Adding up light rather than shade numbers, and softening the shadow edges,
|
|
349
|
+
costs nothing by day and about 0.1 ms a frame at night, where 11 lights reach
|
|
350
|
+
44% of the screen.
|
|
274
351
|
|
|
275
352
|
## License
|
|
276
353
|
|
package/dist/lib/index.js
CHANGED
|
@@ -59,53 +59,6 @@ const patchCrtUniforms = (context, { program, uniforms }, { width, height, pixel
|
|
|
59
59
|
* The effect uses full RGB color, not the palette.
|
|
60
60
|
*/
|
|
61
61
|
const drawCrt = (context, crt, picture, time) => pipe(context, (gl) => runEffect(gl, () => patchCrtTexture(gl, crt, picture)), (gl) => runEffect(gl, () => patchCrtUniforms(gl, crt, picture, time)), (gl) => drawCrtTriangle(gl, crt));
|
|
62
|
-
// libs/engine/src/lighting/internal/compute-field-levels.ts
|
|
63
|
-
const computeFieldLevels = ({ ambient, width, height }, fields) => {
|
|
64
|
-
const levels = new Float64Array(Math.max(0, width * height));
|
|
65
|
-
for (const field of fields) for (let index = 0; index < levels.length; index += 1) levels[index] = (levels[index] ?? 0) + (field[index] ?? 0);
|
|
66
|
-
for (let index = 0; index < levels.length; index += 1) levels[index] = ambient + (levels[index] ?? 0);
|
|
67
|
-
return levels;
|
|
68
|
-
};
|
|
69
|
-
// libs/engine/src/lighting/internal/compute-light-field.ts
|
|
70
|
-
const computeLightField = ({ width, height, occluders }, light) => {
|
|
71
|
-
const field = new Float32Array(width * height);
|
|
72
|
-
const visible = new Uint8Array(width * height);
|
|
73
|
-
const lightRow = Math.floor(light.row);
|
|
74
|
-
const lightColumn = Math.floor(light.column);
|
|
75
|
-
const reach = Math.ceil(light.radius);
|
|
76
|
-
const firstRing = Math.max(0, -lightRow, lightRow - (height - 1), -lightColumn, lightColumn - (width - 1));
|
|
77
|
-
for (let ring = firstRing; ring <= reach; ring += 1) {
|
|
78
|
-
const top = Math.max(-ring, -lightRow);
|
|
79
|
-
const bottom = Math.min(ring, height - 1 - lightRow);
|
|
80
|
-
for (let rowOffset = top; rowOffset <= bottom; rowOffset += 1) {
|
|
81
|
-
const isEdgeRow = rowOffset === -ring || rowOffset === ring;
|
|
82
|
-
const left = isEdgeRow ? Math.max(-ring, -lightColumn) : -ring;
|
|
83
|
-
const right = isEdgeRow ? Math.min(ring, width - 1 - lightColumn) : ring;
|
|
84
|
-
const step = isEdgeRow ? 1 : 2 * ring;
|
|
85
|
-
for (let columnOffset = left; columnOffset <= right; columnOffset += step) {
|
|
86
|
-
const row = lightRow + rowOffset;
|
|
87
|
-
const column = lightColumn + columnOffset;
|
|
88
|
-
if (column < 0 || column >= width) continue;
|
|
89
|
-
const parentRow = lightRow + Math.round(rowOffset * (ring - 1) / ring);
|
|
90
|
-
const parentColumn = lightColumn + Math.round(columnOffset * (ring - 1) / ring);
|
|
91
|
-
const parentIndex = parentRow * width + parentColumn;
|
|
92
|
-
const isVisible = !(ring >= 2 && parentRow >= 0 && parentRow < height && parentColumn >= 0 && parentColumn < width) || visible[parentIndex] === 1 && occluders[parentIndex] !== 1;
|
|
93
|
-
const index = row * width + column;
|
|
94
|
-
const distance = Math.sqrt(rowOffset * rowOffset + columnOffset * columnOffset);
|
|
95
|
-
visible[index] = isVisible ? 1 : 0;
|
|
96
|
-
if (isVisible && distance < light.radius) field[index] = light.intensity * (1 - distance / light.radius);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return field;
|
|
101
|
-
};
|
|
102
|
-
// libs/engine/src/lighting/compute-light-levels.ts
|
|
103
|
-
const computeLightLevels = (lighting) => computeFieldLevels(lighting, map(lighting.lights, (light) => computeLightField(lighting, light)));
|
|
104
|
-
// libs/engine/src/frame/internal/compute-frame-light.ts
|
|
105
|
-
const computeFrameLight = ({ pixels, lighting }) => ({
|
|
106
|
-
pixels,
|
|
107
|
-
levels: computeLightLevels(lighting)
|
|
108
|
-
});
|
|
109
62
|
// libs/engine/src/palette/consts.ts
|
|
110
63
|
/** The number of base colors in a palette. */
|
|
111
64
|
const COLOR_COUNT = 16;
|
|
@@ -135,6 +88,176 @@ const WHITE = Object.freeze({
|
|
|
135
88
|
green: 255,
|
|
136
89
|
blue: 255
|
|
137
90
|
});
|
|
91
|
+
// libs/engine/src/lighting/consts.ts
|
|
92
|
+
/**
|
|
93
|
+
* How many extended shades one doubling of light moves a pixel: one sprite
|
|
94
|
+
* shade per doubling. Lights add up as light and are read off this scale
|
|
95
|
+
* afterwards, so every doubling costs the same number of shades, the way the
|
|
96
|
+
* eye sees brightness.
|
|
97
|
+
*/
|
|
98
|
+
const EXTENDED_SHADES_PER_STOP = 4;
|
|
99
|
+
/**
|
|
100
|
+
* The most light can move a pixel, in extended shades. A whole ramp in
|
|
101
|
+
* either direction is more than any pixel needs: every pixel stops at the
|
|
102
|
+
* darkest or the lightest extended shade well before this.
|
|
103
|
+
*/
|
|
104
|
+
const MAX_SHADE_OFFSET = 32;
|
|
105
|
+
const MIN_EXPOSURE = 2 ** (-32 / EXTENDED_SHADES_PER_STOP);
|
|
106
|
+
const MAX_EXPOSURE = 2 ** (MAX_SHADE_OFFSET / EXTENDED_SHADES_PER_STOP);
|
|
107
|
+
/**
|
|
108
|
+
* How much of the light at a pixel comes from each side of the pixel ahead
|
|
109
|
+
* of it, toward the light, rather than all of it from that one pixel. It
|
|
110
|
+
* gives every shadow a penumbra: its edge ramps from full light to full
|
|
111
|
+
* shadow over two pixels instead of stepping down in one, the way an edge
|
|
112
|
+
* looks under a light that is not a single point.
|
|
113
|
+
*
|
|
114
|
+
* Only the edge softens. What blocks a light still blocks it, so the heart
|
|
115
|
+
* of a shadow stays as dark as ever, and a shadow never fades out with
|
|
116
|
+
* distance. 0 gives the hard edges of a point source, and 0.25 spreads the
|
|
117
|
+
* ramp evenly, which is as soft as three pixels go.
|
|
118
|
+
*/
|
|
119
|
+
const SHADOW_SOFTNESS = .25;
|
|
120
|
+
// libs/engine/src/lighting/internal/compute-falloff.ts
|
|
121
|
+
const computeWindow = (squaredReach) => 1 - squaredReach * squaredReach;
|
|
122
|
+
/**
|
|
123
|
+
* Returns the share of a light that reaches a point: 1 at its center, 0 at
|
|
124
|
+
* its radius. `squaredReach` is the distance to the light as a share of the
|
|
125
|
+
* radius, squared, from 0 at the center to 1 at the radius. Squared, because
|
|
126
|
+
* then the caller needs no square root, and this runs once per lit pixel.
|
|
127
|
+
*
|
|
128
|
+
* The share thins out with distance the way light spreads out in the world,
|
|
129
|
+
* and a window closes it off smoothly at the radius, so a light leaves a
|
|
130
|
+
* bright core and a long dim halo instead of a flat disc with a visible rim.
|
|
131
|
+
* Callers keep to the center and the radius; a share past 1 means nothing.
|
|
132
|
+
*/
|
|
133
|
+
const computeFalloff = (squaredReach) => computeWindow(squaredReach) ** 2 / (1 + 8 * squaredReach);
|
|
134
|
+
// libs/engine/src/lighting/internal/to-exposure.ts
|
|
135
|
+
/**
|
|
136
|
+
* Returns the amount of light that shows a pixel `shades` extended shades
|
|
137
|
+
* from its authored color. `toExposure(0)` is `REFERENCE_EXPOSURE`, and every
|
|
138
|
+
* `EXTENDED_SHADES_PER_STOP` shades up double it.
|
|
139
|
+
*
|
|
140
|
+
* Ambient light and light intensities are written in extended shades because
|
|
141
|
+
* that is what you see on screen. This turns one of them back into the amount
|
|
142
|
+
* of light behind it, which is what adds up.
|
|
143
|
+
*/
|
|
144
|
+
const toExposure = (shades) => 2 ** (shades / EXTENDED_SHADES_PER_STOP);
|
|
145
|
+
// libs/engine/src/lighting/internal/compute-light-exposure.ts
|
|
146
|
+
const SHARE_AHEAD = 1 - 2 * SHADOW_SOFTNESS;
|
|
147
|
+
/**
|
|
148
|
+
* Returns how much light one light adds to every pixel. The values are
|
|
149
|
+
* amounts of light, not extended shades, because that is what adds up:
|
|
150
|
+
* `computeShadeOffsets` sums the amounts and reads the shades off them.
|
|
151
|
+
*
|
|
152
|
+
* At its center the light adds as much light as its intensity stands for,
|
|
153
|
+
* which thins out to nothing at its radius. Sprites that cast shadows block
|
|
154
|
+
* it, and the edge of a shadow ramps down over two pixels rather than
|
|
155
|
+
* stepping down in one. A negative intensity takes light away instead.
|
|
156
|
+
*/
|
|
157
|
+
const computeLightExposure = ({ width, height, occluders }, light) => {
|
|
158
|
+
const exposures = new Float32Array(width * height);
|
|
159
|
+
const visible = new Uint8Array(width * height);
|
|
160
|
+
const lightRow = Math.floor(light.row);
|
|
161
|
+
const lightColumn = Math.floor(light.column);
|
|
162
|
+
const reach = Math.ceil(light.radius);
|
|
163
|
+
const squaredRadius = light.radius * light.radius;
|
|
164
|
+
const emission = toExposure(light.intensity) - 1;
|
|
165
|
+
const firstRing = Math.max(0, -lightRow, lightRow - (height - 1), -lightColumn, lightColumn - (width - 1));
|
|
166
|
+
for (let ring = firstRing; ring <= reach; ring += 1) {
|
|
167
|
+
const top = Math.max(-ring, -lightRow);
|
|
168
|
+
const bottom = Math.min(ring, height - 1 - lightRow);
|
|
169
|
+
const sideReach = ring - 1;
|
|
170
|
+
for (let rowOffset = top; rowOffset <= bottom; rowOffset += 1) {
|
|
171
|
+
const isEdgeRow = rowOffset === -ring || rowOffset === ring;
|
|
172
|
+
const left = isEdgeRow ? Math.max(-ring, -lightColumn) : -ring;
|
|
173
|
+
const right = isEdgeRow ? Math.min(ring, width - 1 - lightColumn) : ring;
|
|
174
|
+
const step = isEdgeRow ? 1 : 2 * ring;
|
|
175
|
+
for (let columnOffset = left; columnOffset <= right; columnOffset += step) {
|
|
176
|
+
const row = lightRow + rowOffset;
|
|
177
|
+
const column = lightColumn + columnOffset;
|
|
178
|
+
if (column < 0 || column >= width) continue;
|
|
179
|
+
const parentRowOffset = Math.round(rowOffset * sideReach / ring);
|
|
180
|
+
const parentColumnOffset = Math.round(columnOffset * sideReach / ring);
|
|
181
|
+
const parentRow = lightRow + parentRowOffset;
|
|
182
|
+
const parentColumn = lightColumn + parentColumnOffset;
|
|
183
|
+
const parentIndex = parentRow * width + parentColumn;
|
|
184
|
+
const hasParent = ring >= 2 && parentRow >= 0 && parentRow < height && parentColumn >= 0 && parentColumn < width;
|
|
185
|
+
const litAhead = !hasParent || visible[parentIndex] === 1 && occluders[parentIndex] !== 1 ? 1 : 0;
|
|
186
|
+
const runsAlongRow = Math.abs(parentRowOffset) >= Math.abs(parentColumnOffset);
|
|
187
|
+
const sideStep = runsAlongRow ? 1 : width;
|
|
188
|
+
const sideOffset = runsAlongRow ? parentColumnOffset : parentRowOffset;
|
|
189
|
+
const sidePlace = runsAlongRow ? parentColumn : parentRow;
|
|
190
|
+
const sideLimit = runsAlongRow ? width : height;
|
|
191
|
+
const lowIndex = parentIndex - sideStep;
|
|
192
|
+
const highIndex = parentIndex + sideStep;
|
|
193
|
+
const litLow = hasParent && sideOffset > -sideReach && sidePlace > 0 ? visible[lowIndex] === 1 && occluders[lowIndex] !== 1 ? 1 : 0 : litAhead;
|
|
194
|
+
const litHigh = hasParent && sideOffset < sideReach && sidePlace < sideLimit - 1 ? visible[highIndex] === 1 && occluders[highIndex] !== 1 ? 1 : 0 : litAhead;
|
|
195
|
+
const share = SHARE_AHEAD * litAhead + SHADOW_SOFTNESS * (litLow + litHigh);
|
|
196
|
+
const index = row * width + column;
|
|
197
|
+
const squaredDistance = rowOffset * rowOffset + columnOffset * columnOffset;
|
|
198
|
+
visible[index] = litAhead;
|
|
199
|
+
if (share > 0 && squaredDistance < squaredRadius) exposures[index] = emission * share * computeFalloff(squaredDistance / squaredRadius);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return exposures;
|
|
204
|
+
};
|
|
205
|
+
// libs/engine/src/lighting/internal/to-shade-offset.ts
|
|
206
|
+
/**
|
|
207
|
+
* Returns how many extended shades an amount of light moves a pixel from its
|
|
208
|
+
* authored color. This undoes `toExposure`: `REFERENCE_EXPOSURE` gives 0,
|
|
209
|
+
* twice as much light gives `EXTENDED_SHADES_PER_STOP` more shades, and half
|
|
210
|
+
* as much gives that many fewer. Light beyond what a pixel can show stops at
|
|
211
|
+
* the ends of the scale.
|
|
212
|
+
*
|
|
213
|
+
* Because every doubling costs the same number of shades, a scene that is
|
|
214
|
+
* already bright barely shows one more lamp, while the same lamp in the dark
|
|
215
|
+
* shows in full. That is how the eye reads brightness, and it is why lights
|
|
216
|
+
* no longer stack up into white.
|
|
217
|
+
*
|
|
218
|
+
* It clamps with `Math.min` and `Math.max` rather than `clamp`, because it
|
|
219
|
+
* runs once per lit pixel.
|
|
220
|
+
*/
|
|
221
|
+
const toShadeOffset = (exposure) => EXTENDED_SHADES_PER_STOP * Math.log2(Math.min(MAX_EXPOSURE, Math.max(MIN_EXPOSURE, exposure)));
|
|
222
|
+
// libs/engine/src/lighting/internal/compute-shade-offsets.ts
|
|
223
|
+
/**
|
|
224
|
+
* Adds up the ambient light and every light that reaches a pixel, then reads
|
|
225
|
+
* off how many extended shades that much light moves the pixel.
|
|
226
|
+
*
|
|
227
|
+
* The amounts of light are added, not the shades. Two lamps on one spot are
|
|
228
|
+
* twice the light, which is one shade step more than one lamp, not twice as
|
|
229
|
+
* many shade steps, and a lamp in daylight hardly tells at all. That is why
|
|
230
|
+
* a lit scene keeps its shading instead of washing out to white.
|
|
231
|
+
*/
|
|
232
|
+
const computeShadeOffsets = ({ ambient, width, height }, exposures) => {
|
|
233
|
+
const offsets = new Float64Array(Math.max(0, width * height));
|
|
234
|
+
const ambientExposure = toExposure(ambient);
|
|
235
|
+
for (const exposure of exposures) for (let index = 0; index < offsets.length; index += 1) offsets[index] = (offsets[index] ?? 0) + (exposure[index] ?? 0);
|
|
236
|
+
for (let index = 0; index < offsets.length; index += 1) {
|
|
237
|
+
const light = offsets[index] ?? 0;
|
|
238
|
+
offsets[index] = light === 0 ? ambient : toShadeOffset(ambientExposure + light);
|
|
239
|
+
}
|
|
240
|
+
return offsets;
|
|
241
|
+
};
|
|
242
|
+
// libs/engine/src/lighting/compute-light-levels.ts
|
|
243
|
+
/**
|
|
244
|
+
* Returns how many extended shades the light moves every pixel, row by row
|
|
245
|
+
* from the top left.
|
|
246
|
+
*
|
|
247
|
+
* Lights are combined the way light behaves, not the way numbers add. Each
|
|
248
|
+
* light and the ambient light are turned into amounts of light, those amounts
|
|
249
|
+
* are added together, and the shade is read off the total on a scale where
|
|
250
|
+
* every doubling of light is worth `EXTENDED_SHADES_PER_STOP` shades. So one
|
|
251
|
+
* light on an unlit scene moves a pixel by exactly its intensity, while a
|
|
252
|
+
* second light of the same intensity adds only one more step, and a lamp in
|
|
253
|
+
* bright daylight barely shows. Nothing stacks up into flat white.
|
|
254
|
+
*/
|
|
255
|
+
const computeLightLevels = (lighting) => computeShadeOffsets(lighting, map(lighting.lights, (light) => computeLightExposure(lighting, light)));
|
|
256
|
+
// libs/engine/src/frame/internal/compute-frame-light.ts
|
|
257
|
+
const computeFrameLight = ({ pixels, lighting }) => ({
|
|
258
|
+
pixels,
|
|
259
|
+
levels: computeLightLevels(lighting)
|
|
260
|
+
});
|
|
138
261
|
// libs/engine/src/palette/internal/is-index.ts
|
|
139
262
|
const isIndex = (index, size) => isInteger(index) && inRange(index, size);
|
|
140
263
|
// libs/engine/src/palette/to-extended-pixel.ts
|
package/dist/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["nonNullable","nonNullable","nonNullable","nonNullable","nonNullable","nonNullable","number","nonNullable","nonNullable","nonNullable"],"sources":["../../libs/common/src/effects/run-effect.ts","../../libs/engine/src/crt/consts.ts","../../libs/engine/src/crt/internal/draw-crt-triangle.ts","../../libs/engine/src/crt/internal/patch-crt-texture.ts","../../libs/engine/src/crt/internal/patch-crt-uniforms.ts","../../libs/engine/src/crt/draw-crt.ts","../../libs/engine/src/lighting/internal/compute-field-levels.ts","../../libs/engine/src/lighting/internal/compute-light-field.ts","../../libs/engine/src/lighting/compute-light-levels.ts","../../libs/engine/src/frame/internal/compute-frame-light.ts","../../libs/engine/src/palette/consts.ts","../../libs/engine/src/palette/internal/is-index.ts","../../libs/engine/src/palette/to-extended-pixel.ts","../../libs/engine/src/palette/to-pixel.ts","../../libs/engine/src/palette/internal/to-extended-shade.ts","../../libs/engine/src/palette/internal/create-shade-ramps.ts","../../libs/engine/src/palette/internal/shade-ramps.ts","../../libs/engine/src/palette/is-pixel.ts","../../libs/engine/src/frame/internal/compute-frame-pixels.ts","../../libs/engine/src/palette/to-lit-pixels.ts","../../libs/engine/src/frame/internal/patch-frame-shades.ts","../../libs/engine/src/frame/compute-frame.ts","../../libs/engine/src/display/internal/to-frame-bytes.ts","../../libs/engine/src/display/draw-screen.ts","../../libs/engine/src/crt/internal/find-crt-uniforms.ts","../../libs/engine/src/crt/internal/format-glsl-float.ts","../../libs/engine/src/crt/internal/fragment-shader-source.ts","../../libs/engine/src/crt/internal/try-create-shader.ts","../../libs/engine/src/crt/internal/try-link-program.ts","../../libs/engine/src/crt/internal/vertex-shader-source.ts","../../libs/engine/src/crt/internal/try-create-program.ts","../../libs/engine/src/crt/internal/try-create-texture.ts","../../libs/engine/src/crt/internal/try-create-vertices.ts","../../libs/engine/src/crt/try-create-crt.ts","../../libs/engine/src/palette/internal/compute-rgb-mix.ts","../../libs/engine/src/palette/internal/compute-extended-shade.ts","../../libs/engine/src/palette/internal/create-palette-entries.ts","../../libs/engine/src/palette/internal/split-hex-rgb.ts","../../libs/engine/src/palette/internal/to-rgb.ts","../../libs/engine/src/palette/create-palette.ts","../../libs/engine/src/display/internal/compute-display-size.ts","../../libs/engine/src/display/internal/try-to-display.ts","../../libs/engine/src/display/try-create-display.ts","../../libs/engine/src/palette/to-lit-pixel.ts","../../libs/engine/src/screen/internal/filter-other-lights.ts","../../libs/engine/src/screen/add-light.ts","../../libs/engine/src/screen/internal/filter-other-sprites.ts","../../libs/engine/src/screen/add-sprite.ts","../../libs/engine/src/screen/create-screen.ts","../../libs/engine/src/screen/find-light.ts","../../libs/engine/src/screen/find-sprite.ts","../../libs/engine/src/screen/patch-ambient.ts","../../libs/engine/src/screen/internal/patch-matching-light.ts","../../libs/engine/src/screen/patch-light.ts","../../libs/engine/src/screen/patch-light-by-offset.ts","../../libs/engine/src/screen/internal/patch-sprite-placement.ts","../../libs/engine/src/screen/patch-sprite.ts","../../libs/engine/src/screen/patch-sprite-by-offset.ts","../../libs/engine/src/screen/remove-light.ts","../../libs/engine/src/screen/remove-sprite.ts","../../libs/engine/src/screen/to-sprite.ts"],"sourcesContent":["import { match } from 'ts-pattern';\n\n/**\n * Calls `effect` with `value`, then returns `value`. Use it to run a side\n * effect as a step in a `pipe`: `(scene) => runEffect(scene, draw)`.\n */\nexport const runEffect = <T>(value: T, effect: (value: T) => void): T =>\n match(effect(value)).otherwise(() => value);\n","import type { CrtUniformNames } from './types/crt-uniform-names';\n\nexport const GLOW_STRENGTH = 0.6;\n\nexport const GLOW_THRESHOLD = 0.45;\n\nexport const GLOW_REACH = 2;\n\nexport const SCANLINE_DEPTH = 0.09;\n\nexport const GRAIN_AMOUNT = 0.015;\n\nexport const GRAIN_RATE = 24;\n\nexport const SURFACE_AMOUNT = 0.0125;\n\nexport const SURFACE_SCALE = 3;\n\nexport const POSITION_ATTRIBUTE = 'a_position';\n\nexport const POSITION_LOCATION = 0;\n\nexport const FULL_SCREEN_TRIANGLE: readonly number[] = Object.freeze([\n -1, -1, 3, -1, -1, 3,\n]);\n\nexport const CRT_UNIFORM_NAMES: CrtUniformNames = Object.freeze({\n frame: 'u_frame',\n frameSize: 'u_frameSize',\n canvasSize: 'u_canvasSize',\n pixelSize: 'u_pixelSize',\n time: 'u_time',\n});\n\nexport const CRT_CONTEXT_ATTRIBUTES: WebGLContextAttributes = Object.freeze({\n alpha: true,\n antialias: false,\n depth: false,\n stencil: false,\n premultipliedAlpha: true,\n preserveDrawingBuffer: false,\n});\n","import { runEffect } from '@cassette-canvas/common';\nimport { pipe } from 'remeda';\nimport { POSITION_LOCATION } from '../consts';\nimport type { Crt } from '../types/crt';\n\nconst VALUES_PER_CORNER = 2;\n\nconst CORNER_COUNT = 3;\n\nexport const drawCrtTriangle = (\n context: WebGLRenderingContext,\n { vertices }: Crt,\n): void =>\n pipe(\n context,\n (gl) =>\n runEffect(gl, () =>\n gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight),\n ),\n (gl) => runEffect(gl, () => gl.bindBuffer(gl.ARRAY_BUFFER, vertices)),\n (gl) => runEffect(gl, () => gl.enableVertexAttribArray(POSITION_LOCATION)),\n (gl) =>\n runEffect(gl, () =>\n gl.vertexAttribPointer(\n POSITION_LOCATION,\n VALUES_PER_CORNER,\n gl.FLOAT,\n false,\n 0,\n 0,\n ),\n ),\n (gl) => gl.drawArrays(gl.TRIANGLES, 0, CORNER_COUNT),\n );\n","import { runEffect } from '@cassette-canvas/common';\nimport { pipe } from 'remeda';\nimport type { Crt } from '../types/crt';\nimport type { CrtPicture } from '../types/crt-picture';\n\nexport const patchCrtTexture = (\n context: WebGLRenderingContext,\n { texture }: Crt,\n { bytes, width, height }: CrtPicture,\n): WebGLRenderingContext =>\n pipe(\n context,\n (gl) => runEffect(gl, () => gl.bindTexture(gl.TEXTURE_2D, texture)),\n (gl) =>\n runEffect(gl, () =>\n gl.texImage2D(\n gl.TEXTURE_2D,\n 0,\n gl.RGBA,\n width,\n height,\n 0,\n gl.RGBA,\n gl.UNSIGNED_BYTE,\n new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength),\n ),\n ),\n );\n","import { runEffect } from '@cassette-canvas/common';\nimport { pipe } from 'remeda';\nimport type { Crt } from '../types/crt';\nimport type { CrtPicture } from '../types/crt-picture';\n\nconst FRAME_TEXTURE_UNIT = 0;\n\nexport const patchCrtUniforms = (\n context: WebGLRenderingContext,\n { program, uniforms }: Crt,\n { width, height, pixelSize }: CrtPicture,\n time: number,\n): WebGLRenderingContext =>\n pipe(\n context,\n (gl) => runEffect(gl, () => gl.useProgram(program)),\n (gl) =>\n runEffect(gl, () => gl.uniform1i(uniforms.frame, FRAME_TEXTURE_UNIT)),\n (gl) =>\n runEffect(gl, () => gl.uniform2f(uniforms.frameSize, width, height)),\n (gl) =>\n runEffect(gl, () =>\n gl.uniform2f(\n uniforms.canvasSize,\n gl.drawingBufferWidth,\n gl.drawingBufferHeight,\n ),\n ),\n (gl) => runEffect(gl, () => gl.uniform1f(uniforms.pixelSize, pixelSize)),\n (gl) => runEffect(gl, () => gl.uniform1f(uniforms.time, time)),\n );\n","import { runEffect } from '@cassette-canvas/common';\nimport { pipe } from 'remeda';\nimport { drawCrtTriangle } from './internal/draw-crt-triangle';\nimport { patchCrtTexture } from './internal/patch-crt-texture';\nimport { patchCrtUniforms } from './internal/patch-crt-uniforms';\nimport type { Crt } from './types/crt';\nimport type { CrtPicture } from './types/crt-picture';\n\n/**\n * Draws a frame on the canvas with the monitor effect:\n *\n * - each logical pixel becomes a sharp `pixelSize` square;\n * - bright pixels glow up to 2 canvas pixels outward;\n * - each pixel row has a darker scanline at its bottom;\n * - film grain (animated by `time`, in milliseconds) and a fixed surface\n * texture are added on top.\n *\n * The effect uses full RGB color, not the palette.\n */\nexport const drawCrt = (\n context: WebGLRenderingContext,\n crt: Crt,\n picture: CrtPicture,\n time: number,\n): void =>\n pipe(\n context,\n (gl) => runEffect(gl, () => patchCrtTexture(gl, crt, picture)),\n (gl) => runEffect(gl, () => patchCrtUniforms(gl, crt, picture, time)),\n (gl) => drawCrtTriangle(gl, crt),\n );\n","/* eslint-disable no-restricted-syntax -- Appendix B exception 1, measured below */\nimport type { Lighting } from '../types/lighting';\n\n// Style guide - Appendix B exception 1 (hot numeric kernel).\n// As a pipeline (times, with a sumBy over the fields for every place), adding\n// up two light fields and the ambient light on the demo screen (320x180) took\n// 1.7 ms per frame (Vite bundle, Node 24). This loop takes 0.24 ms. It adds\n// the fields first and the ambient light last, in the same order as sumBy, so\n// every level comes out the same. It only changes the typed array it creates\n// itself, and the array does not change after it returns it.\nexport const computeFieldLevels = (\n { ambient, width, height }: Lighting,\n fields: readonly Float32Array[],\n): Float64Array => {\n const levels = new Float64Array(Math.max(0, width * height));\n\n for (const field of fields) {\n for (let index = 0; index < levels.length; index += 1) {\n levels[index] = (levels[index] ?? 0) + (field[index] ?? 0);\n }\n }\n\n for (let index = 0; index < levels.length; index += 1) {\n levels[index] = ambient + (levels[index] ?? 0);\n }\n\n return levels;\n};\n","/* eslint-disable no-restricted-syntax -- Appendix B exception 1, measured below */\nimport type { Light } from '../../screen/types/light';\nimport type { Lighting } from '../types/lighting';\n\n// Style guide - Appendix B exception 1 (hot numeric kernel).\n// As a pipeline, this ring sweep\n// took 8.84 ms per frame for two lights (7,919 cells) on a 192x128 screen\n// (Vite bundle, Node 24). This loop takes 0.43 ms. On the demo screen\n// (320x180, two lights), reading the shadow casters from a Uint8Array instead\n// of a record, and Math.sqrt instead of Math.hypot, took it from 0.48 ms to\n// 0.21 ms. It only changes typed arrays it creates itself, and they do not\n// change after it returns them.\nexport const computeLightField = (\n { width, height, occluders }: Lighting,\n light: Light,\n): Float32Array => {\n const field = new Float32Array(width * height);\n const visible = new Uint8Array(width * height);\n const lightRow = Math.floor(light.row);\n const lightColumn = Math.floor(light.column);\n const reach = Math.ceil(light.radius);\n const firstRing = Math.max(\n 0,\n -lightRow,\n lightRow - (height - 1),\n -lightColumn,\n lightColumn - (width - 1),\n );\n\n for (let ring = firstRing; ring <= reach; ring += 1) {\n const top = Math.max(-ring, -lightRow);\n const bottom = Math.min(ring, height - 1 - lightRow);\n\n for (let rowOffset = top; rowOffset <= bottom; rowOffset += 1) {\n const isEdgeRow = rowOffset === -ring || rowOffset === ring;\n const left = isEdgeRow ? Math.max(-ring, -lightColumn) : -ring;\n const right = isEdgeRow ? Math.min(ring, width - 1 - lightColumn) : ring;\n const step = isEdgeRow ? 1 : 2 * ring;\n\n for (\n let columnOffset = left;\n columnOffset <= right;\n columnOffset += step\n ) {\n const row = lightRow + rowOffset;\n const column = lightColumn + columnOffset;\n\n if (column < 0 || column >= width) {\n continue;\n }\n\n const parentRow =\n lightRow + Math.round((rowOffset * (ring - 1)) / ring);\n const parentColumn =\n lightColumn + Math.round((columnOffset * (ring - 1)) / ring);\n const parentIndex = parentRow * width + parentColumn;\n const hasParent =\n ring >= 2 &&\n parentRow >= 0 &&\n parentRow < height &&\n parentColumn >= 0 &&\n parentColumn < width;\n const isVisible =\n !hasParent ||\n (visible[parentIndex] === 1 && occluders[parentIndex] !== 1);\n const index = row * width + column;\n const distance = Math.sqrt(\n rowOffset * rowOffset + columnOffset * columnOffset,\n );\n\n visible[index] = isVisible ? 1 : 0;\n\n if (isVisible && distance < light.radius) {\n field[index] = light.intensity * (1 - distance / light.radius);\n }\n }\n }\n }\n\n return field;\n};\n","import { map } from 'lodash-es';\nimport { computeFieldLevels } from './internal/compute-field-levels';\nimport { computeLightField } from './internal/compute-light-field';\nimport type { Lighting } from './types/lighting';\n\nexport const computeLightLevels = (lighting: Lighting): Float64Array =>\n computeFieldLevels(\n lighting,\n map(lighting.lights, (light) => computeLightField(lighting, light)),\n );\n","import { computeLightLevels } from '../../lighting/compute-light-levels';\nimport type { FrameComposition } from './types/frame-composition';\nimport type { FrameLight } from './types/frame-light';\n\nexport const computeFrameLight = ({\n pixels,\n lighting,\n}: FrameComposition): FrameLight => ({\n pixels,\n levels: computeLightLevels(lighting),\n});\n","import type { Rgb } from './types/rgb';\n\n/** The number of base colors in a palette. */\nexport const COLOR_COUNT = 16;\n\n/** The number of shades of each color that sprites and backgrounds use. */\nexport const SHADE_COUNT = 8;\n\n/**\n * The shade that shows a base color exactly as given. Lower shades are\n * darker, higher shades are lighter.\n */\nexport const BASE_SHADE = 4;\n\n/**\n * The number of shades of each color that lighting uses. The finer scale\n * lets light fade in smaller steps than sprite shades.\n */\nexport const EXTENDED_SHADE_COUNT = 32;\n\n/** The number of extended shades in one sprite shade. */\nexport const EXTENDED_SHADES_PER_SHADE = EXTENDED_SHADE_COUNT / SHADE_COUNT;\n\nexport const EXTENDED_SHADE_OFFSET = 1;\n\nexport const DARKEST_EXTENDED_SHADE = 0;\n\nexport const LIGHTEST_EXTENDED_SHADE = EXTENDED_SHADE_COUNT - 1;\n\nexport const COLOR_STRIDE = 0x10;\n\n/** The pixel value that draws nothing. Whatever is below shows through. */\nexport const TRANSPARENT = -1;\n\nexport const CHANNEL_MAX = 255;\n\nexport const BLACK: Rgb = Object.freeze({ red: 0, green: 0, blue: 0 });\n\nexport const WHITE: Rgb = Object.freeze({\n red: CHANNEL_MAX,\n green: CHANNEL_MAX,\n blue: CHANNEL_MAX,\n});\n","import { inRange, isInteger } from 'lodash-es';\n\nexport const isIndex = (index: number, size: number): boolean =>\n isInteger(index) && inRange(index, size);\n","import { match } from 'ts-pattern';\nimport { COLOR_COUNT, EXTENDED_SHADE_COUNT, TRANSPARENT } from './consts';\nimport { isIndex } from './internal/is-index';\n\n/**\n * Returns the extended pixel for a color (0 to 15) and an extended shade\n * (0 to 31): `color * 32 + extendedShade`. Lit frames hold extended pixels,\n * and the palette is keyed by them.\n *\n * Sprite shade `s` is extended shade `4 * s + 1`. Extended shades 0, 30 and\n * 31 are outside the sprite shades; only light reaches them. Returns\n * `TRANSPARENT` for out-of-range input.\n */\nexport const toExtendedPixel = (color: number, extendedShade: number): number =>\n match(\n isIndex(color, COLOR_COUNT) && isIndex(extendedShade, EXTENDED_SHADE_COUNT),\n )\n .with(true, () => color * EXTENDED_SHADE_COUNT + extendedShade)\n .otherwise(() => TRANSPARENT);\n","import { match } from 'ts-pattern';\nimport { COLOR_COUNT, COLOR_STRIDE, SHADE_COUNT, TRANSPARENT } from './consts';\nimport { isIndex } from './internal/is-index';\n\n/**\n * Returns the pixel value for a color (0 to 15) and a shade (0 to 7):\n * `toPixel(3, 5)` is `0x35`. Returns `TRANSPARENT` for out-of-range input.\n */\nexport const toPixel = (color: number, shade: number): number =>\n match(isIndex(color, COLOR_COUNT) && isIndex(shade, SHADE_COUNT))\n .with(true, () => color * COLOR_STRIDE + shade)\n .otherwise(() => TRANSPARENT);\n","import { EXTENDED_SHADE_OFFSET, EXTENDED_SHADES_PER_SHADE } from '../consts';\n\nexport const toExtendedShade = (shade: number): number =>\n EXTENDED_SHADE_OFFSET + shade * EXTENDED_SHADES_PER_SHADE;\n","import { flatMap, fromPairs, map, range } from 'lodash-es';\nimport { COLOR_COUNT, EXTENDED_SHADE_COUNT, SHADE_COUNT } from '../consts';\nimport { toExtendedPixel } from '../to-extended-pixel';\nimport { toPixel } from '../to-pixel';\nimport { toExtendedShade } from './to-extended-shade';\nimport type { ShadeRamp } from './types/shade-ramp';\nimport type { ShadeRampEntry } from './types/shade-ramp-entry';\n\nconst createColorEntries = (\n color: number,\n pixels: readonly number[],\n): ShadeRampEntry[] =>\n map(range(SHADE_COUNT), (shade): ShadeRampEntry => [\n toPixel(color, shade),\n { pixels, extendedShade: toExtendedShade(shade) },\n ]);\n\nexport const createShadeRamps = (): Readonly<Record<number, ShadeRamp>> =>\n fromPairs(\n flatMap(range(COLOR_COUNT), (color) =>\n createColorEntries(\n color,\n map(range(EXTENDED_SHADE_COUNT), (extendedShade) =>\n toExtendedPixel(color, extendedShade),\n ),\n ),\n ),\n );\n","import { createShadeRamps } from './create-shade-ramps';\nimport type { ShadeRamp } from './types/shade-ramp';\n\nexport const SHADE_RAMPS: Readonly<Record<number, ShadeRamp>> =\n Object.freeze(createShadeRamps());\n","import { isUndefined } from 'lodash-es';\nimport { SHADE_RAMPS } from './internal/shade-ramps';\n\n/**\n * Returns `true` if the number is a pixel the palette can draw: a color from\n * 0 to 15 in the high hex digit and a shade from 0 to 7 in the low hex digit.\n * Returns `false` for `TRANSPARENT` and all other values.\n */\nexport const isPixel = (pixel: number): boolean =>\n !isUndefined(SHADE_RAMPS[pixel]);\n","/* eslint-disable no-restricted-syntax -- Appendix B exception 1, measured below */\nimport { TRANSPARENT } from '../../palette/consts';\nimport { isPixel } from '../../palette/is-pixel';\nimport type { Screen } from '../../screen/types/screen';\nimport type { FrameComposition } from './types/frame-composition';\n\nconst SHADOW_CASTER = 1;\n\n// Style guide - Appendix B exception 1 (hot numeric kernel).\n// As a pipeline (one object per sprite pixel, a filter, edits keyed by place,\n// then a fill with the background), composing the demo screen (320x180, 182\n// sprites) took 4.9 ms per frame (Vite bundle, Node 24). This loop takes\n// 0.55 ms. It only changes typed arrays it creates itself, and they do not\n// change after it returns them.\nexport const computeFramePixels = ({\n screen,\n spriteSize,\n width,\n height,\n}: {\n screen: Screen;\n spriteSize: number;\n width: number;\n height: number;\n}): FrameComposition => {\n const size = Math.max(0, width * height);\n const pixels = new Int16Array(size).fill(\n isPixel(screen.background) ? screen.background : TRANSPARENT,\n );\n const occluders = new Uint8Array(size);\n const spritePixelCount = spriteSize * spriteSize;\n\n for (const { sprite, row, column, castsShadow } of screen.sprites) {\n const top = Math.floor(row);\n const left = Math.floor(column);\n const count = Math.min(sprite.length, spritePixelCount);\n\n for (let index = 0; index < count; index += 1) {\n const pixel = sprite[index] ?? TRANSPARENT;\n const pixelRow = top + Math.floor(index / spriteSize);\n const pixelColumn = left + (index % spriteSize);\n const isOnScreen =\n pixelRow >= 0 &&\n pixelRow < height &&\n pixelColumn >= 0 &&\n pixelColumn < width;\n\n if (isOnScreen && isPixel(pixel)) {\n const place = pixelRow * width + pixelColumn;\n\n pixels[place] = pixel;\n\n if (castsShadow) {\n occluders[place] = SHADOW_CASTER;\n }\n }\n }\n }\n\n return {\n pixels,\n lighting: {\n ambient: screen.ambient,\n lights: screen.lights,\n width,\n height,\n occluders,\n },\n };\n};\n","/* eslint-disable no-restricted-syntax -- Appendix B exception 1, measured below */\nimport {\n DARKEST_EXTENDED_SHADE,\n LIGHTEST_EXTENDED_SHADE,\n TRANSPARENT,\n} from './consts';\nimport { SHADE_RAMPS } from './internal/shade-ramps';\n\n// Style guide - Appendix B exception 1 (hot numeric kernel).\n// As a pipeline (zipWith over toLitPixel, one match per place), shading the\n// demo screen (320x180) took 13.6 ms per frame (Vite bundle, Node 24); with\n// lookup tables and no match it still took 2.5 ms. This loop takes 1.2 ms. It\n// gives the same pixel as toLitPixel for every place. It only changes the\n// array it creates itself, and the array does not change after it returns it.\nexport const toLitPixels = (\n pixels: ArrayLike<number>,\n shadeOffsets: ArrayLike<number>,\n): number[] => {\n const litPixels = new Array<number>(pixels.length);\n\n for (let index = 0; index < pixels.length; index += 1) {\n const ramp = SHADE_RAMPS[pixels[index] ?? TRANSPARENT];\n const extendedShade = Math.round(\n (ramp?.extendedShade ?? 0) + (shadeOffsets[index] ?? 0),\n );\n\n litPixels[index] =\n ramp?.pixels[\n Math.min(\n LIGHTEST_EXTENDED_SHADE,\n Math.max(DARKEST_EXTENDED_SHADE, extendedShade),\n )\n ] ?? TRANSPARENT;\n }\n\n return litPixels;\n};\n","import { toLitPixels } from '../../palette/to-lit-pixels';\nimport type { Frame } from '../types/frame';\nimport type { FrameLight } from './types/frame-light';\n\nexport const patchFrameShades = ({ pixels, levels }: FrameLight): Frame =>\n toLitPixels(pixels, levels);\n","import { pipe } from 'remeda';\nimport type { Screen } from '../screen/types/screen';\nimport { computeFrameLight } from './internal/compute-frame-light';\nimport { computeFramePixels } from './internal/compute-frame-pixels';\nimport { patchFrameShades } from './internal/patch-frame-shades';\nimport type { Frame } from './types/frame';\n\n/**\n * Turns a screen into a frame of `width * height` extended pixels:\n *\n * 1. Fills the frame with the background.\n * 2. Draws the sprites in order. Transparent pixels are skipped, and parts\n * outside the frame are cut off.\n * 3. Lights every pixel: moves it along its 32-shade scale by the ambient\n * light plus each light that reaches it. Sprites that cast shadows block\n * light.\n *\n * With ambient 0 and no lights, every pixel keeps its authored color.\n */\nexport const computeFrame = (\n screen: Screen,\n spriteSize: number,\n width: number,\n height: number,\n): Frame =>\n pipe(\n { screen, spriteSize, width, height },\n computeFramePixels,\n computeFrameLight,\n patchFrameShades,\n );\n","/* eslint-disable no-restricted-syntax -- Appendix B exception 1, measured below */\nimport type { Frame } from '../../frame/types/frame';\nimport { CHANNEL_MAX, TRANSPARENT } from '../../palette/consts';\nimport type { Palette } from '../../palette/types/palette';\n\nconst BYTES_PER_PIXEL = 4;\n\nconst RED_OFFSET = 0;\n\nconst GREEN_OFFSET = 1;\n\nconst BLUE_OFFSET = 2;\n\nconst ALPHA_OFFSET = 3;\n\n// Style guide - Appendix B exception 1 (hot numeric kernel).\n// As a pipeline (flatMap to four bytes per place, one match per place), the\n// demo screen (320x180) took 16.5 ms per frame (Vite bundle, Node 24); as\n// Uint32Array.from over packed palette colors it still took 5.8 ms. This\n// loop takes 0.5 ms. Places the palette does not hold stay clear (all four\n// bytes 0). It only changes the typed array it creates itself, and the array\n// does not change after it returns it.\nexport const toFrameBytes = (\n frame: Frame,\n palette: Palette,\n): Uint8ClampedArray => {\n const bytes = new Uint8ClampedArray(frame.length * BYTES_PER_PIXEL);\n\n for (let index = 0; index < frame.length; index += 1) {\n const rgb = palette[frame[index] ?? TRANSPARENT];\n\n if (rgb !== undefined) {\n const offset = index * BYTES_PER_PIXEL;\n\n bytes[offset + RED_OFFSET] = rgb.red;\n bytes[offset + GREEN_OFFSET] = rgb.green;\n bytes[offset + BLUE_OFFSET] = rgb.blue;\n bytes[offset + ALPHA_OFFSET] = CHANNEL_MAX;\n }\n }\n\n return bytes;\n};\n","import { runEffect } from '@cassette-canvas/common';\nimport { pipe } from 'remeda';\nimport { drawCrt } from '../crt/draw-crt';\nimport { computeFrame } from '../frame/compute-frame';\nimport type { Screen } from '../screen/types/screen';\nimport { toFrameBytes } from './internal/to-frame-bytes';\nimport type { Display } from './types/display';\n\n/**\n * Draws the screen on the canvas. It composes and lights the frame, then\n * draws it with the monitor effect. `time` is the current time in\n * milliseconds, for example the `requestAnimationFrame` timestamp. It\n * animates the film grain. Returns the same display.\n */\nexport const drawScreen = (\n display: Display,\n screen: Screen,\n time: number,\n): Display =>\n pipe(\n computeFrame(screen, display.spriteSize, display.width, display.height),\n (frame) => toFrameBytes(frame, display.palette),\n (bytes) =>\n runEffect(display, ({ context, crt, width, height, pixelSize }) =>\n drawCrt(context, crt, { bytes, width, height, pixelSize }, time),\n ),\n );\n","import { mapValues } from 'lodash-es';\nimport { match, P } from 'ts-pattern';\nimport { CRT_UNIFORM_NAMES } from '../consts';\nimport type { CrtUniforms } from '../types/crt-uniforms';\n\nconst { nonNullable } = P;\n\nexport const findCrtUniforms = (\n context: WebGLRenderingContext,\n program: WebGLProgram,\n): CrtUniforms | undefined =>\n match(\n mapValues(\n CRT_UNIFORM_NAMES,\n (name) => context.getUniformLocation(program, name) ?? undefined,\n ),\n )\n .with(\n {\n frame: nonNullable,\n frameSize: nonNullable,\n canvasSize: nonNullable,\n pixelSize: nonNullable,\n time: nonNullable,\n },\n (uniforms) => uniforms,\n )\n .otherwise(() => undefined);\n","const GLSL_FLOAT_DIGITS = 4;\n\nexport const formatGlslFloat = (value: number): string =>\n value.toFixed(GLSL_FLOAT_DIGITS);\n","import {\n CRT_UNIFORM_NAMES,\n GLOW_REACH,\n GLOW_STRENGTH,\n GLOW_THRESHOLD,\n GRAIN_AMOUNT,\n GRAIN_RATE,\n SCANLINE_DEPTH,\n SURFACE_AMOUNT,\n SURFACE_SCALE,\n} from '../consts';\nimport { formatGlslFloat } from './format-glsl-float';\n\nexport const FRAGMENT_SHADER_SOURCE = `\n#ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n#else\nprecision mediump float;\n#endif\n\nuniform sampler2D ${CRT_UNIFORM_NAMES.frame};\nuniform vec2 ${CRT_UNIFORM_NAMES.frameSize};\nuniform vec2 ${CRT_UNIFORM_NAMES.canvasSize};\nuniform float ${CRT_UNIFORM_NAMES.pixelSize};\nuniform float ${CRT_UNIFORM_NAMES.time};\n\nconst float GLOW_STRENGTH = ${formatGlslFloat(GLOW_STRENGTH)};\nconst float GLOW_THRESHOLD = ${formatGlslFloat(GLOW_THRESHOLD)};\nconst int GLOW_REACH = ${GLOW_REACH};\nconst float SCANLINE_DEPTH = ${formatGlslFloat(SCANLINE_DEPTH)};\nconst float GRAIN_AMOUNT = ${formatGlslFloat(GRAIN_AMOUNT)};\nconst float GRAIN_RATE = ${formatGlslFloat(GRAIN_RATE)};\nconst float SURFACE_AMOUNT = ${formatGlslFloat(SURFACE_AMOUNT)};\nconst float SURFACE_SCALE = ${formatGlslFloat(SURFACE_SCALE)};\nconst vec3 LUMINANCE = vec3(0.299, 0.587, 0.114);\n\nvec4 sampleFrame(vec2 position) {\n vec2 logical = floor(position / ${CRT_UNIFORM_NAMES.pixelSize});\n\n if (logical.x < 0.0 || logical.y < 0.0\n || logical.x >= ${CRT_UNIFORM_NAMES.frameSize}.x\n || logical.y >= ${CRT_UNIFORM_NAMES.frameSize}.y) {\n return vec4(0.0);\n }\n\n return texture2D(\n ${CRT_UNIFORM_NAMES.frame},\n (logical + 0.5) / ${CRT_UNIFORM_NAMES.frameSize}\n );\n}\n\nfloat hash(vec2 point) {\n vec3 mixed = fract(vec3(point.xyx) * 0.1031);\n mixed += dot(mixed, mixed.yzx + 33.33);\n return fract((mixed.x + mixed.y) * mixed.z);\n}\n\nfloat valueNoise(vec2 point) {\n vec2 cell = floor(point);\n vec2 blend = fract(point);\n blend = blend * blend * (3.0 - 2.0 * blend);\n\n return mix(\n mix(hash(cell), hash(cell + vec2(1.0, 0.0)), blend.x),\n mix(hash(cell + vec2(0.0, 1.0)), hash(cell + vec2(1.0, 1.0)), blend.x),\n blend.y\n );\n}\n\nvec3 computeBright(vec3 color) {\n return color * smoothstep(GLOW_THRESHOLD, 1.0, dot(color, LUMINANCE));\n}\n\nvec3 computeGlow(vec2 position, vec3 base) {\n vec3 glow = vec3(0.0);\n float total = 0.0;\n\n for (int row = -GLOW_REACH; row <= GLOW_REACH; row++) {\n for (int column = -GLOW_REACH; column <= GLOW_REACH; column++) {\n vec2 offset = vec2(float(column), float(row));\n float reach = length(offset);\n\n if (reach > float(GLOW_REACH)) {\n continue;\n }\n\n float weight = 1.0 - reach / (float(GLOW_REACH) + 1.0);\n\n glow += computeBright(sampleFrame(position + offset).rgb) * weight;\n total += weight;\n }\n }\n\n return max(glow / total - computeBright(base), 0.0);\n}\n\nfloat computeScanline(vec2 position) {\n float period = max(${CRT_UNIFORM_NAMES.pixelSize}, 2.0);\n float phase = (mod(floor(position.y), period) + 0.5) / period;\n\n return 1.0 - SCANLINE_DEPTH * smoothstep(0.4, 1.0, phase);\n}\n\nfloat computeGrain(vec2 position) {\n float grainFrame = floor(${CRT_UNIFORM_NAMES.time} * GRAIN_RATE / 1000.0);\n vec2 shift = fract(vec2(grainFrame * 0.6180339, grainFrame * 0.3819660))\n * 1024.0;\n\n return (hash(position + shift) - 0.5) * GRAIN_AMOUNT;\n}\n\nfloat computeSurface(vec2 position) {\n return (valueNoise(position / SURFACE_SCALE) - 0.5) * SURFACE_AMOUNT;\n}\n\nvoid main() {\n vec2 position = vec2(\n gl_FragCoord.x,\n ${CRT_UNIFORM_NAMES.canvasSize}.y - gl_FragCoord.y\n );\n vec4 base = sampleFrame(position);\n vec3 color = (base.rgb + computeGlow(position, base.rgb) * GLOW_STRENGTH)\n * computeScanline(position)\n * (1.0 + computeSurface(position))\n + computeGrain(position) * base.a;\n\n color = clamp(color, 0.0, 1.0);\n\n gl_FragColor = vec4(color, max(base.a, max(color.r, max(color.g, color.b))));\n}\n`;\n","import { runEffect } from '@cassette-canvas/common';\nimport { pipe } from 'remeda';\nimport { match, P } from 'ts-pattern';\n\nconst { nonNullable } = P;\n\nconst tryCompileShader = (\n context: WebGLRenderingContext,\n shader: WebGLShader,\n source: string,\n): WebGLShader | undefined =>\n pipe(\n shader,\n (created) =>\n runEffect(created, (target) => context.shaderSource(target, source)),\n (sourced) => runEffect(sourced, (target) => context.compileShader(target)),\n (compiled) =>\n match(context.getShaderParameter(compiled, context.COMPILE_STATUS))\n .with(true, () => compiled)\n .otherwise(() => undefined),\n );\n\nexport const tryCreateShader = (\n context: WebGLRenderingContext,\n type: number,\n source: string,\n): WebGLShader | undefined =>\n match(context.createShader(type))\n .with(nonNullable, (shader) => tryCompileShader(context, shader, source))\n .otherwise(() => undefined);\n","import { runEffect } from '@cassette-canvas/common';\nimport { pipe } from 'remeda';\nimport { match } from 'ts-pattern';\nimport { POSITION_ATTRIBUTE, POSITION_LOCATION } from '../consts';\n\nexport const tryLinkProgram = (\n context: WebGLRenderingContext,\n program: WebGLProgram,\n vertexShader: WebGLShader,\n fragmentShader: WebGLShader,\n): WebGLProgram | undefined =>\n pipe(\n program,\n (created) =>\n runEffect(created, (target) =>\n context.attachShader(target, vertexShader),\n ),\n (attached) =>\n runEffect(attached, (target) =>\n context.attachShader(target, fragmentShader),\n ),\n (attached) =>\n runEffect(attached, (target) =>\n context.bindAttribLocation(\n target,\n POSITION_LOCATION,\n POSITION_ATTRIBUTE,\n ),\n ),\n (bound) => runEffect(bound, (target) => context.linkProgram(target)),\n (linked) =>\n match(context.getProgramParameter(linked, context.LINK_STATUS))\n .with(true, () => linked)\n .otherwise(() => undefined),\n );\n","import { POSITION_ATTRIBUTE } from '../consts';\n\nexport const VERTEX_SHADER_SOURCE = `\nattribute vec2 ${POSITION_ATTRIBUTE};\n\nvoid main() {\n gl_Position = vec4(${POSITION_ATTRIBUTE}, 0.0, 1.0);\n}\n`;\n","import { match, P } from 'ts-pattern';\nimport { FRAGMENT_SHADER_SOURCE } from './fragment-shader-source';\nimport { tryCreateShader } from './try-create-shader';\nimport { tryLinkProgram } from './try-link-program';\nimport { VERTEX_SHADER_SOURCE } from './vertex-shader-source';\n\nconst { nonNullable } = P;\n\nexport const tryCreateProgram = (\n context: WebGLRenderingContext,\n): WebGLProgram | undefined =>\n match({\n program: context.createProgram(),\n vertexShader: tryCreateShader(\n context,\n context.VERTEX_SHADER,\n VERTEX_SHADER_SOURCE,\n ),\n fragmentShader: tryCreateShader(\n context,\n context.FRAGMENT_SHADER,\n FRAGMENT_SHADER_SOURCE,\n ),\n })\n .with(\n {\n program: nonNullable,\n vertexShader: nonNullable,\n fragmentShader: nonNullable,\n },\n ({ program, vertexShader, fragmentShader }) =>\n tryLinkProgram(context, program, vertexShader, fragmentShader),\n )\n .otherwise(() => undefined);\n","import { runEffect } from '@cassette-canvas/common';\nimport { pipe } from 'remeda';\nimport { match, P } from 'ts-pattern';\n\nconst { nonNullable } = P;\n\nconst patchTextureSampling = (\n context: WebGLRenderingContext,\n texture: WebGLTexture,\n): WebGLTexture =>\n pipe(\n texture,\n (created) =>\n runEffect(created, (target) =>\n context.bindTexture(context.TEXTURE_2D, target),\n ),\n (bound) =>\n runEffect(bound, () =>\n context.texParameteri(\n context.TEXTURE_2D,\n context.TEXTURE_MIN_FILTER,\n context.NEAREST,\n ),\n ),\n (bound) =>\n runEffect(bound, () =>\n context.texParameteri(\n context.TEXTURE_2D,\n context.TEXTURE_MAG_FILTER,\n context.NEAREST,\n ),\n ),\n (bound) =>\n runEffect(bound, () =>\n context.texParameteri(\n context.TEXTURE_2D,\n context.TEXTURE_WRAP_S,\n context.CLAMP_TO_EDGE,\n ),\n ),\n (bound) =>\n runEffect(bound, () =>\n context.texParameteri(\n context.TEXTURE_2D,\n context.TEXTURE_WRAP_T,\n context.CLAMP_TO_EDGE,\n ),\n ),\n );\n\nexport const tryCreateTexture = (\n context: WebGLRenderingContext,\n): WebGLTexture | undefined =>\n match(context.createTexture())\n .with(nonNullable, (texture) => patchTextureSampling(context, texture))\n .otherwise(() => undefined);\n","import { runEffect } from '@cassette-canvas/common';\nimport { pipe } from 'remeda';\nimport { match, P } from 'ts-pattern';\nimport { FULL_SCREEN_TRIANGLE } from '../consts';\n\nconst { nonNullable } = P;\n\nconst patchVertices = (\n context: WebGLRenderingContext,\n vertices: WebGLBuffer,\n): WebGLBuffer =>\n pipe(\n vertices,\n (created) =>\n runEffect(created, (target) =>\n context.bindBuffer(context.ARRAY_BUFFER, target),\n ),\n (bound) =>\n runEffect(bound, () =>\n context.bufferData(\n context.ARRAY_BUFFER,\n new Float32Array(FULL_SCREEN_TRIANGLE),\n context.STATIC_DRAW,\n ),\n ),\n );\n\nexport const tryCreateVertices = (\n context: WebGLRenderingContext,\n): WebGLBuffer | undefined =>\n match(context.createBuffer())\n .with(nonNullable, (vertices) => patchVertices(context, vertices))\n .otherwise(() => undefined);\n","import { match, P } from 'ts-pattern';\nimport { findCrtUniforms } from './internal/find-crt-uniforms';\nimport { tryCreateProgram } from './internal/try-create-program';\nimport { tryCreateTexture } from './internal/try-create-texture';\nimport { tryCreateVertices } from './internal/try-create-vertices';\nimport type { CrtDraft } from './internal/types/crt-draft';\nimport type { Crt } from './types/crt';\n\nconst { nonNullable } = P;\n\nconst createCrtDraft = (\n context: WebGLRenderingContext,\n program: WebGLProgram | undefined,\n): CrtDraft => ({\n program,\n texture: tryCreateTexture(context),\n vertices: tryCreateVertices(context),\n uniforms: match(program)\n .with(nonNullable, (linked) => findCrtUniforms(context, linked))\n .otherwise(() => undefined),\n});\n\nconst tryToCrt = (draft: CrtDraft): Crt | undefined =>\n match(draft)\n .with(\n {\n program: nonNullable,\n texture: nonNullable,\n vertices: nonNullable,\n uniforms: nonNullable,\n },\n (crt) => crt,\n )\n .otherwise(() => undefined);\n\n/**\n * Sets up the monitor effect on a WebGL context: the shader program, the\n * texture that holds the frame, and a triangle that covers the canvas.\n * Returns `undefined` if there is no context or any step fails.\n */\nexport const tryCreateCrt = (\n context: WebGLRenderingContext | undefined,\n): Crt | undefined =>\n match(context)\n .with(nonNullable, (ready) =>\n tryToCrt(createCrtDraft(ready, tryCreateProgram(ready))),\n )\n .otherwise(() => undefined);\n","import { round } from 'lodash-es';\nimport type { Rgb } from '../types/rgb';\n\nconst computeChannelMix = (\n channel: number,\n target: number,\n amount: number,\n): number => round(channel + (target - channel) * amount);\n\nexport const computeRgbMix = (\n color: Rgb,\n target: Rgb,\n amount: number,\n): Rgb => ({\n red: computeChannelMix(color.red, target.red, amount),\n green: computeChannelMix(color.green, target.green, amount),\n blue: computeChannelMix(color.blue, target.blue, amount),\n});\n","import { match, P } from 'ts-pattern';\nimport {\n BASE_SHADE,\n BLACK,\n EXTENDED_SHADES_PER_SHADE,\n SHADE_COUNT,\n WHITE,\n} from '../consts';\nimport type { Rgb } from '../types/rgb';\nimport { computeRgbMix } from './compute-rgb-mix';\nimport { toExtendedShade } from './to-extended-shade';\n\nconst { number } = P;\n\nconst EXTENDED_BASE_SHADE = toExtendedShade(BASE_SHADE);\n\nconst DARKER_SHADES = BASE_SHADE;\n\nconst LIGHTER_SHADES = SHADE_COUNT - BASE_SHADE - 1;\n\nconst DARKEN_STEP = 1 / ((DARKER_SHADES + 1) * EXTENDED_SHADES_PER_SHADE);\n\nconst LIGHTEN_STEP = 1 / ((LIGHTER_SHADES + 1) * EXTENDED_SHADES_PER_SHADE);\n\nexport const computeExtendedShade = (base: Rgb, extendedShade: number): Rgb =>\n match(extendedShade)\n .with(number.lt(EXTENDED_BASE_SHADE), (darker) =>\n computeRgbMix(base, BLACK, (EXTENDED_BASE_SHADE - darker) * DARKEN_STEP),\n )\n .with(number.gt(EXTENDED_BASE_SHADE), (lighter) =>\n computeRgbMix(\n base,\n WHITE,\n (lighter - EXTENDED_BASE_SHADE) * LIGHTEN_STEP,\n ),\n )\n .otherwise(() => base);\n","import { map, range } from 'lodash-es';\nimport { EXTENDED_SHADE_COUNT } from '../consts';\nimport { toExtendedPixel } from '../to-extended-pixel';\nimport type { Rgb } from '../types/rgb';\nimport { computeExtendedShade } from './compute-extended-shade';\nimport type { PaletteEntry } from './types/palette-entry';\n\nexport const createPaletteEntries = (\n base: Rgb,\n color: number,\n): PaletteEntry[] =>\n map(range(EXTENDED_SHADE_COUNT), (extendedShade): PaletteEntry => [\n toExtendedPixel(color, extendedShade),\n computeExtendedShade(base, extendedShade),\n ]);\n","import { map, parseInt, words } from 'lodash-es';\nimport { pipe } from 'remeda';\nimport type { Rgb } from '../types/rgb';\n\nconst HEX_CHANNEL_PATTERN = /[\\da-f]{2}/gi;\n\nconst HEX_RADIX = 16;\n\nexport const splitHexRgb = (color: string): Rgb =>\n pipe(\n words(color, HEX_CHANNEL_PATTERN),\n (channels) => map(channels, (channel) => parseInt(channel, HEX_RADIX)),\n ([red = 0, green = 0, blue = 0]) => ({ red, green, blue }),\n );\n","import { match, P } from 'ts-pattern';\nimport { BLACK } from '../consts';\nimport type { HexColor } from '../types/hex-color';\nimport type { Rgb } from '../types/rgb';\nimport { splitHexRgb } from './split-hex-rgb';\n\nconst { string } = P;\n\nconst HEX_COLOR_PATTERN = /^#[\\da-f]{6}$/i;\n\nexport const toRgb = (color: HexColor): Rgb =>\n match(color)\n .with(string.regex(HEX_COLOR_PATTERN), splitHexRgb)\n .otherwise(() => BLACK);\n","import { flatMap, fromPairs, map } from 'lodash-es';\nimport { pipe } from 'remeda';\nimport { createPaletteEntries } from './internal/create-palette-entries';\nimport { toRgb } from './internal/to-rgb';\nimport type { Palette } from './types/palette';\nimport type { PaletteColors } from './types/palette-colors';\n\n/**\n * Expands the 16 base colors into 512 colors: 32 extended shades per color,\n * keyed by extended pixel.\n *\n * Sprite shade `s` is extended shade `4 * s + 1`. Shade `BASE_SHADE` is the\n * base color itself. Darker shades are mixed with black, lighter shades with\n * white. Only light reaches the extended shades between and beyond the\n * sprite shades. A color that is not in `#rrggbb` form is read as black.\n */\nexport const createPalette = (colors: PaletteColors): Palette =>\n pipe(\n map(colors, toRgb),\n (bases) => flatMap(bases, createPaletteEntries),\n (entries) => fromPairs(entries),\n );\n","import { floor } from 'lodash-es';\nimport type { DisplaySize } from './types/display-size';\n\nexport const computeDisplaySize = (\n canvas: Pick<HTMLCanvasElement, 'width' | 'height'>,\n pixelSize: number,\n): DisplaySize => ({\n width: floor(canvas.width / pixelSize),\n height: floor(canvas.height / pixelSize),\n});\n","import { match, P } from 'ts-pattern';\nimport type { Display } from '../types/display';\nimport type { DisplayDraft } from './types/display-draft';\n\nconst { nonNullable, number } = P;\n\nconst POSITIVE_INTEGER = number.int().positive();\n\nexport const tryToDisplay = (draft: DisplayDraft): Display | undefined =>\n match(draft)\n .with(\n {\n context: nonNullable,\n crt: nonNullable,\n pixelSize: POSITIVE_INTEGER,\n spriteSize: POSITIVE_INTEGER,\n width: POSITIVE_INTEGER,\n height: POSITIVE_INTEGER,\n },\n (display) => display,\n )\n .otherwise(() => undefined);\n","import { pipe } from 'remeda';\nimport { CRT_CONTEXT_ATTRIBUTES } from '../crt/consts';\nimport { tryCreateCrt } from '../crt/try-create-crt';\nimport { createPalette } from '../palette/create-palette';\nimport { computeDisplaySize } from './internal/compute-display-size';\nimport { tryToDisplay } from './internal/try-to-display';\nimport type { Display } from './types/display';\nimport type { DisplaySettings } from './types/display-settings';\n\n/**\n * Creates a display on the canvas.\n *\n * - `colors`: the 16 base colors.\n * - `pixelSize`: the width and height of one logical pixel, in canvas pixels.\n * - `spriteSize`: the width and height of every sprite, in logical pixels.\n *\n * The screen size in logical pixels is the canvas size divided by\n * `pixelSize`. The display draws with WebGL, and the monitor effect is\n * always on.\n *\n * Returns `undefined` if the canvas has no WebGL context, the monitor effect\n * cannot be set up, a size is not a positive whole number, or the canvas is\n * smaller than one logical pixel.\n */\nexport const tryCreateDisplay = (\n canvas: HTMLCanvasElement,\n { colors, pixelSize, spriteSize }: DisplaySettings,\n): Display | undefined =>\n pipe(\n {\n context: canvas.getContext('webgl', CRT_CONTEXT_ATTRIBUTES) ?? undefined,\n size: computeDisplaySize(canvas, pixelSize),\n },\n ({ context, size }) => ({\n context,\n crt: tryCreateCrt(context),\n palette: createPalette(colors),\n pixelSize,\n spriteSize,\n width: size.width,\n height: size.height,\n }),\n tryToDisplay,\n );\n","import { clamp, round } from 'lodash-es';\nimport { match, P } from 'ts-pattern';\nimport {\n DARKEST_EXTENDED_SHADE,\n LIGHTEST_EXTENDED_SHADE,\n TRANSPARENT,\n} from './consts';\nimport { SHADE_RAMPS } from './internal/shade-ramps';\n\nconst { nonNullable } = P;\n\n/**\n * Returns the extended pixel that a pixel shows under light. The color stays\n * the same. The shade moves `shadeOffset` extended shades up or down\n * (4 extended shades = 1 sprite shade). The result is rounded and stops at\n * the darkest and lightest extended shade. `toLitPixel(pixel, 0)` returns\n * the authored color. Returns `TRANSPARENT` for `TRANSPARENT` and for any\n * value that is not a pixel.\n */\nexport const toLitPixel = (pixel: number, shadeOffset: number): number =>\n match(SHADE_RAMPS[pixel])\n .with(\n nonNullable,\n ({ pixels, extendedShade }) =>\n pixels[\n clamp(\n round(extendedShade + shadeOffset),\n DARKEST_EXTENDED_SHADE,\n LIGHTEST_EXTENDED_SHADE,\n )\n ] ?? TRANSPARENT,\n )\n .otherwise(() => TRANSPARENT);\n","import { filter } from 'lodash-es';\nimport type { Light } from '../types/light';\n\nexport const filterOtherLights = (\n lights: readonly Light[],\n id: string,\n): Light[] => filter(lights, (light) => light.id !== id);\n","import { filterOtherLights } from './internal/filter-other-lights';\nimport type { LightSettings } from './types/light-settings';\nimport type { Screen } from './types/screen';\n\n/**\n * Adds a light to the screen. If a light with the same id exists, it is\n * replaced. Returns a new screen.\n */\nexport const addLight = (\n screen: Screen,\n id: string,\n { row, column, radius, intensity }: LightSettings,\n): Screen => ({\n ...screen,\n lights: [\n ...filterOtherLights(screen.lights, id),\n { id, row, column, radius, intensity },\n ],\n});\n","import { filter } from 'lodash-es';\nimport type { SpritePlacement } from '../types/sprite-placement';\n\nexport const filterOtherSprites = (\n sprites: readonly SpritePlacement[],\n id: string,\n): SpritePlacement[] => filter(sprites, (placement) => placement.id !== id);\n","import { filterOtherSprites } from './internal/filter-other-sprites';\nimport type { Screen } from './types/screen';\nimport type { Sprite } from './types/sprite';\n\n/**\n * Adds a sprite on top of the other sprites. `row` and `column` set its\n * top-left pixel. If a sprite with the same id exists, it is removed first.\n * The new sprite does not cast a shadow; to change that, call\n * `patchSprite(screen, id, { castsShadow: true })`. Returns a new screen.\n */\nexport const addSprite = (\n screen: Screen,\n id: string,\n sprite: Sprite,\n row: number,\n column: number,\n): Screen => ({\n ...screen,\n sprites: [\n ...filterOtherSprites(screen.sprites, id),\n { id, sprite, row, column, castsShadow: false },\n ],\n});\n","import { TRANSPARENT } from '../palette/consts';\nimport type { Screen } from './types/screen';\n\n/**\n * Creates an empty screen, with no sprites and no lights, filled with\n * `background`. `ambient` shifts every pixel by that many extended shades\n * (4 extended shades = 1 sprite shade). For example, a night scene could\n * use -16.\n */\nexport const createScreen = (\n background: number = TRANSPARENT,\n ambient = 0,\n): Screen => ({\n background,\n ambient,\n sprites: [],\n lights: [],\n});\n","import { find } from 'lodash-es';\nimport type { Light } from './types/light';\nimport type { Screen } from './types/screen';\n\n/** Returns the light with the id, or `undefined` if there is none. */\nexport const findLight = (screen: Screen, id: string): Light | undefined =>\n find(screen.lights, (light) => light.id === id);\n","import { find } from 'lodash-es';\nimport type { Screen } from './types/screen';\nimport type { SpritePlacement } from './types/sprite-placement';\n\n/** Returns the sprite with the id, or `undefined` if there is none. */\nexport const findSprite = (\n screen: Screen,\n id: string,\n): SpritePlacement | undefined =>\n find(screen.sprites, (placement) => placement.id === id);\n","import type { Screen } from './types/screen';\n\n/**\n * Sets the ambient light: how many extended shades every pixel moves up\n * (positive) or down (negative) before lights are added. Change it over time\n * to fade between day and night. Returns a new screen.\n */\nexport const patchAmbient = (screen: Screen, ambient: number): Screen => ({\n ...screen,\n ambient,\n});\n","import { match } from 'ts-pattern';\nimport type { Light } from '../types/light';\nimport type { LightChanges } from '../types/light-changes';\n\nexport const patchMatchingLight = (\n light: Light,\n id: string,\n changes: LightChanges,\n): Light =>\n match(light.id === id)\n .with(true, () => ({\n id,\n row: changes.row ?? light.row,\n column: changes.column ?? light.column,\n radius: changes.radius ?? light.radius,\n intensity: changes.intensity ?? light.intensity,\n }))\n .otherwise(() => light);\n","import { map } from 'lodash-es';\nimport { patchMatchingLight } from './internal/patch-matching-light';\nimport type { LightChanges } from './types/light-changes';\nimport type { Screen } from './types/screen';\n\n/**\n * Changes a light's `row`, `column`, `radius` or `intensity`. Fields you\n * leave out keep their values. Returns a new screen. If no light has the id,\n * nothing changes.\n */\nexport const patchLight = (\n screen: Screen,\n id: string,\n changes: LightChanges,\n): Screen => ({\n ...screen,\n lights: map(screen.lights, (light) => patchMatchingLight(light, id, changes)),\n});\n","import { match, P } from 'ts-pattern';\nimport { findLight } from './find-light';\nimport { patchLight } from './patch-light';\nimport type { Screen } from './types/screen';\n\nconst { nonNullable } = P;\n\n/**\n * Moves a light `rowOffset` pixels down and `columnOffset` pixels right.\n * Negative values move it up and left. Returns a new screen. If no light has\n * the id, nothing changes.\n */\nexport const patchLightByOffset = (\n screen: Screen,\n id: string,\n rowOffset: number,\n columnOffset: number,\n): Screen =>\n match(findLight(screen, id))\n .with(nonNullable, ({ row, column }) =>\n patchLight(screen, id, {\n row: row + rowOffset,\n column: column + columnOffset,\n }),\n )\n .otherwise(() => screen);\n","import { match } from 'ts-pattern';\nimport type { SpriteChanges } from '../types/sprite-changes';\nimport type { SpritePlacement } from '../types/sprite-placement';\n\nexport const patchSpritePlacement = (\n placement: SpritePlacement,\n id: string,\n changes: SpriteChanges,\n): SpritePlacement =>\n match(placement.id === id)\n .with(true, () => ({\n id,\n sprite: changes.sprite ?? placement.sprite,\n row: changes.row ?? placement.row,\n column: changes.column ?? placement.column,\n castsShadow: changes.castsShadow ?? placement.castsShadow,\n }))\n .otherwise(() => placement);\n","import { map } from 'lodash-es';\nimport { patchSpritePlacement } from './internal/patch-sprite-placement';\nimport type { Screen } from './types/screen';\nimport type { SpriteChanges } from './types/sprite-changes';\n\n/**\n * Changes a sprite's `sprite` (for animation), `row`, `column` or\n * `castsShadow`. Fields you leave out keep their values. The sprite keeps its\n * place in the drawing order. Returns a new screen. If no sprite has the id,\n * nothing changes.\n */\nexport const patchSprite = (\n screen: Screen,\n id: string,\n changes: SpriteChanges,\n): Screen => ({\n ...screen,\n sprites: map(screen.sprites, (placement) =>\n patchSpritePlacement(placement, id, changes),\n ),\n});\n","import { match, P } from 'ts-pattern';\nimport { findSprite } from './find-sprite';\nimport { patchSprite } from './patch-sprite';\nimport type { Screen } from './types/screen';\n\nconst { nonNullable } = P;\n\n/**\n * Moves a sprite `rowOffset` pixels down and `columnOffset` pixels right.\n * Negative values move it up and left. Returns a new screen. If no sprite\n * has the id, nothing changes.\n */\nexport const patchSpriteByOffset = (\n screen: Screen,\n id: string,\n rowOffset: number,\n columnOffset: number,\n): Screen =>\n match(findSprite(screen, id))\n .with(nonNullable, ({ row, column }) =>\n patchSprite(screen, id, {\n row: row + rowOffset,\n column: column + columnOffset,\n }),\n )\n .otherwise(() => screen);\n","import { filterOtherLights } from './internal/filter-other-lights';\nimport type { Screen } from './types/screen';\n\n/** Removes the light with the id. Returns a new screen. */\nexport const removeLight = (screen: Screen, id: string): Screen => ({\n ...screen,\n lights: filterOtherLights(screen.lights, id),\n});\n","import { filterOtherSprites } from './internal/filter-other-sprites';\nimport type { Screen } from './types/screen';\n\n/** Removes the sprite with the id. Returns a new screen. */\nexport const removeSprite = (screen: Screen, id: string): Screen => ({\n ...screen,\n sprites: filterOtherSprites(screen.sprites, id),\n});\n","import { flatMap, map, split } from 'lodash-es';\nimport { TRANSPARENT } from '../palette/consts';\nimport type { Sprite } from './types/sprite';\nimport type { SpriteLegend } from './types/sprite-legend';\n\n/**\n * Builds a sprite from text art: one string per row, one character per\n * pixel. Each character is looked up in `legend`. Characters that are not in\n * the legend become transparent.\n */\nexport const toSprite = (\n rows: readonly string[],\n legend: SpriteLegend,\n): Sprite =>\n flatMap(rows, (row) =>\n map(split(row, ''), (symbol) => legend[symbol] ?? TRANSPARENT),\n );\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["nonNullable","nonNullable","nonNullable","nonNullable","nonNullable","nonNullable","number","nonNullable","nonNullable","nonNullable"],"sources":["../../libs/common/src/effects/run-effect.ts","../../libs/engine/src/crt/consts.ts","../../libs/engine/src/crt/internal/draw-crt-triangle.ts","../../libs/engine/src/crt/internal/patch-crt-texture.ts","../../libs/engine/src/crt/internal/patch-crt-uniforms.ts","../../libs/engine/src/crt/draw-crt.ts","../../libs/engine/src/palette/consts.ts","../../libs/engine/src/lighting/consts.ts","../../libs/engine/src/lighting/internal/compute-falloff.ts","../../libs/engine/src/lighting/internal/to-exposure.ts","../../libs/engine/src/lighting/internal/compute-light-exposure.ts","../../libs/engine/src/lighting/internal/to-shade-offset.ts","../../libs/engine/src/lighting/internal/compute-shade-offsets.ts","../../libs/engine/src/lighting/compute-light-levels.ts","../../libs/engine/src/frame/internal/compute-frame-light.ts","../../libs/engine/src/palette/internal/is-index.ts","../../libs/engine/src/palette/to-extended-pixel.ts","../../libs/engine/src/palette/to-pixel.ts","../../libs/engine/src/palette/internal/to-extended-shade.ts","../../libs/engine/src/palette/internal/create-shade-ramps.ts","../../libs/engine/src/palette/internal/shade-ramps.ts","../../libs/engine/src/palette/is-pixel.ts","../../libs/engine/src/frame/internal/compute-frame-pixels.ts","../../libs/engine/src/palette/to-lit-pixels.ts","../../libs/engine/src/frame/internal/patch-frame-shades.ts","../../libs/engine/src/frame/compute-frame.ts","../../libs/engine/src/display/internal/to-frame-bytes.ts","../../libs/engine/src/display/draw-screen.ts","../../libs/engine/src/crt/internal/find-crt-uniforms.ts","../../libs/engine/src/crt/internal/format-glsl-float.ts","../../libs/engine/src/crt/internal/fragment-shader-source.ts","../../libs/engine/src/crt/internal/try-create-shader.ts","../../libs/engine/src/crt/internal/try-link-program.ts","../../libs/engine/src/crt/internal/vertex-shader-source.ts","../../libs/engine/src/crt/internal/try-create-program.ts","../../libs/engine/src/crt/internal/try-create-texture.ts","../../libs/engine/src/crt/internal/try-create-vertices.ts","../../libs/engine/src/crt/try-create-crt.ts","../../libs/engine/src/palette/internal/compute-rgb-mix.ts","../../libs/engine/src/palette/internal/compute-extended-shade.ts","../../libs/engine/src/palette/internal/create-palette-entries.ts","../../libs/engine/src/palette/internal/split-hex-rgb.ts","../../libs/engine/src/palette/internal/to-rgb.ts","../../libs/engine/src/palette/create-palette.ts","../../libs/engine/src/display/internal/compute-display-size.ts","../../libs/engine/src/display/internal/try-to-display.ts","../../libs/engine/src/display/try-create-display.ts","../../libs/engine/src/palette/to-lit-pixel.ts","../../libs/engine/src/screen/internal/filter-other-lights.ts","../../libs/engine/src/screen/add-light.ts","../../libs/engine/src/screen/internal/filter-other-sprites.ts","../../libs/engine/src/screen/add-sprite.ts","../../libs/engine/src/screen/create-screen.ts","../../libs/engine/src/screen/find-light.ts","../../libs/engine/src/screen/find-sprite.ts","../../libs/engine/src/screen/patch-ambient.ts","../../libs/engine/src/screen/internal/patch-matching-light.ts","../../libs/engine/src/screen/patch-light.ts","../../libs/engine/src/screen/patch-light-by-offset.ts","../../libs/engine/src/screen/internal/patch-sprite-placement.ts","../../libs/engine/src/screen/patch-sprite.ts","../../libs/engine/src/screen/patch-sprite-by-offset.ts","../../libs/engine/src/screen/remove-light.ts","../../libs/engine/src/screen/remove-sprite.ts","../../libs/engine/src/screen/to-sprite.ts"],"sourcesContent":["import { match } from 'ts-pattern';\n\n/**\n * Calls `effect` with `value`, then returns `value`. Use it to run a side\n * effect as a step in a `pipe`: `(scene) => runEffect(scene, draw)`.\n */\nexport const runEffect = <T>(value: T, effect: (value: T) => void): T =>\n match(effect(value)).otherwise(() => value);\n","import type { CrtUniformNames } from './types/crt-uniform-names';\n\nexport const GLOW_STRENGTH = 0.6;\n\nexport const GLOW_THRESHOLD = 0.45;\n\nexport const GLOW_REACH = 2;\n\nexport const SCANLINE_DEPTH = 0.09;\n\nexport const GRAIN_AMOUNT = 0.015;\n\nexport const GRAIN_RATE = 24;\n\nexport const SURFACE_AMOUNT = 0.0125;\n\nexport const SURFACE_SCALE = 3;\n\nexport const POSITION_ATTRIBUTE = 'a_position';\n\nexport const POSITION_LOCATION = 0;\n\nexport const FULL_SCREEN_TRIANGLE: readonly number[] = Object.freeze([\n -1, -1, 3, -1, -1, 3,\n]);\n\nexport const CRT_UNIFORM_NAMES: CrtUniformNames = Object.freeze({\n frame: 'u_frame',\n frameSize: 'u_frameSize',\n canvasSize: 'u_canvasSize',\n pixelSize: 'u_pixelSize',\n time: 'u_time',\n});\n\nexport const CRT_CONTEXT_ATTRIBUTES: WebGLContextAttributes = Object.freeze({\n alpha: true,\n antialias: false,\n depth: false,\n stencil: false,\n premultipliedAlpha: true,\n preserveDrawingBuffer: false,\n});\n","import { runEffect } from '@cassette-canvas/common';\nimport { pipe } from 'remeda';\nimport { POSITION_LOCATION } from '../consts';\nimport type { Crt } from '../types/crt';\n\nconst VALUES_PER_CORNER = 2;\n\nconst CORNER_COUNT = 3;\n\nexport const drawCrtTriangle = (\n context: WebGLRenderingContext,\n { vertices }: Crt,\n): void =>\n pipe(\n context,\n (gl) =>\n runEffect(gl, () =>\n gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight),\n ),\n (gl) => runEffect(gl, () => gl.bindBuffer(gl.ARRAY_BUFFER, vertices)),\n (gl) => runEffect(gl, () => gl.enableVertexAttribArray(POSITION_LOCATION)),\n (gl) =>\n runEffect(gl, () =>\n gl.vertexAttribPointer(\n POSITION_LOCATION,\n VALUES_PER_CORNER,\n gl.FLOAT,\n false,\n 0,\n 0,\n ),\n ),\n (gl) => gl.drawArrays(gl.TRIANGLES, 0, CORNER_COUNT),\n );\n","import { runEffect } from '@cassette-canvas/common';\nimport { pipe } from 'remeda';\nimport type { Crt } from '../types/crt';\nimport type { CrtPicture } from '../types/crt-picture';\n\nexport const patchCrtTexture = (\n context: WebGLRenderingContext,\n { texture }: Crt,\n { bytes, width, height }: CrtPicture,\n): WebGLRenderingContext =>\n pipe(\n context,\n (gl) => runEffect(gl, () => gl.bindTexture(gl.TEXTURE_2D, texture)),\n (gl) =>\n runEffect(gl, () =>\n gl.texImage2D(\n gl.TEXTURE_2D,\n 0,\n gl.RGBA,\n width,\n height,\n 0,\n gl.RGBA,\n gl.UNSIGNED_BYTE,\n new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength),\n ),\n ),\n );\n","import { runEffect } from '@cassette-canvas/common';\nimport { pipe } from 'remeda';\nimport type { Crt } from '../types/crt';\nimport type { CrtPicture } from '../types/crt-picture';\n\nconst FRAME_TEXTURE_UNIT = 0;\n\nexport const patchCrtUniforms = (\n context: WebGLRenderingContext,\n { program, uniforms }: Crt,\n { width, height, pixelSize }: CrtPicture,\n time: number,\n): WebGLRenderingContext =>\n pipe(\n context,\n (gl) => runEffect(gl, () => gl.useProgram(program)),\n (gl) =>\n runEffect(gl, () => gl.uniform1i(uniforms.frame, FRAME_TEXTURE_UNIT)),\n (gl) =>\n runEffect(gl, () => gl.uniform2f(uniforms.frameSize, width, height)),\n (gl) =>\n runEffect(gl, () =>\n gl.uniform2f(\n uniforms.canvasSize,\n gl.drawingBufferWidth,\n gl.drawingBufferHeight,\n ),\n ),\n (gl) => runEffect(gl, () => gl.uniform1f(uniforms.pixelSize, pixelSize)),\n (gl) => runEffect(gl, () => gl.uniform1f(uniforms.time, time)),\n );\n","import { runEffect } from '@cassette-canvas/common';\nimport { pipe } from 'remeda';\nimport { drawCrtTriangle } from './internal/draw-crt-triangle';\nimport { patchCrtTexture } from './internal/patch-crt-texture';\nimport { patchCrtUniforms } from './internal/patch-crt-uniforms';\nimport type { Crt } from './types/crt';\nimport type { CrtPicture } from './types/crt-picture';\n\n/**\n * Draws a frame on the canvas with the monitor effect:\n *\n * - each logical pixel becomes a sharp `pixelSize` square;\n * - bright pixels glow up to 2 canvas pixels outward;\n * - each pixel row has a darker scanline at its bottom;\n * - film grain (animated by `time`, in milliseconds) and a fixed surface\n * texture are added on top.\n *\n * The effect uses full RGB color, not the palette.\n */\nexport const drawCrt = (\n context: WebGLRenderingContext,\n crt: Crt,\n picture: CrtPicture,\n time: number,\n): void =>\n pipe(\n context,\n (gl) => runEffect(gl, () => patchCrtTexture(gl, crt, picture)),\n (gl) => runEffect(gl, () => patchCrtUniforms(gl, crt, picture, time)),\n (gl) => drawCrtTriangle(gl, crt),\n );\n","import type { Rgb } from './types/rgb';\n\n/** The number of base colors in a palette. */\nexport const COLOR_COUNT = 16;\n\n/** The number of shades of each color that sprites and backgrounds use. */\nexport const SHADE_COUNT = 8;\n\n/**\n * The shade that shows a base color exactly as given. Lower shades are\n * darker, higher shades are lighter.\n */\nexport const BASE_SHADE = 4;\n\n/**\n * The number of shades of each color that lighting uses. The finer scale\n * lets light fade in smaller steps than sprite shades.\n */\nexport const EXTENDED_SHADE_COUNT = 32;\n\n/** The number of extended shades in one sprite shade. */\nexport const EXTENDED_SHADES_PER_SHADE = EXTENDED_SHADE_COUNT / SHADE_COUNT;\n\nexport const EXTENDED_SHADE_OFFSET = 1;\n\nexport const DARKEST_EXTENDED_SHADE = 0;\n\nexport const LIGHTEST_EXTENDED_SHADE = EXTENDED_SHADE_COUNT - 1;\n\nexport const COLOR_STRIDE = 0x10;\n\n/** The pixel value that draws nothing. Whatever is below shows through. */\nexport const TRANSPARENT = -1;\n\nexport const CHANNEL_MAX = 255;\n\nexport const BLACK: Rgb = Object.freeze({ red: 0, green: 0, blue: 0 });\n\nexport const WHITE: Rgb = Object.freeze({\n red: CHANNEL_MAX,\n green: CHANNEL_MAX,\n blue: CHANNEL_MAX,\n});\n","import {\n EXTENDED_SHADE_COUNT,\n EXTENDED_SHADES_PER_SHADE,\n} from '../palette/consts';\n\n/**\n * How many extended shades one doubling of light moves a pixel: one sprite\n * shade per doubling. Lights add up as light and are read off this scale\n * afterwards, so every doubling costs the same number of shades, the way the\n * eye sees brightness.\n */\nexport const EXTENDED_SHADES_PER_STOP = EXTENDED_SHADES_PER_SHADE;\n\n/** The amount of light that shows a pixel in its authored color. */\nexport const REFERENCE_EXPOSURE = 1;\n\n/**\n * The most light can move a pixel, in extended shades. A whole ramp in\n * either direction is more than any pixel needs: every pixel stops at the\n * darkest or the lightest extended shade well before this.\n */\nexport const MAX_SHADE_OFFSET = EXTENDED_SHADE_COUNT;\n\nexport const MIN_SHADE_OFFSET = -MAX_SHADE_OFFSET;\n\nexport const MIN_EXPOSURE = 2 ** (MIN_SHADE_OFFSET / EXTENDED_SHADES_PER_STOP);\n\nexport const MAX_EXPOSURE = 2 ** (MAX_SHADE_OFFSET / EXTENDED_SHADES_PER_STOP);\n\n/**\n * How much of the light at a pixel comes from each side of the pixel ahead\n * of it, toward the light, rather than all of it from that one pixel. It\n * gives every shadow a penumbra: its edge ramps from full light to full\n * shadow over two pixels instead of stepping down in one, the way an edge\n * looks under a light that is not a single point.\n *\n * Only the edge softens. What blocks a light still blocks it, so the heart\n * of a shadow stays as dark as ever, and a shadow never fades out with\n * distance. 0 gives the hard edges of a point source, and 0.25 spreads the\n * ramp evenly, which is as soft as three pixels go.\n */\nexport const SHADOW_SOFTNESS = 0.25;\n\n/**\n * How sharply a light's core outshines its rim. A light thins out with\n * distance like `1 / (1 + this * squaredReach)`, where `squaredReach` runs\n * from 0 at the center to 1 at the radius, the way light spreads out in the\n * world. So a lamp keeps a bright core and a long dim halo instead of a flat\n * disc.\n */\nexport const LIGHT_CORE_SHARPNESS = 8;\n","import { LIGHT_CORE_SHARPNESS } from '../consts';\n\nconst computeWindow = (squaredReach: number): number =>\n 1 - squaredReach * squaredReach;\n\n/**\n * Returns the share of a light that reaches a point: 1 at its center, 0 at\n * its radius. `squaredReach` is the distance to the light as a share of the\n * radius, squared, from 0 at the center to 1 at the radius. Squared, because\n * then the caller needs no square root, and this runs once per lit pixel.\n *\n * The share thins out with distance the way light spreads out in the world,\n * and a window closes it off smoothly at the radius, so a light leaves a\n * bright core and a long dim halo instead of a flat disc with a visible rim.\n * Callers keep to the center and the radius; a share past 1 means nothing.\n */\nexport const computeFalloff = (squaredReach: number): number =>\n computeWindow(squaredReach) ** 2 / (1 + LIGHT_CORE_SHARPNESS * squaredReach);\n","import { EXTENDED_SHADES_PER_STOP } from '../consts';\n\n/**\n * Returns the amount of light that shows a pixel `shades` extended shades\n * from its authored color. `toExposure(0)` is `REFERENCE_EXPOSURE`, and every\n * `EXTENDED_SHADES_PER_STOP` shades up double it.\n *\n * Ambient light and light intensities are written in extended shades because\n * that is what you see on screen. This turns one of them back into the amount\n * of light behind it, which is what adds up.\n */\nexport const toExposure = (shades: number): number =>\n 2 ** (shades / EXTENDED_SHADES_PER_STOP);\n","/* eslint-disable no-restricted-syntax -- Appendix B exception 1, measured below */\nimport type { Light } from '../../screen/types/light';\nimport { REFERENCE_EXPOSURE, SHADOW_SOFTNESS } from '../consts';\nimport type { Lighting } from '../types/lighting';\nimport { computeFalloff } from './compute-falloff';\nimport { toExposure } from './to-exposure';\n\nconst SHARE_AHEAD = 1 - 2 * SHADOW_SOFTNESS;\n\n/**\n * Returns how much light one light adds to every pixel. The values are\n * amounts of light, not extended shades, because that is what adds up:\n * `computeShadeOffsets` sums the amounts and reads the shades off them.\n *\n * At its center the light adds as much light as its intensity stands for,\n * which thins out to nothing at its radius. Sprites that cast shadows block\n * it, and the edge of a shadow ramps down over two pixels rather than\n * stepping down in one. A negative intensity takes light away instead.\n */\n// Style guide - Appendix B exception 1 (hot numeric kernel).\n// As a pipeline, this ring sweep took 8.84 ms per frame for two lights\n// (7,919 cells) on a 192x128 screen (Vite bundle, Node 24); as a loop,\n// 0.43 ms. Reading the shadow casters from a Uint8Array instead of a record\n// cut it further. It compares squared distances, so it needs no square root\n// and the falloff comes for free: on the demo screen at night (320x180, 11\n// lights) it takes 1.4 ms per frame, where the straight-line falloff and the\n// hard shadow edges it replaces took 1.26 ms. Reading the two pixels beside\n// the one ahead, rather than carrying a soft share outward from the light,\n// keeps that to 0.14 ms and keeps a shadow from fading out as it lengthens.\n// It only changes typed arrays it creates itself, and they do not change\n// after it returns them.\nexport const computeLightExposure = (\n { width, height, occluders }: Lighting,\n light: Light,\n): Float32Array => {\n const exposures = new Float32Array(width * height);\n const visible = new Uint8Array(width * height);\n const lightRow = Math.floor(light.row);\n const lightColumn = Math.floor(light.column);\n const reach = Math.ceil(light.radius);\n const squaredRadius = light.radius * light.radius;\n const emission = toExposure(light.intensity) - REFERENCE_EXPOSURE;\n const firstRing = Math.max(\n 0,\n -lightRow,\n lightRow - (height - 1),\n -lightColumn,\n lightColumn - (width - 1),\n );\n\n for (let ring = firstRing; ring <= reach; ring += 1) {\n const top = Math.max(-ring, -lightRow);\n const bottom = Math.min(ring, height - 1 - lightRow);\n const sideReach = ring - 1;\n\n for (let rowOffset = top; rowOffset <= bottom; rowOffset += 1) {\n const isEdgeRow = rowOffset === -ring || rowOffset === ring;\n const left = isEdgeRow ? Math.max(-ring, -lightColumn) : -ring;\n const right = isEdgeRow ? Math.min(ring, width - 1 - lightColumn) : ring;\n const step = isEdgeRow ? 1 : 2 * ring;\n\n for (\n let columnOffset = left;\n columnOffset <= right;\n columnOffset += step\n ) {\n const row = lightRow + rowOffset;\n const column = lightColumn + columnOffset;\n\n if (column < 0 || column >= width) {\n continue;\n }\n\n const parentRowOffset = Math.round((rowOffset * sideReach) / ring);\n const parentColumnOffset = Math.round(\n (columnOffset * sideReach) / ring,\n );\n const parentRow = lightRow + parentRowOffset;\n const parentColumn = lightColumn + parentColumnOffset;\n const parentIndex = parentRow * width + parentColumn;\n const hasParent =\n ring >= 2 &&\n parentRow >= 0 &&\n parentRow < height &&\n parentColumn >= 0 &&\n parentColumn < width;\n const litAhead =\n !hasParent ||\n (visible[parentIndex] === 1 && occluders[parentIndex] !== 1)\n ? 1\n : 0;\n\n // The two pixels beside the one ahead, along the ring it sits on.\n // Past a corner of that ring, or off the screen, they fall back to\n // the pixel ahead, so this only ever reads rings already swept.\n const runsAlongRow =\n Math.abs(parentRowOffset) >= Math.abs(parentColumnOffset);\n const sideStep = runsAlongRow ? 1 : width;\n const sideOffset = runsAlongRow ? parentColumnOffset : parentRowOffset;\n const sidePlace = runsAlongRow ? parentColumn : parentRow;\n const sideLimit = runsAlongRow ? width : height;\n const lowIndex = parentIndex - sideStep;\n const highIndex = parentIndex + sideStep;\n const litLow =\n hasParent && sideOffset > -sideReach && sidePlace > 0\n ? visible[lowIndex] === 1 && occluders[lowIndex] !== 1\n ? 1\n : 0\n : litAhead;\n const litHigh =\n hasParent && sideOffset < sideReach && sidePlace < sideLimit - 1\n ? visible[highIndex] === 1 && occluders[highIndex] !== 1\n ? 1\n : 0\n : litAhead;\n const share =\n SHARE_AHEAD * litAhead + SHADOW_SOFTNESS * (litLow + litHigh);\n const index = row * width + column;\n const squaredDistance =\n rowOffset * rowOffset + columnOffset * columnOffset;\n\n visible[index] = litAhead;\n\n if (share > 0 && squaredDistance < squaredRadius) {\n exposures[index] =\n emission * share * computeFalloff(squaredDistance / squaredRadius);\n }\n }\n }\n }\n\n return exposures;\n};\n","import {\n EXTENDED_SHADES_PER_STOP,\n MAX_EXPOSURE,\n MIN_EXPOSURE,\n} from '../consts';\n\n/**\n * Returns how many extended shades an amount of light moves a pixel from its\n * authored color. This undoes `toExposure`: `REFERENCE_EXPOSURE` gives 0,\n * twice as much light gives `EXTENDED_SHADES_PER_STOP` more shades, and half\n * as much gives that many fewer. Light beyond what a pixel can show stops at\n * the ends of the scale.\n *\n * Because every doubling costs the same number of shades, a scene that is\n * already bright barely shows one more lamp, while the same lamp in the dark\n * shows in full. That is how the eye reads brightness, and it is why lights\n * no longer stack up into white.\n *\n * It clamps with `Math.min` and `Math.max` rather than `clamp`, because it\n * runs once per lit pixel.\n */\nexport const toShadeOffset = (exposure: number): number =>\n EXTENDED_SHADES_PER_STOP *\n Math.log2(Math.min(MAX_EXPOSURE, Math.max(MIN_EXPOSURE, exposure)));\n","/* eslint-disable no-restricted-syntax -- Appendix B exception 1, measured below */\nimport type { Lighting } from '../types/lighting';\nimport { toExposure } from './to-exposure';\nimport { toShadeOffset } from './to-shade-offset';\n\n/**\n * Adds up the ambient light and every light that reaches a pixel, then reads\n * off how many extended shades that much light moves the pixel.\n *\n * The amounts of light are added, not the shades. Two lamps on one spot are\n * twice the light, which is one shade step more than one lamp, not twice as\n * many shade steps, and a lamp in daylight hardly tells at all. That is why\n * a lit scene keeps its shading instead of washing out to white.\n */\n// Style guide - Appendix B exception 1 (hot numeric kernel).\n// As a pipeline (times, with a sumBy over the lights for every place), the\n// demo screen at night (320x180, 11 lights) took 6.8 ms per frame (Vite\n// bundle, Node 24). This loop takes 1.2 ms, and 0.28 ms at noon, where one\n// light covers a small part of the screen. Places that no light reaches skip\n// the shade scale: the ambient light on its own always reads back as itself,\n// so skipping it saves 0.4 ms a frame at night and keeps the value exact. It\n// only changes the typed array it creates itself, and the array does not\n// change after it returns it.\nexport const computeShadeOffsets = (\n { ambient, width, height }: Lighting,\n exposures: readonly Float32Array[],\n): Float64Array => {\n const offsets = new Float64Array(Math.max(0, width * height));\n const ambientExposure = toExposure(ambient);\n\n for (const exposure of exposures) {\n for (let index = 0; index < offsets.length; index += 1) {\n offsets[index] = (offsets[index] ?? 0) + (exposure[index] ?? 0);\n }\n }\n\n for (let index = 0; index < offsets.length; index += 1) {\n const light = offsets[index] ?? 0;\n\n offsets[index] =\n light === 0 ? ambient : toShadeOffset(ambientExposure + light);\n }\n\n return offsets;\n};\n","import { map } from 'lodash-es';\nimport { computeLightExposure } from './internal/compute-light-exposure';\nimport { computeShadeOffsets } from './internal/compute-shade-offsets';\nimport type { Lighting } from './types/lighting';\n\n/**\n * Returns how many extended shades the light moves every pixel, row by row\n * from the top left.\n *\n * Lights are combined the way light behaves, not the way numbers add. Each\n * light and the ambient light are turned into amounts of light, those amounts\n * are added together, and the shade is read off the total on a scale where\n * every doubling of light is worth `EXTENDED_SHADES_PER_STOP` shades. So one\n * light on an unlit scene moves a pixel by exactly its intensity, while a\n * second light of the same intensity adds only one more step, and a lamp in\n * bright daylight barely shows. Nothing stacks up into flat white.\n */\nexport const computeLightLevels = (lighting: Lighting): Float64Array =>\n computeShadeOffsets(\n lighting,\n map(lighting.lights, (light) => computeLightExposure(lighting, light)),\n );\n","import { computeLightLevels } from '../../lighting/compute-light-levels';\nimport type { FrameComposition } from './types/frame-composition';\nimport type { FrameLight } from './types/frame-light';\n\nexport const computeFrameLight = ({\n pixels,\n lighting,\n}: FrameComposition): FrameLight => ({\n pixels,\n levels: computeLightLevels(lighting),\n});\n","import { inRange, isInteger } from 'lodash-es';\n\nexport const isIndex = (index: number, size: number): boolean =>\n isInteger(index) && inRange(index, size);\n","import { match } from 'ts-pattern';\nimport { COLOR_COUNT, EXTENDED_SHADE_COUNT, TRANSPARENT } from './consts';\nimport { isIndex } from './internal/is-index';\n\n/**\n * Returns the extended pixel for a color (0 to 15) and an extended shade\n * (0 to 31): `color * 32 + extendedShade`. Lit frames hold extended pixels,\n * and the palette is keyed by them.\n *\n * Sprite shade `s` is extended shade `4 * s + 1`. Extended shades 0, 30 and\n * 31 are outside the sprite shades; only light reaches them. Returns\n * `TRANSPARENT` for out-of-range input.\n */\nexport const toExtendedPixel = (color: number, extendedShade: number): number =>\n match(\n isIndex(color, COLOR_COUNT) && isIndex(extendedShade, EXTENDED_SHADE_COUNT),\n )\n .with(true, () => color * EXTENDED_SHADE_COUNT + extendedShade)\n .otherwise(() => TRANSPARENT);\n","import { match } from 'ts-pattern';\nimport { COLOR_COUNT, COLOR_STRIDE, SHADE_COUNT, TRANSPARENT } from './consts';\nimport { isIndex } from './internal/is-index';\n\n/**\n * Returns the pixel value for a color (0 to 15) and a shade (0 to 7):\n * `toPixel(3, 5)` is `0x35`. Returns `TRANSPARENT` for out-of-range input.\n */\nexport const toPixel = (color: number, shade: number): number =>\n match(isIndex(color, COLOR_COUNT) && isIndex(shade, SHADE_COUNT))\n .with(true, () => color * COLOR_STRIDE + shade)\n .otherwise(() => TRANSPARENT);\n","import { EXTENDED_SHADE_OFFSET, EXTENDED_SHADES_PER_SHADE } from '../consts';\n\nexport const toExtendedShade = (shade: number): number =>\n EXTENDED_SHADE_OFFSET + shade * EXTENDED_SHADES_PER_SHADE;\n","import { flatMap, fromPairs, map, range } from 'lodash-es';\nimport { COLOR_COUNT, EXTENDED_SHADE_COUNT, SHADE_COUNT } from '../consts';\nimport { toExtendedPixel } from '../to-extended-pixel';\nimport { toPixel } from '../to-pixel';\nimport { toExtendedShade } from './to-extended-shade';\nimport type { ShadeRamp } from './types/shade-ramp';\nimport type { ShadeRampEntry } from './types/shade-ramp-entry';\n\nconst createColorEntries = (\n color: number,\n pixels: readonly number[],\n): ShadeRampEntry[] =>\n map(range(SHADE_COUNT), (shade): ShadeRampEntry => [\n toPixel(color, shade),\n { pixels, extendedShade: toExtendedShade(shade) },\n ]);\n\nexport const createShadeRamps = (): Readonly<Record<number, ShadeRamp>> =>\n fromPairs(\n flatMap(range(COLOR_COUNT), (color) =>\n createColorEntries(\n color,\n map(range(EXTENDED_SHADE_COUNT), (extendedShade) =>\n toExtendedPixel(color, extendedShade),\n ),\n ),\n ),\n );\n","import { createShadeRamps } from './create-shade-ramps';\nimport type { ShadeRamp } from './types/shade-ramp';\n\nexport const SHADE_RAMPS: Readonly<Record<number, ShadeRamp>> =\n Object.freeze(createShadeRamps());\n","import { isUndefined } from 'lodash-es';\nimport { SHADE_RAMPS } from './internal/shade-ramps';\n\n/**\n * Returns `true` if the number is a pixel the palette can draw: a color from\n * 0 to 15 in the high hex digit and a shade from 0 to 7 in the low hex digit.\n * Returns `false` for `TRANSPARENT` and all other values.\n */\nexport const isPixel = (pixel: number): boolean =>\n !isUndefined(SHADE_RAMPS[pixel]);\n","/* eslint-disable no-restricted-syntax -- Appendix B exception 1, measured below */\nimport { TRANSPARENT } from '../../palette/consts';\nimport { isPixel } from '../../palette/is-pixel';\nimport type { Screen } from '../../screen/types/screen';\nimport type { FrameComposition } from './types/frame-composition';\n\nconst SHADOW_CASTER = 1;\n\n// Style guide - Appendix B exception 1 (hot numeric kernel).\n// As a pipeline (one object per sprite pixel, a filter, edits keyed by place,\n// then a fill with the background), composing the demo screen (320x180, 182\n// sprites) took 4.9 ms per frame (Vite bundle, Node 24). This loop takes\n// 0.55 ms. It only changes typed arrays it creates itself, and they do not\n// change after it returns them.\nexport const computeFramePixels = ({\n screen,\n spriteSize,\n width,\n height,\n}: {\n screen: Screen;\n spriteSize: number;\n width: number;\n height: number;\n}): FrameComposition => {\n const size = Math.max(0, width * height);\n const pixels = new Int16Array(size).fill(\n isPixel(screen.background) ? screen.background : TRANSPARENT,\n );\n const occluders = new Uint8Array(size);\n const spritePixelCount = spriteSize * spriteSize;\n\n for (const { sprite, row, column, castsShadow } of screen.sprites) {\n const top = Math.floor(row);\n const left = Math.floor(column);\n const count = Math.min(sprite.length, spritePixelCount);\n\n for (let index = 0; index < count; index += 1) {\n const pixel = sprite[index] ?? TRANSPARENT;\n const pixelRow = top + Math.floor(index / spriteSize);\n const pixelColumn = left + (index % spriteSize);\n const isOnScreen =\n pixelRow >= 0 &&\n pixelRow < height &&\n pixelColumn >= 0 &&\n pixelColumn < width;\n\n if (isOnScreen && isPixel(pixel)) {\n const place = pixelRow * width + pixelColumn;\n\n pixels[place] = pixel;\n\n if (castsShadow) {\n occluders[place] = SHADOW_CASTER;\n }\n }\n }\n }\n\n return {\n pixels,\n lighting: {\n ambient: screen.ambient,\n lights: screen.lights,\n width,\n height,\n occluders,\n },\n };\n};\n","/* eslint-disable no-restricted-syntax -- Appendix B exception 1, measured below */\nimport {\n DARKEST_EXTENDED_SHADE,\n LIGHTEST_EXTENDED_SHADE,\n TRANSPARENT,\n} from './consts';\nimport { SHADE_RAMPS } from './internal/shade-ramps';\n\n// Style guide - Appendix B exception 1 (hot numeric kernel).\n// As a pipeline (zipWith over toLitPixel, one match per place), shading the\n// demo screen (320x180) took 13.6 ms per frame (Vite bundle, Node 24); with\n// lookup tables and no match it still took 2.5 ms. This loop takes 1.2 ms. It\n// gives the same pixel as toLitPixel for every place. It only changes the\n// array it creates itself, and the array does not change after it returns it.\nexport const toLitPixels = (\n pixels: ArrayLike<number>,\n shadeOffsets: ArrayLike<number>,\n): number[] => {\n const litPixels = new Array<number>(pixels.length);\n\n for (let index = 0; index < pixels.length; index += 1) {\n const ramp = SHADE_RAMPS[pixels[index] ?? TRANSPARENT];\n const extendedShade = Math.round(\n (ramp?.extendedShade ?? 0) + (shadeOffsets[index] ?? 0),\n );\n\n litPixels[index] =\n ramp?.pixels[\n Math.min(\n LIGHTEST_EXTENDED_SHADE,\n Math.max(DARKEST_EXTENDED_SHADE, extendedShade),\n )\n ] ?? TRANSPARENT;\n }\n\n return litPixels;\n};\n","import { toLitPixels } from '../../palette/to-lit-pixels';\nimport type { Frame } from '../types/frame';\nimport type { FrameLight } from './types/frame-light';\n\nexport const patchFrameShades = ({ pixels, levels }: FrameLight): Frame =>\n toLitPixels(pixels, levels);\n","import { pipe } from 'remeda';\nimport type { Screen } from '../screen/types/screen';\nimport { computeFrameLight } from './internal/compute-frame-light';\nimport { computeFramePixels } from './internal/compute-frame-pixels';\nimport { patchFrameShades } from './internal/patch-frame-shades';\nimport type { Frame } from './types/frame';\n\n/**\n * Turns a screen into a frame of `width * height` extended pixels:\n *\n * 1. Fills the frame with the background.\n * 2. Draws the sprites in order. Transparent pixels are skipped, and parts\n * outside the frame are cut off.\n * 3. Lights every pixel: moves it along its 32-shade scale by the ambient\n * light plus each light that reaches it. Sprites that cast shadows block\n * light.\n *\n * With ambient 0 and no lights, every pixel keeps its authored color.\n */\nexport const computeFrame = (\n screen: Screen,\n spriteSize: number,\n width: number,\n height: number,\n): Frame =>\n pipe(\n { screen, spriteSize, width, height },\n computeFramePixels,\n computeFrameLight,\n patchFrameShades,\n );\n","/* eslint-disable no-restricted-syntax -- Appendix B exception 1, measured below */\nimport type { Frame } from '../../frame/types/frame';\nimport { CHANNEL_MAX, TRANSPARENT } from '../../palette/consts';\nimport type { Palette } from '../../palette/types/palette';\n\nconst BYTES_PER_PIXEL = 4;\n\nconst RED_OFFSET = 0;\n\nconst GREEN_OFFSET = 1;\n\nconst BLUE_OFFSET = 2;\n\nconst ALPHA_OFFSET = 3;\n\n// Style guide - Appendix B exception 1 (hot numeric kernel).\n// As a pipeline (flatMap to four bytes per place, one match per place), the\n// demo screen (320x180) took 16.5 ms per frame (Vite bundle, Node 24); as\n// Uint32Array.from over packed palette colors it still took 5.8 ms. This\n// loop takes 0.5 ms. Places the palette does not hold stay clear (all four\n// bytes 0). It only changes the typed array it creates itself, and the array\n// does not change after it returns it.\nexport const toFrameBytes = (\n frame: Frame,\n palette: Palette,\n): Uint8ClampedArray => {\n const bytes = new Uint8ClampedArray(frame.length * BYTES_PER_PIXEL);\n\n for (let index = 0; index < frame.length; index += 1) {\n const rgb = palette[frame[index] ?? TRANSPARENT];\n\n if (rgb !== undefined) {\n const offset = index * BYTES_PER_PIXEL;\n\n bytes[offset + RED_OFFSET] = rgb.red;\n bytes[offset + GREEN_OFFSET] = rgb.green;\n bytes[offset + BLUE_OFFSET] = rgb.blue;\n bytes[offset + ALPHA_OFFSET] = CHANNEL_MAX;\n }\n }\n\n return bytes;\n};\n","import { runEffect } from '@cassette-canvas/common';\nimport { pipe } from 'remeda';\nimport { drawCrt } from '../crt/draw-crt';\nimport { computeFrame } from '../frame/compute-frame';\nimport type { Screen } from '../screen/types/screen';\nimport { toFrameBytes } from './internal/to-frame-bytes';\nimport type { Display } from './types/display';\n\n/**\n * Draws the screen on the canvas. It composes and lights the frame, then\n * draws it with the monitor effect. `time` is the current time in\n * milliseconds, for example the `requestAnimationFrame` timestamp. It\n * animates the film grain. Returns the same display.\n */\nexport const drawScreen = (\n display: Display,\n screen: Screen,\n time: number,\n): Display =>\n pipe(\n computeFrame(screen, display.spriteSize, display.width, display.height),\n (frame) => toFrameBytes(frame, display.palette),\n (bytes) =>\n runEffect(display, ({ context, crt, width, height, pixelSize }) =>\n drawCrt(context, crt, { bytes, width, height, pixelSize }, time),\n ),\n );\n","import { mapValues } from 'lodash-es';\nimport { match, P } from 'ts-pattern';\nimport { CRT_UNIFORM_NAMES } from '../consts';\nimport type { CrtUniforms } from '../types/crt-uniforms';\n\nconst { nonNullable } = P;\n\nexport const findCrtUniforms = (\n context: WebGLRenderingContext,\n program: WebGLProgram,\n): CrtUniforms | undefined =>\n match(\n mapValues(\n CRT_UNIFORM_NAMES,\n (name) => context.getUniformLocation(program, name) ?? undefined,\n ),\n )\n .with(\n {\n frame: nonNullable,\n frameSize: nonNullable,\n canvasSize: nonNullable,\n pixelSize: nonNullable,\n time: nonNullable,\n },\n (uniforms) => uniforms,\n )\n .otherwise(() => undefined);\n","const GLSL_FLOAT_DIGITS = 4;\n\nexport const formatGlslFloat = (value: number): string =>\n value.toFixed(GLSL_FLOAT_DIGITS);\n","import {\n CRT_UNIFORM_NAMES,\n GLOW_REACH,\n GLOW_STRENGTH,\n GLOW_THRESHOLD,\n GRAIN_AMOUNT,\n GRAIN_RATE,\n SCANLINE_DEPTH,\n SURFACE_AMOUNT,\n SURFACE_SCALE,\n} from '../consts';\nimport { formatGlslFloat } from './format-glsl-float';\n\nexport const FRAGMENT_SHADER_SOURCE = `\n#ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n#else\nprecision mediump float;\n#endif\n\nuniform sampler2D ${CRT_UNIFORM_NAMES.frame};\nuniform vec2 ${CRT_UNIFORM_NAMES.frameSize};\nuniform vec2 ${CRT_UNIFORM_NAMES.canvasSize};\nuniform float ${CRT_UNIFORM_NAMES.pixelSize};\nuniform float ${CRT_UNIFORM_NAMES.time};\n\nconst float GLOW_STRENGTH = ${formatGlslFloat(GLOW_STRENGTH)};\nconst float GLOW_THRESHOLD = ${formatGlslFloat(GLOW_THRESHOLD)};\nconst int GLOW_REACH = ${GLOW_REACH};\nconst float SCANLINE_DEPTH = ${formatGlslFloat(SCANLINE_DEPTH)};\nconst float GRAIN_AMOUNT = ${formatGlslFloat(GRAIN_AMOUNT)};\nconst float GRAIN_RATE = ${formatGlslFloat(GRAIN_RATE)};\nconst float SURFACE_AMOUNT = ${formatGlslFloat(SURFACE_AMOUNT)};\nconst float SURFACE_SCALE = ${formatGlslFloat(SURFACE_SCALE)};\nconst vec3 LUMINANCE = vec3(0.299, 0.587, 0.114);\n\nvec4 sampleFrame(vec2 position) {\n vec2 logical = floor(position / ${CRT_UNIFORM_NAMES.pixelSize});\n\n if (logical.x < 0.0 || logical.y < 0.0\n || logical.x >= ${CRT_UNIFORM_NAMES.frameSize}.x\n || logical.y >= ${CRT_UNIFORM_NAMES.frameSize}.y) {\n return vec4(0.0);\n }\n\n return texture2D(\n ${CRT_UNIFORM_NAMES.frame},\n (logical + 0.5) / ${CRT_UNIFORM_NAMES.frameSize}\n );\n}\n\nfloat hash(vec2 point) {\n vec3 mixed = fract(vec3(point.xyx) * 0.1031);\n mixed += dot(mixed, mixed.yzx + 33.33);\n return fract((mixed.x + mixed.y) * mixed.z);\n}\n\nfloat valueNoise(vec2 point) {\n vec2 cell = floor(point);\n vec2 blend = fract(point);\n blend = blend * blend * (3.0 - 2.0 * blend);\n\n return mix(\n mix(hash(cell), hash(cell + vec2(1.0, 0.0)), blend.x),\n mix(hash(cell + vec2(0.0, 1.0)), hash(cell + vec2(1.0, 1.0)), blend.x),\n blend.y\n );\n}\n\nvec3 computeBright(vec3 color) {\n return color * smoothstep(GLOW_THRESHOLD, 1.0, dot(color, LUMINANCE));\n}\n\nvec3 computeGlow(vec2 position, vec3 base) {\n vec3 glow = vec3(0.0);\n float total = 0.0;\n\n for (int row = -GLOW_REACH; row <= GLOW_REACH; row++) {\n for (int column = -GLOW_REACH; column <= GLOW_REACH; column++) {\n vec2 offset = vec2(float(column), float(row));\n float reach = length(offset);\n\n if (reach > float(GLOW_REACH)) {\n continue;\n }\n\n float weight = 1.0 - reach / (float(GLOW_REACH) + 1.0);\n\n glow += computeBright(sampleFrame(position + offset).rgb) * weight;\n total += weight;\n }\n }\n\n return max(glow / total - computeBright(base), 0.0);\n}\n\nfloat computeScanline(vec2 position) {\n float period = max(${CRT_UNIFORM_NAMES.pixelSize}, 2.0);\n float phase = (mod(floor(position.y), period) + 0.5) / period;\n\n return 1.0 - SCANLINE_DEPTH * smoothstep(0.4, 1.0, phase);\n}\n\nfloat computeGrain(vec2 position) {\n float grainFrame = floor(${CRT_UNIFORM_NAMES.time} * GRAIN_RATE / 1000.0);\n vec2 shift = fract(vec2(grainFrame * 0.6180339, grainFrame * 0.3819660))\n * 1024.0;\n\n return (hash(position + shift) - 0.5) * GRAIN_AMOUNT;\n}\n\nfloat computeSurface(vec2 position) {\n return (valueNoise(position / SURFACE_SCALE) - 0.5) * SURFACE_AMOUNT;\n}\n\nvoid main() {\n vec2 position = vec2(\n gl_FragCoord.x,\n ${CRT_UNIFORM_NAMES.canvasSize}.y - gl_FragCoord.y\n );\n vec4 base = sampleFrame(position);\n vec3 color = (base.rgb + computeGlow(position, base.rgb) * GLOW_STRENGTH)\n * computeScanline(position)\n * (1.0 + computeSurface(position))\n + computeGrain(position) * base.a;\n\n color = clamp(color, 0.0, 1.0);\n\n gl_FragColor = vec4(color, max(base.a, max(color.r, max(color.g, color.b))));\n}\n`;\n","import { runEffect } from '@cassette-canvas/common';\nimport { pipe } from 'remeda';\nimport { match, P } from 'ts-pattern';\n\nconst { nonNullable } = P;\n\nconst tryCompileShader = (\n context: WebGLRenderingContext,\n shader: WebGLShader,\n source: string,\n): WebGLShader | undefined =>\n pipe(\n shader,\n (created) =>\n runEffect(created, (target) => context.shaderSource(target, source)),\n (sourced) => runEffect(sourced, (target) => context.compileShader(target)),\n (compiled) =>\n match(context.getShaderParameter(compiled, context.COMPILE_STATUS))\n .with(true, () => compiled)\n .otherwise(() => undefined),\n );\n\nexport const tryCreateShader = (\n context: WebGLRenderingContext,\n type: number,\n source: string,\n): WebGLShader | undefined =>\n match(context.createShader(type))\n .with(nonNullable, (shader) => tryCompileShader(context, shader, source))\n .otherwise(() => undefined);\n","import { runEffect } from '@cassette-canvas/common';\nimport { pipe } from 'remeda';\nimport { match } from 'ts-pattern';\nimport { POSITION_ATTRIBUTE, POSITION_LOCATION } from '../consts';\n\nexport const tryLinkProgram = (\n context: WebGLRenderingContext,\n program: WebGLProgram,\n vertexShader: WebGLShader,\n fragmentShader: WebGLShader,\n): WebGLProgram | undefined =>\n pipe(\n program,\n (created) =>\n runEffect(created, (target) =>\n context.attachShader(target, vertexShader),\n ),\n (attached) =>\n runEffect(attached, (target) =>\n context.attachShader(target, fragmentShader),\n ),\n (attached) =>\n runEffect(attached, (target) =>\n context.bindAttribLocation(\n target,\n POSITION_LOCATION,\n POSITION_ATTRIBUTE,\n ),\n ),\n (bound) => runEffect(bound, (target) => context.linkProgram(target)),\n (linked) =>\n match(context.getProgramParameter(linked, context.LINK_STATUS))\n .with(true, () => linked)\n .otherwise(() => undefined),\n );\n","import { POSITION_ATTRIBUTE } from '../consts';\n\nexport const VERTEX_SHADER_SOURCE = `\nattribute vec2 ${POSITION_ATTRIBUTE};\n\nvoid main() {\n gl_Position = vec4(${POSITION_ATTRIBUTE}, 0.0, 1.0);\n}\n`;\n","import { match, P } from 'ts-pattern';\nimport { FRAGMENT_SHADER_SOURCE } from './fragment-shader-source';\nimport { tryCreateShader } from './try-create-shader';\nimport { tryLinkProgram } from './try-link-program';\nimport { VERTEX_SHADER_SOURCE } from './vertex-shader-source';\n\nconst { nonNullable } = P;\n\nexport const tryCreateProgram = (\n context: WebGLRenderingContext,\n): WebGLProgram | undefined =>\n match({\n program: context.createProgram(),\n vertexShader: tryCreateShader(\n context,\n context.VERTEX_SHADER,\n VERTEX_SHADER_SOURCE,\n ),\n fragmentShader: tryCreateShader(\n context,\n context.FRAGMENT_SHADER,\n FRAGMENT_SHADER_SOURCE,\n ),\n })\n .with(\n {\n program: nonNullable,\n vertexShader: nonNullable,\n fragmentShader: nonNullable,\n },\n ({ program, vertexShader, fragmentShader }) =>\n tryLinkProgram(context, program, vertexShader, fragmentShader),\n )\n .otherwise(() => undefined);\n","import { runEffect } from '@cassette-canvas/common';\nimport { pipe } from 'remeda';\nimport { match, P } from 'ts-pattern';\n\nconst { nonNullable } = P;\n\nconst patchTextureSampling = (\n context: WebGLRenderingContext,\n texture: WebGLTexture,\n): WebGLTexture =>\n pipe(\n texture,\n (created) =>\n runEffect(created, (target) =>\n context.bindTexture(context.TEXTURE_2D, target),\n ),\n (bound) =>\n runEffect(bound, () =>\n context.texParameteri(\n context.TEXTURE_2D,\n context.TEXTURE_MIN_FILTER,\n context.NEAREST,\n ),\n ),\n (bound) =>\n runEffect(bound, () =>\n context.texParameteri(\n context.TEXTURE_2D,\n context.TEXTURE_MAG_FILTER,\n context.NEAREST,\n ),\n ),\n (bound) =>\n runEffect(bound, () =>\n context.texParameteri(\n context.TEXTURE_2D,\n context.TEXTURE_WRAP_S,\n context.CLAMP_TO_EDGE,\n ),\n ),\n (bound) =>\n runEffect(bound, () =>\n context.texParameteri(\n context.TEXTURE_2D,\n context.TEXTURE_WRAP_T,\n context.CLAMP_TO_EDGE,\n ),\n ),\n );\n\nexport const tryCreateTexture = (\n context: WebGLRenderingContext,\n): WebGLTexture | undefined =>\n match(context.createTexture())\n .with(nonNullable, (texture) => patchTextureSampling(context, texture))\n .otherwise(() => undefined);\n","import { runEffect } from '@cassette-canvas/common';\nimport { pipe } from 'remeda';\nimport { match, P } from 'ts-pattern';\nimport { FULL_SCREEN_TRIANGLE } from '../consts';\n\nconst { nonNullable } = P;\n\nconst patchVertices = (\n context: WebGLRenderingContext,\n vertices: WebGLBuffer,\n): WebGLBuffer =>\n pipe(\n vertices,\n (created) =>\n runEffect(created, (target) =>\n context.bindBuffer(context.ARRAY_BUFFER, target),\n ),\n (bound) =>\n runEffect(bound, () =>\n context.bufferData(\n context.ARRAY_BUFFER,\n new Float32Array(FULL_SCREEN_TRIANGLE),\n context.STATIC_DRAW,\n ),\n ),\n );\n\nexport const tryCreateVertices = (\n context: WebGLRenderingContext,\n): WebGLBuffer | undefined =>\n match(context.createBuffer())\n .with(nonNullable, (vertices) => patchVertices(context, vertices))\n .otherwise(() => undefined);\n","import { match, P } from 'ts-pattern';\nimport { findCrtUniforms } from './internal/find-crt-uniforms';\nimport { tryCreateProgram } from './internal/try-create-program';\nimport { tryCreateTexture } from './internal/try-create-texture';\nimport { tryCreateVertices } from './internal/try-create-vertices';\nimport type { CrtDraft } from './internal/types/crt-draft';\nimport type { Crt } from './types/crt';\n\nconst { nonNullable } = P;\n\nconst createCrtDraft = (\n context: WebGLRenderingContext,\n program: WebGLProgram | undefined,\n): CrtDraft => ({\n program,\n texture: tryCreateTexture(context),\n vertices: tryCreateVertices(context),\n uniforms: match(program)\n .with(nonNullable, (linked) => findCrtUniforms(context, linked))\n .otherwise(() => undefined),\n});\n\nconst tryToCrt = (draft: CrtDraft): Crt | undefined =>\n match(draft)\n .with(\n {\n program: nonNullable,\n texture: nonNullable,\n vertices: nonNullable,\n uniforms: nonNullable,\n },\n (crt) => crt,\n )\n .otherwise(() => undefined);\n\n/**\n * Sets up the monitor effect on a WebGL context: the shader program, the\n * texture that holds the frame, and a triangle that covers the canvas.\n * Returns `undefined` if there is no context or any step fails.\n */\nexport const tryCreateCrt = (\n context: WebGLRenderingContext | undefined,\n): Crt | undefined =>\n match(context)\n .with(nonNullable, (ready) =>\n tryToCrt(createCrtDraft(ready, tryCreateProgram(ready))),\n )\n .otherwise(() => undefined);\n","import { round } from 'lodash-es';\nimport type { Rgb } from '../types/rgb';\n\nconst computeChannelMix = (\n channel: number,\n target: number,\n amount: number,\n): number => round(channel + (target - channel) * amount);\n\nexport const computeRgbMix = (\n color: Rgb,\n target: Rgb,\n amount: number,\n): Rgb => ({\n red: computeChannelMix(color.red, target.red, amount),\n green: computeChannelMix(color.green, target.green, amount),\n blue: computeChannelMix(color.blue, target.blue, amount),\n});\n","import { match, P } from 'ts-pattern';\nimport {\n BASE_SHADE,\n BLACK,\n EXTENDED_SHADES_PER_SHADE,\n SHADE_COUNT,\n WHITE,\n} from '../consts';\nimport type { Rgb } from '../types/rgb';\nimport { computeRgbMix } from './compute-rgb-mix';\nimport { toExtendedShade } from './to-extended-shade';\n\nconst { number } = P;\n\nconst EXTENDED_BASE_SHADE = toExtendedShade(BASE_SHADE);\n\nconst DARKER_SHADES = BASE_SHADE;\n\nconst LIGHTER_SHADES = SHADE_COUNT - BASE_SHADE - 1;\n\nconst DARKEN_STEP = 1 / ((DARKER_SHADES + 1) * EXTENDED_SHADES_PER_SHADE);\n\nconst LIGHTEN_STEP = 1 / ((LIGHTER_SHADES + 1) * EXTENDED_SHADES_PER_SHADE);\n\nexport const computeExtendedShade = (base: Rgb, extendedShade: number): Rgb =>\n match(extendedShade)\n .with(number.lt(EXTENDED_BASE_SHADE), (darker) =>\n computeRgbMix(base, BLACK, (EXTENDED_BASE_SHADE - darker) * DARKEN_STEP),\n )\n .with(number.gt(EXTENDED_BASE_SHADE), (lighter) =>\n computeRgbMix(\n base,\n WHITE,\n (lighter - EXTENDED_BASE_SHADE) * LIGHTEN_STEP,\n ),\n )\n .otherwise(() => base);\n","import { map, range } from 'lodash-es';\nimport { EXTENDED_SHADE_COUNT } from '../consts';\nimport { toExtendedPixel } from '../to-extended-pixel';\nimport type { Rgb } from '../types/rgb';\nimport { computeExtendedShade } from './compute-extended-shade';\nimport type { PaletteEntry } from './types/palette-entry';\n\nexport const createPaletteEntries = (\n base: Rgb,\n color: number,\n): PaletteEntry[] =>\n map(range(EXTENDED_SHADE_COUNT), (extendedShade): PaletteEntry => [\n toExtendedPixel(color, extendedShade),\n computeExtendedShade(base, extendedShade),\n ]);\n","import { map, parseInt, words } from 'lodash-es';\nimport { pipe } from 'remeda';\nimport type { Rgb } from '../types/rgb';\n\nconst HEX_CHANNEL_PATTERN = /[\\da-f]{2}/gi;\n\nconst HEX_RADIX = 16;\n\nexport const splitHexRgb = (color: string): Rgb =>\n pipe(\n words(color, HEX_CHANNEL_PATTERN),\n (channels) => map(channels, (channel) => parseInt(channel, HEX_RADIX)),\n ([red = 0, green = 0, blue = 0]) => ({ red, green, blue }),\n );\n","import { match, P } from 'ts-pattern';\nimport { BLACK } from '../consts';\nimport type { HexColor } from '../types/hex-color';\nimport type { Rgb } from '../types/rgb';\nimport { splitHexRgb } from './split-hex-rgb';\n\nconst { string } = P;\n\nconst HEX_COLOR_PATTERN = /^#[\\da-f]{6}$/i;\n\nexport const toRgb = (color: HexColor): Rgb =>\n match(color)\n .with(string.regex(HEX_COLOR_PATTERN), splitHexRgb)\n .otherwise(() => BLACK);\n","import { flatMap, fromPairs, map } from 'lodash-es';\nimport { pipe } from 'remeda';\nimport { createPaletteEntries } from './internal/create-palette-entries';\nimport { toRgb } from './internal/to-rgb';\nimport type { Palette } from './types/palette';\nimport type { PaletteColors } from './types/palette-colors';\n\n/**\n * Expands the 16 base colors into 512 colors: 32 extended shades per color,\n * keyed by extended pixel.\n *\n * Sprite shade `s` is extended shade `4 * s + 1`. Shade `BASE_SHADE` is the\n * base color itself. Darker shades are mixed with black, lighter shades with\n * white. Only light reaches the extended shades between and beyond the\n * sprite shades. A color that is not in `#rrggbb` form is read as black.\n */\nexport const createPalette = (colors: PaletteColors): Palette =>\n pipe(\n map(colors, toRgb),\n (bases) => flatMap(bases, createPaletteEntries),\n (entries) => fromPairs(entries),\n );\n","import { floor } from 'lodash-es';\nimport type { DisplaySize } from './types/display-size';\n\nexport const computeDisplaySize = (\n canvas: Pick<HTMLCanvasElement, 'width' | 'height'>,\n pixelSize: number,\n): DisplaySize => ({\n width: floor(canvas.width / pixelSize),\n height: floor(canvas.height / pixelSize),\n});\n","import { match, P } from 'ts-pattern';\nimport type { Display } from '../types/display';\nimport type { DisplayDraft } from './types/display-draft';\n\nconst { nonNullable, number } = P;\n\nconst POSITIVE_INTEGER = number.int().positive();\n\nexport const tryToDisplay = (draft: DisplayDraft): Display | undefined =>\n match(draft)\n .with(\n {\n context: nonNullable,\n crt: nonNullable,\n pixelSize: POSITIVE_INTEGER,\n spriteSize: POSITIVE_INTEGER,\n width: POSITIVE_INTEGER,\n height: POSITIVE_INTEGER,\n },\n (display) => display,\n )\n .otherwise(() => undefined);\n","import { pipe } from 'remeda';\nimport { CRT_CONTEXT_ATTRIBUTES } from '../crt/consts';\nimport { tryCreateCrt } from '../crt/try-create-crt';\nimport { createPalette } from '../palette/create-palette';\nimport { computeDisplaySize } from './internal/compute-display-size';\nimport { tryToDisplay } from './internal/try-to-display';\nimport type { Display } from './types/display';\nimport type { DisplaySettings } from './types/display-settings';\n\n/**\n * Creates a display on the canvas.\n *\n * - `colors`: the 16 base colors.\n * - `pixelSize`: the width and height of one logical pixel, in canvas pixels.\n * - `spriteSize`: the width and height of every sprite, in logical pixels.\n *\n * The screen size in logical pixels is the canvas size divided by\n * `pixelSize`. The display draws with WebGL, and the monitor effect is\n * always on.\n *\n * Returns `undefined` if the canvas has no WebGL context, the monitor effect\n * cannot be set up, a size is not a positive whole number, or the canvas is\n * smaller than one logical pixel.\n */\nexport const tryCreateDisplay = (\n canvas: HTMLCanvasElement,\n { colors, pixelSize, spriteSize }: DisplaySettings,\n): Display | undefined =>\n pipe(\n {\n context: canvas.getContext('webgl', CRT_CONTEXT_ATTRIBUTES) ?? undefined,\n size: computeDisplaySize(canvas, pixelSize),\n },\n ({ context, size }) => ({\n context,\n crt: tryCreateCrt(context),\n palette: createPalette(colors),\n pixelSize,\n spriteSize,\n width: size.width,\n height: size.height,\n }),\n tryToDisplay,\n );\n","import { clamp, round } from 'lodash-es';\nimport { match, P } from 'ts-pattern';\nimport {\n DARKEST_EXTENDED_SHADE,\n LIGHTEST_EXTENDED_SHADE,\n TRANSPARENT,\n} from './consts';\nimport { SHADE_RAMPS } from './internal/shade-ramps';\n\nconst { nonNullable } = P;\n\n/**\n * Returns the extended pixel that a pixel shows under light. The color stays\n * the same. The shade moves `shadeOffset` extended shades up or down\n * (4 extended shades = 1 sprite shade). The result is rounded and stops at\n * the darkest and lightest extended shade. `toLitPixel(pixel, 0)` returns\n * the authored color. Returns `TRANSPARENT` for `TRANSPARENT` and for any\n * value that is not a pixel.\n */\nexport const toLitPixel = (pixel: number, shadeOffset: number): number =>\n match(SHADE_RAMPS[pixel])\n .with(\n nonNullable,\n ({ pixels, extendedShade }) =>\n pixels[\n clamp(\n round(extendedShade + shadeOffset),\n DARKEST_EXTENDED_SHADE,\n LIGHTEST_EXTENDED_SHADE,\n )\n ] ?? TRANSPARENT,\n )\n .otherwise(() => TRANSPARENT);\n","import { filter } from 'lodash-es';\nimport type { Light } from '../types/light';\n\nexport const filterOtherLights = (\n lights: readonly Light[],\n id: string,\n): Light[] => filter(lights, (light) => light.id !== id);\n","import { filterOtherLights } from './internal/filter-other-lights';\nimport type { LightSettings } from './types/light-settings';\nimport type { Screen } from './types/screen';\n\n/**\n * Adds a light to the screen. If a light with the same id exists, it is\n * replaced. Returns a new screen.\n */\nexport const addLight = (\n screen: Screen,\n id: string,\n { row, column, radius, intensity }: LightSettings,\n): Screen => ({\n ...screen,\n lights: [\n ...filterOtherLights(screen.lights, id),\n { id, row, column, radius, intensity },\n ],\n});\n","import { filter } from 'lodash-es';\nimport type { SpritePlacement } from '../types/sprite-placement';\n\nexport const filterOtherSprites = (\n sprites: readonly SpritePlacement[],\n id: string,\n): SpritePlacement[] => filter(sprites, (placement) => placement.id !== id);\n","import { filterOtherSprites } from './internal/filter-other-sprites';\nimport type { Screen } from './types/screen';\nimport type { Sprite } from './types/sprite';\n\n/**\n * Adds a sprite on top of the other sprites. `row` and `column` set its\n * top-left pixel. If a sprite with the same id exists, it is removed first.\n * The new sprite does not cast a shadow; to change that, call\n * `patchSprite(screen, id, { castsShadow: true })`. Returns a new screen.\n */\nexport const addSprite = (\n screen: Screen,\n id: string,\n sprite: Sprite,\n row: number,\n column: number,\n): Screen => ({\n ...screen,\n sprites: [\n ...filterOtherSprites(screen.sprites, id),\n { id, sprite, row, column, castsShadow: false },\n ],\n});\n","import { TRANSPARENT } from '../palette/consts';\nimport type { Screen } from './types/screen';\n\n/**\n * Creates an empty screen, with no sprites and no lights, filled with\n * `background`. `ambient` shifts every pixel by that many extended shades\n * (4 extended shades = 1 sprite shade). For example, a night scene could\n * use -16.\n */\nexport const createScreen = (\n background: number = TRANSPARENT,\n ambient = 0,\n): Screen => ({\n background,\n ambient,\n sprites: [],\n lights: [],\n});\n","import { find } from 'lodash-es';\nimport type { Light } from './types/light';\nimport type { Screen } from './types/screen';\n\n/** Returns the light with the id, or `undefined` if there is none. */\nexport const findLight = (screen: Screen, id: string): Light | undefined =>\n find(screen.lights, (light) => light.id === id);\n","import { find } from 'lodash-es';\nimport type { Screen } from './types/screen';\nimport type { SpritePlacement } from './types/sprite-placement';\n\n/** Returns the sprite with the id, or `undefined` if there is none. */\nexport const findSprite = (\n screen: Screen,\n id: string,\n): SpritePlacement | undefined =>\n find(screen.sprites, (placement) => placement.id === id);\n","import type { Screen } from './types/screen';\n\n/**\n * Sets the ambient light: how many extended shades every pixel moves up\n * (positive) or down (negative) before lights are added. Change it over time\n * to fade between day and night. Returns a new screen.\n */\nexport const patchAmbient = (screen: Screen, ambient: number): Screen => ({\n ...screen,\n ambient,\n});\n","import { match } from 'ts-pattern';\nimport type { Light } from '../types/light';\nimport type { LightChanges } from '../types/light-changes';\n\nexport const patchMatchingLight = (\n light: Light,\n id: string,\n changes: LightChanges,\n): Light =>\n match(light.id === id)\n .with(true, () => ({\n id,\n row: changes.row ?? light.row,\n column: changes.column ?? light.column,\n radius: changes.radius ?? light.radius,\n intensity: changes.intensity ?? light.intensity,\n }))\n .otherwise(() => light);\n","import { map } from 'lodash-es';\nimport { patchMatchingLight } from './internal/patch-matching-light';\nimport type { LightChanges } from './types/light-changes';\nimport type { Screen } from './types/screen';\n\n/**\n * Changes a light's `row`, `column`, `radius` or `intensity`. Fields you\n * leave out keep their values. Returns a new screen. If no light has the id,\n * nothing changes.\n */\nexport const patchLight = (\n screen: Screen,\n id: string,\n changes: LightChanges,\n): Screen => ({\n ...screen,\n lights: map(screen.lights, (light) => patchMatchingLight(light, id, changes)),\n});\n","import { match, P } from 'ts-pattern';\nimport { findLight } from './find-light';\nimport { patchLight } from './patch-light';\nimport type { Screen } from './types/screen';\n\nconst { nonNullable } = P;\n\n/**\n * Moves a light `rowOffset` pixels down and `columnOffset` pixels right.\n * Negative values move it up and left. Returns a new screen. If no light has\n * the id, nothing changes.\n */\nexport const patchLightByOffset = (\n screen: Screen,\n id: string,\n rowOffset: number,\n columnOffset: number,\n): Screen =>\n match(findLight(screen, id))\n .with(nonNullable, ({ row, column }) =>\n patchLight(screen, id, {\n row: row + rowOffset,\n column: column + columnOffset,\n }),\n )\n .otherwise(() => screen);\n","import { match } from 'ts-pattern';\nimport type { SpriteChanges } from '../types/sprite-changes';\nimport type { SpritePlacement } from '../types/sprite-placement';\n\nexport const patchSpritePlacement = (\n placement: SpritePlacement,\n id: string,\n changes: SpriteChanges,\n): SpritePlacement =>\n match(placement.id === id)\n .with(true, () => ({\n id,\n sprite: changes.sprite ?? placement.sprite,\n row: changes.row ?? placement.row,\n column: changes.column ?? placement.column,\n castsShadow: changes.castsShadow ?? placement.castsShadow,\n }))\n .otherwise(() => placement);\n","import { map } from 'lodash-es';\nimport { patchSpritePlacement } from './internal/patch-sprite-placement';\nimport type { Screen } from './types/screen';\nimport type { SpriteChanges } from './types/sprite-changes';\n\n/**\n * Changes a sprite's `sprite` (for animation), `row`, `column` or\n * `castsShadow`. Fields you leave out keep their values. The sprite keeps its\n * place in the drawing order. Returns a new screen. If no sprite has the id,\n * nothing changes.\n */\nexport const patchSprite = (\n screen: Screen,\n id: string,\n changes: SpriteChanges,\n): Screen => ({\n ...screen,\n sprites: map(screen.sprites, (placement) =>\n patchSpritePlacement(placement, id, changes),\n ),\n});\n","import { match, P } from 'ts-pattern';\nimport { findSprite } from './find-sprite';\nimport { patchSprite } from './patch-sprite';\nimport type { Screen } from './types/screen';\n\nconst { nonNullable } = P;\n\n/**\n * Moves a sprite `rowOffset` pixels down and `columnOffset` pixels right.\n * Negative values move it up and left. Returns a new screen. If no sprite\n * has the id, nothing changes.\n */\nexport const patchSpriteByOffset = (\n screen: Screen,\n id: string,\n rowOffset: number,\n columnOffset: number,\n): Screen =>\n match(findSprite(screen, id))\n .with(nonNullable, ({ row, column }) =>\n patchSprite(screen, id, {\n row: row + rowOffset,\n column: column + columnOffset,\n }),\n )\n .otherwise(() => screen);\n","import { filterOtherLights } from './internal/filter-other-lights';\nimport type { Screen } from './types/screen';\n\n/** Removes the light with the id. Returns a new screen. */\nexport const removeLight = (screen: Screen, id: string): Screen => ({\n ...screen,\n lights: filterOtherLights(screen.lights, id),\n});\n","import { filterOtherSprites } from './internal/filter-other-sprites';\nimport type { Screen } from './types/screen';\n\n/** Removes the sprite with the id. Returns a new screen. */\nexport const removeSprite = (screen: Screen, id: string): Screen => ({\n ...screen,\n sprites: filterOtherSprites(screen.sprites, id),\n});\n","import { flatMap, map, split } from 'lodash-es';\nimport { TRANSPARENT } from '../palette/consts';\nimport type { Sprite } from './types/sprite';\nimport type { SpriteLegend } from './types/sprite-legend';\n\n/**\n * Builds a sprite from text art: one string per row, one character per\n * pixel. Each character is looked up in `legend`. Characters that are not in\n * the legend become transparent.\n */\nexport const toSprite = (\n rows: readonly string[],\n legend: SpriteLegend,\n): Sprite =>\n flatMap(rows, (row) =>\n map(split(row, ''), (symbol) => legend[symbol] ?? TRANSPARENT),\n );\n"],"mappings":"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"}
|