@8bitscript/c64 0.1.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/src/mouse.8bs ADDED
@@ -0,0 +1,265 @@
1
+ // @8bitscript/c64/mouse — a 1351 mouse in a control port: is one there,
2
+ // where has it moved, which button is down.
3
+ //
4
+ // Named by "8bitscript".exports["./mouse"] in this package's package.json:
5
+ //
6
+ // import { mouse } from "@8bitscript/c64/mouse";
7
+ //
8
+ // mouse.select(Mouse.PORT_1); // once, at start-up
9
+ // ...
10
+ // waitFrame();
11
+ // mouse.poll(); // once a frame
12
+ // if (mouse.present()) { ... mouse.x() ... }
13
+ //
14
+ // A run-time hardware probe *and* the driver that uses it, because for a
15
+ // mouse the two cannot be separated: a 1351 says nothing about itself
16
+ // except through the same two lines its movement arrives on. A build
17
+ // fitted with one (`--hardware port1=mouse1351`) has `Input.MOUSE` true
18
+ // from @8bitscript/system, meaning this build may use a mouse; this is
19
+ // how it finds out whether one is really plugged in. A program that never
20
+ // imports this file carries none of it.
21
+ //
22
+ // **How a 1351 talks.** It is not a switch like a joystick. Each axis is
23
+ // a counter the mouse advances as it moves, presented on the port's
24
+ // analogue line, which the SID converts and hands back at `$D419` (X) and
25
+ // `$D41A` (Y) — the same registers a paddle uses. VICE's `mouse_1351.c`
26
+ // returns `(counter & 0x7f) + 0x40`, so the value lives in 64..191 and
27
+ // wraps; movement is the *difference* between two readings, not the
28
+ // reading. Sampled once a frame, a difference above half the range is the
29
+ // counter having wrapped the other way, which is what makes it a signed
30
+ // step rather than a jump.
31
+ //
32
+ // **A 1-count is fuzz until it happens twice the same way.** VICE's
33
+ // 1351 is a 7-bit counter — `(x & 0x7f) + 0x40` in `mouse_1351.c` — so a
34
+ // host pixel is often a 1-count step, and at rest the pots flicker 64/65.
35
+ // Applying every 1-count walked the pointer with no host motion. Masking
36
+ // to bits 1–6 and ignoring `|delta| < 3` (the previous driver) threw slow
37
+ // motion away: `last` still advanced, so a slow move never accumulated
38
+ // and the pointer hung until the host jumped several counts in one frame
39
+ // (Studio, 2026-09-07). Holding the first 1-count and applying on the
40
+ // second keeps rest still and lets a slow move through.
41
+ //
42
+ // **How presence is answered, and how honestly.** Measured under VICE
43
+ // with `-controlport1device` at each of its four settings, at rest, both
44
+ // lines read: nothing 255, a joystick 255, paddles 255, a 1351 **64** —
45
+ // exactly the zero of the formula above. So a reading inside the 1351's
46
+ // range is what says a mouse is there, and 255 says one is not. Two
47
+ // things to be honest about: a real paddle sits wherever it was last
48
+ // turned, so a paddle at mid-travel reads in this range too and only
49
+ // movement tells them apart; and a mouse is only *found* once the program
50
+ // has waited a frame after `select()` for the SID's converter to settle.
51
+ // `present()` therefore means "consistent with a 1351", and a program
52
+ // that must be certain should watch for movement before believing it.
53
+ //
54
+ // **The buttons** are not analogue at all: they ride the port's ordinary
55
+ // joystick lines, left on FIRE and right on UP (`mouse_1351.c`), so they
56
+ // are read exactly as a joystick's switches are — which also means a
57
+ // joystick pushed up looks like a right button, and this file will not
58
+ // pretend otherwise.
59
+ //
60
+ // **The port select** is CIA1 port A's top two bits: `%01` reads port 1's
61
+ // analogue lines, `%10` port 2's (confirmed here — `$DC00 = $40` is what
62
+ // made the 1351 readable in port 1). Those same bits are keyboard column
63
+ // outputs, so `select()` leaves the other six high, driving no column;
64
+ // `@8bitscript/c64/keyboard`'s scan writes all eight itself and must be
65
+ // followed by another `select()` before the next `poll()` if both are
66
+ // used. The converter needs about half a millisecond after a select, so
67
+ // the order that works is: `select()` at start-up, then one `poll()` a
68
+ // frame.
69
+ import { paddleX, paddleY, cia1PortA, cia1PortB } from "./index.8bs";
70
+
71
+ export namespace Mouse {
72
+ const PORT_1: utinyint = 0x7F; // %01 in the top bits, no keyboard column driven
73
+ const PORT_2: utinyint = 0xBF; // %10
74
+ }
75
+
76
+ namespace Pot {
77
+ const COUNTER: utinyint = 0x7F; // VICE's 1351 is 7 bits; bit 0 is also fuzz
78
+ const LOWEST: utinyint = 64; // a 1351 at its zero point
79
+ const HIGHEST: utinyint = 191; // and at the other end of its wrap
80
+ const HALF: utinyint = 64; // half the 7-bit wrap space: a bigger step is a wrap
81
+ }
82
+
83
+ // Per-axis filter for 1-count steps. Rest's 64/65 flicker is +1 then -1
84
+ // and never leaves WAIT; a slow host move is +1, +1 and then tracks at
85
+ // one pixel a count instead of jumping two and stalling on the next.
86
+ namespace Axis {
87
+ const IDLE: utinyint = 0;
88
+ const WAIT_POS: utinyint = 1;
89
+ const RUN_POS: utinyint = 2;
90
+ const WAIT_NEG: utinyint = 3;
91
+ const RUN_NEG: utinyint = 4;
92
+ }
93
+
94
+ // The last counter reading on each axis, and where the pointer has got to.
95
+ let lastX: utinyint = 0;
96
+ let lastY: utinyint = 0;
97
+ let atX: usmallint = 0;
98
+ let atY: usmallint = 0;
99
+ let started: bool = false;
100
+ let hold: array<utinyint, 2>;
101
+
102
+ // Whether the reading the last poll() took was consistent with a 1351.
103
+ // **Kept, rather than asked again**, and that is not an optimisation: the
104
+ // SID's converter runs continuously, so two reads of `$D419` are two
105
+ // different moments, and a program that asks twice in one frame can get
106
+ // two different answers. Measured under x64sc with a 1351 fitted in port
107
+ // 1: `@8bitscript/c64/input`'s poll() concluded *no pointer* while a read
108
+ // taken a few instructions later in the same frame said there was one, so
109
+ // an arrow drawn from one answer and hit-tested from the other flickered
110
+ // out of existence. One reading a frame, and everyone shares it.
111
+ let there: bool = false;
112
+
113
+ // How far the pointer may travel on each axis. A 1351's counters run on
114
+ // for as long as the mouse is pushed, so without a ceiling a pointer
115
+ // shoved off the right of the screen has to be dragged all the way back
116
+ // before it reappears — the accumulator, not the display, is what has to
117
+ // stop. Wide open until a program calls `setLimits()`.
118
+ let limitX: usmallint = 65535;
119
+ let limitY: usmallint = 65535;
120
+
121
+ export namespace mouse {
122
+ // Point the SID's analogue lines at one control port: Mouse.PORT_1 or
123
+ // Mouse.PORT_2. Once, at start-up — the converter needs a frame to
124
+ // settle before the first poll() means anything.
125
+ function select(port: utinyint): void {
126
+ cia1PortA = port;
127
+ }
128
+
129
+ // The ceiling each axis stops at, in mouse counts — `setLimits(319,
130
+ // 199)` for a C64's pixels, `setLimits(39, 24)` for its cells if the
131
+ // program scales before it gets here. Both axes stop at 0 either way.
132
+ // Set it once at start-up; a program that never calls this gets an
133
+ // accumulator that runs as far as the mouse is pushed.
134
+ function setLimits(x: usmallint, y: usmallint): void {
135
+ limitX = x;
136
+ limitY = y;
137
+ }
138
+
139
+ // One axis' movement since the last reading, added to `at` and held
140
+ // between 0 and `limit`. The difference is taken in all seven bits,
141
+ // and a difference past half the 7-bit wrap space is the counter
142
+ // having gone the other way.
143
+ //
144
+ // **A 1-count is fuzz until it happens twice the same way, then it
145
+ // tracks.** VICE's rest reading flickers 64/65; applying every
146
+ // 1-count walked the pointer with no host motion. Masking to bits 1–6
147
+ // and ignoring `|delta| < 3` *and* updating `last` (the previous
148
+ // driver) ate slow host motion: a trackpad step is often 1–2 counts a
149
+ // frame, so the pointer hung until the host jumped (Studio,
150
+ // 2026-09-07). Confirming the first 1-count, then applying each
151
+ // further 1-count, keeps rest still and lets a slow move through one
152
+ // pixel at a time. A still frame after a *run* drops back to idle so
153
+ // rest cannot walk; a still frame while *waiting* is kept, because a
154
+ // slow host move is often one count then several frames of zero —
155
+ // idling there ate the second count (Studio, 2026-09-07).
156
+ function step(at: usmallint, last: utinyint, now: utinyint, limit: usmallint, axis: utinyint): usmallint {
157
+ let delta: utinyint = (now - last) & Pot.COUNTER;
158
+ let state: utinyint = hold[axis];
159
+ if (delta == 0) {
160
+ if (state == Axis.RUN_POS || state == Axis.RUN_NEG) {
161
+ hold[axis] = Axis.IDLE;
162
+ }
163
+ return at;
164
+ }
165
+ if (delta < Pot.HALF) {
166
+ if (delta == 1) {
167
+ if (state == Axis.WAIT_POS || state == Axis.RUN_POS) {
168
+ hold[axis] = Axis.RUN_POS;
169
+ } else {
170
+ hold[axis] = Axis.WAIT_POS;
171
+ return at;
172
+ }
173
+ } else {
174
+ hold[axis] = Axis.RUN_POS;
175
+ }
176
+ if (limit - at < delta) {
177
+ return limit;
178
+ }
179
+ return at + delta;
180
+ }
181
+ let back: utinyint = 128 - delta;
182
+ if (back == 1) {
183
+ if (state == Axis.WAIT_NEG || state == Axis.RUN_NEG) {
184
+ hold[axis] = Axis.RUN_NEG;
185
+ } else {
186
+ hold[axis] = Axis.WAIT_NEG;
187
+ return at;
188
+ }
189
+ } else {
190
+ hold[axis] = Axis.RUN_NEG;
191
+ }
192
+ if (back > at) {
193
+ return 0;
194
+ }
195
+ return at - back;
196
+ }
197
+
198
+ // Read both axes. Once a frame, after waitFrame().
199
+ //
200
+ // **One reading of each line, answering both questions.** Whether a
201
+ // mouse is there and how far it moved come out of the same two bytes,
202
+ // because they are two readings of one moment: the range check says a
203
+ // 1351 and the difference from last frame says how far. Reading the
204
+ // registers a second time later in the frame — which is what
205
+ // `present()` used to do — samples a converter that has moved on.
206
+ function poll(): void {
207
+ let rawX: utinyint = paddleX;
208
+ let rawY: utinyint = paddleY;
209
+ there = rawX >= Pot.LOWEST && rawX <= Pot.HIGHEST && rawY >= Pot.LOWEST && rawY <= Pot.HIGHEST;
210
+ if (started) {
211
+ atX = mouse.step(atX, lastX, rawX, limitX, 0);
212
+ // Y runs opposite the screen. Verified under x64sc (Studio,
213
+ // 2026-09-07): the same sign as X moved the arrow up when the
214
+ // mouse moved down. last and now are swapped so the *delta*
215
+ // flips and the rest position stays 0 (top-left). Reporting
216
+ // `limitY - atY` would park the arrow at the bottom instead.
217
+ atY = mouse.step(atY, rawY, lastY, limitY, 1);
218
+ }
219
+ lastX = rawX;
220
+ lastY = rawY;
221
+ started = true;
222
+ }
223
+
224
+ // Is a 1351 in the selected port? True when both lines read inside
225
+ // the 1351's range: nothing, a joystick and paddles at rest all read
226
+ // 255. A paddle turned to the middle of its travel also reads here,
227
+ // so this means "consistent with a mouse" — movement is what proves
228
+ // one.
229
+ //
230
+ // **This is what the last `poll()` saw, not a fresh reading**, so it
231
+ // is false until the first poll and it gives every caller in a frame
232
+ // the same answer. See `there` above for the measurement that made
233
+ // that necessary.
234
+ function present(): bool {
235
+ return there;
236
+ }
237
+
238
+ // Where the pointer has moved to since the program started, in mouse
239
+ // counts. A program scales these to its own screen.
240
+ function x(): usmallint {
241
+ return atX;
242
+ }
243
+
244
+ function y(): usmallint {
245
+ return atY;
246
+ }
247
+
248
+ // The buttons, on the port's joystick lines: left is FIRE, right is
249
+ // UP. Port 1 is CIA1 port B; port 2 is port A, which is also the
250
+ // register select() writes, so a port-2 mouse's buttons are read from
251
+ // what the port reads back, not from what was written.
252
+ function left(port: utinyint): bool {
253
+ if (port == Mouse.PORT_1) {
254
+ return (cia1PortB & 16) == 0;
255
+ }
256
+ return (cia1PortA & 16) == 0;
257
+ }
258
+
259
+ function right(port: utinyint): bool {
260
+ if (port == Mouse.PORT_1) {
261
+ return (cia1PortB & 1) == 0;
262
+ }
263
+ return (cia1PortA & 1) == 0;
264
+ }
265
+ }
@@ -0,0 +1,183 @@
1
+ // @8bitscript/c64/pointer — the C64 behind @8bitscript/pointer: an arrow,
2
+ // drawn with one of the VIC-II's eight sprites.
3
+ //
4
+ // Named by "8bitscript".exports["./pointer"] in this package's package.json,
5
+ // and by "8bitscript".entry.c64 in @8bitscript/pointer's, so a portable
6
+ // program writes
7
+ //
8
+ // import { pointer } from "@8bitscript/pointer";
9
+ //
10
+ // and gets this file on a C64. It is the *visible* half of the pointer
11
+ // @8bitscript/input already reports: `input` says where the pointer is and
12
+ // whether its button went down, and this says what the user sees while
13
+ // they aim it. Two packages rather than one because reading and drawing
14
+ // are separate everywhere else in this library — `text` draws, `input`
15
+ // reads — and because a machine can perfectly well answer `pointerCell()`
16
+ // and have nothing whatever to draw an arrow with. Eight of the nine are
17
+ // in exactly that position today.
18
+ //
19
+ // ---- why a sprite, and which one ------------------------------------------
20
+ //
21
+ // A cursor has to move without disturbing what is under it, and on this
22
+ // machine the only thing that does that is a movable object: a character
23
+ // cell cursor would have to put back the cell it covered, and
24
+ // @8bitscript/text can write a cell but not read one, so it could not.
25
+ // A sprite also moves a pixel at a time where a cell cursor moves eight,
26
+ // which is the difference between aiming at a menu item and guessing at
27
+ // one.
28
+ //
29
+ // **This file owns sprite 0 and the last shape block, and says so.** The
30
+ // VIC has eight sprites in a fixed front-to-back order and sprite 0 is
31
+ // always in front, which is where a cursor belongs — it is over the
32
+ // interface, not in it. The shape goes in the *last* block
33
+ // @8bitscript/c64/sprites owns rather than the first, because a program
34
+ // laying its own shapes out counts up from `sprites.FIRST_BLOCK` and would
35
+ // otherwise land on the cursor's. A program that wants all eight sprites
36
+ // and no cursor simply does not import this file, and pays nothing for it.
37
+ //
38
+ // ---- the position comes from the mouse's accumulator ----------------------
39
+ //
40
+ // `update()` reads ./mouse.8bs's x and y directly, in **pixels**, rather
41
+ // than taking `input.pointerCell()` — a cell is 8 pixels and a cursor that
42
+ // jumped 8 pixels at a time would be the cell cursor this file exists to
43
+ // avoid. Reading the accumulator is free and safe: ./input.8bs's `poll()`
44
+ // is what advances it, once a frame, and this only looks at where it got
45
+ // to. That is the one ordering rule here, and it is the same one `input`
46
+ // already has: **`poll()` then `update()`, once each a frame.** A program
47
+ // that never calls `input.poll()` gets an arrow that never moves.
48
+ //
49
+ // The arrow's tip is its top-left pixel, so the sprite goes at the mouse's
50
+ // pixel plus the screen's origin in the VIC's coordinate space
51
+ // (`sprites.LEFT`, `sprites.TOP` — 24 and 50, documented in
52
+ // ./sprites.8bs): the tip lands exactly on the pixel the mouse is at, and
53
+ // `input.pointerCell()` is the cell that pixel is in, so what the user
54
+ // aims at and what the program hit-tests are the same thing by
55
+ // construction.
56
+ //
57
+ // ---- a build without a mouse pays nothing ---------------------------------
58
+ //
59
+ // `#fact(input.mouse)` is false on a stock C64, so `HAS_MOUSE` is a const,
60
+ // `if (HAS_MOUSE)` is `if (false)`, and the arrow, its 63 bytes of shape
61
+ // and every register write below are neither reached, referenced nor
62
+ // linked. Fitting a mouse is what compiles them in — measured on Studio,
63
+ // where the arrow costs **182 bytes of program and 1 of RAM** on top of
64
+ // the input layer's mouse, and a mouse and its arrow together take that
65
+ // program from 1523 bytes and 28 of RAM to 2208 and 47.
66
+ import { sprites } from "./sprites.8bs";
67
+ import { mouse } from "./mouse.8bs";
68
+
69
+ // Was this build fitted with a mouse? Folded while compiling.
70
+ const HAS_MOUSE: bool = #fact(input.mouse);
71
+
72
+ // The colour the arrow starts in, in the VIC's sixteen. White reads against
73
+ // every background @8bitscript/screen offers, and `setColor` changes it.
74
+ const WHITE: utinyint = 1;
75
+
76
+ // The arrow: 21 rows of 3 bytes, one bit a pixel, left to right — the
77
+ // shape format ./sprites.8bs documents. Only the leftmost byte of each row
78
+ // is set, so the arrow is 8 pixels wide inside a 24-pixel sprite; the tip
79
+ // is the top-left pixel, which is what makes the sprite's position and the
80
+ // pointer's position the same number.
81
+ //
82
+ // X....... XXXXXXXX
83
+ // XX...... XXXXXX..
84
+ // XXX..... XXX.XX..
85
+ // XXXX.... XX...XX.
86
+ // XXXXX... X....XX.
87
+ // XXXXXX.. ......XX
88
+ // XXXXXXX. .......X
89
+ //
90
+ // A const array is data in the program image and never in RAM, so the
91
+ // picture costs no memory the program could have used.
92
+ const ARROW: array<utinyint, 63> = [
93
+ 0x80, 0, 0,
94
+ 0xC0, 0, 0,
95
+ 0xE0, 0, 0,
96
+ 0xF0, 0, 0,
97
+ 0xF8, 0, 0,
98
+ 0xFC, 0, 0,
99
+ 0xFE, 0, 0,
100
+ 0xFF, 0, 0,
101
+ 0xFC, 0, 0,
102
+ 0xEC, 0, 0,
103
+ 0xC6, 0, 0,
104
+ 0x86, 0, 0,
105
+ 0x06, 0, 0,
106
+ 0x03, 0, 0,
107
+ 0, 0, 0,
108
+ 0, 0, 0,
109
+ 0, 0, 0,
110
+ 0, 0, 0,
111
+ 0, 0, 0,
112
+ 0, 0, 0,
113
+ 0, 0, 0,
114
+ ];
115
+
116
+ export namespace pointer {
117
+
118
+ // Does this machine draw a pointer at all? A compile-time constant, so
119
+ // a program can lay itself out around the answer — and on the C64 it is
120
+ // "only if this build was fitted with a mouse", which is the same
121
+ // question `input.pointer()` answers at run time.
122
+ const DRAWS: bool = #fact(input.mouse);
123
+
124
+ // The sprite and the shape block this file claims. Named rather than
125
+ // hidden so a program using @8bitscript/c64/sprites alongside a cursor
126
+ // knows which two it may not have.
127
+ const SPRITE: utinyint = 0;
128
+ const BLOCK: utinyint = 254; // sprites.FIRST_BLOCK + sprites.BLOCK_COUNT - 1
129
+
130
+ // Once, at start-up, after input.begin(): copy the arrow into its block
131
+ // and point the sprite at it. The shape is copied rather than pointed
132
+ // at where it lies because the VIC reads its own bank and a const array
133
+ // is wherever the linker put it — the copy loop ./sprites.8bs's header
134
+ // describes. On a build with no mouse this compiles to nothing.
135
+ function begin(): void {
136
+ if (HAS_MOUSE) {
137
+ let i: utinyint = 0;
138
+ while (i < sprites.BYTES) {
139
+ sprites.setShapeByte(pointer.BLOCK, i, ARROW[i]);
140
+ i = i + 1;
141
+ }
142
+ sprites.setShape(pointer.SPRITE, pointer.BLOCK);
143
+ sprites.setColor(pointer.SPRITE, WHITE);
144
+ }
145
+ }
146
+
147
+ // The arrow's colour, in the VIC's sixteen — the same numbers
148
+ // @8bitscript/text's colours are.
149
+ function setColor(color: utinyint): void {
150
+ if (HAS_MOUSE) {
151
+ sprites.setColor(pointer.SPRITE, color);
152
+ }
153
+ }
154
+
155
+ // Once a frame, after input.poll(): put the arrow where the mouse got
156
+ // to, and show it only while a mouse is really answering. `present()`
157
+ // is ./mouse.8bs's probe — a build may be fitted for a mouse and not
158
+ // have one plugged in, and an arrow parked in a corner would be a lie
159
+ // about that.
160
+ function update(): void {
161
+ if (HAS_MOUSE) {
162
+ if (mouse.present()) {
163
+ // The VIC's Y is 8 bits and the screen's 200 rows of pixels
164
+ // start at 50, so the sum is at most 249 and cannot carry
165
+ // out of a byte — which is why this needs no clamp of its
166
+ // own beyond the ceiling input.begin() gave the mouse.
167
+ sprites.place(pointer.SPRITE, sprites.LEFT + mouse.x(), sprites.TOP + mouse.y());
168
+ sprites.show(pointer.SPRITE);
169
+ } else {
170
+ sprites.hide(pointer.SPRITE);
171
+ }
172
+ }
173
+ }
174
+
175
+ // Take the arrow off the screen and leave it off until the next
176
+ // update(). What a program does while it is doing something the user
177
+ // cannot point at.
178
+ function hide(): void {
179
+ if (HAS_MOUSE) {
180
+ sprites.hide(pointer.SPRITE);
181
+ }
182
+ }
183
+ }
package/src/random.8bs ADDED
@@ -0,0 +1,81 @@
1
+ // @8bitscript/c64/random — SID voice 3's hardware entropy.
2
+ //
3
+ // Named by "8bitscript".exports["./random"] in this package's package.json:
4
+ //
5
+ // import { random } from "@8bitscript/c64/random";
6
+ //
7
+ // random.begin();
8
+ // let seed: utinyint = random.byte();
9
+ //
10
+ // Its own import, and not part of ./sid.8bs, on purpose — the same reason
11
+ // @8bitscript/atari8/random stands apart from ./pokey.8bs there. Voice 3's
12
+ // oscillator ($D41B) and envelope generator ($D41C) are readable *hardware*
13
+ // state: with the oscillator set to the noise waveform, $D41B is the top
14
+ // byte of a free-running 23-bit polynomial counter, sampled wherever it
15
+ // happens to be when the CPU reads it, so two runs of the same program
16
+ // never agree. That is exactly what a program wants for a seed and exactly
17
+ // what it does not want anywhere else — a game that reads it inside its
18
+ // logic cannot be replayed, cannot be tested against a recorded input, and
19
+ // cannot be debugged from a screenshot. The root AGENTS.md rule is that
20
+ // hardware entropy is never the deterministic PRNG's seed by default and
21
+ // never arrives without being asked for; this file is the asking.
22
+ //
23
+ // The usual shape: read one byte at start-up, seed a deterministic
24
+ // generator (`@8bitscript/random` or `@8bitscript/random/table`) with it,
25
+ // and never touch this again.
26
+ //
27
+ // ---- this claims voice 3 whole ---------------------------------------------
28
+ //
29
+ // Reading real noise means voice 3 has to actually be making it: `begin()`
30
+ // sets its frequency to the fastest the register allows (the noise LFSR is
31
+ // clocked by the oscillator's own carries, so a low frequency barely
32
+ // changes it between two reads a frame apart) and its waveform to noise,
33
+ // then sets $D418's voice-3-off bit so none of that reaches the speaker.
34
+ // The SID's registers below $D419 are write-only — the chip has no way to
35
+ // report what is already there — so `begin()` writes $D418 as a whole
36
+ // known byte (silent, no filter, volume 0) rather than trying to preserve
37
+ // whatever a program had set before. **Call `begin()` first, before
38
+ // `@8bitscript/c64/sid` touches the volume or the filter**, or its own
39
+ // write will win and this file has no way to know that happened — the two
40
+ // packages do not coordinate, the same way @8bitscript/atari8/random warns
41
+ // about AUDCTL. A program that wants both real sound *and* this seed reads
42
+ // the seed once, at start-up, before it plays a single note; using voice 3
43
+ // for both music and entropy at the same time is not something this file
44
+ // can make safe.
45
+ //
46
+ // ---- what the number actually is -------------------------------------------
47
+ //
48
+ // The noise waveform's LFSR is 23 bits, and $D41B is its top 8, so
49
+ // consecutive reads a few cycles apart are consecutive shifts of one
50
+ // register, not independent samples — the same caveat
51
+ // @8bitscript/atari8/random's header gives POKEY's counter. `word()` is
52
+ // here for a program that wants more than eight bits and knows that;
53
+ // anything that needs many independent values wants a PRNG seeded once
54
+ // from here. Nothing here is a source of cryptographic randomness, on this
55
+ // chip or on POKEY's.
56
+ import { sidRegisters, voice3Oscillator } from "./index.8bs";
57
+
58
+ export namespace random {
59
+ // Claims voice 3: fastest-clocked noise, silenced. See the header for
60
+ // why this must run before anything else touches $D418.
61
+ function begin(): void {
62
+ sidRegisters[14] = 0xFF; // voice 3 frequency, low byte
63
+ sidRegisters[15] = 0xFF; // voice 3 frequency, high byte — fastest LFSR clock
64
+ sidRegisters[18] = 0x80; // voice 3 control: noise waveform, gate off
65
+ sidRegisters[24] = 0x80; // $D418: voice 3 off, filter off, volume 0 — a whole known byte, not a read-modify-write of a write-only register
66
+ }
67
+
68
+ // One byte from the oscillator, 0-255.
69
+ function byte(): utinyint {
70
+ return voice3Oscillator;
71
+ }
72
+
73
+ // Two bytes as one 16-bit value. See the header: the two reads are
74
+ // consecutive shifts of one register, close together in time, so this
75
+ // is wider than `byte()` but not twice as random.
76
+ function word(): usmallint {
77
+ let high: usmallint = voice3Oscillator;
78
+ let low: usmallint = voice3Oscillator;
79
+ return high * 256 + low;
80
+ }
81
+ }