# Development Guide

Setup, customization, deployment, and troubleshooting for maintainers.

---

## Prerequisites

| Requirement | Notes |
|-------------|-------|
| Modern browser | Chrome, Firefox, Safari, Edge |
| Text editor | Any |
| **Node.js** | Only needed to run `scripts/bundle.js` after editing source |

No npm install or package dependencies.

---

## Running the Game

### Production (default)

Double-click **`index.html`** or open it from the filesystem. The page loads **`js/app.js`** — a single bundled script that works over `file://`.

### After editing source

Source files in `js/` are ES modules. Regenerate the bundle before testing via `index.html`:

```bash
node scripts/bundle.js
```

Verify `js/app.js` was updated (file timestamp / size). The game reads only `app.js` in production.

### Optional: ES module development

For faster iteration without rebuilding, serve over HTTP and temporarily switch `index.html` to:

```html
<script type="module" src="js/main.js"></script>
```

```bash
python3 -m http.server 8080
# Open http://localhost:8080
```

Revert to `js/app.js` (no `type="module"`) before shipping or sharing the `file://` build.

---

## Bundle Workflow

`scripts/bundle.js` concatenates modules in dependency order into one IIFE at `js/app.js`.

### Module order (do not reorder casually)

```
config.js → piece.js → scoring.js → grid.js → board.js → renderer.js →
particles.js → audio.js → das.js → lock-delay.js → auto.js → tetris.js →
presentation.js → ui.js → board-scaling.js → controls.js → ambience.js → main.js
```

When adding a new module:

1. Add the file to the `FILES` array in `scripts/bundle.js`
2. Place it after its dependencies in the list
3. Run `node scripts/bundle.js`

### Import rules for bundle-safe code

| Do | Don't |
|----|-------|
| `import { createPiece } from './piece.js'` | `import { createPiece as buildPiece }` — alias is stripped, name breaks |
| `const makePiece = createPiece` when shadowing | Call `createPiece()` inside `Board.createPiece()` — infinite recursion |
| Plain `export function foo()` | `export { foo as bar }` |

The bundler runs `assertBundleSafe()` and fails the build if `import … as …` is detected.

---

## Configuration Reference

All tunable gameplay values are in **`js/config.js`**.

### Board

| Constant | Default | Description |
|----------|---------|-------------|
| `COLS` | 10 | Board width in cells |
| `ROWS` | 20 | Board height in cells |
| `BLOCK` | 30 | Pixel size of one cell (canvas units) |
| `NEXT_PREVIEW_COUNT` | 3 | Pieces shown in Next panel |
| `QUEUE_SIZE` | 5 | Internal piece queue length |

### Timing (`TIMING` object)

| Key | Default | Description |
|-----|---------|-------------|
| `dasDelay` | 170 ms | Hold ←/→ this long before auto-repeat |
| `arr` | 50 ms | Interval between auto-repeated moves |
| `lockDelay` | 500 ms | Time before a grounded piece locks |
| `maxLockResets` | 15 | Max slide/rotate extensions while grounded |
| `lineClearAnimStep` | 0.07 | Line-clear shimmer speed (0–1 per frame) |

### Scoring (`SCORE_TABLE`)

| Key | Default | Description |
|-----|---------|-------------|
| `single` | 100 | 1 line clear base score |
| `double` | 300 | 2 lines |
| `triple` | 500 | 3 lines |
| `tetris` | 800 | 4 lines |
| `softDrop` | 1 | Points per cell (manual ↓) |
| `hardDrop` | 2 | Points per cell (Space) |
| `comboMultiplier` | 50 | Bonus per combo level × level |

### Drop speed

`getDropInterval(level)` returns milliseconds per gravity tick. Defined as an array of 20 values in `config.js`. Level 1 = 1000 ms, level 10 = 64 ms, level 20 = 1 ms.

### Storage

| Key | Default | Description |
|-----|---------|-------------|
| `HIGH_SCORE_KEY` | `'tetris-highscore'` | localStorage key for best score |

Access is wrapped in try/catch in `scoring.js` for environments where storage is blocked.

---

## Common Customizations

### Change colors

Edit piece colors in `js/piece.js → PIECES`:

