5.4 KiB
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 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
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–255integers (love.graphics.setColor(255, 255, 255)), not0–1floats. sprite:getData()/img:getPixelare used (seesprite_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); keys1/2/3switch to a single active color (Room:switchPlayerColor→Input.playerColor).- Per-color collision matrices —
Room.collidableMatrices/switchMatricesare{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:movePlayerdoes a flood-fill push check and a symmetric pull check per color. - Merging —
Room:playerJoinCheckactivates 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.drawrenders the room togameCanvaswith 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:switchCheckreports, per color, whether all switches of that color are pressed;Door:setColorsunlocks 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 inlove.drawand theworldShascreen-jitter amount (adjust withw/s).entity.lua—Entitybase 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 thesha()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—Colorclass + the canonical RGB(W) table.gColorglobals are created inmain._initGlobalColors.sprite_manager.lua— loads all images into thespritesglobal and precomputesspriteCells(which 16×16 cells of a sprite are non-empty, for multi-cell collision shapes). Sprites are16px-per-cell.input.lua,shaders.lua(wiggle,glowGLSL),_helpers.lua(lerp/map/constrain),_debug_helpers.lua(printMatrix,shallowTablePrint,randomPosition).dialogue_manager.luais currently empty;npc.lua/art.lua/artifact.luaare stubs or reference art folders not present inart/.
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 vialibs/TSerial.lua(TSerial.pack/unpack).Room:savewrites to LÖVE's save directory;Room:loadreads from there. - Schema drift warning: the existing
.savfiles (e.g.rooms/start.sav) use a newer schema — keys likestatic_walls,color_changing_floor_tiles,players,sages,exits,wall_shake— thatRoom:serialize/:deserializeinroom.luadoes not yet read or write (that code keys off class names likeWall,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 toart/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 anart/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).