the-maze/AGENTS.md
File Type: text/plain
# Agent notes for the-maze
## Session handoff
- At the **start** or **continuation** of a session, read `.grok/NOTES.md` if it exists. It is the personal, git-ignored progress log (synced between machines).
- When the user asks to wrap up, summarize the day, or update notes: **update** `.grok/NOTES.md` — refresh *Current focus*, *Status*, and *Next steps*, and append a dated entry under *Session log*. Keep it concise and actionable.
- Remember that the current agent session might not have been the last process to write to NOTES.md; it, along with the code and git history, might have been edited elsewhere, whether by a human or an agent running on a different computer & synced here. Its **purpose** is to keep those different agents in-the-loop with a similar session context.
- Do not commit `.grok/`; it is gitignored on purpose.
## Project goals
Hobby revival of a DOS QBasic maze game on Linux via FreeBASIC — not a commercial rewrite.
**Done / in progress**
- `game.bas` on `#lang "fb"` with structured types and a clear main loop.
- Windowed display: fixed 640×400 logical framebuffer (80×25 text cells), OpenGL scale / fullscreen / CLI flags; nearest-neighbor filtering when scaled.
- Logical `tileMap` + `TileId` separate collision/gameplay from drawing (`--ascii` vs `--tiles`; **custom tiles are the default**).
- Runtime `.til` text tilesets; non-blocking QB-style `Play` SFX via SDL2 (`play.bas`).
- Scope refactor: session settings and level defs in `GameConfig`; per-run play state (including `tileMap`) in `GameState`.
- Makefile builds into `out/` and copies assets; `make run` is the supported launch path.
**Near-term direction**
- Refine default / alternate tile art; optional runtime tint for multicolor monsters.
- Keep improving structure only where it helps gameplay or those goals — avoid drive-by rewrites.
**Non-goals**
- Full engine rewrite, new language port, or perfect OOP purity.
- Breaking the room item string format in `.dat` files without an explicit migration plan.
- Treating this like production software with heavy process; prefer small, testable steps and gameplay parity.
Reference originals live in `original-code-and-data/` (read-only history). License is public domain (CC0).
## Layout
| Path | Role |
|------|------|
| `game.bas` | Main game — modern FB dialect; primary development target |
| `play.bas` | QB-style `Play` MML + SDL2 square-wave audio (linked with game) |
| `edit.bas` | Level editor — still QB-ish (`fbc -lang qb`); launched via `Shell "./edit"` |
| `Makefile` | `fbc -w all` for `game.bas` + `play.bas`; `-lang qb` for edit; outputs under `out/` |
| `themaze.dat` | Default world (`DEFAULT_WORLDFILE`) |
| `tiles.til` | Default tileset (`DEFAULT_TILEFILE`) |
| `game.ini`, `edit.ini` | Still copied to `out/`; editor uses `edit.ini`. World/tileset for the game come from **defaults + CLI**, not `game.ini` |
| `original-code-and-data/` | Historical QBasic sources — do not “fix” in place |
| `README.md` | Player-facing build/run/keys docs — update when CLI or UX changes |
| `.grok/NOTES.md` | Personal session handoff (gitignored) |
## Build and run
```bash
make # game + edit + assets → out/
make run # build game and run from out/
make run ARGS='--ascii --scale=3'
make run ARGS='--worldfile=themaze.dat --tilefile=tiles.til'
make clean
- Always run from
out/(ormake run) so relative paths resolve. - Add new images/sounds/levels to
ASSET_FILESin the Makefile so they are copied. - Compile warnings are enabled (
-w all); don’t introduce new ones casually. - Needs FreeBASIC + SDL2 (+ OpenGL headers used for texture filter tweaks).
Architecture (game.bas)
Ownership / scope
Two main buckets after the scope refactor — prefer extending these over new globals:
| Bucket | Storage | Holds |
|---|---|---|
GameConfig |
Dim Shared config |
Video mode, scale, paths (worldFile, tileFile), tileDefs(), rooms() (level definitions + per-room flags) |
GameState |
Local game, passed ByRef |
Player, entities, timers/cycles, checkpoints, tileMap, run flags (isDead, entityHurtPlayer, …) |
- Startup order:
InitializeConfig→InitTileTable→ParseCommandLine→InitVideo/InitAudio→InitializeGame→RunGameLoop→ shutdown. InitializeConfigsets defaults (GFX_CUSTOM,DEFAULT_WORLDFILE/DEFAULT_TILEFILE, scale, etc.).ParseCommandLineoverrides config (including--worldfile,--tilefile,--ascii/--tiles, scale, fullscreen) and setsdepth(8 ASCII / 32 custom).- Do not reintroduce free-floating
Sharedfor playfield or video options; keep the config vs state split.
Grid, video, tiles
- Dialect:
#lang "fb"withfbgfx.bi,GL/gl.bi,Using FB. - Grid: Classic layout —
GRID_COLS=80, play rows 1–20, HUD rows 21–23. Coordinates are 1-based text cells (Locate y, x), not pixels. - Video: Logical size always
LOGICAL_W×LOGICAL_H(640×400). Scaling usesSET_GL_2D_MODE+SET_GL_SCALEbeforeScreenReswithGFX_OPENGL. Do not enlargeScreenResitself (breaks the default font). After a GL scale open, nearest filtering is set on the 2D texture. - Playfield map: Lives on
game.tileMap. Helpers that read/write cells take the map explicitly, e.g.GetTile(game.tileMap(), y, x),DrawTile game.tileMap(), y, x, id. Call sites passgame.tileMap(). - Presentation:
DrawTile→ ASCII (config.tileDefsglyph/color) or custom (config.tileDefs(i).imgviaPut). HUD/messages useDrawText(does not touchtileMap). - Custom art:
LoadTileFileparses INI-style.til([palette]+ per-tile 8×16 grids:#primary,+dim,.black,0-9palette). Path isconfig.tileFile. Missing file → solid placeholders. Baked images live inconfig.tileDefs().img(freed inFreeTileGraphics). - Default graphics mode: custom tiles (
GFX_CUSTOM).--ascii/-afor classic glyphs.
Timing, input, audio, CLI
- Timing: Fixed timestep via
Sleep SLEEPTIME+cycleCount/ShouldProcess(not busy-wait onTimer). Keep the CPU gentle. - Input: Movement uses
MultiKeywith fbgfxSC_UP/SC_DOWN/SC_LEFT/SC_RIGHTplusInKey$arrow escapes. Don’t regress double-key or pegged-CPU issues.Elaunches the editor; Esc quits. (In-game level/tileset pickers were removed; use CLI--worldfile/--tilefile.) - CLI:
ParseCommandLinemutatesconfig; diagnostics viaLogConsole(Open Cons— barePrinthits the graphics surface afterScreenRes). - Audio:
InitAudio/ShutdownAudio/Playinplay.bas(SDL2 queue; non-blocking). NewPlayclears prior queued audio (monophonic). Octave mapping matches QB (O3= middle C) with optionalPLAY_OCTAVE_SHIFT/SetPlayOctaveShift. If SDL audio fails,Playis a silent no-op.
Code style (prefer matching game.bas)
- Indent: 4 spaces.
- Names:
PascalCasefor types, enums, and routines (GameState,GameConfig,DrawRoom,LoadTileGraphics). Constants oftenUPPER_SNAKE(MAX_ROOMS,TILE_WALL,COL_YELLOW,DEFAULT_WORLDFILE). Fields/localscamelCase(playerX,cycleCount,entityHurtPlayer,worldFile). Enum members may use a short prefix (Dir.UpDir,TILE_*,GFX_*,COL_*,SC_*from fbgfx). - Types: Explicit
As Integer/As String/ etc. UseByVal/ByRefon parameters; passgame As GameStateby ref into gameplay subs. Array params for the map usetileMap(Any, Any) As Integer. - Control flow: Structured
Sub/Function(no newGOSUBingame.bas). PreferSelect Casefor tile/entity kinds.AndAlso/OrElsewhere short-circuit matters.+=/ similar ops are fine where already used. - Comments: Explain non-obvious constraints (fbgfx/OpenGL quirks, coordinate systems,
.til/ room string formats). Skip narrating obvious code. - Drawing vs logic: Collision and rules use
TileId/GetTileongame.tileMap, not screen reads. Don’t reintroduceScreen()-based playfield queries. - Extending tiles: Add to
TileId+InitTileTable(writesconfig.tileDefs) +.tilsection name inTileIdFromNameif needed. Glyph/color table remains the ASCII source of truth. - FB keyword pitfalls: Avoid locals named
line,val,color,rgb,name(clash with keywords/intrinsics). - Formatting: Match surrounding file; light cleanup only in code you touch. No repo-wide reformat drives.
Editor (edit.bas)
- Still compiled with
-lang qband largely original style (GOSUB, untyped vars,Screen 9). - Touch only when game/editor integration or shared formats require it. Do not bulk-port the editor to
-lang fbunless explicitly requested. - Game launches editor with
Shell "./edit"fromout/.
Level and tileset data
- Room items are compact strings in
config.rooms().data()(type char + coordinates), same spirit as the original.datformat. - Default world:
themaze.dat. Preserve.datcompatibility unless the user asks for a format change. - Default tileset:
tiles.til. Select via--tilefile=...(and--tiles/ custom mode, which is already default). - Don’t casually overwrite shipped
themaze.dat/tiles.tilin docs or examples.
Working practices
- Prefer incremental commits that leave the game buildable (
make). - No need to suggest git commits; they’ll be done manually after confirming functionality via test runs.
- After display/input/tile changes, sanity-check with
make run(try--asciiwhen touching text mode; custom is default). - Update
README.mdonly when command line options, flags, keys, build steps, or dependencies change. - Keep secrets and personal scratch in
.grok/; keep durable agent instructions here inAGENTS.md.
```