```javascript
I: { shape: [...], color: '#00f0f0' },
```

UI accent colors are CSS variables in `css/style.css → :root`.

### Adjust particle intensity

Edit caps and spawn rates in `js/particles.js`:

```javascript
const MAX_PARTICLES = 100;
const MAX_SPARKS = 40;
```

Also see `getParticleIntensity()` in `js/scoring.js`.

### Disable background music

In `js/tetris.js`, comment out `this.audio.startMusic()` in `start()` and `resume()`.

### Change level progression

In `js/config.js`:

```javascript
export const LINES_PER_LEVEL = 10;  // lines needed per level
```

### Tune auto-play AI

Heuristic weights and timing live in `js/auto.js`. The AI uses `grid.js` for simulation — keep grid logic shared rather than duplicating in `auto.js`.

### Rebrand for your site

1. Update `<title>` and meta description in `index.html`
2. Update canonical URL: `<link rel="canonical" href="...">`
3. Update footer link in `index.html` and `help.html`
4. Replace tagline text in the left sidebar header (`header--side`)

---

## Deployment Checklist

Upload the **entire folder** preserving structure:

```
Tetris/
├── index.html
├── help.html
├── css/
├── js/
│   └── app.js          ← required (bundled runtime)
└── scripts/
    └── bundle.js       ← optional on server (dev only)
```

- [ ] Run `node scripts/bundle.js` so `js/app.js` matches latest sources
- [ ] All files uploaded (including `docs/` if you want dev reference on server)
- [ ] Test `index.html` loads without console errors (via `file://` and HTTP)
- [ ] Test `help.html` link works
- [ ] Update canonical URL in `index.html` to your domain
- [ ] Update footer links to your live URL
- [ ] Verify audio plays after clicking Start (browser autoplay policy)
- [ ] Test on mobile (touch controls appear ≤ 600px)
- [ ] Test auto mode (A key / Auto Mode button)
- [ ] Optional: embed via iframe (see README)

### iframe embed

```html
<iframe
  src="https://yoursite.com/game/tetris/index.html"
  width="900"
  height="800"
  frameborder="0"
  allow="autoplay"
  style="border:none; max-width:100%;"
  title="Tetris Game"
></iframe>
```

The `allow="autoplay"` attribute is required for background music in iframes.

---

## Module Editing Guide

### Adding a new input action

1. Add constant to `ACTIONS` in `config.js`
2. Handle it in `TetrisGame.handleAction()` in `tetris.js`
3. Map a key in `KEY_MAP` in `controls.js`
4. Optionally add a mobile button in `index.html`
5. Rebuild: `node scripts/bundle.js`
6. Document in `help.html`

### Adding a new UI celebration

1. Add DOM/CSS in `css/style.css`
2. Add helper in `presentation.js`
3. Call from `GameUI.update()` in `ui.js`
4. Emit the trigger flag from `tetris.js → emitUpdate()`

### Changing board size

Update `COLS`, `ROWS`, `BLOCK` in `config.js`, then update canvas dimensions in `index.html`:

```html
<canvas id="game-canvas" width="300" height="600"></canvas>
<!-- width = COLS × BLOCK, height = ROWS × BLOCK -->
```

Also update `#particle-canvas` to match. `board-scaling.js` handles display scaling automatically.

### Adding shared grid logic

Put collision/lock/clear helpers in **`grid.js`**, not duplicated in `board.js` or `auto.js`. `board.js` wraps grid functions for the live `Board` instance.

---

## Troubleshooting

### "Could not start the game" / `buildPiece is not defined`

- Cause: bundled `app.js` out of sync with sources, or `import { x as y }` alias in source
- Fix: run `node scripts/bundle.js`; ensure `board.js` uses `const makePiece = createPiece`, not an import alias

### Game loads but nothing happens

- Check browser console for errors
- Confirm `index.html` loads `js/app.js` (not a missing or stale file)
- If using `type="module"` + `main.js`, you must serve over HTTP

### No sound

- Audio requires user interaction first (click Start)
- Check tab is not muted
- In iframes, ensure `allow="autoplay"` is set

### Pieces stop falling after line clear

