Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Repository layout

Three independent surfaces of the same Turing machine concept:

- `TurMac_1.1.py` — original single-file Python reference implementation by David Hartkop. Runs from a CLI: `python3 TurMac_1.1.py`. The control-memory tuple format and 4-element rule semantics `(read, write, dir, next_state)` defined here are the canonical model that the other surfaces mirror.
- `web-app/` — browser-based simulator with two views (`public/machine.html`, `public/cards.html`) sharing engine code under `src/core/`.
- `card-game/` — a 0-indexed variant of the cards view; its `index.html` imports modules directly from `../web-app/src/` and only adds the markup/style differences listed in `card-game/README.md`.

## Running the web app

ES modules are used everywhere, so `file://` URLs do not work. Serve the repo root over HTTP and open the pages from there:

```
python3 -m http.server 8000
# then open:
# http://localhost:8000/web-app/public/index.html (landing)
# http://localhost:8000/web-app/public/machine.html (engineer view)
# http://localhost:8000/web-app/public/cards.html (card-game view)
# http://localhost:8000/card-game/index.html (0-indexed variant)
```

There is no build step, no package.json, no test runner. Edits to `.js`/`.html`/`.css` are picked up on browser reload.

## Engine architecture (`web-app/src/core/turing.js`)

`TuringMachine` is the single source of execution truth shared by both views. Important details that are not obvious from the file structure:

- **Rule shape**: `[read, write, dir, nextState]` where `dir` is `0` (left) or `1` (right). A state whose rule list is empty is a halt state. These match the Python tuple format exactly.
- **State indices**: the engine itself is 0-indexed. The Python file and the machine view also surface 0-indexed states; the cards view (`src/cards/main.js`) displays 1-indexed states to match the physical card game, and the card-program format (`src/cards/compiler.js`) uses 1-indexed next-state references that get translated to 0-indexed when compiled.
- **`boundsCheck` modes**: the cards view passes `'before'` (check head bounds at the top of `step()`, so a rule that moves the head off-tape into a halt state halts cleanly). The machine view passes `'after'` (apply the rule first, then report the post-step head). Both behaviors are intentional — preserve them when modifying `step()`.
- **`step()` return discriminator**: returns `{ kind: ... }` with one of `'already-halted' | 'cycle-limit' | 'halt' | 'no-rule' | 'oob' | 'oob-after' | 'step'`. View code switches on `kind` to log; add new cases here, do not throw.
- **`reset()` is destructive**: it re-clones `initialTape`/`initialControl`. Anything constructed against the live `engine.tape` / `engine.control` will become stale after a reset — re-read from the engine.

## Shared core utilities

- `core/runner.js` — `setInterval`-driven autoplay loop. The interval is read fresh each tick via `getInterval()`, but changing speed mid-run still requires calling `runner.restart()` to apply (both views wire this up on the speed slider's `input` event).
- `core/logger.js` — appends `<div class="log-line">` rows to a container; optional badge element counts lines.
- `core/dom.js` — `$(id)` shortcut plus a small `el(tag, opts)` builder.

## Cards view specifics (`web-app/src/cards/`)

- **Card format**: integers 0–7 only. Front face shows 0/1/2/3 (T/R/B/L); back face shows 5/6/7/4. `valueToFaceAndRotation` in `svg.js` is the source of truth. Boundary values that were `8` in the Python (`TurMac_1.1.py`) are substituted with `7` in presets because cards top out at 7.
- **Compiled program format** (`compiler.js`): a flat comma/whitespace-separated integer sequence: `0, numStates, state#, ruleCount, [read,write,dir,next]×ruleCount, ...`. Use `compileProgramLenient` for user input — it auto-fixes truncation, out-of-range values, and missing markers, and reports each fix back through the logger. `compileProgram` (strict) is kept as a reference but is not wired into the UI.
- **Click-to-edit**: every editable card has `data-role` plus `data-*` indices. `ROLE_HANDLERS` in `cards/main.js` is the dispatch table — add new editable cards by adding a role handler and emitting `makeCard(..., { role, data })`. Clicks rotate forward; Shift+click rotates backward.
- **Edits rebuild the engine**: any role-handler edit calls `buildEngine()` and `syncEditorFromState()`. The textarea is the serialized form, not the source of truth — the in-memory `view.states` is.

## Card-game variant (`card-game/`)

`card-game/index.html` is a single file that imports the cards modules directly from `../web-app/src/cards/` and re-implements just the rendering pieces that need 0-indexed semantics (state rows, head position, per-tape-cell index labels) and a single combined "#states" card. When changing shared cards code, verify this page still works because it depends on the same exports (`makeCard`, `compileProgramLenient`, `serializeStates`, `PRESETS`). The differences from `cards.html` are spelled out in `card-game/README.md`.

## When changing rule/program semantics

If you touch the rule tuple shape, the halt convention, or the bounds-check behavior, update **all four** places that encode it: `TurMac_1.1.py`, `web-app/src/core/turing.js`, the cards compiler (`web-app/src/cards/compiler.js`), and the card-game variant in `card-game/index.html`. The README.md at the repo root documents the original semantics and is worth updating too if behavior changes.
29 changes: 29 additions & 0 deletions card-game/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Turing Machine Card Game (./index.html)

A variant of `../web-app/public/cards.html` with a simplified, fully 0-indexed model where the tape value at the playhead directly selects the sub-state to run.

## Differences from `cards.html`

1. **States and sub-states are 0-indexed by position.** A state is identified by its row index; a sub-state is identified by its column index within that row.
2. **Tape value == sub-state index.** When the playhead reads value `k` from the tape, sub-state `k` of the current state runs. If the current state has fewer than `k+1` sub-states, the machine halts.
3. **Halt by out-of-range next-state.** There is no dedicated "last state is halt" convention. A `next` reference to any state index that does not exist halts the machine. A state with zero sub-states also halts when entered.
4. **No `#states` card.** Replaced with a `+ Add state` button beneath the state rows.
5. **No leading rule-count card on each state row.** Each row ends with a `+ sub-state` button to append a new sub-state.
6. **No leading `read` card on each sub-state.** The read value is implied by the sub-state's column position. Each sub-state ends with a `×` button to delete it.
7. **Direction is an arrow card.** A custom card showing a left or right arrow, sharing the same `.slot` frame as the colored-dot cards. Clicking toggles direction.
8. **No playhead-position card.** Each tape cell has a `set start` / `★ start` button below it that picks that cell as the playhead's starting position.
9. **Program text format is new.** A flat integer sequence, with no alignment card and no state-marker numbers:
```
ruleCount, [write, dir, next] × ruleCount (state 0)
ruleCount, [write, dir, next] × ruleCount (state 1)
...
```
`dir`: 0 = L, 1 = R. State and `next` references are 0-indexed.

## Implementation notes

The page imports the shared engine and card renderer from `../web-app/src/` (`core/turing.js`, `core/runner.js`, `core/logger.js`, `core/dom.js`, `cards/svg.js`) but defines its own compiler, serializer, and preset programs inline — nothing in `web-app/` is modified.

When building the engine, each view rule `[write, dir, next]` at column `r` is expanded to the engine's `[read, write, dir, next]` shape with `read = r`, so the shared `TuringMachine` keeps its rule-by-read lookup unchanged. The engine's existing behavior of returning `{ kind: 'halt' }` when `control[currentState]` is undefined gives "missing state halts" for free.

`boundsCheck: 'before'` is used so a sub-state that moves the head off the tape into a missing state still halts cleanly on the next step.
Loading