# «ПИТОНЧИК» — in the browser

Not a port. The page loads the 3874 bytes of `../bin/SNAKE.bin` and runs them
on a 6502 written in JavaScript, with just enough Agat-7 around it — a
keyboard, a one-bit speaker, and the fact that `$6000` is what you see — for
the code to be unable to tell the difference. Nothing about the game is
reimplemented, so nothing about it can be got wrong.

Open `index.html`. No build step, no dependencies, works from `file://`.

## What is where

| path | |
|---|---|
| `index.html` | the page: canvas, on-screen pad, key legend, fullscreen |
| `src/cpu6502.js` | the processor — addressing modes, flags, decimal mode, the `JMP ($xxFF)` bug |
| `src/machine.js` | the machine: memory map, `$C000` keyboard, `$C030` speaker, the video page |
| `src/main.js` | canvas, clock, sound, keys, the stored high score |
| `src/opcodes.js` | generated — the opcode table, built from the `dis6502.py` the sibling Rise Out project already had |
| `assets/data.js` | generated — the binary, base64 |
| `tools/extract_assets.py` | regenerates both generated files, and the reference screens |
| `tools/check.js` | headless harness: runs the same files under Node |
| `tools/reference.js` | generated — the four screens as `convert.py` draws them |

## Regenerating and checking

```sh
python3 web/tools/extract_assets.py    # -> assets/data.js, src/opcodes.js,
                                       #    tools/reference.js
node web/tools/check.js                # all four screens against the reference
node web/tools/check.js play 96        # play to a score, print the board
node web/tools/check.js show prizes    # one screen as ASCII
```

`check.js` loads exactly the files the page loads. It boots the machine,
presses SPACE three times and compares each video page against the reference
the Python side draws from the tables:

```
title     ok (0 pixels differ of 65536)
controls  ok (0 pixels differ of 65536)
prizes    ok (0 pixels differ of 65536)
game      ok (0 pixels differ of 65536)
```

Three independent things now agree on those four screens: a renderer that
draws them from the decoded tables, a 6502 in Python, and this one in
JavaScript.

## The clock, and the one number I had to choose

When sound is on, **the audio card is the clock**. Every block of 1024 samples
is made by running the processor for exactly as many cycles as that block is
long, and each sample *is* the speaker's one-bit output at that moment. So
there is no synthesiser here and no note table: the clicks between moves, the
sweep at the start, the three buzzes when you die are the game's own
`$C030` accesses, sampled. A one-pole high pass takes out the DC the speaker
idles at; the only number I picked is the volume.

With sound off there is nothing to pace against, so `requestAnimationFrame`
drives instead, at the same nominal rate.

That rate is **1 MHz**, and it is the one thing the file cannot tell me. The
Agat-7's 6502 ran at about that, and at 1 MHz the shipped frame delay
(`$2B89` = `$CD`) works out to roughly three moves a second — which is slow,
and is exactly why the game puts `+` and `−` on the front page and shows the
speed on the status line. If the real machine was faster, everything here is
proportionally slow and the fix is one constant, `AGAT.CPU_HZ` in
`src/machine.js`.

## Controls

Space walks the three title screens; the game starts on the first arrow. After
a collision it takes three presses of space to get back to the title — that is
the original, not a bug here.

| | |
|---|---|
| `↑` `↓` `←` `→`, or `WASD` | движение |
| `Пробел` | дальше |
| `+` / `;` / `−` | быстрее / медленнее |
| `Esc` | звук вкл / выкл (`<РЕД>` on the Agat) |
| `P`, or `Ctrl`+`C` | остановиться (`<УПР>+C`) |
| `R` | к заставке (the Agat rebooted here) |

On a phone the pad appears by itself: speed on the left, arrows on the right,
sound / pause / restart in a row under the screen. **Tapping the screen counts
as SPACE**, which is all you need for the three title screens and the three
presses after a collision. Landscape puts the two clusters at the edges with
the game between them. On a desktop the pad is off; «Экранные кнопки» turns it
on.

## Where this differs from an Agat-7

Three places, all of them outside the game:

* **The high score survives.** The original kept it in zero page and lost it at
  power-off; here it goes to `localStorage` and is poked back into `$2C/$2D`
  after the cold-start code has run. Nothing else is ever written into the
  machine from outside.
* **`R` restarts instead of rebooting.** Only because there is no DOS to reboot
  into — the game's own code path is untouched, and it still goes the long way
  round through `JMP ($FFFC)` into the reset vector it installed at start-up.
* **The ROM/RAM expansion board is one page of `$FF` at `$C300`.** The search
  at `$2000` needs to find something; writes to it go nowhere, which is all the
  game does with it besides unprotecting `$FFFC` to install that vector.

Everything else — every glyph, every table, the shutter transition, the
scrolling line that scrolls an empty row, the prize sequence driven by how you
press the keys — is the 1988 code doing what it always did.