- The main loop only runs when `state === playing`
- Fixed in `tetris.js → animateLineClear()` — must call `this.loop()` when animation completes
- If reintroduced, verify that line is present

### Hold piece crashes / infinite recursion

- `Board.createPiece()` must call `makePiece()`, not `createPiece()` directly
- The method name shadows the imported `createPiece` from `piece.js`

### Auto mode stuck or ignores toggle

- Auto cancels on pause/game over — check `tetris.js` pause/start handlers
- Manual input is blocked while auto is on; pause (P) still works
- UI syncs via `emitUpdate({ autoMode })` — do not call `ui.setAutoMode` separately from `controls.js`

### Board / overlay misaligned after resize

- `setupBoardScaling()` in `board-scaling.js` sets `--board-display-w/h` on `.board-frame`
- Ensure overlay/callout CSS uses those vars, not hard-coded canvas pixel sizes

### Next piece preview overlapping

- Preview slot height must use canvas **height**, not width
- See `renderer.js → drawMiniPiece()` — uses `canvasH / slotCount`

### Particle explosion covers board

- Particles are capped at 100 with fast decay
- If too intense, lower `MAX_PARTICLES` or `perCell` count in `particles.js → burst()`

### High score shows "NEW HIGH SCORE" on ties

- High score only updates when `score > highScore` (strictly greater)
- UI uses `isNewRecord` flag from engine, not a manual comparison

### Pause causes piece to float

- Pause must call `lockDelay.resetState()` and cancel auto execution
- Resume must call `lockDelay.touchDown()` if piece cannot move down
- See `tetris.js → pause()` and `resume()`

### Mobile buttons fire twice

- Touch + click both fire on mobile
- `controls.js` uses a `touchHandled` debounce flag — preserve this pattern

### localStorage errors in strict environments

- `scoring.js` catches storage failures — high score simply won't persist; game should still run

---

## Testing

### Manual smoke test

1. Open `index.html` via `file://` — no console errors
2. Start game — piece falls, music plays
3. Move, rotate, soft drop, hard drop
4. Hold a piece (C key)
5. Clear 1, 2, 3, and 4 lines — verify callouts and particles
6. Chain 2+ combos — verify combo display
7. Toggle auto mode (A key and button) — gold AUTO badge, piece plays itself
8. Pause while auto is on — auto stops; resume restarts if still enabled
9. Pause while piece is grounded — resume — piece should still lock
10. Reach game over — retry works
11. Beat high score — "NEW HIGH SCORE" appears; tie does not
12. Resize window — board fills height, overlay stays aligned
13. Test on mobile width — touch buttons work once per tap

### Syntax check

```bash
for f in js/*.js scripts/*.js; do node --check "$f"; done
```

### Rebuild and verify bundle

```bash
node scripts/bundle.js && node --check js/app.js
```

### Module integration check (HTTP / Node ESM)

```bash
node --input-type=module -e "import { TetrisGame } from './js/tetris.js'; import { Board } from './js/board.js'; const b = new Board(); console.log('OK', b.nextPiece().type);"
```

---

## Version History (maintainer notes)

| Date | Change |
|------|--------|
| Initial | Core game: SRS, 7-bag, hold, ghost, scoring |
| UI pass | Cyberpunk theme, particles, music, combos, callouts |
| Bug fixes | Next preview overlap, particle overload, loop after line clear, high score ties, mobile double-tap |
| Refactor | Split into modules: config, piece, scoring, das, lock-delay, ui, controls, etc. |
| Audit | Pause/lock-delay fix, board.createPiece recursion fix |
| Auto mode | Heuristic AI (`auto.js`), A key + button, manual input blocked while active |
| file:// support | `scripts/bundle.js` → `js/app.js`; no server required for players |
| Layout | Full-height board, sidebar branding, responsive scaling (`board-scaling.js`) |
| Refactor v2 | Shared `grid.js`, extracted `board-scaling.js`, bundle safety checks |

---

## Related Docs

- [README.md](../README.md) — Project overview
- [ARCHITECTURE.md](./ARCHITECTURE.md) — Module design and state machine
- [help.html](../help.html) — Player-facing guide
