From b19f58726c04c6a5cba0597eb91fda8c6791f97e Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 6 Aug 2026 23:15:45 -0400 Subject: [PATCH] Add CLAUDE.md project guidance Co-Authored-By: Claude Opus 4.8 --- CLAUDE.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..62b720b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,60 @@ +# 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 `0–255` integers (`love.graphics.setColor(255, 255, 255)`), not `0–1` 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 ` — remap to `art/palette`/`palette.png`. +- `extract.sh ` — 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).