chroma_solstice/CLAUDE.md
Your Name b19f58726c Add CLAUDE.md project guidance
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 23:15:45 -04:00

60 lines
5.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What this is
CHROMA SOLSTICE — a grid-based color-puzzle game written in Lua for the [LÖVE](https://love2d.org) framework. There is no build step, no test suite, and no package manifest; the whole game is the tree of `.lua` files loaded by `main.lua`.
## Running
```sh
love . # run from the project root
```
`love` is not installed in this environment — you cannot run the game here; reason about the code statically.
**LÖVE version matters.** The code targets the **0.10.x** API, not 11.x. Signals of this that you must preserve when editing:
- Colors are `0255` integers (`love.graphics.setColor(255, 255, 255)`), not `01` floats.
- `sprite:getData()` / `img:getPixel` are used (see `sprite_manager.lua`); the 11.x replacements differ.
Do not "modernize" these to the 11.x API unless explicitly asked — it would break the whole render/color path.
## The core mechanic (read this before touching gameplay code)
The player is not one entity — it is **up to three simultaneous colored copies** (`red`, `green`, `blue`), each living on its own collision layer. This RGB-channel model drives almost every design decision:
- **`Room.activeColors`** — which channels are currently "on". Arrow keys move *every* active-colored copy at once (`Room:movePlayer`); keys `1`/`2`/`3` switch to a single active color (`Room:switchPlayerColor``Input.playerColor`).
- **Per-color collision matrices** — `Room.collidableMatrices` / `switchMatrices` are `{red=grid, green=grid, blue=grid}`, each an 11×11 grid of entity references rebuilt every tick. A move is legal only if it's legal on *all* active channels. `Room:movePlayer` does a flood-fill push check *and* a symmetric pull check per color.
- **Merging** — `Room:playerJoinCheck` activates an inactive color when all active copies land on top of it, and deactivates a color that couldn't move. This overlap logic is the puzzle heart; change it carefully.
- **Additive rendering = color mixing.** `love.draw` renders the room to `gameCanvas` with blend mode `"screen"`, so overlapping red+green+blue copies literally add up to white on screen. The visual is downstream of the mechanic.
- **Switches → doors.** `Room:switchCheck` reports, per color, whether *all* switches of that color are pressed; `Door:setColors` unlocks only when all three channels are satisfied.
## Code structure
- `main.lua` — LÖVE callbacks (`love.load/update/draw/keypressed`) and `_init*` globals setup. Also holds the post-process bloom/feedback loop in `love.draw` and the `worldSha` screen-jitter amount (adjust with `w`/`s`).
- `entity.lua``Entity` base class (middleclass). Everything on the grid subclasses it: `Player`, `Box`, `Wall`, `Switch`, `Door`, `Artifact`, `Art`, `Ass` (assorted decor), `npc`. Grid↔pixel conversion, occupied-cell shapes, and the `sha()` jitter all live here.
- `room.lua` — the orchestrator: entity registries, per-color matrices, movement/push/pull, tick, switch/door logic, room transitions (`nextRoom`), and serialization.
- `color.lua``Color` class + the canonical RGB(W) table. `gColor` globals are created in `main._initGlobalColors`.
- `sprite_manager.lua` — loads all images into the `sprites` global and precomputes `spriteCells` (which 16×16 cells of a sprite are non-empty, for multi-cell collision shapes). Sprites are `16px`-per-cell.
- `input.lua`, `shaders.lua` (`wiggle`, `glow` GLSL), `_helpers.lua` (`lerp`/`map`/`constrain`), `_debug_helpers.lua` (`printMatrix`, `shallowTablePrint`, `randomPosition`).
- `dialogue_manager.lua` is currently empty; `npc.lua`/`art.lua`/`artifact.lua` are stubs or reference art folders not present in `art/`.
### Conventions
- Many globals are intentional (`gridWidth`, `gridHeight`, `drawScale`, `width`, `height`, `sprites`, `gColor`, `currentRoom`, `worldSha`). New shared state generally follows the same pattern rather than modules.
- Comments and some identifiers have a missing/typo'd `k` (`bloc_`, `chec_`, `li_e`) — a quirk of the author's keyboard, not a naming scheme. Match surrounding style; don't mass-fix.
## Levels & serialization
- Rooms live in `rooms/*.sav`, serialized via `libs/TSerial.lua` (`TSerial.pack`/`unpack`). `Room:save` writes to LÖVE's save directory; `Room:load` reads from there.
- **Schema drift warning:** the existing `.sav` files (e.g. `rooms/start.sav`) use a *newer* schema — keys like `static_walls`, `color_changing_floor_tiles`, `players`, `sages`, `exits`, `wall_shake` — that `Room:serialize`/`:deserialize` in `room.lua` does **not** yet read or write (that code keys off class names like `Wall`, `Box`, `Switch`). If a room won't load, this mismatch is the likely cause; reconcile the loader with the on-disk format rather than assuming either side is correct.
## Art pipeline
Sprites are authored in full color, then split into per-channel `_r`/`_g`/`_b` PNGs so each color copy draws its own layer. The `art/*.sh` scripts drive this with ImageMagick `convert`:
- `reduce_colors.sh <file>` — remap to `art/palette`/`palette.png`.
- `extract.sh <file>` — split one image into R/G/B channel PNGs.
- `reduce_and_extract.sh` — both steps over every PNG in the cwd (run from inside an `art/` subfolder).
## Bundled libraries (`libs/`, do not edit)
`middleclass` (OOP), `hump` (only `timer` is used), `sfxrlua` (procedural SFX — the looping static in `_initSounds`), `json4lua`, `TSerial` (level save format).