diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 28f6ffd..a0f508f 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -20,21 +20,21 @@ Rogue 5.4.4 is a single-process, single-threaded, turn-based terminal game of classic 1980s BSD game: - **All state is global.** ~120 global variables (defined in `extern.c`, - declared in `rogue.h`/`extern.h`) hold the entire game state: the player, - the level map, monster and object lists, UI state, and configuration. + declared in `rogue.h`/`extern.h`) hold the entire game state: the player, the + level map, monster and object lists, UI state, and configuration. - **One data type to rule them all.** A single C `union thing` (`THING`) represents both creatures (player/monsters) and objects (weapons, potions, ...), discriminated by which union arm the code chooses to access. Doubly linked lists of THINGs (via embedded `l_next`/`l_prev` pointers) form the monster list, the level-object list, and every creature's pack. -- **The screen is the data structure.** Much game logic reads what character - is displayed at a map cell (via curses `mvinch` or the `places[]` shadow - map) to decide behavior — walls, doors, items, and monsters are identified - by their display glyph. +- **The screen is the data structure.** Much game logic reads what character is + displayed at a map cell (via curses `mvinch` or the `places[]` shadow map) to + decide behavior — walls, doors, items, and monsters are identified by their + display glyph. - **Turn engine via daemons and fuses.** A 20-slot table of function pointers runs recurring per-turn callbacks ("daemons": healing, hunger, wandering - monsters) and one-shot countdowns ("fuses": potion effects wearing off), - each in a BEFORE or AFTER phase around the player's command. + monsters) and one-shot countdowns ("fuses": potion effects wearing off), each + in a BEFORE or AFTER phase around the player's command. - **Determinism by seeded LCG.** All randomness flows through `rnd()` using a hand-rolled linear congruential generator (`seed = seed*11109+13849`), so a given seed (dungeon number `dnum`) reproduces a dungeon exactly. @@ -53,7 +53,7 @@ main() command() ├─ do_daemons(BEFORE); do_fuses(BEFORE) ├─ status(); readchar() # input (with repeat counts, run prefixes) - ├─ switch(ch) → do_move/search/quaff/read_scroll/wield/... + ├─ switch(ch) → do_move/search/quaff/read_scroll/wield/... └─ if (after): do_daemons(AFTER); do_fuses(AFTER) # the `runners` AFTER daemon moves all monsters death()/total_winner()/quit() → score() → exit @@ -61,44 +61,44 @@ main() ## 2. Source file inventory -| File | Lines | Role | -|---|---|---| -| `rogue.h` | 753 | Master header: all constants, macros, types, extern declarations | -| `extern.h` | 197 | Platform feature detection, mdport/mach_dep declarations | -| `score.h` | 26 | Scoreboard entry struct | -| `main.c` | 395 | Entry point, RNG, main loop, signals | -| `command.c` | 820 | Keyboard command dispatch | -| `io.c` | 277 | Message line, status line, input | -| `init.c` | 447 | Per-game randomization and data-table setup | -| `extern.c` | 391 | All global variable definitions + data tables | -| `new_level.c` | 231 | Level orchestration, item placement, treasure rooms | -| `rooms.c` | 472 | Room and maze generation, room enter/leave | -| `passages.c` | 424 | Corridor generation, passage numbering | -| `move.c` | 425 | Player movement, traps | -| `chase.c` | 541 | Monster AI (chase/flee/targeting), visibility | -| `monsters.c` | 252 | Monster creation, saving throws | -| `fight.c` | 686 | Combat resolution, special monster attacks | -| `things.c` | 713 | Item naming, random item creation, discoveries | -| `pack.c` | 503 | Inventory management, item selection UI | -| `potions.c` | 375 | 14 potion effects | -| `scrolls.c` | 329 | 18 scroll effects | -| `rings.c` | 204 | 14 ring types, wearing/removal | -| `sticks.c` | 431 | 14 wand/staff types, bolts | -| `weapons.c` | 288 | 9 weapon types, throwing | -| `armor.c` | 89 | 8 armor types, wearing | -| `misc.c` | 597 | look() display update, direction input, eat, level-up | -| `daemon.c` | 181 | Daemon/fuse scheduler | -| `daemons.c` | 295 | The 11 daemon/fuse callbacks | -| `list.c` | 113 | THING linked-list allocation | -| `rip.c` | 448 | Tombstone, victory, scoreboard display | -| `save.c` | 390 | Save/restore, XOR-encrypted I/O, score file I/O | -| `state.c` | 2134 | Hand-written serialization of every global | -| `options.c` | 501 | Options screen, ROGUEOPTS parsing | -| `wizard.c` | 284 | Debug/wizard commands, item identification | -| `mach_dep.c` | 457 | Score file locking, tty/signal setup, vestigial load checks | -| `mdport.c` | 1432 | Platform abstraction (the 900-line `md_readchar` keyboard decoder) | -| `xcrypt.c` | 707 | DES crypt(3) for the wizard password only | -| `vers.c` | 17 | Version string + XOR key strings | +| File | Lines | Role | +| ------------- | ----- | ------------------------------------------------------------------ | +| `rogue.h` | 753 | Master header: all constants, macros, types, extern declarations | +| `extern.h` | 197 | Platform feature detection, mdport/mach_dep declarations | +| `score.h` | 26 | Scoreboard entry struct | +| `main.c` | 395 | Entry point, RNG, main loop, signals | +| `command.c` | 820 | Keyboard command dispatch | +| `io.c` | 277 | Message line, status line, input | +| `init.c` | 447 | Per-game randomization and data-table setup | +| `extern.c` | 391 | All global variable definitions + data tables | +| `new_level.c` | 231 | Level orchestration, item placement, treasure rooms | +| `rooms.c` | 472 | Room and maze generation, room enter/leave | +| `passages.c` | 424 | Corridor generation, passage numbering | +| `move.c` | 425 | Player movement, traps | +| `chase.c` | 541 | Monster AI (chase/flee/targeting), visibility | +| `monsters.c` | 252 | Monster creation, saving throws | +| `fight.c` | 686 | Combat resolution, special monster attacks | +| `things.c` | 713 | Item naming, random item creation, discoveries | +| `pack.c` | 503 | Inventory management, item selection UI | +| `potions.c` | 375 | 14 potion effects | +| `scrolls.c` | 329 | 18 scroll effects | +| `rings.c` | 204 | 14 ring types, wearing/removal | +| `sticks.c` | 431 | 14 wand/staff types, bolts | +| `weapons.c` | 288 | 9 weapon types, throwing | +| `armor.c` | 89 | 8 armor types, wearing | +| `misc.c` | 597 | look() display update, direction input, eat, level-up | +| `daemon.c` | 181 | Daemon/fuse scheduler | +| `daemons.c` | 295 | The 11 daemon/fuse callbacks | +| `list.c` | 113 | THING linked-list allocation | +| `rip.c` | 448 | Tombstone, victory, scoreboard display | +| `save.c` | 390 | Save/restore, XOR-encrypted I/O, score file I/O | +| `state.c` | 2134 | Hand-written serialization of every global | +| `options.c` | 501 | Options screen, ROGUEOPTS parsing | +| `wizard.c` | 284 | Debug/wizard commands, item identification | +| `mach_dep.c` | 457 | Score file locking, tty/signal setup, vestigial load checks | +| `mdport.c` | 1432 | Platform abstraction (the 900-line `md_readchar` keyboard decoder) | +| `xcrypt.c` | 707 | DES crypt(3) for the wizard password only | +| `vers.c` | 17 | Version string + XOR key strings | ## 3. Type system @@ -133,8 +133,8 @@ struct stats { ### 3.4 `union thing` (`THING`) — the universal entity The central type: a union of a creature arm (`_t`) and an object arm (`_o`), -with the two link pointers common to both. Access is via field macros -(`t_pos` → `_t._t_pos`, `o_type` → `_o._o_type`, ...). +with the two link pointers common to both. Access is via field macros (`t_pos` → +`_t._t_pos`, `o_type` → `_o._o_type`, ...). ```c union thing { @@ -175,12 +175,12 @@ typedef union thing THING; Key consequences the port must respect: -- `winat(y,x)` display logic: a monster's *disguise* is shown, not its type. -- `o_arm` is overloaded three ways: armor class (armor), charge count - (sticks, via `o_charges`), gold value (gold piles, via `o_goldval`). -- `t_dest` aliases *other entities' interiors*: it points at `hero` - (player position), a room's `r_gold` coordinate, or another monster's - `t_pos`. The save format encodes this as (kind, index) pairs. +- `winat(y,x)` display logic: a monster's _disguise_ is shown, not its type. +- `o_arm` is overloaded three ways: armor class (armor), charge count (sticks, + via `o_charges`), gold value (gold piles, via `o_goldval`). +- `t_dest` aliases _other entities' interiors_: it points at `hero` (player + position), a room's `r_gold` coordinate, or another monster's `t_pos`. The + save format encodes this as (kind, index) pairs. ### 3.5 `PLACE` — one map cell, and the `places[]` map @@ -212,8 +212,8 @@ struct room { }; ``` -`rooms[MAXROOMS=9]` on a 3×3 grid; `passages[MAXPASS=13]` reuses the same -struct for corridor networks (exits = doors touched, flags = ISGONE|ISDARK). +`rooms[MAXROOMS=9]` on a 3×3 grid; `passages[MAXPASS=13]` reuses the same struct +for corridor networks (exits = doors touched, flags = ISGONE|ISDARK). ### 3.7 `struct monster` — bestiary entry @@ -257,7 +257,8 @@ struct delayed_action { - `struct h_list { char h_ch; char *h_desc; bool h_print; }` — help table rows. - `STONE { char *st_name; int st_value; }` — ring stones with worth. -- `SCORE` (score.h) — scoreboard entry (uid, score, flags, monster, name, level, time). +- `SCORE` (score.h) — scoreboard entry (uid, score, flags, monster, name, level, + time). - `OPTION` (options.c) — option descriptor with get/put function pointers. - `PACT` (potions.c) — potion type → flag/fuse-callback/duration. - `SPOT` (rooms.c) — maze generation cell. @@ -269,13 +270,13 @@ All are `#define`s in rogue.h: - **Geometry:** NUMLINES 24, NUMCOLS 80, MAXLINES 32, MAXCOLS 80, STATLINE 23. - **Limits:** MAXROOMS 9, MAXTHINGS 9, MAXOBJ 9, MAXPACK 23, MAXTRAPS 10, MAXPASS 13, MAXDAEMONS 20, AMULETLEVEL 26, NUMTHINGS 7, BORE_LEVEL 50. -- **Object type glyphs (`o_type`/map chars):** PASSAGE `#`, DOOR `+`, FLOOR - `.`, PLAYER `@`, TRAP `^`, STAIRS `%`, GOLD `*`, POTION `!`, SCROLL `?`, - MAGIC `$`, FOOD `:`, WEAPON `)`, ARMOR `]`, AMULET `,`, RING `=`, STICK `/`; - pseudo-types CALLABLE -1, R_OR_S -2 for item-prompt filters. -- **Subtype enums:** 8 traps (T_DOOR..T_MYST), 14 potions (P_*), 18 scrolls - (S_*), 9 weapons (MACE..SPEAR, +FLAME pseudo), 8 armors (LEATHER..PLATE_MAIL), - 14 rings (R_*), 14 sticks (WS_*). +- **Object type glyphs (`o_type`/map chars):** PASSAGE `#`, DOOR `+`, FLOOR `.`, + PLAYER `@`, TRAP `^`, STAIRS `%`, GOLD `*`, POTION `!`, SCROLL `?`, MAGIC `$`, + FOOD `:`, WEAPON `)`, ARMOR `]`, AMULET `,`, RING `=`, STICK `/`; pseudo-types + CALLABLE -1, R_OR_S -2 for item-prompt filters. +- **Subtype enums:** 8 traps (T_DOOR..T_MYST), 14 potions (P__), 18 scrolls + (S__), 9 weapons (MACE..SPEAR, +FLAME pseudo), 8 armors (LEATHER..PLATE_MAIL), + 14 rings (R__), 14 sticks (WS__). - **Creature flag bits:** CANHUH, CANSEE, ISBLIND, ISCANC, ISLEVIT, ISFOUND, ISGREED, ISHASTE, ISTARGET, ISHELD, ISHUH, ISINVIS, ISMEAN, ISHALU, ISREGEN, ISRUN, SEEMONST, ISFLY, ISSLOW (note deliberate collisions: ISCANC=ISLEVIT, @@ -283,12 +284,12 @@ All are `#define`s in rogue.h: - **Object flag bits:** ISCURSED, ISKNOW, ISMISL, ISMANY, ISFOUND (shared), ISPROT. - **Room flags:** ISDARK, ISGONE, ISMAZE. -- **Place flags:** F_PASS 0x80, F_SEEN 0x40, F_DROPPED/F_LOCKED 0x20, - F_REAL 0x10, F_PNUM 0x0f (passage number), F_TMASK 0x07 (trap type). -- **Durations/tuning:** HEALTIME 30, HUHDURATION 20, SEEDURATION 850, - HUNGERTIME 1300, MORETIME 150, STOMACHSIZE 2000, STARVETIME 850, - BOLT_LENGTH 6, LAMPDIST 3, BEARTIME/SLEEPTIME/HOLDTIME/WANDERTIME/BEFORE/ - AFTER as `spread()` randomized durations. +- **Place flags:** F_PASS 0x80, F_SEEN 0x40, F_DROPPED/F_LOCKED 0x20, F_REAL + 0x10, F_PNUM 0x0f (passage number), F_TMASK 0x07 (trap type). +- **Durations/tuning:** HEALTIME 30, HUHDURATION 20, SEEDURATION 850, HUNGERTIME + 1300, MORETIME 150, STOMACHSIZE 2000, STARVETIME 850, BOLT_LENGTH 6, LAMPDIST + 3, BEARTIME/SLEEPTIME/HOLDTIME/WANDERTIME/BEFORE/ AFTER as `spread()` + randomized durations. - **Saving throws:** VS_POISON 0, VS_PARALYZATION 0, VS_DEATH 0, VS_BREATH 2, VS_MAGIC 3. - **Get-function returns:** NORM 0, QUIT 1, MINUS 2. **Inventory styles:** @@ -298,30 +299,30 @@ All are `#define`s in rogue.h: rogue.h defines a thick macro layer the port must translate to methods: -| Macro | Expansion | Port meaning | -|---|---|---| -| `when` / `otherwise` / `until(e)` | `break;case` / `break;default` / `while(!e)` | plain Go control flow | -| `hero` | `player.t_pos` | `g.Player.Pos` | -| `pstats` | `player.t_stats` | `g.Player.Stats` | -| `pack` | `player.t_pack` | `g.Player.Pack` | -| `proom` | `player.t_room` | `g.Player.Room` | -| `max_hp` | `player.t_stats.s_maxhp` | `g.Player.Stats.MaxHP` | -| `on(thing,flag)` | flag bit test | `t.Flags.Has(...)` | -| `INDEX/chat/flat/moat(y,x)` | places[] cell access | `g.Level.At(p).Ch/Flags/Monster` | -| `winat(y,x)` | monster disguise or map char | `g.Level.VisibleChar(p)` | -| `ce(a,b)` | coord equality | `a == b` (value type) | -| `ISRING(h,r)` / `ISWEARING(r)` | ring-worn tests | `g.Player.IsWearing(ring)` | -| `ISMULT(type)` | stackable type test | method on ObjType | -| `GOLDCALC` | `rnd(50+10*level)+2` | method on Level | -| `RN` | LCG step | `Rng.Next()` | -| `DISTANCE/dist` | squared distance | geometry helper | -| `attach/detach/free_list` | list ops via `&list` | slice operations | +| Macro | Expansion | Port meaning | +| --------------------------------- | -------------------------------------------- | -------------------------------- | +| `when` / `otherwise` / `until(e)` | `break;case` / `break;default` / `while(!e)` | plain Go control flow | +| `hero` | `player.t_pos` | `g.Player.Pos` | +| `pstats` | `player.t_stats` | `g.Player.Stats` | +| `pack` | `player.t_pack` | `g.Player.Pack` | +| `proom` | `player.t_room` | `g.Player.Room` | +| `max_hp` | `player.t_stats.s_maxhp` | `g.Player.Stats.MaxHP` | +| `on(thing,flag)` | flag bit test | `t.Flags.Has(...)` | +| `INDEX/chat/flat/moat(y,x)` | places[] cell access | `g.Level.At(p).Ch/Flags/Monster` | +| `winat(y,x)` | monster disguise or map char | `g.Level.VisibleChar(p)` | +| `ce(a,b)` | coord equality | `a == b` (value type) | +| `ISRING(h,r)` / `ISWEARING(r)` | ring-worn tests | `g.Player.IsWearing(ring)` | +| `ISMULT(type)` | stackable type test | method on ObjType | +| `GOLDCALC` | `rnd(50+10*level)+2` | method on Level | +| `RN` | LCG step | `Rng.Next()` | +| `DISTANCE/dist` | squared distance | geometry helper | +| `attach/detach/free_list` | list ops via `&list` | slice operations | ## 6. Global variable census Everything below lives in `extern.c` (game state) or as file-scope statics -(module state). In the Go port **all of these become fields of the -`RogueGame` struct** (user directive: no package-level globals). +(module state). In the Go port **all of these become fields of the `RogueGame` +struct** (user directive: no package-level globals). - **Player & world:** `player` (THING), `places[]`, `rooms[9]`, `passages[13]`, `mlist`, `lvl_obj`, `stairs`, `level`, `max_level`, `purse`, `amulet`, @@ -337,8 +338,8 @@ Everything below lives in `extern.c` (game state) or as file-scope statics `has_hit`, `move_on`, `dir_ch`, `countch/direction/newcount` (command.c statics). - **Options:** `terse`, `fight_flush`, `jump`, `see_floor`, `passgo`, - `tombstone`, `inv_type`, `inv_describe`, `lower_msg`, `msg_esc`, - `save_msg`, `q_comm`, `stat_msg`. + `tombstone`, `inv_type`, `inv_describe`, `lower_msg`, `msg_esc`, `save_msg`, + `q_comm`, `stat_msg`. - **Item identity tables:** `p_colors[]`, `s_names[]`, `r_stones[]`, `ws_made[]`, `ws_type[]`, `a_class[]`, 7 obj_info tables, `pack_used[26]`, `group`, `inpack`, `n_objs`, `no_food`. @@ -348,523 +349,667 @@ Everything below lives in `extern.c` (game state) or as file-scope statics `seenstairs`, `vf_hit`, `e_levels[]`. - **Static name data (immutable):** `monsters[26]`, `rainbow[]`, `stones[]`, `wood[]`, `metal[]`, `sylls[]`, `inv_t_name[]`, `tr_name[]`, `helpstr[]`, - `release`, `encstr`, `statlist`, `version`, `h_names[]`/`m_names[]` - (fight.c), `init_dam[]` (weapons.c), `lvl_mons[]`/`wand_mons[]` - (monsters.c), `uses[]` (rings.c), `p_actions[]` (potions.c). + `release`, `encstr`, `statlist`, `version`, `h_names[]`/`m_names[]` (fight.c), + `init_dam[]` (weapons.c), `lvl_mons[]`/`wand_mons[]` (monsters.c), `uses[]` + (rings.c), `p_actions[]` (potions.c). ## 7. Function catalog by file ### main.c (396 lines) — Entry point and main game loop control -| Function | Signature | Description | -|---|---|---| -| `main` | `int main(int argc, char **argv, char **envp)` | Entry point; initializes game state, terminal, initializes all subsystems (probabilities, player, names, colors, stones, materials), sets up windows and daemons, then calls `playit()`. Sets globals: `wizard`, `whoami`, `dnum`, `seed`, `file_name`, `home` | -| `endit` | `void endit(int sig)` | Signal handler for abnormal termination; calls `fatal()` with goodbye message | -| `fatal` | `void fatal(char *s)` | Prints message to status line, refreshes screen, ends curses, and exits | -| `rnd` | `int rnd(int range)` | Returns random number 0 to range-1 using macro `RN`; core randomness function | -| `roll` | `int roll(int number, int sides)` | Simulates rolling dice; calls `rnd()` for each die | -| `tstp` | `void tstp(int ignored)` | Signal handler for SIGTSTP/SIGCONT; saves cursor position, suspends curses, calls `md_tstpsignal()` and `md_tstpresume()`, restores screen state | -| `playit` | `void playit()` | Main game loop; runs daemons/fuses before and after each turn, reads `ROGUEOPTS` to set options, loops on `playing` flag calling `command()` | -| `quit` | `void quit(int sig)` | Confirm quit prompt; on 'y' displays final score and calls `score()`, reads/writes globals `purse`, `q_comm`, `count`, `to_death` | -| `leave` | `void leave(int sig)` | Quick exit during error; ends curses and exits cleanly | -| `shell` | `void shell()` | Shell escape; saves cursor, ends curses, calls `md_shellescape()`, restores curses; reads/writes global `in_shell` | -| `my_exit` | `void my_exit(int st)` | Proper exit wrapper; calls `resetltchars()` then `exit()` | +| Function | Signature | Description | +| --------- | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `main` | `int main(int argc, char **argv, char **envp)` | Entry point; initializes game state, terminal, initializes all subsystems (probabilities, player, names, colors, stones, materials), sets up windows and daemons, then calls `playit()`. Sets globals: `wizard`, `whoami`, `dnum`, `seed`, `file_name`, `home` | +| `endit` | `void endit(int sig)` | Signal handler for abnormal termination; calls `fatal()` with goodbye message | +| `fatal` | `void fatal(char *s)` | Prints message to status line, refreshes screen, ends curses, and exits | +| `rnd` | `int rnd(int range)` | Returns random number 0 to range-1 using macro `RN`; core randomness function | +| `roll` | `int roll(int number, int sides)` | Simulates rolling dice; calls `rnd()` for each die | +| `tstp` | `void tstp(int ignored)` | Signal handler for SIGTSTP/SIGCONT; saves cursor position, suspends curses, calls `md_tstpsignal()` and `md_tstpresume()`, restores screen state | +| `playit` | `void playit()` | Main game loop; runs daemons/fuses before and after each turn, reads `ROGUEOPTS` to set options, loops on `playing` flag calling `command()` | +| `quit` | `void quit(int sig)` | Confirm quit prompt; on 'y' displays final score and calls `score()`, reads/writes globals `purse`, `q_comm`, `count`, `to_death` | +| `leave` | `void leave(int sig)` | Quick exit during error; ends curses and exits cleanly | +| `shell` | `void shell()` | Shell escape; saves cursor, ends curses, calls `md_shellescape()`, restores curses; reads/writes global `in_shell` | +| `my_exit` | `void my_exit(int st)` | Proper exit wrapper; calls `resetltchars()` then `exit()` | -**Notes:** Globals initialized here from environment: `whoami`, `dnum`, `seed`, `file_name`, `home`. Signal handlers registered: `endit()`, `tstp()`, `quit()`, `leave()`. Heavy curses use (`initscr()`, `endwin()`, `newwin()`). Platform shims: `md_init()`, `md_normaluser()`, `md_tstpsignal()`, `md_tstpresume()`. +**Notes:** Globals initialized here from environment: `whoami`, `dnum`, `seed`, +`file_name`, `home`. Signal handlers registered: `endit()`, `tstp()`, `quit()`, +`leave()`. Heavy curses use (`initscr()`, `endwin()`, `newwin()`). Platform +shims: `md_init()`, `md_normaluser()`, `md_tstpsignal()`, `md_tstpresume()`. ### command.c (821 lines) — User command processing and game actions -| Function | Signature | Description | -|---|---|---| -| `command` | `void command()` | Main command dispatcher; reads user input, handles counts/prefixes (digits for repeat), processes movement (hjklyubn), items (pick, drop, quaff, read, eat, wield, wear, take_off, ring_on, ring_off), special commands (search, help, identify, etc.). Uses static variables `countch`, `direction`, `newcount`. Handles daemons/fuses before/after. Modifies globals: `running`, `count`, `after`, `to_death`, `noscore`, `wizard`, `last_comm`, `last_dir`, etc. | -| `illcom` | `void illcom(int ch)` | Error handler for illegal commands; displays message via `msg()`, modifies `save_msg`, `count` | -| `search` | `void search()` | Search current square and adjacent 8; finds hidden doors/traps/passages with probability adjusted for hallucination/blindness; modifies `running`, `count` on discovery | -| `help` | `void help()` | Displays command help; reads from `helpstr[]` array, uses `hw` window for full help display | -| `identify` | `void identify()` | Identify objects/monsters by character; uses local `ident_list[]` and `monsters[]` array | -| `d_level` | `void d_level()` | Go down stairs; checks `levit_check()`, verifies stairs at current position, increments `level`, calls `new_level()` | -| `u_level` | `void u_level()` | Go up stairs; checks for stairs, checks `amulet` flag, decrements `level`, checks win condition `level==0`, calls `new_level()` | -| `levit_check` | `bool levit_check()` | Check levitation status; returns TRUE if player has ISLEVIT flag (and messages that you float up) | -| `call` | `void call()` | Name/rename items; calls `get_item()`, prompts for new name, allocates memory via `malloc()`, updates `oi_guess` in object info structures | -| `current` | `void current(THING *cur, char *how, char *where)` | Print currently equipped item (weapon/armor/rings); formats output using `inv_name()` | +| Function | Signature | Description | +| ------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `command` | `void command()` | Main command dispatcher; reads user input, handles counts/prefixes (digits for repeat), processes movement (hjklyubn), items (pick, drop, quaff, read, eat, wield, wear, take_off, ring_on, ring_off), special commands (search, help, identify, etc.). Uses static variables `countch`, `direction`, `newcount`. Handles daemons/fuses before/after. Modifies globals: `running`, `count`, `after`, `to_death`, `noscore`, `wizard`, `last_comm`, `last_dir`, etc. | +| `illcom` | `void illcom(int ch)` | Error handler for illegal commands; displays message via `msg()`, modifies `save_msg`, `count` | +| `search` | `void search()` | Search current square and adjacent 8; finds hidden doors/traps/passages with probability adjusted for hallucination/blindness; modifies `running`, `count` on discovery | +| `help` | `void help()` | Displays command help; reads from `helpstr[]` array, uses `hw` window for full help display | +| `identify` | `void identify()` | Identify objects/monsters by character; uses local `ident_list[]` and `monsters[]` array | +| `d_level` | `void d_level()` | Go down stairs; checks `levit_check()`, verifies stairs at current position, increments `level`, calls `new_level()` | +| `u_level` | `void u_level()` | Go up stairs; checks for stairs, checks `amulet` flag, decrements `level`, checks win condition `level==0`, calls `new_level()` | +| `levit_check` | `bool levit_check()` | Check levitation status; returns TRUE if player has ISLEVIT flag (and messages that you float up) | +| `call` | `void call()` | Name/rename items; calls `get_item()`, prompts for new name, allocates memory via `malloc()`, updates `oi_guess` in object info structures | +| `current` | `void current(THING *cur, char *how, char *where)` | Print currently equipped item (weapon/armor/rings); formats output using `inv_name()` | -**Notes:** Static variables `countch`, `direction`, `newcount` in `command()` track repeat count state. Uses `when` macro for switch cases. Wizard mode (MASTER) adds debug commands (Ctrl-key commands: create object, show map, teleport, etc.). Command parsing via nested switch statements. Uses `readchar()` for input, `msg()`/`addmsg()` for output. +**Notes:** Static variables `countch`, `direction`, `newcount` in `command()` +track repeat count state. Uses `when` macro for switch cases. Wizard mode +(MASTER) adds debug commands (Ctrl-key commands: create object, show map, +teleport, etc.). Command parsing via nested switch statements. Uses `readchar()` +for input, `msg()`/`addmsg()` for output. ### io.c (278 lines) — Input/output and terminal display -| Function | Signature | Description | -|---|---|---| -| `msg` | `int msg(char *fmt, ...)` | Display message at top of screen; variadic wrapper using `va_list`, calls `doadd()`, `endmsg()`. Returns ESCAPE or ~ESCAPE. Clears line if format is empty string | -| `addmsg` | `void addmsg(char *fmt, ...)` | Append to current message buffer; variadic, calls `doadd()` | -| `endmsg` | `int endmsg()` | Finish message display; handles "--More--" continuation, auto-capitalizes first character unless `lower_msg`, saves message to `huh` for redisplay | -| `doadd` | `void doadd(char *fmt, va_list args)` | Internal: vsprintf into message buffer; checks overflow, concatenates to `msgbuf` | -| `step_ok` | `int step_ok(int ch)` | Check if terrain character allows stepping; returns FALSE for walls/space, TRUE for floor/doors/items (non-alpha chars) | -| `readchar` | `char readchar()` | Read character with error checking; calls `md_readchar()`, intercepts Ctrl-C (sends to `quit()`) | -| `status` | `void status()` | Display status line at STATLINE; shows level/gold/HP/armor/strength/experience/hunger; uses static tracking variables (`s_hp`, `s_pur`, etc.) to avoid redundant redraws | -| `wait_for` | `void wait_for(int ch)` | Block until specific character typed (or newline if ch is newline) | -| `show_win` | `void show_win(char *message)` | Display message in `hw` window and wait for space; used for full-screen displays | +| Function | Signature | Description | +| ---------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `msg` | `int msg(char *fmt, ...)` | Display message at top of screen; variadic wrapper using `va_list`, calls `doadd()`, `endmsg()`. Returns ESCAPE or ~ESCAPE. Clears line if format is empty string | +| `addmsg` | `void addmsg(char *fmt, ...)` | Append to current message buffer; variadic, calls `doadd()` | +| `endmsg` | `int endmsg()` | Finish message display; handles "--More--" continuation, auto-capitalizes first character unless `lower_msg`, saves message to `huh` for redisplay | +| `doadd` | `void doadd(char *fmt, va_list args)` | Internal: vsprintf into message buffer; checks overflow, concatenates to `msgbuf` | +| `step_ok` | `int step_ok(int ch)` | Check if terrain character allows stepping; returns FALSE for walls/space, TRUE for floor/doors/items (non-alpha chars) | +| `readchar` | `char readchar()` | Read character with error checking; calls `md_readchar()`, intercepts Ctrl-C (sends to `quit()`) | +| `status` | `void status()` | Display status line at STATLINE; shows level/gold/HP/armor/strength/experience/hunger; uses static tracking variables (`s_hp`, `s_pur`, etc.) to avoid redundant redraws | +| `wait_for` | `void wait_for(int ch)` | Block until specific character typed (or newline if ch is newline) | +| `show_win` | `void show_win(char *message)` | Display message in `hw` window and wait for space; used for full-screen displays | -**Notes:** Static `msgbuf[2*MAXMSG+1]`, `newpos` track accumulating multi-part messages (`addmsg`/`msg`/`endmsg` protocol). `huh[]` global stores last message for the `Ctrl-R` repeat command. Status line optimized with 8 static shadow variables to minimize terminal writes. +**Notes:** Static `msgbuf[2*MAXMSG+1]`, `newpos` track accumulating multi-part +messages (`addmsg`/`msg`/`endmsg` protocol). `huh[]` global stores last message +for the `Ctrl-R` repeat command. Status line optimized with 8 static shadow +variables to minimize terminal writes. ### init.c (448 lines) — Initialization of game state and object tables -| Function | Signature | Description | -|---|---|---| -| `init_player` | `void init_player()` | Roll up player character; copies `max_stats` to `pstats`, creates starting items (food, ring mail +1, mace +1/+1, bow +1, 25-39 arrows), initializes `food_left`, sets `cur_armor` and `cur_weapon` | -| `init_colors` | `void init_colors()` | Randomize potion colors; assigns `p_colors[]` randomly from `rainbow[]` list, no duplicates | -| `init_names` | `void init_names()` | Generate random scroll names from `sylls[]` syllable fragments; stores mallocated names in `s_names[]` | -| `init_stones` | `void init_stones()` | Randomize ring stones; assigns `r_stones[]` from `stones[]` array (no duplicates), adds stone value to `ring_info[].oi_worth` | -| `init_materials` | `void init_materials()` | Initialize wand/staff materials; randomly picks from `metal[]` (wand) or `wood[]` (staff), sets `ws_type[]` and `ws_made[]` | -| `sumprobs` | `void sumprobs(struct obj_info *info, int bound)` | Converts item probability arrays to cumulative distributions (running sum) | -| `init_probs` | `void init_probs()` | Calls `sumprobs()` for all seven obj_info tables | -| `badcheck` | `void badcheck(char *name, struct obj_info *info, int bound)` | (MASTER) Verifies probabilities sum to 100; aborts with message if not | -| `pick_color` | `char *pick_color(char *col)` | Returns given color, or a random one if hero is hallucinating | +| Function | Signature | Description | +| ---------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `init_player` | `void init_player()` | Roll up player character; copies `max_stats` to `pstats`, creates starting items (food, ring mail +1, mace +1/+1, bow +1, 25-39 arrows), initializes `food_left`, sets `cur_armor` and `cur_weapon` | +| `init_colors` | `void init_colors()` | Randomize potion colors; assigns `p_colors[]` randomly from `rainbow[]` list, no duplicates | +| `init_names` | `void init_names()` | Generate random scroll names from `sylls[]` syllable fragments; stores mallocated names in `s_names[]` | +| `init_stones` | `void init_stones()` | Randomize ring stones; assigns `r_stones[]` from `stones[]` array (no duplicates), adds stone value to `ring_info[].oi_worth` | +| `init_materials` | `void init_materials()` | Initialize wand/staff materials; randomly picks from `metal[]` (wand) or `wood[]` (staff), sets `ws_type[]` and `ws_made[]` | +| `sumprobs` | `void sumprobs(struct obj_info *info, int bound)` | Converts item probability arrays to cumulative distributions (running sum) | +| `init_probs` | `void init_probs()` | Calls `sumprobs()` for all seven obj_info tables | +| `badcheck` | `void badcheck(char *name, struct obj_info *info, int bound)` | (MASTER) Verifies probabilities sum to 100; aborts with message if not | +| `pick_color` | `char *pick_color(char *col)` | Returns given color, or a random one if hero is hallucinating | -**Notes:** Static data defined here: `rainbow[]` (27 colors) + `cNCOLORS`, `sylls[]` (syllables), `stones[]` (26 STONE entries with worth) + `cNSTONES`, `wood[]` (33 woods) + `cNWOOD`, `metal[]` (22 metals) + `cNMETAL`. The name arrays `p_colors[]`, `s_names[]`, `r_stones[]`, `ws_type[]`, `ws_made[]` are defined in extern.c but populated here at game start (per-game randomization of item appearance). +**Notes:** Static data defined here: `rainbow[]` (27 colors) + `cNCOLORS`, +`sylls[]` (syllables), `stones[]` (26 STONE entries with worth) + `cNSTONES`, +`wood[]` (33 woods) + `cNWOOD`, `metal[]` (22 metals) + `cNMETAL`. The name +arrays `p_colors[]`, `s_names[]`, `r_stones[]`, `ws_type[]`, `ws_made[]` are +defined in extern.c but populated here at game start (per-game randomization of +item appearance). ### extern.c (392 lines) — Global variable definitions -**Boolean flags:** `after` (want after-daemons), `again` (repeat last command), `seenstairs`, `amulet` (found the Amulet), `door_stop` (stop running at doors), `fight_flush` (flush typeahead on fight), `firstmove`, `got_ltc`, `has_hit`, `in_shell`, `inv_describe`, `jump` (show running as jumps), `kamikaze`, `lower_msg`, `move_on`, `msg_esc`, `passgo` (follow passage turns), `playing`, `q_comm`, `running`, `save_msg`, `see_floor`, `stat_msg`, `terse`, `to_death`, `tombstone`, `wizard` (MASTER), `pack_used[26]`. +**Boolean flags:** `after` (want after-daemons), `again` (repeat last command), +`seenstairs`, `amulet` (found the Amulet), `door_stop` (stop running at doors), +`fight_flush` (flush typeahead on fight), `firstmove`, `got_ltc`, `has_hit`, +`in_shell`, `inv_describe`, `jump` (show running as jumps), `kamikaze`, +`lower_msg`, `move_on`, `msg_esc`, `passgo` (follow passage turns), `playing`, +`q_comm`, `running`, `save_msg`, `see_floor`, `stat_msg`, `terse`, `to_death`, +`tombstone`, `wizard` (MASTER), `pack_used[26]`. -**Char/string state:** `dir_ch`, `file_name[MAXSTR]`, `huh[MAXSTR]` (last message), `prbuf[2*MAXSTR]` (sprintf scratch), `runch`, `take`, `whoami[MAXSTR]`, `fruit[MAXSTR]` ("slime-mold"), `home[MAXSTR]`, `l_last_comm`, `l_last_dir`, `last_comm`, `last_dir`; appearance-name arrays `p_colors[MAXPOTIONS]`, `r_stones[MAXRINGS]`, `s_names[MAXSCROLLS]`, `ws_made[MAXSTICKS]`, `ws_type[MAXSTICKS]`; fixed string tables `inv_t_name[]` (Overwrite/Slow/Clear), `tr_name[]` (8 trap names), `release` (version). +**Char/string state:** `dir_ch`, `file_name[MAXSTR]`, `huh[MAXSTR]` (last +message), `prbuf[2*MAXSTR]` (sprintf scratch), `runch`, `take`, +`whoami[MAXSTR]`, `fruit[MAXSTR]` ("slime-mold"), `home[MAXSTR]`, `l_last_comm`, +`l_last_dir`, `last_comm`, `last_dir`; appearance-name arrays +`p_colors[MAXPOTIONS]`, `r_stones[MAXRINGS]`, `s_names[MAXSCROLLS]`, +`ws_made[MAXSTICKS]`, `ws_type[MAXSTICKS]`; fixed string tables `inv_t_name[]` +(Overwrite/Slow/Clear), `tr_name[]` (8 trap names), `release` (version). -**Int state:** `n_objs`, `ntraps`, `hungry_state` (0-3), `inpack`, `inv_type`, `level`, `max_hit`, `max_level`, `mpos` (cursor pos on msg line), `no_food`, `count` (command repeat), `food_left`, `lastscore`, `no_command` (turns asleep), `no_move` (turns held), `noscore`, `purse` (gold), `quiet` (rest turns), `vf_hit` (flytrap hits), `dnum` (dungeon number), `seed` (RNG seed), `numscores`, `a_class[MAXARMORS]` (armor class table: 8 5 6 4 3 3 4 3... actually per-type base AC), `e_levels[]` (experience thresholds: 10 20 40 80 ... doubling to level 19), `orig_dsusp`. +**Int state:** `n_objs`, `ntraps`, `hungry_state` (0-3), `inpack`, `inv_type`, +`level`, `max_hit`, `max_level`, `mpos` (cursor pos on msg line), `no_food`, +`count` (command repeat), `food_left`, `lastscore`, `no_command` (turns asleep), +`no_move` (turns held), `noscore`, `purse` (gold), `quiet` (rest turns), +`vf_hit` (flytrap hits), `dnum` (dungeon number), `seed` (RNG seed), +`numscores`, `a_class[MAXARMORS]` (armor class table: 8 5 6 4 3 3 4 3... +actually per-type base AC), `e_levels[]` (experience thresholds: 10 20 40 80 ... +doubling to level 19), `orig_dsusp`. -**Coords/structural:** `delta` (last direction), `oldpos`, `stairs`; `places[MAXLINES*MAXCOLS]` (the level map: PLACE = ch, flags, monster ptr); `player` (THING); `cur_armor`, `cur_ring[2]`, `cur_weapon`, `l_last_pick`, `last_pick` (THING ptrs); `lvl_obj` (level object list), `mlist` (monster list). +**Coords/structural:** `delta` (last direction), `oldpos`, `stairs`; +`places[MAXLINES*MAXCOLS]` (the level map: PLACE = ch, flags, monster ptr); +`player` (THING); `cur_armor`, `cur_ring[2]`, `cur_weapon`, `l_last_pick`, +`last_pick` (THING ptrs); `lvl_obj` (level object list), `mlist` (monster list). -**Tables:** `rooms[MAXROOMS]`, `passages[MAXPASS]` (12 passage "rooms"), `max_stats` (initial player stats: str 16, 0 exp, lvl 1, ac 10, 12 hp, "1x4"), `oldrp`, `monsters[26]` (full A-Z monster table with names, carry %, flags, stats), 7 `obj_info` tables: `things[NUMTHINGS]` (potion 26%, scroll 36%, food 16%, weapon 7%, armor 7%, ring 4%, stick 4%), `pot_info[MAXPOTIONS]`, `scr_info[MAXSCROLLS]`, `ring_info[MAXRINGS]`, `ws_info[MAXSTICKS]`, `weap_info[MAXWEAPONS+1]`, `arm_info[MAXARMORS]` — each entry: name, probability, worth, guess ptr, know flag. `helpstr[]` (help text). `hw` (WINDOW* scratch), `scoreboard` (FILE*). +**Tables:** `rooms[MAXROOMS]`, `passages[MAXPASS]` (12 passage "rooms"), +`max_stats` (initial player stats: str 16, 0 exp, lvl 1, ac 10, 12 hp, "1x4"), +`oldrp`, `monsters[26]` (full A-Z monster table with names, carry %, flags, +stats), 7 `obj_info` tables: `things[NUMTHINGS]` (potion 26%, scroll 36%, food +16%, weapon 7%, armor 7%, ring 4%, stick 4%), `pot_info[MAXPOTIONS]`, +`scr_info[MAXSCROLLS]`, `ring_info[MAXRINGS]`, `ws_info[MAXSTICKS]`, +`weap_info[MAXWEAPONS+1]`, `arm_info[MAXARMORS]` — each entry: name, +probability, worth, guess ptr, know flag. `helpstr[]` (help text). `hw` (WINDOW* +scratch), `scoreboard` (FILE*). -**Notes:** Pure data file, no functions. The `monsters[]` table is the canonical A-Z bestiary (Aquator, Bat, Centaur, Dragon, Emu, Venus Flytrap, Griffin, Hobgoblin, Ice monster, Jabberwock, Kestrel, Leprechaun, Medusa, Nymph, Orc, Phantom, Quagga, Rattlesnake, Snake, Troll, Black unicorn, Vampire, Wraith, Xeroc, Yeti, Zombie) with per-monster carry probability, flag word (ISMEAN/ISFLY/ISREGEN/ISGREED/ISINVIS/CANHUH), and stats (exp, level, AC, HP dice, damage strings). +**Notes:** Pure data file, no functions. The `monsters[]` table is the canonical +A-Z bestiary (Aquator, Bat, Centaur, Dragon, Emu, Venus Flytrap, Griffin, +Hobgoblin, Ice monster, Jabberwock, Kestrel, Leprechaun, Medusa, Nymph, Orc, +Phantom, Quagga, Rattlesnake, Snake, Troll, Black unicorn, Vampire, Wraith, +Xeroc, Yeti, Zombie) with per-monster carry probability, flag word +(ISMEAN/ISFLY/ISREGEN/ISGREED/ISINVIS/CANHUH), and stats (exp, level, AC, HP +dice, damage strings). ### mach_dep.c (458 lines) — Installation and machine-dependent setup -| Function | Signature | Description | -|---|---|---| -| `init_check` | `void init_check()` | Check if system load/user count too high (compile-time optional; mostly vestigial) | -| `open_score` | `void open_score()` | Open score file for read/write; creates if missing; sets global `scoreboard` FILE* | -| `setup` | `void setup()` | Install signal handlers (`md_onsignal_autosave`), start checkout timer, set curses raw/noecho modes, fetch local tty chars | -| `getltchars` | `void getltchars()` | Save original tty dsusp char for later restore; sets `got_ltc`, `orig_dsusp` | -| `resetltchars` | `void resetltchars()` | Restore original tty chars (on exit/shell) | -| `playltchars` | `void playltchars()` | Set tty chars to in-game values | -| `start_score` | `void start_score()` | Stop checkout timer before scoring (vestigial) | -| `is_symlink` | `bool is_symlink(char *sp)` | lstat() check whether path is a symbolic link (save-file anti-cheat) | -| `too_much`/`author`/`checkout`/`chmsg`/`ucount` | various | Compile-time-guarded system-load limiting and author-exemption checks (vestigial on modern systems) | -| `lock_sc` | `bool lock_sc()` | Lock score file via lock file with retry/prompt and stale-lock aging | -| `unlock_sc` | `void unlock_sc()` | Release score file lock | -| `flush_type` | `void flush_type()` | Flush keyboard typeahead buffer (`flushinp()`) | +| Function | Signature | Description | +| ----------------------------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| `init_check` | `void init_check()` | Check if system load/user count too high (compile-time optional; mostly vestigial) | +| `open_score` | `void open_score()` | Open score file for read/write; creates if missing; sets global `scoreboard` FILE* | +| `setup` | `void setup()` | Install signal handlers (`md_onsignal_autosave`), start checkout timer, set curses raw/noecho modes, fetch local tty chars | +| `getltchars` | `void getltchars()` | Save original tty dsusp char for later restore; sets `got_ltc`, `orig_dsusp` | +| `resetltchars` | `void resetltchars()` | Restore original tty chars (on exit/shell) | +| `playltchars` | `void playltchars()` | Set tty chars to in-game values | +| `start_score` | `void start_score()` | Stop checkout timer before scoring (vestigial) | +| `is_symlink` | `bool is_symlink(char *sp)` | lstat() check whether path is a symbolic link (save-file anti-cheat) | +| `too_much`/`author`/`checkout`/`chmsg`/`ucount` | various | Compile-time-guarded system-load limiting and author-exemption checks (vestigial on modern systems) | +| `lock_sc` | `bool lock_sc()` | Lock score file via lock file with retry/prompt and stale-lock aging | +| `unlock_sc` | `void unlock_sc()` | Release score file lock | +| `flush_type` | `void flush_type()` | Flush keyboard typeahead buffer (`flushinp()`) | -**Notes:** Almost everything here is either curses/tty plumbing or vestigial 1980s multi-user courtesy features. A Go port needs only: score-file open/lock (use `os.OpenFile` + advisory locking or just best-effort), signal handling (`os/signal`), and typeahead flush (tcell handles). +**Notes:** Almost everything here is either curses/tty plumbing or vestigial +1980s multi-user courtesy features. A Go port needs only: score-file open/lock +(use `os.OpenFile` + advisory locking or just best-effort), signal handling +(`os/signal`), and typeahead flush (tcell handles). ### mdport.c (1433 lines) — Platform abstraction layer for Unix/Windows/DOS All functions are platform shims replaceable with Go stdlib: -| Function | Go equivalent | -|---|---| -| `md_init` | none needed (tcell handles terminal init) | -| `md_onsignal_default/exit/autosave` | `os/signal.Notify` | -| `md_hasclreol`, `md_putchar`, `md_raw_standout/standend` | tcell rendering | -| `md_unlink`, `md_unlink_open_file`, `md_chmod` | `os.Remove`, `os.Chmod` | -| `md_normaluser` | omit (no setuid in Go port) | -| `md_getuid`, `md_getpid` | `os.Getuid()`, `os.Getpid()` | -| `md_getusername`, `md_getrealname` | `os/user.Current()` | -| `md_gethomedir` | `os.UserHomeDir()` | -| `md_sleep` | `time.Sleep` | -| `md_getshell`, `md_shellescape` | `os/exec` (or omit shell escape) | -| `md_crypt`, `md_getpass` | not needed (wizard password via env; no crypt) | -| `md_erasechar`, `md_killchar`, `md_dsuspchar`, `md_setdsuspchar`, `md_suspchar` | tcell key events | -| `md_readchar` | tcell `PollEvent` (the 900-line escape-sequence state machine disappears entirely) | -| `md_loadav`, `md_ignoreallsignals`, `md_tstp*`, checkout timers | omit / `os/signal` | +| Function | Go equivalent | +| ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | +| `md_init` | none needed (tcell handles terminal init) | +| `md_onsignal_default/exit/autosave` | `os/signal.Notify` | +| `md_hasclreol`, `md_putchar`, `md_raw_standout/standend` | tcell rendering | +| `md_unlink`, `md_unlink_open_file`, `md_chmod` | `os.Remove`, `os.Chmod` | +| `md_normaluser` | omit (no setuid in Go port) | +| `md_getuid`, `md_getpid` | `os.Getuid()`, `os.Getpid()` | +| `md_getusername`, `md_getrealname` | `os/user.Current()` | +| `md_gethomedir` | `os.UserHomeDir()` | +| `md_sleep` | `time.Sleep` | +| `md_getshell`, `md_shellescape` | `os/exec` (or omit shell escape) | +| `md_crypt`, `md_getpass` | not needed (wizard password via env; no crypt) | +| `md_erasechar`, `md_killchar`, `md_dsuspchar`, `md_setdsuspchar`, `md_suspchar` | tcell key events | +| `md_readchar` | tcell `PollEvent` (the 900-line escape-sequence state machine disappears entirely) | +| `md_loadav`, `md_ignoreallsignals`, `md_tstp*`, checkout timers | omit / `os/signal` | -**Notes:** The dominant complexity is `md_readchar()` — a 900-line keypad/escape-sequence decoder state machine for a dozen terminal types. tcell's event model replaces all of it. Nothing in this file needs a line-by-line port. +**Notes:** The dominant complexity is `md_readchar()` — a 900-line +keypad/escape-sequence decoder state machine for a dozen terminal types. tcell's +event model replaces all of it. Nothing in this file needs a line-by-line port. ### vers.c (18 lines) — Version and build information -| Variable | Value | Purpose | -|---|---|---| -| `release` | "5.4.4" | Version number string (also written into save files for validation) | -| `encstr` | obfuscated bytes | XOR key stream #1 for save/score encryption | -| `statlist` | obfuscated bytes | XOR key stream #2 for save/score encryption | -| `version` | "rogue (rogueforge) 09/05/07" | Full version/build string | +| Variable | Value | Purpose | +| ---------- | ----------------------------- | ------------------------------------------------------------------- | +| `release` | "5.4.4" | Version number string (also written into save files for validation) | +| `encstr` | obfuscated bytes | XOR key stream #1 for save/score encryption | +| `statlist` | obfuscated bytes | XOR key stream #2 for save/score encryption | +| `version` | "rogue (rogueforge) 09/05/07" | Full version/build string | -**Notes:** `encstr`/`statlist` are the two XOR key strings consumed by `encwrite()`/`encread()` in save.c. +**Notes:** `encstr`/`statlist` are the two XOR key strings consumed by +`encwrite()`/`encread()` in save.c. ### new_level.c (232 lines) — Dig and draw a new level -| Function | Signature | Description | -|---|---|---| -| `new_level` | `void new_level()` | Initializes a new dungeon level by clearing previous monsters and objects, drawing rooms and passages, placing traps and stairs, positioning monsters in their rooms, and placing the player. Updates max_level and resets hero position. | -| `rnd_room` | `int rnd_room()` | Selects and returns a random room index that actually exists (where ISGONE flag is not set). | -| `put_things` | `void put_things()` | Places potions, scrolls, and other items on the current level with 36% probability for each of MAXOBJ slots. Handles treasure rooms (1 in TREAS_ROOM chance) and places the amulet at level AMULETLEVEL if not yet found. | -| `treas_room` | `void treas_room()` | Creates a treasure room by selecting a random existing room and filling it with MINTREAS to MAXTREAS treasure items plus monsters from the next level down (incremented temporarily). | +| Function | Signature | Description | +| ------------ | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `new_level` | `void new_level()` | Initializes a new dungeon level by clearing previous monsters and objects, drawing rooms and passages, placing traps and stairs, positioning monsters in their rooms, and placing the player. Updates max_level and resets hero position. | +| `rnd_room` | `int rnd_room()` | Selects and returns a random room index that actually exists (where ISGONE flag is not set). | +| `put_things` | `void put_things()` | Places potions, scrolls, and other items on the current level with 36% probability for each of MAXOBJ slots. Handles treasure rooms (1 in TREAS_ROOM chance) and places the amulet at level AMULETLEVEL if not yet found. | +| `treas_room` | `void treas_room()` | Creates a treasure room by selecting a random existing room and filling it with MINTREAS to MAXTREAS treasure items plus monsters from the next level down (incremented temporarily). | -**Notes:** File-scope constants: TREAS_ROOM (1/20 chance), MAXTREAS (10 items max), MINTREAS (2 items min), MAXTRIES (10 tries limit). No static tables. Uses macros: chat() for character at position, flat() for flags, F_REAL flag. Key side effects: modifies global level, max_level, places[], mlist; calls helper functions do_rooms(), do_passages(), put_things(). +**Notes:** File-scope constants: TREAS_ROOM (1/20 chance), MAXTREAS (10 items +max), MINTREAS (2 items min), MAXTRIES (10 tries limit). No static tables. Uses +macros: chat() for character at position, flat() for flags, F_REAL flag. Key +side effects: modifies global level, max_level, places[], mlist; calls helper +functions do_rooms(), do_passages(), put_things(). ### rooms.c (473 lines) — Create the layout for the new level -| Function | Signature | Description | -|---|---|---| -| `do_rooms` | `void do_rooms()` | Creates all rooms on a level by partitioning space into 3x3 grid and generating random room sizes/positions. Marks some rooms as ISGONE, sets ISDARK and ISMAZE flags, places gold and monsters in each room based on probabilities. | -| `draw_room` | `void draw_room(struct room *rp)` | Draws walls (-, \|) around a room and lays down FLOOR characters inside; handles maze rooms by calling do_maze() instead. | -| `vert` | `void vert(struct room *rp, int startx)` | Draws a vertical wall line of '\|' characters from top to bottom of room. | -| `horiz` | `void horiz(struct room *rp, int starty)` | Draws a horizontal wall line of '-' characters across room width. | -| `do_maze` | `void do_maze(struct room *rp)` | Generates a maze within a room using recursive depth-first digging. Initializes maze SPOT array tracking visited positions and exits, sets up starting position, and calls dig() to carve passages. | -| `dig` | `void dig(int y, int x)` | Recursively digs maze passages by choosing random unvisited neighbors, accounting for exits, and calling putpass() to mark passage cells. Returns when no more valid neighbors exist. | -| `accnt_maze` | `void accnt_maze(int y, int x, int ny, int nx)` | Records a maze exit connection between adjacent SPOT cells, checking for duplicates before adding. | -| `rnd_pos` | `void rnd_pos(struct room *rp, coord *cp)` | Fills coord with random x,y within room bounds (excluding walls). | -| `find_floor` | `bool find_floor(struct room *rp, coord *cp, int limit, bool monst)` | Finds a valid floor/passage spot in given room or random room (if rp==NULL) within limit attempts. Returns TRUE if found, FALSE if limit exceeded. If monst==TRUE, checks for monster-valid position (empty with step_ok() char). | -| `enter_room` | `void enter_room(coord *cp)` | Called when player enters a room; sets proom, opens doors, and redraws non-dark rooms by displaying correct characters and monsters. Updates terminal display. | -| `leave_room` | `void leave_room(coord *cp)` | Called when player exits a room; clears room display by replacing FLOOR with space or passage chars, handles monsters with standout mode, restores doors. | +| Function | Signature | Description | +| ------------ | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `do_rooms` | `void do_rooms()` | Creates all rooms on a level by partitioning space into 3x3 grid and generating random room sizes/positions. Marks some rooms as ISGONE, sets ISDARK and ISMAZE flags, places gold and monsters in each room based on probabilities. | +| `draw_room` | `void draw_room(struct room *rp)` | Draws walls (-, \|) around a room and lays down FLOOR characters inside; handles maze rooms by calling do_maze() instead. | +| `vert` | `void vert(struct room *rp, int startx)` | Draws a vertical wall line of '\|' characters from top to bottom of room. | +| `horiz` | `void horiz(struct room *rp, int starty)` | Draws a horizontal wall line of '-' characters across room width. | +| `do_maze` | `void do_maze(struct room *rp)` | Generates a maze within a room using recursive depth-first digging. Initializes maze SPOT array tracking visited positions and exits, sets up starting position, and calls dig() to carve passages. | +| `dig` | `void dig(int y, int x)` | Recursively digs maze passages by choosing random unvisited neighbors, accounting for exits, and calling putpass() to mark passage cells. Returns when no more valid neighbors exist. | +| `accnt_maze` | `void accnt_maze(int y, int x, int ny, int nx)` | Records a maze exit connection between adjacent SPOT cells, checking for duplicates before adding. | +| `rnd_pos` | `void rnd_pos(struct room *rp, coord *cp)` | Fills coord with random x,y within room bounds (excluding walls). | +| `find_floor` | `bool find_floor(struct room *rp, coord *cp, int limit, bool monst)` | Finds a valid floor/passage spot in given room or random room (if rp==NULL) within limit attempts. Returns TRUE if found, FALSE if limit exceeded. If monst==TRUE, checks for monster-valid position (empty with step_ok() char). | +| `enter_room` | `void enter_room(coord *cp)` | Called when player enters a room; sets proom, opens doors, and redraws non-dark rooms by displaying correct characters and monsters. Updates terminal display. | +| `leave_room` | `void leave_room(coord *cp)` | Called when player exits a room; clears room display by replacing FLOOR with space or passage chars, handles monsters with standout mode, restores doors. | -**Notes:** File-scope static variables: Maxy, Maxx, Starty, Startx (maze bounds), and maze[NUMLINES/3+1][NUMCOLS/3+1] array (2D array of SPOT structures tracking maze generation). SPOT typedef contains: nexits (count of exits), exits[4] (array of exit coordinates), used (boolean flag). Uses "until" macro with do-while loops for retries. Key macros: chat() and flat() for accessing places[] array, FLOOR/DOOR/PASSAGE character constants, ISMAZE/ISDARK/ISGONE room flags. +**Notes:** File-scope static variables: Maxy, Maxx, Starty, Startx (maze +bounds), and maze[NUMLINES/3+1][NUMCOLS/3+1] array (2D array of SPOT structures +tracking maze generation). SPOT typedef contains: nexits (count of exits), +exits[4] (array of exit coordinates), used (boolean flag). Uses "until" macro +with do-while loops for retries. Key macros: chat() and flat() for accessing +places[] array, FLOOR/DOOR/PASSAGE character constants, ISMAZE/ISDARK/ISGONE +room flags. ### passages.c (425 lines) — Draw the connecting passages -| Function | Signature | Description | -|---|---|---| -| `do_passages` | `void do_passages()` | Draws all passages on a level using a connectivity graph algorithm. Builds spanning tree connecting all MAXROOMS rooms, then adds 0-4 random extra connections. Initializes rdes[] array and calls conn() to draw each passage, then calls passnum() to number them. | -| `conn` | `void conn(int r1, int r2)` | Draws a corridor between two rooms, determining direction (right or down based on room indices), finding door positions, and drawing passage tiles with optional turn points. Places DOOR characters at room entries. | -| `putpass` | `void putpass(coord *cp)` | Sets F_PASS flag at position and marks as PASSAGE character; randomly makes secret passages (unreal) at deeper levels. | -| `door` | `void door(struct room *rm, coord *cp)` | Records door position in room's r_exit array, increments r_nexits. For non-maze rooms, randomly creates secret doors (\|/- unreal) or regular DOOR characters. | -| `add_pass` | `void add_pass()` | [MASTER ifdef] Wizard command that displays all passages and doors on screen by iterating places[] and showing passages as PASSAGE, secret doors with standout. | -| `passnum` | `void passnum()` | Assigns passage numbers (0 to MAXPASS-1) by iterating through room exits and calling numpass() on each, resetting passage r_nexits. Uses static pnum and newpnum tracking. | -| `numpass` | `void numpass(int y, int x)` | Recursively numbers a passageway region by flood-fill; sets F_PNUM bits in flat flags, records door/secret door exits in passages[] array, and recurses on adjacent cells. | +| Function | Signature | Description | +| ------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `do_passages` | `void do_passages()` | Draws all passages on a level using a connectivity graph algorithm. Builds spanning tree connecting all MAXROOMS rooms, then adds 0-4 random extra connections. Initializes rdes[] array and calls conn() to draw each passage, then calls passnum() to number them. | +| `conn` | `void conn(int r1, int r2)` | Draws a corridor between two rooms, determining direction (right or down based on room indices), finding door positions, and drawing passage tiles with optional turn points. Places DOOR characters at room entries. | +| `putpass` | `void putpass(coord *cp)` | Sets F_PASS flag at position and marks as PASSAGE character; randomly makes secret passages (unreal) at deeper levels. | +| `door` | `void door(struct room *rm, coord *cp)` | Records door position in room's r_exit array, increments r_nexits. For non-maze rooms, randomly creates secret doors (\|/- unreal) or regular DOOR characters. | +| `add_pass` | `void add_pass()` | [MASTER ifdef] Wizard command that displays all passages and doors on screen by iterating places[] and showing passages as PASSAGE, secret doors with standout. | +| `passnum` | `void passnum()` | Assigns passage numbers (0 to MAXPASS-1) by iterating through room exits and calling numpass() on each, resetting passage r_nexits. Uses static pnum and newpnum tracking. | +| `numpass` | `void numpass(int y, int x)` | Recursively numbers a passageway region by flood-fill; sets F_PNUM bits in flat flags, records door/secret door exits in passages[] array, and recurses on adjacent cells. | -**Notes:** File-scope static variables: pnum (current passage number), newpnum (boolean flag). Static rdes[] array contains MAXROOMS room description structures with conn[] (adjacency matrix for possible connections), isconn[] (boolean array of actual connections made), and ingraph (boolean). The connectivity matrix hardcodes a 3x3 room grid topology. Uses macros: chat()/flat() for places[] access, F_PASS/F_PNUM flags, INDEX() macro for array indexing, ce() for coordinate equality testing. +**Notes:** File-scope static variables: pnum (current passage number), newpnum +(boolean flag). Static rdes[] array contains MAXROOMS room description +structures with conn[] (adjacency matrix for possible connections), isconn[] +(boolean array of actual connections made), and ingraph (boolean). The +connectivity matrix hardcodes a 3x3 room grid topology. Uses macros: +chat()/flat() for places[] access, F_PASS/F_PNUM flags, INDEX() macro for array +indexing, ce() for coordinate equality testing. ### move.c (426 lines) — Hero movement commands -| Function | Signature | Description | -|---|---|---| -| `do_run` | `void do_run(char ch)` | Initiates running mode by setting running=TRUE, runch=ch (direction), and after=FALSE. | -| `do_move` | `void do_move(int dy, int dx)` | Main movement handler; validates move legality (bounds, diagonal ok), handles confused movement, checks for traps/doors/monsters. Updates hero position, calls fight() for combat, handles room changes, updates display. Complex switch on destination cell character (DOOR/TRAP/PASSAGE/FLOOR/STAIRS/monster). | -| `turn_ok` | `bool turn_ok(int y, int x)` | Checks if position is a DOOR or both F_REAL and F_PASS flags set (valid turning point in corridors during runs). | -| `turnref` | `void turnref()` | Marks position F_SEEN and optionally refreshes screen if jump mode enabled (for passage turn display). | -| `door_open` | `void door_open(struct room *rp)` | Illuminates a room by iterating through all positions and waking any monsters found (calling wake_monster()). | -| `be_trapped` | `char be_trapped(coord *tc)` | Handles trap activation with switch on trap type: T_DOOR (level down), T_BEAR (no_move delay), T_MYST (random messages), T_SLEEP (no_command delay), T_ARROW (damage/rolls), T_TELEP (teleport), T_DART (poisoned damage), T_RUST (armor damage). Returns trap type. | -| `rndmove` | `coord *rndmove(THING *who)` | Generates random movement by adding rnd(3)-1 to current position; validates legality with diag_ok() and step_ok(), returns position or original if invalid. | -| `rust_armor` | `void rust_armor(THING *arm)` | Decrements armor AC (increases o_arm value) unless protected or wearing R_SUSTARM ring, with message output varying by terse flag. Returns if armor NULL, not ARMOR type, or already very rusty. | +| Function | Signature | Description | +| ------------ | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `do_run` | `void do_run(char ch)` | Initiates running mode by setting running=TRUE, runch=ch (direction), and after=FALSE. | +| `do_move` | `void do_move(int dy, int dx)` | Main movement handler; validates move legality (bounds, diagonal ok), handles confused movement, checks for traps/doors/monsters. Updates hero position, calls fight() for combat, handles room changes, updates display. Complex switch on destination cell character (DOOR/TRAP/PASSAGE/FLOOR/STAIRS/monster). | +| `turn_ok` | `bool turn_ok(int y, int x)` | Checks if position is a DOOR or both F_REAL and F_PASS flags set (valid turning point in corridors during runs). | +| `turnref` | `void turnref()` | Marks position F_SEEN and optionally refreshes screen if jump mode enabled (for passage turn display). | +| `door_open` | `void door_open(struct room *rp)` | Illuminates a room by iterating through all positions and waking any monsters found (calling wake_monster()). | +| `be_trapped` | `char be_trapped(coord *tc)` | Handles trap activation with switch on trap type: T_DOOR (level down), T_BEAR (no_move delay), T_MYST (random messages), T_SLEEP (no_command delay), T_ARROW (damage/rolls), T_TELEP (teleport), T_DART (poisoned damage), T_RUST (armor damage). Returns trap type. | +| `rndmove` | `coord *rndmove(THING *who)` | Generates random movement by adding rnd(3)-1 to current position; validates legality with diag_ok() and step_ok(), returns position or original if invalid. | +| `rust_armor` | `void rust_armor(THING *arm)` | Decrements armor AC (increases o_arm value) unless protected or wearing R_SUSTARM ring, with message output varying by terse flag. Returns if armor NULL, not ARMOR type, or already very rusty. | -**Notes:** File-scope global variable: nh (coord holding new hero position). Uses macros: winat() for visible character, chat() for actual cell character, flat() for flags, moat() for monster at position, INDEX() for place lookup. Trap type constants: T_DOOR, T_BEAR (BEARTIME), T_MYST, T_SLEEP (SLEEPTIME), T_ARROW, T_TELEP, T_DART, T_RUST. Uses "when" macro for compact case statements and "goto over" for retry after confused move calculation. +**Notes:** File-scope global variable: nh (coord holding new hero position). +Uses macros: winat() for visible character, chat() for actual cell character, +flat() for flags, moat() for monster at position, INDEX() for place lookup. Trap +type constants: T_DOOR, T_BEAR (BEARTIME), T_MYST, T_SLEEP (SLEEPTIME), T_ARROW, +T_TELEP, T_DART, T_RUST. Uses "when" macro for compact case statements and "goto +over" for retry after confused move calculation. ### chase.c (542 lines) — Code for one creature to chase another -| Function | Signature | Description | -|---|---|---| -| `runners` | `void runners()` | Iterates through all monsters in mlist and moves each running monster (ISRUN flag) via move_monst(), handling fast monsters with second move and target flag updates. Clears has_hit message flag. | -| `move_monst` | `int move_monst(THING *tp)` | Executes one monster turn: calls do_chase() once for ISSLOW/ISHASTE alternate turns, toggles t_turn flag. Returns -1 if monster died. | -| `relocate` | `void relocate(THING *th, coord *new_loc)` | Updates monster position from th->t_pos to new_loc, refreshes moat() pointers, calls set_oldch() for display character, updates room pointer, recalculates destination if room changed, and redraws monster on screen. | -| `do_chase` | `int do_chase(THING *th)` | Primary chase logic: finds room of chaser and chasee, navigates to target by exiting current room and approaching chasee. For dragons, checks line-of-sight and fires flame bolt at hero if in range (DRAGONSHOT odds). Calls chase() to find best move, handles combat when goal reached, calls relocate(). Returns 0. | -| `set_oldch` | `void set_oldch(THING *tp, coord *cp)` | Sets tp->t_oldch to the screen character at cp for proper redrawing; accounts for darkness, lamp range, and blind status to choose correct background. | -| `see_monst` | `bool see_monst(THING *mp)` | Returns TRUE if hero can see monster: checks ISBLIND, ISINVIS (requires CANSEE), LAMPDIST proximity, and room visibility (same room and not dark). | -| `runto` | `void runto(coord *runner)` | Sets monster at runner position to ISRUN flag, clears ISHELD, sets t_dest via find_dest(), starts monster chasing. | -| `chase` | `bool chase(THING *tp, coord *ee)` | Finds best move for tp toward target ee; if confused (ISHUH), makes random move via rndmove(); otherwise iterates 3x3 surrounding cells to find closest approach, avoiding scare scrolls and Xerocs. Returns TRUE if still chasing (curdist != 0), FALSE if reached goal. | -| `roomin` | `struct room *roomin(coord *cp)` | Locates which room contains coordinate cp by checking F_PASS flag first (returns passage room), then iterating rooms[] for overlap; returns NULL/aborts if not found. | -| `diag_ok` | `bool diag_ok(coord *sp, coord *ep)` | Validates move from sp to ep: checks bounds and allows non-diagonals always; for diagonal moves, checks that both adjacent cells (same row and same column) are step_ok(). | -| `cansee` | `bool cansee(int y, int x)` | Returns TRUE if hero can see coordinate: checks ISBLIND, LAMPDIST range with passage blocking checks, or same room with lit/lit conditions. | -| `find_dest` | `coord *find_dest(THING *tp)` | Determines monster target: returns &hero if carrying probability is low, monster in proom, or monster is visible; otherwise searches lvl_obj for items in monster's room not already targeted by other monsters, with probability check; defaults to &hero. | -| `dist` | `int dist(int y1, int x1, int y2, int x2)` | Returns squared Euclidean distance (x delta squared plus y delta squared) for comparison purposes. | -| `dist_cp` | `int dist_cp(coord *c1, coord *c2)` | Wrapper calling dist() with coordinate components extracted from c1 and c2. | +| Function | Signature | Description | +| ------------ | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `runners` | `void runners()` | Iterates through all monsters in mlist and moves each running monster (ISRUN flag) via move_monst(), handling fast monsters with second move and target flag updates. Clears has_hit message flag. | +| `move_monst` | `int move_monst(THING *tp)` | Executes one monster turn: calls do_chase() once for ISSLOW/ISHASTE alternate turns, toggles t_turn flag. Returns -1 if monster died. | +| `relocate` | `void relocate(THING *th, coord *new_loc)` | Updates monster position from th->t_pos to new_loc, refreshes moat() pointers, calls set_oldch() for display character, updates room pointer, recalculates destination if room changed, and redraws monster on screen. | +| `do_chase` | `int do_chase(THING *th)` | Primary chase logic: finds room of chaser and chasee, navigates to target by exiting current room and approaching chasee. For dragons, checks line-of-sight and fires flame bolt at hero if in range (DRAGONSHOT odds). Calls chase() to find best move, handles combat when goal reached, calls relocate(). Returns 0. | +| `set_oldch` | `void set_oldch(THING *tp, coord *cp)` | Sets tp->t_oldch to the screen character at cp for proper redrawing; accounts for darkness, lamp range, and blind status to choose correct background. | +| `see_monst` | `bool see_monst(THING *mp)` | Returns TRUE if hero can see monster: checks ISBLIND, ISINVIS (requires CANSEE), LAMPDIST proximity, and room visibility (same room and not dark). | +| `runto` | `void runto(coord *runner)` | Sets monster at runner position to ISRUN flag, clears ISHELD, sets t_dest via find_dest(), starts monster chasing. | +| `chase` | `bool chase(THING *tp, coord *ee)` | Finds best move for tp toward target ee; if confused (ISHUH), makes random move via rndmove(); otherwise iterates 3x3 surrounding cells to find closest approach, avoiding scare scrolls and Xerocs. Returns TRUE if still chasing (curdist != 0), FALSE if reached goal. | +| `roomin` | `struct room *roomin(coord *cp)` | Locates which room contains coordinate cp by checking F_PASS flag first (returns passage room), then iterating rooms[] for overlap; returns NULL/aborts if not found. | +| `diag_ok` | `bool diag_ok(coord *sp, coord *ep)` | Validates move from sp to ep: checks bounds and allows non-diagonals always; for diagonal moves, checks that both adjacent cells (same row and same column) are step_ok(). | +| `cansee` | `bool cansee(int y, int x)` | Returns TRUE if hero can see coordinate: checks ISBLIND, LAMPDIST range with passage blocking checks, or same room with lit/lit conditions. | +| `find_dest` | `coord *find_dest(THING *tp)` | Determines monster target: returns &hero if carrying probability is low, monster in proom, or monster is visible; otherwise searches lvl_obj for items in monster's room not already targeted by other monsters, with probability check; defaults to &hero. | +| `dist` | `int dist(int y1, int x1, int y2, int x2)` | Returns squared Euclidean distance (x delta squared plus y delta squared) for comparison purposes. | +| `dist_cp` | `int dist_cp(coord *c1, coord *c2)` | Wrapper calling dist() with coordinate components extracted from c1 and c2. | -**Notes:** File-scope static variable: ch_ret (coord holding where chasing takes you, returned by chase() and used by do_chase()). DRAGONSHOT macro constant (5, odds for dragon breath). Uses macros: moat() for monster at position, chat()/flat() for cell access, on() for flag testing, ce() for coordinate equality. Key dragon logic: checks 8-directional line-of-sight (row match, column match, or diagonal), BOLT_LENGTH range squared, and calls fire_bolt(). Monster types referenced: 'D' (dragon), 'P' (invisible stalker), 'B' (bat), 'F' (flytrap), 'X' (xeroc). +**Notes:** File-scope static variable: ch_ret (coord holding where chasing takes +you, returned by chase() and used by do_chase()). DRAGONSHOT macro constant (5, +odds for dragon breath). Uses macros: moat() for monster at position, +chat()/flat() for cell access, on() for flag testing, ce() for coordinate +equality. Key dragon logic: checks 8-directional line-of-sight (row match, +column match, or diagonal), BOLT_LENGTH range squared, and calls fire_bolt(). +Monster types referenced: 'D' (dragon), 'P' (invisible stalker), 'B' (bat), 'F' +(flytrap), 'X' (xeroc). ### monsters.c (253 lines) — File with various monster functions -| Function | Signature | Description | -|---|---|---| -| `randmonster` | `char randmonster(bool wander)` | Selects a monster type from either lvl_mons[] or wand_mons[] array based on wander flag. Calculates index from level offset with noise (rnd(10)-6), clamps to valid range, skips nulls in array. Returns monster letter 'A'-'Z'. | -| `new_monster` | `void new_monster(THING *tp, char type, coord *cp)` | Initializes new monster: sets type, disguise, position; attaches to mlist; looks up stats from monsters[] table using type-'A' index; adjusts level based on depth (level > AMULETLEVEL); sets flags, hit points via roll(). If ISWEARING R_AGGR ring, calls runto(). For 'X' (xeroc), randomizes disguise. | -| `exp_add` | `int exp_add(THING *tp)` | Calculates experience reward based on monster level and max HP; modifies by 20x for level > 9, 4x for level > 6, and divides HP by 6 (or 8 if level 1). | -| `wanderer` | `void wanderer()` | Creates a wandering monster at random floor position in different room than proom. Allocates THING via new_item(), calls randmonster(TRUE), new_monster(), displays if SEEMONST, and sets running via runto(). | -| `wake_monster` | `THING *wake_monster(int y, int x)` | Activates monster at position: checks 1/3 chance to start chasing if ISMEAN and conditions met (not held, not wearing stealth, etc.); handles Medusa ('M') gaze attack (confusion if not saved). For ISGREED monsters, sets guard target to gold position or hero. Returns monster pointer. | -| `give_pack` | `void give_pack(THING *tp)` | If at max_level and random check (%) against m_carry probability succeeds, attaches one random item to monster's pack via new_thing(). | -| `save_throw` | `int save_throw(int which, THING *tp)` | Calculates saving throw: need value = 14 + which - (s_lvl / 2), rolls 1d20, returns TRUE if roll >= need. | -| `save` | `int save(int which)` | Hero's save check: for VS_MAGIC, reduces difficulty by protection ring bonuses (left and right ring armor values), then calls save_throw() with &player. | +| Function | Signature | Description | +| -------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `randmonster` | `char randmonster(bool wander)` | Selects a monster type from either lvl_mons[] or wand_mons[] array based on wander flag. Calculates index from level offset with noise (rnd(10)-6), clamps to valid range, skips nulls in array. Returns monster letter 'A'-'Z'. | +| `new_monster` | `void new_monster(THING *tp, char type, coord *cp)` | Initializes new monster: sets type, disguise, position; attaches to mlist; looks up stats from monsters[] table using type-'A' index; adjusts level based on depth (level > AMULETLEVEL); sets flags, hit points via roll(). If ISWEARING R_AGGR ring, calls runto(). For 'X' (xeroc), randomizes disguise. | +| `exp_add` | `int exp_add(THING *tp)` | Calculates experience reward based on monster level and max HP; modifies by 20x for level > 9, 4x for level > 6, and divides HP by 6 (or 8 if level 1). | +| `wanderer` | `void wanderer()` | Creates a wandering monster at random floor position in different room than proom. Allocates THING via new_item(), calls randmonster(TRUE), new_monster(), displays if SEEMONST, and sets running via runto(). | +| `wake_monster` | `THING *wake_monster(int y, int x)` | Activates monster at position: checks 1/3 chance to start chasing if ISMEAN and conditions met (not held, not wearing stealth, etc.); handles Medusa ('M') gaze attack (confusion if not saved). For ISGREED monsters, sets guard target to gold position or hero. Returns monster pointer. | +| `give_pack` | `void give_pack(THING *tp)` | If at max_level and random check (%) against m_carry probability succeeds, attaches one random item to monster's pack via new_thing(). | +| `save_throw` | `int save_throw(int which, THING *tp)` | Calculates saving throw: need value = 14 + which - (s_lvl / 2), rolls 1d20, returns TRUE if roll >= need. | +| `save` | `int save(int which)` | Hero's save check: for VS_MAGIC, reduces difficulty by protection ring bonuses (left and right ring armor values), then calls save_throw() with &player. | -**Notes:** File-scope static arrays: lvl_mons[] (26 monsters by level 0-25: K E B S H I R O Z L C Q A N Y F T W P X U M V G J D), wand_mons[] (same with 0s for F U D where wands unavailable). Monster type encoding: 'A' through 'Z' map to indices 0-25 via t_type-'A'. The monsters[] global array (defined elsewhere) is indexed this way. Key monster types: 'M' (Medusa), 'X' (Xeroc), 'F' (Flytrap), 'D' (Dragon). References AMULETLEVEL constant for depth-scaling experience. Uses macros: ISWEARING() for ring checks, on() for flag testing, rnd() for probabilities, roll() for hit dice. +**Notes:** File-scope static arrays: lvl_mons[] (26 monsters by level 0-25: K E +B S H I R O Z L C Q A N Y F T W P X U M V G J D), wand_mons[] (same with 0s for +F U D where wands unavailable). Monster type encoding: 'A' through 'Z' map to +indices 0-25 via t_type-'A'. The monsters[] global array (defined elsewhere) is +indexed this way. Key monster types: 'M' (Medusa), 'X' (Xeroc), 'F' (Flytrap), +'D' (Dragon). References AMULETLEVEL constant for depth-scaling experience. Uses +macros: ISWEARING() for ring checks, on() for flag testing, rnd() for +probabilities, roll() for hit dice. ### wizard.c (285 lines) — Special wizard commands (some also non-wizard under strange circumstances) -| Function | Signature | Description | -|---|---|---| -| `whatis` | `void whatis(bool insist, int type)` | Identifies an item from pack by prompting with get_item() and setting ISKNOW flag. Calls set_know() with appropriate info array (scr_info, pot_info, ws_info, ring_info) based on o_type. If insist, loops until valid selection or user cancels. | -| `set_know` | `void set_know(THING *obj, struct obj_info *info)` | Sets oi_know=TRUE for object type in info array, sets obj ISKNOW flag, and frees/clears any oi_guess string for that type. | -| `type_name` | `char *type_name(int type)` | Returns string description for object type by searching static tlist[] array: "potion", "scroll", "food", "ring, wand or staff", "ring", "wand or staff", "weapon", "suit of armor". | -| `create_obj` | `void create_obj()` | [MASTER ifdef] Wizard command to create arbitrary items: prompts for type and which (0-f hex), handles blessing for weapons/armor/rings, calls init_weapon() or sets o_arm, processes special ring logic, calls fix_stick() for staves, and adds to pack. | -| `teleport` | `void teleport()` | Instantly moves hero to random valid floor position, redraws old position with floor_at(), updates proom if changed rooms (leave_room/enter_room), and resets movement/combat flags (no_move, count, running). Resets ISHELD flag if held by flytrap (resets vf_hit and damage dice). | -| `passwd` | `int passwd()` | [MASTER ifdef] Prompts for wizard password, reads line with backspace/kill character handling, returns TRUE if strcmp(input, md_crypt(input, "mT")) matches PASSWD constant. | -| `show_map` | `void show_map()` | [MASTER ifdef] Displays full level map in hw window: iterates places[] drawing each cell with standout for non-real (F_REAL unset) cells, then shows "---More---" prompt. | +| Function | Signature | Description | +| ------------ | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `whatis` | `void whatis(bool insist, int type)` | Identifies an item from pack by prompting with get_item() and setting ISKNOW flag. Calls set_know() with appropriate info array (scr_info, pot_info, ws_info, ring_info) based on o_type. If insist, loops until valid selection or user cancels. | +| `set_know` | `void set_know(THING *obj, struct obj_info *info)` | Sets oi_know=TRUE for object type in info array, sets obj ISKNOW flag, and frees/clears any oi_guess string for that type. | +| `type_name` | `char *type_name(int type)` | Returns string description for object type by searching static tlist[] array: "potion", "scroll", "food", "ring, wand or staff", "ring", "wand or staff", "weapon", "suit of armor". | +| `create_obj` | `void create_obj()` | [MASTER ifdef] Wizard command to create arbitrary items: prompts for type and which (0-f hex), handles blessing for weapons/armor/rings, calls init_weapon() or sets o_arm, processes special ring logic, calls fix_stick() for staves, and adds to pack. | +| `teleport` | `void teleport()` | Instantly moves hero to random valid floor position, redraws old position with floor_at(), updates proom if changed rooms (leave_room/enter_room), and resets movement/combat flags (no_move, count, running). Resets ISHELD flag if held by flytrap (resets vf_hit and damage dice). | +| `passwd` | `int passwd()` | [MASTER ifdef] Prompts for wizard password, reads line with backspace/kill character handling, returns TRUE if strcmp(input, md_crypt(input, "mT")) matches PASSWD constant. | +| `show_map` | `void show_map()` | [MASTER ifdef] Displays full level map in hw window: iterates places[] drawing each cell with standout for non-real (F_REAL unset) cells, then shows "---More---" prompt. | -**Notes:** File-scope static data: tlist[] array in type_name() with h_list structures mapping type constants to descriptions. Functions marked [MASTER ifdef] are wizard-only debug/cheat commands (create_obj, passwd, show_map). The teleport() function is non-wizard and can be invoked by T_TELEP trap or other game mechanics. Uses macros: on() for flag testing, chat()/flat()/moat() for places[] access, INDEX() for place lookup, ISWEARING() for ring checks. References global variables: pack (player inventory), hero (player position), proom (player's current room), player (player THING), monsters[] (for name lookups in wizard feedback). +**Notes:** File-scope static data: tlist[] array in type_name() with h_list +structures mapping type constants to descriptions. Functions marked [MASTER +ifdef] are wizard-only debug/cheat commands (create_obj, passwd, show_map). The +teleport() function is non-wizard and can be invoked by T_TELEP trap or other +game mechanics. Uses macros: on() for flag testing, chat()/flat()/moat() for +places[] access, INDEX() for place lookup, ISWEARING() for ring checks. +References global variables: pack (player inventory), hero (player position), +proom (player's current room), player (player THING), monsters[] (for name +lookups in wizard feedback). ### things.c (714 lines) — Item naming and creation -| Function | Signature | Description | -|---|---|---| -| `inv_name` | `char* inv_name(THING *obj, bool drop)` | Formats item name for inventory display; handles all item types with modifiers like "(being worn)" or "(weapon in hand)"; writes to global `prbuf` | -| `drop` | `void drop()` | Player drops item from pack; checks floor availability, calls `leave_pack()`, attaches to level object list, updates display and global `amulet` flag | -| `dropcheck` | `bool dropcheck(THING *obj)` | Validates item can be dropped (checks cursed status and equipment status); handles ring stat adjustments like R_ADDSTR; returns FALSE if cursed | -| `new_thing` | `THING* new_thing()` | Creates random item for dungeon level; probabilistically selects type (potion/scroll/food/weapon/armor/ring/stick); initializes enchantments and cursing | -| `pick_one` | `int pick_one(struct obj_info *info, int nitems)` | Selects random item from cumulative probability table; returns index of selected item | -| `discovered` | `void discovered()` | Prompts player for which item type to list discoveries for; calls `print_disc()` and `end_line()` | -| `print_disc` | `void print_disc(char type)` | Lists all discovered items of given type (known or guessed); builds temporary THING for name formatting | -| `set_order` | `void set_order(int *order, int numthings)` | Fisher-Yates shuffle of array indices for randomized discovery list order | -| `add_line` | `char add_line(char *fmt, char *arg)` | Adds line to discovery window display with pagination; handles INV_SLOW vs INV_OVER modes; manages window creation and "Press space" prompts | -| `end_line` | `void end_line()` | Finalizes discovery list display; clears pagination state | -| `nothing` | `char* nothing(char type)` | Constructs "nothing found" message for specific item type | -| `nameit` | `void nameit(THING *obj, char *type, char *which, struct obj_info *op, char *(*prfunc)(THING *))` | Helper to format potion/ring/stick names with color/material and charge status via function pointer | -| `nullstr` | `char* nullstr(THING *ignored)` | Returns empty string; used as null function pointer in `nameit()` calls | -| `pr_list` | `void pr_list()` | MASTER-only: lists all possible items by type for wizard debugging | -| `pr_spec` | `void pr_spec(struct obj_info *info, int nitems)` | MASTER-only: prints all items in category with probability percentages | +| Function | Signature | Description | +| ------------ | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `inv_name` | `char* inv_name(THING *obj, bool drop)` | Formats item name for inventory display; handles all item types with modifiers like "(being worn)" or "(weapon in hand)"; writes to global `prbuf` | +| `drop` | `void drop()` | Player drops item from pack; checks floor availability, calls `leave_pack()`, attaches to level object list, updates display and global `amulet` flag | +| `dropcheck` | `bool dropcheck(THING *obj)` | Validates item can be dropped (checks cursed status and equipment status); handles ring stat adjustments like R_ADDSTR; returns FALSE if cursed | +| `new_thing` | `THING* new_thing()` | Creates random item for dungeon level; probabilistically selects type (potion/scroll/food/weapon/armor/ring/stick); initializes enchantments and cursing | +| `pick_one` | `int pick_one(struct obj_info *info, int nitems)` | Selects random item from cumulative probability table; returns index of selected item | +| `discovered` | `void discovered()` | Prompts player for which item type to list discoveries for; calls `print_disc()` and `end_line()` | +| `print_disc` | `void print_disc(char type)` | Lists all discovered items of given type (known or guessed); builds temporary THING for name formatting | +| `set_order` | `void set_order(int *order, int numthings)` | Fisher-Yates shuffle of array indices for randomized discovery list order | +| `add_line` | `char add_line(char *fmt, char *arg)` | Adds line to discovery window display with pagination; handles INV_SLOW vs INV_OVER modes; manages window creation and "Press space" prompts | +| `end_line` | `void end_line()` | Finalizes discovery list display; clears pagination state | +| `nothing` | `char* nothing(char type)` | Constructs "nothing found" message for specific item type | +| `nameit` | `void nameit(THING *obj, char *type, char *which, struct obj_info *op, char *(*prfunc)(THING *))` | Helper to format potion/ring/stick names with color/material and charge status via function pointer | +| `nullstr` | `char* nullstr(THING *ignored)` | Returns empty string; used as null function pointer in `nameit()` calls | +| `pr_list` | `void pr_list()` | MASTER-only: lists all possible items by type for wizard debugging | +| `pr_spec` | `void pr_spec(struct obj_info *info, int nitems)` | MASTER-only: prints all items in category with probability percentages | -**Notes:** Static file-scope variables `line_cnt`, `newpage`, `lastfmt`, `lastarg` manage discovery display pagination. Function-pointer callback pattern in `nameit()` (`nullstr`, `ring_num`, `charge_str`). +**Notes:** Static file-scope variables `line_cnt`, `newpage`, `lastfmt`, +`lastarg` manage discovery display pagination. Function-pointer callback pattern +in `nameit()` (`nullstr`, `ring_num`, `charge_str`). ### pack.c (504 lines) — Pack management and item selection interface -| Function | Signature | Description | -|---|---|---| -| `add_pack` | `void add_pack(THING *obj, bool silent)` | Adds item to player pack or picks up from floor; handles grouped items by incrementing count; keeps pack sorted by type; updates monster dest pointers and `amulet` flag | -| `pack_room` | `bool pack_room(bool from_floor, THING *obj)` | Validates pack space (`inpack` vs MAXPACK); removes from floor and updates display if successful | -| `leave_pack` | `THING* leave_pack(THING *obj, bool newobj, bool all)` | Removes item from pack; decrements `inpack`; splits stacks if not removing all; updates `pack_used` | -| `pack_char` | `char pack_char()` | Returns next unused pack slot letter (a-z); marks in `pack_used` array | -| `inventory` | `bool inventory(THING *list, int type)` | Lists pack items filtered by type; uses `add_line()` pagination; sets `n_objs` | -| `pick_up` | `void pick_up(char ch)` | Handles item pickup by type (gold vs objects); calls `add_pack()` or `money()` | -| `move_msg` | `void move_msg(THING *obj)` | Prints message for stepping onto object without picking it up (move_on mode) | -| `picky_inven` | `void picky_inven()` | Shows single item from pack by inventory letter | -| `get_item` | `THING* get_item(char *purpose, int type)` | Prompts player to select item from pack by letter or '*' for full list; handles ESCAPE and `again` repeat via `last_pick` | -| `money` | `void money(int value)` | Adds/subtracts gold from `purse`; updates display floor character | -| `floor_ch` | `char floor_ch()` | Returns appropriate floor display character based on room state and `show_floor()` | -| `floor_at` | `char floor_at()` | Returns character at hero position accounting for floor visibility | -| `reset_last` | `void reset_last()` | Resets saved last command state (`last_comm`, `last_dir`, `last_pick`) from backup copies | +| Function | Signature | Description | +| ------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `add_pack` | `void add_pack(THING *obj, bool silent)` | Adds item to player pack or picks up from floor; handles grouped items by incrementing count; keeps pack sorted by type; updates monster dest pointers and `amulet` flag | +| `pack_room` | `bool pack_room(bool from_floor, THING *obj)` | Validates pack space (`inpack` vs MAXPACK); removes from floor and updates display if successful | +| `leave_pack` | `THING* leave_pack(THING *obj, bool newobj, bool all)` | Removes item from pack; decrements `inpack`; splits stacks if not removing all; updates `pack_used` | +| `pack_char` | `char pack_char()` | Returns next unused pack slot letter (a-z); marks in `pack_used` array | +| `inventory` | `bool inventory(THING *list, int type)` | Lists pack items filtered by type; uses `add_line()` pagination; sets `n_objs` | +| `pick_up` | `void pick_up(char ch)` | Handles item pickup by type (gold vs objects); calls `add_pack()` or `money()` | +| `move_msg` | `void move_msg(THING *obj)` | Prints message for stepping onto object without picking it up (move_on mode) | +| `picky_inven` | `void picky_inven()` | Shows single item from pack by inventory letter | +| `get_item` | `THING* get_item(char *purpose, int type)` | Prompts player to select item from pack by letter or '*' for full list; handles ESCAPE and `again` repeat via `last_pick` | +| `money` | `void money(int value)` | Adds/subtracts gold from `purse`; updates display floor character | +| `floor_ch` | `char floor_ch()` | Returns appropriate floor display character based on room state and `show_floor()` | +| `floor_at` | `char floor_at()` | Returns character at hero position accounting for floor visibility | +| `reset_last` | `void reset_last()` | Resets saved last command state (`last_comm`, `last_dir`, `last_pick`) from backup copies | -**Notes:** Pack is a linked list anchored at global `pack` (macro for `player.t_pack`); pack letters stored in `o_packch`, allocation tracked in `pack_used[26]`. Grouped items (arrows etc.) stack by `o_group`. Uses `goto` for pack insertion flow. +**Notes:** Pack is a linked list anchored at global `pack` (macro for +`player.t_pack`); pack letters stored in `o_packch`, allocation tracked in +`pack_used[26]`. Grouped items (arrows etc.) stack by `o_group`. Uses `goto` for +pack insertion flow. ### potions.c (376 lines) — Potion effects -| Function | Signature | Description | -|---|---|---| -| `quaff` | `void quaff()` | Drinks potion from pack; switch dispatching all 14 potion types (confusion, LSD, poison, strength, see-invisible, healing, monster-find, magic-find, raise-level, extra-heal, haste, restore, blind, levitate); calls `do_pot()` for fuse-based effects; updates `pot_info` known/guess state | -| `is_magic` | `bool is_magic(THING *obj)` | Tests if object radiates magic (enchanted armor/weapons, or any potion/scroll/stick/ring/amulet) | -| `invis_on` | `void invis_on()` | Enables CANSEE flag; re-displays invisible monsters | -| `turn_see` | `bool turn_see(bool turn_off)` | Toggles SEEMONST magic monster detection; redisplays monsters; handles hallucination display | -| `seen_stairs` | `bool seen_stairs()` | Checks if stairs are visible on map, under hero, or held by a visible monster | -| `raise_level` | `void raise_level()` | Sets experience to next level threshold; calls `check_level()` | -| `do_pot` | `void do_pot(int type, bool knowit)` | Standard potion setup: marks known, sets player flag from `p_actions[]` table, starts fuse or lengthens if already active | +| Function | Signature | Description | +| ------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `quaff` | `void quaff()` | Drinks potion from pack; switch dispatching all 14 potion types (confusion, LSD, poison, strength, see-invisible, healing, monster-find, magic-find, raise-level, extra-heal, haste, restore, blind, levitate); calls `do_pot()` for fuse-based effects; updates `pot_info` known/guess state | +| `is_magic` | `bool is_magic(THING *obj)` | Tests if object radiates magic (enchanted armor/weapons, or any potion/scroll/stick/ring/amulet) | +| `invis_on` | `void invis_on()` | Enables CANSEE flag; re-displays invisible monsters | +| `turn_see` | `bool turn_see(bool turn_off)` | Toggles SEEMONST magic monster detection; redisplays monsters; handles hallucination display | +| `seen_stairs` | `bool seen_stairs()` | Checks if stairs are visible on map, under hero, or held by a visible monster | +| `raise_level` | `void raise_level()` | Sets experience to next level threshold; calls `check_level()` | +| `do_pot` | `void do_pot(int type, bool knowit)` | Standard potion setup: marks known, sets player flag from `p_actions[]` table, starts fuse or lengthens if already active | -**Notes:** `PACT` struct array `p_actions[]` maps potion types → player flag, cleanup daemon function pointer (`unconfuse`, `come_down`, `sight`, etc.), and duration. +**Notes:** `PACT` struct array `p_actions[]` maps potion types → player flag, +cleanup daemon function pointer (`unconfuse`, `come_down`, `sight`, etc.), and +duration. ### scrolls.c (330 lines) — Scroll effects -| Function | Signature | Description | -|---|---|---| -| `read_scroll` | `void read_scroll()` | Reads scroll from pack; switch on all 18 scroll types: confuse-monster (sets CANHUH on hero), magic-map (reveals level), hold-monster, sleep-self, enchant-armor, identify x5 varieties, scare-monster (message only when read), food-detection, teleport, enchant-weapon, create-monster, remove-curse, aggravate, protect-armor; calls `call_it()` to offer naming | -| `uncurse` | `void uncurse(THING *obj)` | Clears ISCURSED flag on item | +| Function | Signature | Description | +| ------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `read_scroll` | `void read_scroll()` | Reads scroll from pack; switch on all 18 scroll types: confuse-monster (sets CANHUH on hero), magic-map (reveals level), hold-monster, sleep-self, enchant-armor, identify x5 varieties, scare-monster (message only when read), food-detection, teleport, enchant-weapon, create-monster, remove-curse, aggravate, protect-armor; calls `call_it()` to offer naming | +| `uncurse` | `void uncurse(THING *obj)` | Clears ISCURSED flag on item | -**Notes:** S_MAP iterates the entire level updating passage/door/floor tiles into seen state; S_HOLD scans 3x3 neighborhood for monsters to hold; S_CREATE places monster adjacent; S_SCARE when *picked up* is handled in pack code (scroll on floor scares monsters via chase.c check). +**Notes:** S_MAP iterates the entire level updating passage/door/floor tiles +into seen state; S_HOLD scans 3x3 neighborhood for monsters to hold; S_CREATE +places monster adjacent; S_SCARE when _picked up_ is handled in pack code +(scroll on floor scares monsters via chase.c check). ### rings.c (205 lines) — Ring equipping and effects -| Function | Signature | Description | -|---|---|---| -| `ring_on` | `void ring_on()` | Equips ring on left or right hand; calls `gethand()` if needed; switch dispatch for immediate effects (R_ADDSTR strength bonus, R_SEEINVIS, R_AGGR awakening) | -| `ring_off` | `void ring_off()` | Removes ring; validates which hand; `dropcheck()` for curse validation | -| `gethand` | `int gethand()` | Prompts player for left or right hand; loops until valid L/R or ESCAPE | -| `ring_eat` | `int ring_eat(int hand)` | Returns extra food consumption for ring on given hand; static `uses[]` table maps ring type to hunger impact (R_DIGEST negative/saves, R_REGEN +2, etc.) | -| `ring_num` | `char* ring_num(THING *obj)` | Returns bonus string "[+n]" if known (protection/add-str/add-dam/add-hit rings) | +| Function | Signature | Description | +| ---------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ring_on` | `void ring_on()` | Equips ring on left or right hand; calls `gethand()` if needed; switch dispatch for immediate effects (R_ADDSTR strength bonus, R_SEEINVIS, R_AGGR awakening) | +| `ring_off` | `void ring_off()` | Removes ring; validates which hand; `dropcheck()` for curse validation | +| `gethand` | `int gethand()` | Prompts player for left or right hand; loops until valid L/R or ESCAPE | +| `ring_eat` | `int ring_eat(int hand)` | Returns extra food consumption for ring on given hand; static `uses[]` table maps ring type to hunger impact (R_DIGEST negative/saves, R_REGEN +2, etc.) | +| `ring_num` | `char* ring_num(THING *obj)` | Returns bonus string "[+n]" if known (protection/add-str/add-dam/add-hit rings) | -**Notes:** Static `uses[]` array encodes per-ring metabolism cost. Rings worn tracked in `cur_ring[LEFT]`/`cur_ring[RIGHT]`. +**Notes:** Static `uses[]` array encodes per-ring metabolism cost. Rings worn +tracked in `cur_ring[LEFT]`/`cur_ring[RIGHT]`. ### sticks.c (432 lines) — Wand and staff zapping -| Function | Signature | Description | -|---|---|---| -| `fix_stick` | `void fix_stick(THING *cur)` | Initializes wand/staff damage strings and charges; staffs 2x3 melee damage, wands 1x1; WS_LIGHT gets 10-19 charges, others 3-7 | -| `do_zap` | `void do_zap()` | Zaps wand in aimed direction; switch over all 14 stick types: light (illuminate room), drain-life, invisibility (make monster invisible), polymorph, teleport-away, teleport-to, cancellation, magic missile, haste-monster, slow-monster, lightning/fire/cold bolts, nop; decrements charges | -| `drain` | `void drain()` | Drains half hero's HP and spreads equal damage across all monsters in room/passage; static `drainee[]` target array | -| `fire_bolt` | `void fire_bolt(coord *start, coord *dir, char *name)` | Fires elemental bolt: travels BOLT_LENGTH, bounces off walls (reversing delta components), damages monsters (with save for half... actually save to avoid), can hit hero on bounce-back; animates with directional characters (| - / \\); dragons immune to flame | -| `charge_str` | `char* charge_str(THING *obj)` | Returns "[n charges]" string if known | +| Function | Signature | Description | +| ------------ | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `fix_stick` | `void fix_stick(THING *cur)` | Initializes wand/staff damage strings and charges; staffs 2x3 melee damage, wands 1x1; WS_LIGHT gets 10-19 charges, others 3-7 | +| `do_zap` | `void do_zap()` | Zaps wand in aimed direction; switch over all 14 stick types: light (illuminate room), drain-life, invisibility (make monster invisible), polymorph, teleport-away, teleport-to, cancellation, magic missile, haste-monster, slow-monster, lightning/fire/cold bolts, nop; decrements charges | +| `drain` | `void drain()` | Drains half hero's HP and spreads equal damage across all monsters in room/passage; static `drainee[]` target array | +| `fire_bolt` | `void fire_bolt(coord *start, coord *dir, char *name)` | Fires elemental bolt: travels BOLT_LENGTH, bounces off walls (reversing delta components), damages monsters (with save for half... actually save to avoid), can hit hero on bounce-back; animates with directional characters ( | - / \\); dragons immune to flame | +| `charge_str` | `char* charge_str(THING *obj)` | Returns "[n charges]" string if known | -**Notes:** `fire_bolt` uses static `bolt` THING and `spotpos[]` path array; bounce logic flips direction on wall hit. WS_POLYMORPH preserves monster's pack through transformation. Used both by player zaps and dragon breath ("flame"). +**Notes:** `fire_bolt` uses static `bolt` THING and `spotpos[]` path array; +bounce logic flips direction on wall hit. WS_POLYMORPH preserves monster's pack +through transformation. Used both by player zaps and dragon breath ("flame"). ### weapons.c (289 lines) — Weapon initialization and throwing -| Function | Signature | Description | -|---|---|---| -| `missile` | `void missile(int ydelta, int xdelta)` | Throws selected weapon: validates, leaves pack (splitting stacks), animates via `do_motion()`, tests hit with `hit_monster()`, falls to ground on miss | -| `do_motion` | `void do_motion(THING *obj, int ydelta, int xdelta)` | Animates projectile across screen one cell at a time until blocked (wall/door/monster) | -| `fall` | `void fall(THING *obj, bool pr)` | Places fallen projectile on ground via `fallpos()`; attaches to `lvl_obj` or discards if no space | -| `init_weapon` | `void init_weapon(THING *weap, int which)` | Sets weapon properties from static `init_dam[]` table (wield damage, hurl damage, launcher, flags); arrows/darts/shurikens get count 8-15 and shared group id | -| `hit_monster` | `int hit_monster(int y, int x, THING *obj)` | Wrapper calling `fight()` for missile combat at position | -| `num` | `char* num(int n1, int n2, char type)` | Formats bonus as "+h,+d" / "+a" strings for inventory | -| `wield` | `void wield()` | Equips weapon; validates not wearing/cursed current; updates `cur_weapon` | -| `fallpos` | `bool fallpos(coord *pos, coord *newpos)` | Finds random empty adjacent square (9 cells) for dropped item; reservoir-samples among valid spots | +| Function | Signature | Description | +| ------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `missile` | `void missile(int ydelta, int xdelta)` | Throws selected weapon: validates, leaves pack (splitting stacks), animates via `do_motion()`, tests hit with `hit_monster()`, falls to ground on miss | +| `do_motion` | `void do_motion(THING *obj, int ydelta, int xdelta)` | Animates projectile across screen one cell at a time until blocked (wall/door/monster) | +| `fall` | `void fall(THING *obj, bool pr)` | Places fallen projectile on ground via `fallpos()`; attaches to `lvl_obj` or discards if no space | +| `init_weapon` | `void init_weapon(THING *weap, int which)` | Sets weapon properties from static `init_dam[]` table (wield damage, hurl damage, launcher, flags); arrows/darts/shurikens get count 8-15 and shared group id | +| `hit_monster` | `int hit_monster(int y, int x, THING *obj)` | Wrapper calling `fight()` for missile combat at position | +| `num` | `char* num(int n1, int n2, char type)` | Formats bonus as "+h,+d" / "+a" strings for inventory | +| `wield` | `void wield()` | Equips weapon; validates not wearing/cursed current; updates `cur_weapon` | +| `fallpos` | `bool fallpos(coord *pos, coord *newpos)` | Finds random empty adjacent square (9 cells) for dropped item; reservoir-samples among valid spots | -**Notes:** Static `init_dam[]` table: mace 2x4, sword 3x4, bow 1x1, arrow 1x1/2x3 (launched by bow), dagger 1x6/1x4, two-handed sword 4x4, dart 1x1/1x3, shuriken 1x2/2x4, spear 2x3/1x6. Global `group` counter assigns stack ids. +**Notes:** Static `init_dam[]` table: mace 2x4, sword 3x4, bow 1x1, arrow +1x1/2x3 (launched by bow), dagger 1x6/1x4, two-handed sword 4x4, dart 1x1/1x3, +shuriken 1x2/2x4, spear 2x3/1x6. Global `group` counter assigns stack ids. ### armor.c (90 lines) — Armor wearing -| Function | Signature | Description | -|---|---|---| -| `wear` | `void wear()` | Equips armor from pack; rejects if already wearing; sets ISKNOW; calls `waste_time()` (1 turn) | -| `take_off` | `void take_off()` | Removes armor; validates exists and not cursed via `dropcheck()` | +| Function | Signature | Description | +| ------------ | ------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `wear` | `void wear()` | Equips armor from pack; rejects if already wearing; sets ISKNOW; calls `waste_time()` (1 turn) | +| `take_off` | `void take_off()` | Removes armor; validates exists and not cursed via `dropcheck()` | | `waste_time` | `void waste_time()` | Runs one turn of daemons/fuses (BEFORE and AFTER) without player action — lets hunger/healing tick during multi-turn actions | ### misc.c (598 lines) — Look/display update, direction input, utilities -| Function | Signature | Description | -|---|---|---| -| `look` | `void look(bool wakeup)` | Core visibility update: redraws area around player (3x3 in corridors/dark, or whole room), erases old lamp area, handles door-stop run termination logic, wakes monsters if wakeup, applies hallucination via `trip_ch()`; maintains `oldpos`/`oldrp` | -| `trip_ch` | `int trip_ch(int y, int x, int ch)` | If hallucinating, replaces item characters with random `rnd_thing()` character | -| `erase_lamp` | `void erase_lamp(coord *pos, struct room *rp)` | Erases 3x3 lamp-lit area around old position in dark rooms | -| `show_floor` | `bool show_floor()` | Returns whether floor under hero should be shown (dark room + not blind + see_floor option) | -| `find_obj` | `THING* find_obj(int y, int x)` | Linear search of `lvl_obj` for object at position (fatal error if none in MASTER) | -| `eat` | `void eat()` | Eats food from pack; sets `food_left` to HUNGERTIME (with random reduction); 30% chance fruit vs ration message; may grant experience | -| `check_level` | `void check_level()` | Checks experience against `e_levels[]`; on level-up increases HP by roll(diff,10) and messages "Welcome to level N" | -| `chg_str` | `void chg_str(int amt)` | Modifies strength with R_SUSTSTR/ring accounting; tracks `max_stats.s_str` | -| `add_str` | `void add_str(str_t *sp, int amt)` | Adds to strength value with bounds (min 3, max 31) | -| `add_haste` | `bool add_haste(bool potion)` | Adds ISHASTE; if already hasted, faint 1-8 turns instead; starts `nohaste` fuse if potion | -| `aggravate` | `void aggravate()` | Calls `runto()` on all monsters — everything wakes and hunts | -| `vowelstr` | `char* vowelstr(char *str)` | Returns "n" if string starts with vowel (for "a"/"an") | -| `is_current` | `bool is_current(THING *obj)` | Checks if item is currently equipped; messages "in use" | -| `get_dir` | `bool get_dir()` | Prompts for direction key (hjklyubn); sets `delta`/`dir_ch`; handles `again` repeat; randomizes if confused/hallucinating; FALSE on ESCAPE | -| `sign` | `int sign(int nm)` | Returns -1/0/1 | -| `spread` | `int spread(int nm)` | Returns nm ±10% random spread (used for durations) | -| `call_it` | `void call_it(struct obj_info *info)` | Prompts player to name an unidentified item; stores in `oi_guess` | -| `rnd_thing` | `char rnd_thing()` | Picks random thing display character (includes AMULET only at deep levels); static `thing_list[]` | -| `choose_str` | `char* choose_str(char *ts, char *ns)` | Returns first string if hallucinating, else second | +| Function | Signature | Description | +| ------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `look` | `void look(bool wakeup)` | Core visibility update: redraws area around player (3x3 in corridors/dark, or whole room), erases old lamp area, handles door-stop run termination logic, wakes monsters if wakeup, applies hallucination via `trip_ch()`; maintains `oldpos`/`oldrp` | +| `trip_ch` | `int trip_ch(int y, int x, int ch)` | If hallucinating, replaces item characters with random `rnd_thing()` character | +| `erase_lamp` | `void erase_lamp(coord *pos, struct room *rp)` | Erases 3x3 lamp-lit area around old position in dark rooms | +| `show_floor` | `bool show_floor()` | Returns whether floor under hero should be shown (dark room + not blind + see_floor option) | +| `find_obj` | `THING* find_obj(int y, int x)` | Linear search of `lvl_obj` for object at position (fatal error if none in MASTER) | +| `eat` | `void eat()` | Eats food from pack; sets `food_left` to HUNGERTIME (with random reduction); 30% chance fruit vs ration message; may grant experience | +| `check_level` | `void check_level()` | Checks experience against `e_levels[]`; on level-up increases HP by roll(diff,10) and messages "Welcome to level N" | +| `chg_str` | `void chg_str(int amt)` | Modifies strength with R_SUSTSTR/ring accounting; tracks `max_stats.s_str` | +| `add_str` | `void add_str(str_t *sp, int amt)` | Adds to strength value with bounds (min 3, max 31) | +| `add_haste` | `bool add_haste(bool potion)` | Adds ISHASTE; if already hasted, faint 1-8 turns instead; starts `nohaste` fuse if potion | +| `aggravate` | `void aggravate()` | Calls `runto()` on all monsters — everything wakes and hunts | +| `vowelstr` | `char* vowelstr(char *str)` | Returns "n" if string starts with vowel (for "a"/"an") | +| `is_current` | `bool is_current(THING *obj)` | Checks if item is currently equipped; messages "in use" | +| `get_dir` | `bool get_dir()` | Prompts for direction key (hjklyubn); sets `delta`/`dir_ch`; handles `again` repeat; randomizes if confused/hallucinating; FALSE on ESCAPE | +| `sign` | `int sign(int nm)` | Returns -1/0/1 | +| `spread` | `int spread(int nm)` | Returns nm ±10% random spread (used for durations) | +| `call_it` | `void call_it(struct obj_info *info)` | Prompts player to name an unidentified item; stores in `oi_guess` | +| `rnd_thing` | `char rnd_thing()` | Picks random thing display character (includes AMULET only at deep levels); static `thing_list[]` | +| `choose_str` | `char* choose_str(char *ts, char *ns)` | Returns first string if hallucinating, else second | -**Notes:** `look()` is the heart of FOV/display maintenance and the trickiest function here (passage-count door-stop logic using `sumhero`/`diffhero` diagonal tests). Static `last_dt` in `get_dir()` for repeats. +**Notes:** `look()` is the heart of FOV/display maintenance and the trickiest +function here (passage-count door-stop logic using `sumhero`/`diffhero` diagonal +tests). Static `last_dt` in `get_dir()` for repeats. ### daemon.c (182 lines) — Daemon and fuse scheduler -| Function | Signature | Description | -|---|---|---| -| `d_slot` | `struct delayed_action* d_slot()` | Finds first EMPTY slot in `d_list[20]` | -| `find_slot` | `struct delayed_action* find_slot(void (*func)())` | Finds occupied slot by function pointer | -| `start_daemon` | `void start_daemon(void (*func)(int), int arg, int type)` | Registers repeating daemon (d_time = DAEMON sentinel) | -| `kill_daemon` | `void kill_daemon(void (*func)())` | Removes daemon by function pointer | -| `do_daemons` | `void do_daemons(int flag)` | Runs all daemons matching BEFORE/AFTER phase | -| `fuse` | `void fuse(void (*func)(int), int arg, int time, int type)` | Registers one-shot countdown fuse | -| `lengthen` | `void lengthen(void (*func)(), int xtime)` | Extends existing fuse's remaining time | -| `extinguish` | `void extinguish(void (*func)())` | Cancels fuse by function pointer | -| `do_fuses` | `void do_fuses(int flag)` | Decrements matching fuses; fires and empties any reaching zero | +| Function | Signature | Description | +| -------------- | ----------------------------------------------------------- | -------------------------------------------------------------- | +| `d_slot` | `struct delayed_action* d_slot()` | Finds first EMPTY slot in `d_list[20]` | +| `find_slot` | `struct delayed_action* find_slot(void (*func)())` | Finds occupied slot by function pointer | +| `start_daemon` | `void start_daemon(void (*func)(int), int arg, int type)` | Registers repeating daemon (d_time = DAEMON sentinel) | +| `kill_daemon` | `void kill_daemon(void (*func)())` | Removes daemon by function pointer | +| `do_daemons` | `void do_daemons(int flag)` | Runs all daemons matching BEFORE/AFTER phase | +| `fuse` | `void fuse(void (*func)(int), int arg, int time, int type)` | Registers one-shot countdown fuse | +| `lengthen` | `void lengthen(void (*func)(), int xtime)` | Extends existing fuse's remaining time | +| `extinguish` | `void extinguish(void (*func)())` | Cancels fuse by function pointer | +| `do_fuses` | `void do_fuses(int flag)` | Decrements matching fuses; fires and empties any reaching zero | -**Notes:** `d_list[MAXDAEMONS=20]` of `struct delayed_action {type, func ptr, arg, time}`. Function pointers are the identity of daemons/fuses — the Go port must replace this with named identifiers (also required by save format, which already maps func ptrs to small ints). +**Notes:** `d_list[MAXDAEMONS=20]` of +`struct delayed_action {type, func ptr, arg, time}`. Function pointers are the +identity of daemons/fuses — the Go port must replace this with named identifiers +(also required by save format, which already maps func ptrs to small ints). ### daemons.c (296 lines) — Daemon and fuse callbacks -| Function | Signature | Description | -|---|---|---| -| `doctor` | `void doctor()` | Healing daemon: heals based on level and `quiet` rest counter; R_REGEN ring bonus; caps at max_hp | -| `swander` | `void swander()` | Fuse that starts the `rollwand` daemon | -| `rollwand` | `void rollwand()` | Every turn after 4: 1-in-6 roll to spawn wandering monster via `wanderer()`, then re-fuse `swander` | -| `unconfuse` | `void unconfuse()` | Fuse: clears ISHUH; message | -| `unsee` | `void unsee()` | Fuse: clears CANSEE; re-hides invisible monsters | -| `sight` | `void sight()` | Fuse: cures blindness; re-lights room | -| `nohaste` | `void nohaste()` | Fuse: clears ISHASTE | -| `stomach` | `void stomach()` | Hunger daemon: decrements `food_left` (+1 per ring worn, adjusted by ring_eat); thresholds → hungry (300)/weak (150)/faint (0 = random paralysis, starvation death) | -| `come_down` | `void come_down()` | Fuse: ends hallucination; kills `visuals` daemon; redraws everything correctly | -| `visuals` | `void visuals()` | Hallucination daemon: continuously randomizes displayed item/monster glyphs | -| `land` | `void land()` | Fuse: ends levitation | +| Function | Signature | Description | +| ----------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `doctor` | `void doctor()` | Healing daemon: heals based on level and `quiet` rest counter; R_REGEN ring bonus; caps at max_hp | +| `swander` | `void swander()` | Fuse that starts the `rollwand` daemon | +| `rollwand` | `void rollwand()` | Every turn after 4: 1-in-6 roll to spawn wandering monster via `wanderer()`, then re-fuse `swander` | +| `unconfuse` | `void unconfuse()` | Fuse: clears ISHUH; message | +| `unsee` | `void unsee()` | Fuse: clears CANSEE; re-hides invisible monsters | +| `sight` | `void sight()` | Fuse: cures blindness; re-lights room | +| `nohaste` | `void nohaste()` | Fuse: clears ISHASTE | +| `stomach` | `void stomach()` | Hunger daemon: decrements `food_left` (+1 per ring worn, adjusted by ring_eat); thresholds → hungry (300)/weak (150)/faint (0 = random paralysis, starvation death) | +| `come_down` | `void come_down()` | Fuse: ends hallucination; kills `visuals` daemon; redraws everything correctly | +| `visuals` | `void visuals()` | Hallucination daemon: continuously randomizes displayed item/monster glyphs | +| `land` | `void land()` | Fuse: ends levitation | -**Notes:** Static `between` counter in `rollwand()`. These 11 functions are exactly the set the save format maps to integer ids 1-9 (+2). +**Notes:** Static `between` counter in `rollwand()`. These 11 functions are +exactly the set the save format maps to integer ids 1-9 (+2). ### list.c (114 lines) — Doubly-linked list allocation -| Function | Signature | Description | -|---|---|---| -| `_detach` | `void _detach(THING **list, THING *item)` | Removes item from linked list (head pointer updated via double pointer) | -| `_attach` | `void _attach(THING **list, THING *item)` | Pushes item onto head of list | -| `_free_list` | `void _free_list(THING **ptr)` | Discards entire list | -| `discard` | `void discard(THING *item)` | free()s one THING | -| `new_item` | `THING* new_item()` | calloc()s a zeroed THING with NULL links | +| Function | Signature | Description | +| ------------ | ----------------------------------------- | ----------------------------------------------------------------------- | +| `_detach` | `void _detach(THING **list, THING *item)` | Removes item from linked list (head pointer updated via double pointer) | +| `_attach` | `void _attach(THING **list, THING *item)` | Pushes item onto head of list | +| `_free_list` | `void _free_list(THING **ptr)` | Discards entire list | +| `discard` | `void discard(THING *item)` | free()s one THING | +| `new_item` | `THING* new_item()` | calloc()s a zeroed THING with NULL links | -**Notes:** Macro API `attach(a,b)`/`detach(a,b)`/`free_list(a)` pass `&list`. In Go this becomes slices or container/list — the port will use slices of typed pointers. +**Notes:** Macro API `attach(a,b)`/`detach(a,b)`/`free_list(a)` pass `&list`. In +Go this becomes slices or container/list — the port will use slices of typed +pointers. ### fight.c (687 lines) — Combat system -| Function | Signature | Description | -|---|---|---| -| `fight` | `int fight(coord *mp, THING *weap, bool thrown)` | Player attacks monster at coordinates with optional weapon. Xeroc disguise revelation, hit/miss messaging, confuse-monster ring/scroll effect (CANHUH), monster death via `killed()` | -| `attack` | `int attack(THING *mp)` | Monster attacks player. Handles damage, player death, and per-monster special effects: A=rust armor, I=freeze (no_command), R=weaken strength (poison, save or -1 STR), W=wraith XP drain, V=vampire maxhp drain, F=flytrap hold (accumulating vf_hit damage), L=leprechaun gold steal, N=nymph item steal | -| `set_mname` | `char *set_mname(THING *tp)` | Returns display name: "something" if unseen, random monster if hallucinating, else "the "; static buffer | -| `swing` | `int swing(int at_lvl, int op_arm, int wplus)` | d20 to-hit roll: `res + wplus >= (20 - at_lvl) - op_arm` | -| `roll_em` | `bool roll_em(THING *thatt, THING *thdef, THING *weap, bool hurl)` | Rolls damage for an attack. Parses damage strings "NxM/NxM", strength to-hit/damage bonus tables, launcher matching for hurled ammo, ring add-hit/add-dam for player; applies to defender's `s_hpt` (min 0). Returns true if any swing hit | -| `prname` | `char *prname(char *mname, bool upper)` | Combatant name: "you" for NULL, optionally capitalized; static buffer | -| `thunk` | `void thunk(THING *weap, char *mname, bool noend)` | "the dagger hits " message for thrown weapons | -| `hit` | `void hit(char *er, char *ee, bool noend)` | Prints hit message from `h_names[]` variants (terse-aware) | -| `miss` | `void miss(char *er, char *ee, bool noend)` | Prints miss message from `m_names[]` variants | -| `bounce` | `void bounce(THING *weap, char *mname, bool noend)` | "the dagger misses" message for thrown weapons | -| `remove_mon` | `void remove_mon(coord *mp, THING *tp, bool waskill)` | Removes monster from level: drops its pack (fall if killed), clears map cell + moat, detaches from mlist, resets to_death/kamikaze if it was target | -| `killed` | `void killed(THING *tp, bool pr)` | Awards experience, special deaths (F releases held hero, L drops gold), removes monster, "defeated the X" message, `check_level()` | +| Function | Signature | Description | +| ------------ | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `fight` | `int fight(coord *mp, THING *weap, bool thrown)` | Player attacks monster at coordinates with optional weapon. Xeroc disguise revelation, hit/miss messaging, confuse-monster ring/scroll effect (CANHUH), monster death via `killed()` | +| `attack` | `int attack(THING *mp)` | Monster attacks player. Handles damage, player death, and per-monster special effects: A=rust armor, I=freeze (no_command), R=weaken strength (poison, save or -1 STR), W=wraith XP drain, V=vampire maxhp drain, F=flytrap hold (accumulating vf_hit damage), L=leprechaun gold steal, N=nymph item steal | +| `set_mname` | `char *set_mname(THING *tp)` | Returns display name: "something" if unseen, random monster if hallucinating, else "the "; static buffer | +| `swing` | `int swing(int at_lvl, int op_arm, int wplus)` | d20 to-hit roll: `res + wplus >= (20 - at_lvl) - op_arm` | +| `roll_em` | `bool roll_em(THING *thatt, THING *thdef, THING *weap, bool hurl)` | Rolls damage for an attack. Parses damage strings "NxM/NxM", strength to-hit/damage bonus tables, launcher matching for hurled ammo, ring add-hit/add-dam for player; applies to defender's `s_hpt` (min 0). Returns true if any swing hit | +| `prname` | `char *prname(char *mname, bool upper)` | Combatant name: "you" for NULL, optionally capitalized; static buffer | +| `thunk` | `void thunk(THING *weap, char *mname, bool noend)` | "the dagger hits " message for thrown weapons | +| `hit` | `void hit(char *er, char *ee, bool noend)` | Prints hit message from `h_names[]` variants (terse-aware) | +| `miss` | `void miss(char *er, char *ee, bool noend)` | Prints miss message from `m_names[]` variants | +| `bounce` | `void bounce(THING *weap, char *mname, bool noend)` | "the dagger misses" message for thrown weapons | +| `remove_mon` | `void remove_mon(coord *mp, THING *tp, bool waskill)` | Removes monster from level: drops its pack (fall if killed), clears map cell + moat, detaches from mlist, resets to_death/kamikaze if it was target | +| `killed` | `void killed(THING *tp, bool pr)` | Awards experience, special deaths (F releases held hero, L drops gold), removes monster, "defeated the X" message, `check_level()` | -**Notes:** Static strength adjustment tables: `str_plus[]` (to-hit bonus for STR 3-31: -7..+3) and `add_dam[]` (damage bonus: -7..+6). `h_names[]`/`m_names[]` message variant arrays. Careful NULL discipline where L/N monsters remove themselves during their own attack (use-after-free avoidance). +**Notes:** Static strength adjustment tables: `str_plus[]` (to-hit bonus for STR +3-31: -7..+3) and `add_dam[]` (damage bonus: -7..+6). `h_names[]`/`m_names[]` +message variant arrays. Careful NULL discipline where L/N monsters remove +themselves during their own attack (use-after-free avoidance). ### rip.c (449 lines) — Death, victory, and scoreboard -| Function | Signature | Description | -|---|---|---| -| `score` | `void score(int amount, int flags, char monst)` | Scoreboard maintenance: reads top-ten via `rd_score()`, inserts player entry if it qualifies (one score per uid unless allscore), prints ranked list with standout for new entry, writes back via `wr_score()` under `lock_sc()` | -| `death` | `void death(char monst)` | Player death: purse -= 10%, draws tombstone ASCII art with name/gold/killer/year, calls `score()`, exits | -| `center` | `int center(char *str)` | Column to center a string on the tombstone | -| `total_winner` | `void total_winner()` | Victory: banner, itemized inventory with gold values (worth calculations per item type: weapons +bonus*100, armor by AC delta, unidentified halved, etc.), score(flags=2), exit | -| `killname` | `char *killname(char monst, bool doart)` | Death cause → string: A-Z monster names; lowercase specials from `nlist[]` (a=arrow, b=bolt, d=dart, h=hypothermia, s=starvation) | -| `death_monst` | `char death_monst()` | Random death cause for the -d demo flag | +| Function | Signature | Description | +| -------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `score` | `void score(int amount, int flags, char monst)` | Scoreboard maintenance: reads top-ten via `rd_score()`, inserts player entry if it qualifies (one score per uid unless allscore), prints ranked list with standout for new entry, writes back via `wr_score()` under `lock_sc()` | +| `death` | `void death(char monst)` | Player death: purse -= 10%, draws tombstone ASCII art with name/gold/killer/year, calls `score()`, exits | +| `center` | `int center(char *str)` | Column to center a string on the tombstone | +| `total_winner` | `void total_winner()` | Victory: banner, itemized inventory with gold values (worth calculations per item type: weapons +bonus*100, armor by AC delta, unidentified halved, etc.), score(flags=2), exit | +| `killname` | `char *killname(char monst, bool doart)` | Death cause → string: A-Z monster names; lowercase specials from `nlist[]` (a=arrow, b=bolt, d=dart, h=hypothermia, s=starvation) | +| `death_monst` | `char death_monst()` | Random death cause for the -d demo flag | -**Notes:** Static `rip[]` tombstone art; `nlist[]` special death causes. SCORE fields: uid, score, flags (0=killed 1=quit 2=winner 3=killed-with-amulet), monster, name, level, time. Scoreboard file XOR-encrypted with same scheme as saves. +**Notes:** Static `rip[]` tombstone art; `nlist[]` special death causes. SCORE +fields: uid, score, flags (0=killed 1=quit 2=winner 3=killed-with-amulet), +monster, name, level, time. Scoreboard file XOR-encrypted with same scheme as +saves. ### save.c (391 lines) — Save/restore and encrypted I/O -| Function | Signature | Description | -|---|---|---| -| `save_game` | `void save_game()` | Interactive save: prompt for filename (default `file_name`), confirm overwrite, open and `save_file()`, exit | -| `auto_save` | `void auto_save(int sig)` | SIGHUP/SIGTERM handler: silently saves to `file_name` and exits | -| `save_file` | `void save_file(FILE *savef)` | Writes version string + "LINES x COLS" header (encrypted), then `rs_save_file()`; chmod 0400; exits | -| `restore` | `bool restore(char *file, char **envp)` | Validates version + screen size, rejects symlinks/multi-link files (anti-cheat), deletes save file (no re-use), `rs_restore_file()`, re-enters `playit()` | -| `encwrite` | `size_t encwrite(char *start, size_t size, FILE *outf)` | XOR-encrypts stream: byte ^ encstr[i] ^ statlist[j] ^ feedback byte, cycling both key strings | -| `encread` | `size_t encread(char *start, size_t size, FILE *inf)` | Mirror decryption of `encwrite` | -| `rd_score` | `void rd_score(SCORE *top_ten)` | Reads/decrypts scoreboard entries (name string + sscanf'd numeric line) | -| `wr_score` | `void wr_score(SCORE *top_ten)` | Writes/encrypts scoreboard entries | +| Function | Signature | Description | +| ----------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `save_game` | `void save_game()` | Interactive save: prompt for filename (default `file_name`), confirm overwrite, open and `save_file()`, exit | +| `auto_save` | `void auto_save(int sig)` | SIGHUP/SIGTERM handler: silently saves to `file_name` and exits | +| `save_file` | `void save_file(FILE *savef)` | Writes version string + "LINES x COLS" header (encrypted), then `rs_save_file()`; chmod 0400; exits | +| `restore` | `bool restore(char *file, char **envp)` | Validates version + screen size, rejects symlinks/multi-link files (anti-cheat), deletes save file (no re-use), `rs_restore_file()`, re-enters `playit()` | +| `encwrite` | `size_t encwrite(char *start, size_t size, FILE *outf)` | XOR-encrypts stream: byte ^ encstr[i] ^ statlist[j] ^ feedback byte, cycling both key strings | +| `encread` | `size_t encread(char *start, size_t size, FILE *inf)` | Mirror decryption of `encwrite` | +| `rd_score` | `void rd_score(SCORE *top_ten)` | Reads/decrypts scoreboard entries (name string + sscanf'd numeric line) | +| `wr_score` | `void wr_score(SCORE *top_ten)` | Writes/encrypts scoreboard entries | -**Notes:** Save anti-cheat: file deleted on restore, symlink/hardlink rejected. Go port decision point: keep byte-compatible format vs. clean format (see Part 2). +**Notes:** Save anti-cheat: file deleted on restore, symlink/hardlink rejected. +Go port decision point: keep byte-compatible format vs. clean format (see Part +2). ### state.c (2135 lines) — Save-state serialization layer -Machine-generated-style marshaling of every global into an endian-stable, XOR-encrypted binary stream with per-block magic markers (0xABCD0001-style RSID_* ids). +Machine-generated-style marshaling of every global into an endian-stable, +XOR-encrypted binary stream with per-block magic markers (0xABCD0001-style +RSID_* ids). -| Family | Functions | Notes | -|---|---|---| -| Primitives | `rs_write/rs_read` (raw), `_int`, `_short`, `_uint`, `_chars`, `_boolean(s)`, `_shorts`, `_marker` | Little-endian normalized; count-prefixed arrays; `format_error`/`read_error`/`write_error` flags | -| Strings | `_string`, `_new_string`, `_string_index`, `_strings` | Length-prefixed; `_string_index` stores index into a master table (e.g. rainbow colors) instead of text | -| Game types | `_coord`, `_str_t`, `_stats`, `_room(s)`, `_monsters`, `_obj_info`, `_daemons`, `_window` | `_daemons` maps function pointers ↔ integer ids (rollwand=1, doctor=2, stomach=3, runners=4, swander=5, nohaste=6, unconfuse=7, unsee=8, sight=9); `_window` dumps curses screen contents cell by cell | -| THINGs | `_object`, `_object_list`, `_object_reference`, `_thing`, `_thing_list`, `rs_fix_thing(_list)` | Lists serialized with size prefix and rebuilt; intra-structure pointers (cur_weapon, monster t_dest) stored as list indices; monster chase targets encoded as (kind, index) pairs and fixed up post-load via `t_reserved` | -| Map | `_places` | All MAXLINES*MAXCOLS cells: ch, flags, monster reference index | -| Helpers | `get_list_item`, `find_list_ptr`, `list_size`, `find_room_coord`, `find_thing_coord`, `find_object_coord` | Pointer→index translation for serialization | -| Top level | `rs_save_file`, `rs_restore_file` | Fixed ordered sequence of ALL globals: 27 bools, ~25 ints, strings/name arrays, coords, player THING, equipment references, lvl_obj list, mlist, places[], rooms/passages, monsters[], all 7 obj_info tables, d_list daemons, and the curses screen | +| Family | Functions | Notes | +| ---------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Primitives | `rs_write/rs_read` (raw), `_int`, `_short`, `_uint`, `_chars`, `_boolean(s)`, `_shorts`, `_marker` | Little-endian normalized; count-prefixed arrays; `format_error`/`read_error`/`write_error` flags | +| Strings | `_string`, `_new_string`, `_string_index`, `_strings` | Length-prefixed; `_string_index` stores index into a master table (e.g. rainbow colors) instead of text | +| Game types | `_coord`, `_str_t`, `_stats`, `_room(s)`, `_monsters`, `_obj_info`, `_daemons`, `_window` | `_daemons` maps function pointers ↔ integer ids (rollwand=1, doctor=2, stomach=3, runners=4, swander=5, nohaste=6, unconfuse=7, unsee=8, sight=9); `_window` dumps curses screen contents cell by cell | +| THINGs | `_object`, `_object_list`, `_object_reference`, `_thing`, `_thing_list`, `rs_fix_thing(_list)` | Lists serialized with size prefix and rebuilt; intra-structure pointers (cur_weapon, monster t_dest) stored as list indices; monster chase targets encoded as (kind, index) pairs and fixed up post-load via `t_reserved` | +| Map | `_places` | All MAXLINES*MAXCOLS cells: ch, flags, monster reference index | +| Helpers | `get_list_item`, `find_list_ptr`, `list_size`, `find_room_coord`, `find_thing_coord`, `find_object_coord` | Pointer→index translation for serialization | +| Top level | `rs_save_file`, `rs_restore_file` | Fixed ordered sequence of ALL globals: 27 bools, ~25 ints, strings/name arrays, coords, player THING, equipment references, lvl_obj list, mlist, places[], rooms/passages, monsters[], all 7 obj_info tables, d_list daemons, and the curses screen | -**Notes:** This file exists because C can't reflect. The Go port replaces nearly all of it with `encoding/gob` (or JSON) over a serializable snapshot struct — only the pointer→index encoding of references (equipment, chase targets) needs explicit code. +**Notes:** This file exists because C can't reflect. The Go port replaces nearly +all of it with `encoding/gob` (or JSON) over a serializable snapshot struct — +only the pointer→index encoding of references (equipment, chase targets) needs +explicit code. ### options.c (502 lines) — Options menu and ROGUEOPTS parsing -| Function | Signature | Description | -|---|---|---| -| `option` | `void option()` | Interactive options screen editing the `optlist[]` entries in place; per-option get/put function pointers; MINUS backs up, ESCAPE quits | -| `pr_optname` | `void pr_optname(OPTION *op)` | Prints "prompt (name): " | -| `put_bool` / `put_str` / `put_inv_t` | `void put_*(void *)` | Display current value (True/False, string, inventory-type name) | -| `get_bool` | `int get_bool(void *vp, WINDOW *win)` | Interactive boolean input (t/f/Enter/Esc/-) | -| `get_sf` | `int get_sf(void *vp, WINDOW *win)` | see_floor wrapper around get_bool with lamp redraw side effects | -| `get_str` | `int get_str(void *vopt, WINDOW *win)` | Line editor: backspace, kill-char, ~ home expansion; MAXINP=50 cap | -| `get_inv_t` | `int get_inv_t(void *vp, WINDOW *win)` | Inventory style selection (Overwrite/Slow/Clear) | -| `get_num` | `int get_num(void *vp, WINDOW *win)` | (MASTER) numeric via get_str + atoi | -| `parse_opts` | `void parse_opts(char *str)` | Parses ROGUEOPTS env var: comma-separated `flag`, `noflag`, `name=value` | -| `strucpy` | `void strucpy(char *s1, char *s2, int len)` | Copies only printable chars, capped | +| Function | Signature | Description | +| ------------------------------------ | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| `option` | `void option()` | Interactive options screen editing the `optlist[]` entries in place; per-option get/put function pointers; MINUS backs up, ESCAPE quits | +| `pr_optname` | `void pr_optname(OPTION *op)` | Prints "prompt (name): " | +| `put_bool` / `put_str` / `put_inv_t` | `void put_*(void *)` | Display current value (True/False, string, inventory-type name) | +| `get_bool` | `int get_bool(void *vp, WINDOW *win)` | Interactive boolean input (t/f/Enter/Esc/-) | +| `get_sf` | `int get_sf(void *vp, WINDOW *win)` | see_floor wrapper around get_bool with lamp redraw side effects | +| `get_str` | `int get_str(void *vopt, WINDOW *win)` | Line editor: backspace, kill-char, ~ home expansion; MAXINP=50 cap | +| `get_inv_t` | `int get_inv_t(void *vp, WINDOW *win)` | Inventory style selection (Overwrite/Slow/Clear) | +| `get_num` | `int get_num(void *vp, WINDOW *win)` | (MASTER) numeric via get_str + atoi | +| `parse_opts` | `void parse_opts(char *str)` | Parses ROGUEOPTS env var: comma-separated `flag`, `noflag`, `name=value` | +| `strucpy` | `void strucpy(char *s1, char *s2, int len)` | Copies only printable chars, capped | -**Notes:** `OPTION` struct: name, prompt, value pointer, put function pointer, get function pointer. Options: terse, flush, jump, seefloor, passgo, tombstone, inven, name, fruit, file. In Go this becomes a slice of interface-typed option descriptors or simple closures. +**Notes:** `OPTION` struct: name, prompt, value pointer, put function pointer, +get function pointer. Options: terse, flush, jump, seefloor, passgo, tombstone, +inven, name, fruit, file. In Go this becomes a slice of interface-typed option +descriptors or simple closures. ### xcrypt.c (708 lines) — DES crypt(3) implementation -Full portable DES implementation (`xcrypt(key, setting)`) with precomputed permutation tables (~100KB of lookup tables), supporting classic 2-char-salt crypt and extended-format crypt. +Full portable DES implementation (`xcrypt(key, setting)`) with precomputed +permutation tables (~100KB of lookup tables), supporting classic 2-char-salt +crypt and extended-format crypt. -**Usage in the game:** only `md_crypt()` → wizard-password check (`passwd()` in wizard.c). NOT used for save encryption (that's the simple XOR in save.c). The Go port can drop this file entirely and gate wizard mode on an environment variable instead. +**Usage in the game:** only `md_crypt()` → wizard-password check (`passwd()` in +wizard.c). NOT used for save encryption (that's the simple XOR in save.c). The +Go port can drop this file entirely and gate wizard mode on an environment +variable instead. ### score.h (27 lines) — Scoreboard entry structure @@ -891,11 +1036,11 @@ Module: **`git.eeqj.de/sneak/rgoue`** ## 1. Goals and principles -1. **Faithful gameplay.** The port reproduces Rogue 5.4.4 behavior exactly: - same dungeon generation for a given seed, same combat math, same item - tables, same messages. The unit of porting is the C function; every C - function becomes a Go method (or function) with a recognizable name, ported - line by line unless a rule below says otherwise. +1. **Faithful gameplay.** The port reproduces Rogue 5.4.4 behavior exactly: same + dungeon generation for a given seed, same combat math, same item tables, same + messages. The unit of porting is the C function; every C function becomes a + Go method (or function) with a recognizable name, ported line by line unless + a rule below says otherwise. 2. **No globals.** All formerly-global state lives in a single instance type, **`RogueGame`**, which owns per-game state and exposes the game's main entrypoint (`Run`). Two `RogueGame` values are two independent games. @@ -907,26 +1052,25 @@ Module: **`git.eeqj.de/sneak/rgoue`** disappears; where the C code's shape depends on list semantics (attach-to-front, detach), small compat helpers keep the ported call sites readable. calloc/free vanish under GC. -4. **OOP where the domain has objects, not everywhere.** The THING union - splits into `Object` and `Creature` (with `Player` and `Monster` variants). - Behavior that is intrinsically about one entity (naming an object, testing - a flag) is a method on that entity. Everything that touches wider game - state (most of the game) is a method on `*RogueGame`, mirroring the C - call graph one-to-one. +4. **OOP where the domain has objects, not everywhere.** The THING union splits + into `Object` and `Creature` (with `Player` and `Monster` variants). Behavior + that is intrinsically about one entity (naming an object, testing a flag) is + a method on that entity. Everything that touches wider game state (most of + the game) is a method on `*RogueGame`, mirroring the C call graph one-to-one. 5. **Standard library first.** Exactly one third-party dependency: `github.com/gdamore/tcell/v2` — the de facto standard Go terminal cell library — replacing curses. Everything else (saves, scores, RNG, signals, options) is stdlib. 6. **Dead weight is dropped, not ported.** `mdport.c`'s 900-line keyboard - decoder (tcell events replace it), `xcrypt.c`'s DES (wizard mode gates on - an env var instead), load-average checks, tty dsusp-char juggling, and the - XOR save encryption all disappear. Each drop is documented in §9. + decoder (tcell events replace it), `xcrypt.c`'s DES (wizard mode gates on an + env var instead), load-average checks, tty dsusp-char juggling, and the XOR + save encryption all disappear. Each drop is documented in §9. ## 2. Repository and package layout -The C reference implementation lives on the `master`/`modern-rogue` -branches; this branch is Go-only. The tcell wrapper ended up in its own -`term` package so that `game` itself has no third-party imports: +The C reference implementation lives on the `master`/`modern-rogue` branches; +this branch is Go-only. The tcell wrapper ended up in its own `term` package so +that `game` itself has no third-party imports: ``` go.mod module git.eeqj.de/sneak/rgoue @@ -970,8 +1114,8 @@ serialization → `save.go`). ## 3. The `RogueGame` type -`RogueGame` is deliberately a god object: it is the C global namespace given -a receiver. Fields are grouped into embedded sub-structs where a subsystem is +`RogueGame` is deliberately a god object: it is the C global namespace given a +receiver. Fields are grouped into embedded sub-structs where a subsystem is coherent, but everything is reachable from `g`. ```go @@ -1041,10 +1185,10 @@ type RogueGame struct { } ``` -Player-centric C globals move onto `Player`, world-centric onto `Level` -(see §4/§5); the remainder sit directly on `RogueGame` with the original -semantics. The command.c/io.c/things.c file-statics become lowercase fields -of `RogueGame`, `MsgLine`, and the inventory pagination helper respectively. +Player-centric C globals move onto `Player`, world-centric onto `Level` (see +§4/§5); the remainder sit directly on `RogueGame` with the original semantics. +The command.c/io.c/things.c file-statics become lowercase fields of `RogueGame`, +`MsgLine`, and the inventory pagination helper respectively. ### Entrypoints @@ -1113,9 +1257,8 @@ type Player struct { } ``` -The `hero`/`pstats`/`pack`/`proom`/`max_hp` macros become -`g.Player.Pos` / `g.Player.Stats` / `g.Player.Pack` / `g.Player.Room` / -`g.Player.Stats.MaxHP`. +The `hero`/`pstats`/`pack`/`proom`/`max_hp` macros become `g.Player.Pos` / +`g.Player.Stats` / `g.Player.Pack` / `g.Player.Room` / `g.Player.Stats.MaxHP`. `Dest *Coord` keeps the C aliasing on purpose: monster targeting works by pointer identity (`m.Dest == &g.Player.Pos` means "hunting the hero"), which @@ -1146,8 +1289,8 @@ func (o *Object) SetCharges(n int) { o.Arm = n } func (o *Object) GoldVal() int { return o.Arm } // gold piles ``` -The overload stays (one field, named accessors) so ported arithmetic like -"rust: `o.Arm++`" keeps its original shape. +The overload stays (one field, named accessors) so ported arithmetic like "rust: +`o.Arm++`" keeps its original shape. ### 4.4 Flags @@ -1172,8 +1315,8 @@ func (f *CreatureFlags) Clear(b CreatureFlags) C's `on(player, ISHALU)` ports to `g.Player.Flags.Has(IsHalu)`. The C bit collisions (ISCANC/ISLEVIT, ISMEAN/ISHALU, ISFLY/SEEMONST) are preserved as -equal values — they are disjoint by owner (monster vs hero), and the save -format keeps working. +equal values — they are disjoint by owner (monster vs hero), and the save format +keeps working. ### 4.5 Level and Place @@ -1219,14 +1362,14 @@ type Room struct { ### 4.7 Static tables -`tables.go` holds the immutable data as package-level **constants and -`var` tables that are never written after init** (the only permitted -package-level data — they are ROM, not state): `monsterTable [26]MonsterKind` -(name, carry %, flags, stats), `initDam` weapon table, base `objInfo` tables -(copied into `RogueGame.Items` at NewGame so per-game mutation — probability -resumming, `oi_know`, `oi_guess` — stays instance-local), `eLevels`, -`rainbow`, `stones`, `woods`, `metals`, `sylls`, trap names, help text, -`lvlMons`/`wandMons` spawn strings, `hNames`/`mNames` combat messages. +`tables.go` holds the immutable data as package-level **constants and `var` +tables that are never written after init** (the only permitted package-level +data — they are ROM, not state): `monsterTable [26]MonsterKind` (name, carry %, +flags, stats), `initDam` weapon table, base `objInfo` tables (copied into +`RogueGame.Items` at NewGame so per-game mutation — probability resumming, +`oi_know`, `oi_guess` — stays instance-local), `eLevels`, `rainbow`, `stones`, +`woods`, `metals`, `sylls`, trap names, help text, `lvlMons`/`wandMons` spawn +strings, `hNames`/`mNames` combat messages. ```go type ObjInfo struct { @@ -1270,13 +1413,13 @@ func (g *RogueGame) Roll(n, sides int) int func (g *RogueGame) Spread(n int) int // misc.c: n ± 10% ``` -Same seed → same dungeon, byte for byte. This is the port's master -correctness oracle (§10). +Same seed → same dungeon, byte for byte. This is the port's master correctness +oracle (§10). ### 5.2 Daemons and fuses — function pointers → identifiers -C identifies daemons by function pointer; the save format already maps them -to small integers. The port makes that mapping primary: +C identifies daemons by function pointer; the save format already maps them to +small integers. The port makes that mapping primary: ```go type DaemonID int @@ -1308,10 +1451,10 @@ func (g *RogueGame) DoDaemons(flag int) // dispatches via one switch on Daemon func (g *RogueGame) DoFuses(flag int) ``` -The 11 callbacks in daemons.go become methods (`g.doctor()`, `g.stomach()`, -...) invoked from the dispatch switch. This is more robust than function -values because it is comparable and serializable — the two properties the C -design needed pointers for. +The 11 callbacks in daemons.go become methods (`g.doctor()`, `g.stomach()`, ...) +invoked from the dispatch switch. This is more robust than function values +because it is comparable and serializable — the two properties the C design +needed pointers for. ### 5.3 Screen — curses → tcell @@ -1336,11 +1479,11 @@ func (s *Screen) ReadChar() byte // event loop → C char codes (arro Ported drawing code keeps its structure: `mvaddch(y, x, ch)` → `g.scr.Std.MvAddCh(y, x, ch)`. Curses `mvinch` reads come from the Window -buffer, preserving the "screen is a data structure" idiom without touching -the real terminal. `md_readchar`'s escape decoding is deleted; tcell's -`EventKey` provides decoded keys and we translate to the byte codes -`command()` already handles (KeyUp → 'k', etc.). SIGTSTP/resume and resize -are handled by tcell; SIGHUP/SIGTERM → `autoSave` via `os/signal`. +buffer, preserving the "screen is a data structure" idiom without touching the +real terminal. `md_readchar`'s escape decoding is deleted; tcell's `EventKey` +provides decoded keys and we translate to the byte codes `command()` already +handles (KeyUp → 'k', etc.). SIGTSTP/resume and resize are handled by tcell; +SIGHUP/SIGTERM → `autoSave` via `os/signal`. ### 5.4 Messaging @@ -1357,8 +1500,8 @@ func (g *RogueGame) EndMsg() bool ``` C's `%s`+`vowelstr` message assembly ports directly on `fmt`. The io.c -status-line shadow statics become fields of an unexported `statusCache` -struct on `RogueGame`. +status-line shadow statics become fields of an unexported `statusCache` struct +on `RogueGame`. ### 5.5 Options @@ -1369,17 +1512,17 @@ type Options struct { } ``` -`ROGUEOPTS` parsing (`parse_opts`) and the interactive option screen port -as-is; the C per-option get/put function pointers become a slice of option -descriptors holding closures. +`ROGUEOPTS` parsing (`parse_opts`) and the interactive option screen port as-is; +the C per-option get/put function pointers become a slice of option descriptors +holding closures. ### 5.6 Save/restore — gob replaces state.c -The 2,134-line hand-rolled serializer exists because C cannot reflect. Go -can: the save file is `encoding/gob` of a `SaveState` snapshot struct +The 2,134-line hand-rolled serializer exists because C cannot reflect. Go can: +the save file is `encoding/gob` of a `SaveState` snapshot struct (version-tagged, gzip-compressed). C-format saves are **not** readable — the -formats were never cross-version compatible anyway (the file embeds the -exact version string). +formats were never cross-version compatible anyway (the file embeds the exact +version string). What still needs explicit code is exactly what state.c's `rs_fix_thing` did: pointers that alias other structures are encoded as indices — @@ -1397,66 +1540,66 @@ type SaveState struct { ``` Save-file hygiene keeps the C behavior: the file is deleted on restore, and -`Run` exits after a successful save. The XOR encryption and symlink checks -are dropped (they were 1980s shared-machine anti-cheat). +`Run` exits after a successful save. The XOR encryption and symlink checks are +dropped (they were 1980s shared-machine anti-cheat). ### 5.7 Scoreboard `SCORE` → `ScoreEnt` struct; the score file is a gob (or JSON) list at -`~/.rogue_scores` (overridable). Locking via `os.OpenFile(O_CREATE|O_EXCL)` -lock file with stale-age takeover, porting `lock_sc`/`unlock_sc` semantics. +`~/.rogue_scores` (overridable). Locking via `os.OpenFile(O_CREATE|O_EXCL)` lock +file with stale-age takeover, porting `lock_sc`/`unlock_sc` semantics. Tombstone/victory screens port verbatim from rip.c. ## 6. C construct → Go construct map -| C construct | Go translation | -|---|---| -| global variable | `RogueGame` field (or Player/Level/subsystem field) | -| file-scope static | unexported field on `RogueGame` or subsystem struct | -| `THING` union | `Creature` / `Object` structs | -| linked lists (`l_next/l_prev`, attach/detach) | slices + `attach(&s, x)`-style helpers (prepend, remove by identity) | -| `coord*` aliasing (t_dest) | `*Coord` pointers into live structs (kept) | -| function pointers (daemons, options, nameit) | enum IDs + dispatch switch (daemons, save-relevant); closures (options, nameit) | -| `when`/`otherwise`/`until` macros | `case`/`default`/`for !cond` | -| `goto` (retry loops in pack.c, move.c, passages.c) | labeled loops / restructured `for` | -| char-indexed tables (`monsters[type-'A']`) | same, `monsterTable[t.Type-'A']` | -| damage strings `"3x4/1x2"` | kept as strings; parsed by `rollEm` (`strings`/`strconv`) | -| curses stdscr/hw windows | `Window` cell buffers over tcell | -| `mvinch` screen reads | `Window.Inch` from the buffer | -| signal handlers (SIGHUP autosave, SIGTSTP, SIGINT) | `os/signal` goroutine → channel checked in ReadChar; tcell handles TSTP/resize | +| C construct | Go translation | +| ---------------------------------------------------- | -------------------------------------------------------------------------------------- | +| global variable | `RogueGame` field (or Player/Level/subsystem field) | +| file-scope static | unexported field on `RogueGame` or subsystem struct | +| `THING` union | `Creature` / `Object` structs | +| linked lists (`l_next/l_prev`, attach/detach) | slices + `attach(&s, x)`-style helpers (prepend, remove by identity) | +| `coord*` aliasing (t_dest) | `*Coord` pointers into live structs (kept) | +| function pointers (daemons, options, nameit) | enum IDs + dispatch switch (daemons, save-relevant); closures (options, nameit) | +| `when`/`otherwise`/`until` macros | `case`/`default`/`for !cond` | +| `goto` (retry loops in pack.c, move.c, passages.c) | labeled loops / restructured `for` | +| char-indexed tables (`monsters[type-'A']`) | same, `monsterTable[t.Type-'A']` | +| damage strings `"3x4/1x2"` | kept as strings; parsed by `rollEm` (`strings`/`strconv`) | +| curses stdscr/hw windows | `Window` cell buffers over tcell | +| `mvinch` screen reads | `Window.Inch` from the buffer | +| signal handlers (SIGHUP autosave, SIGTSTP, SIGINT) | `os/signal` goroutine → channel checked in ReadChar; tcell handles TSTP/resize | | `setjmp`-free exits (`my_exit`, `exit()` everywhere) | error returns from `Run` + a small `gameOver` sentinel; no `os.Exit` inside game logic | -| `vsprintf` message building | `fmt.Sprintf` | -| XOR-encrypted binary saves (state.c) | gob snapshot (§5.6) | -| DES wizard password | `ROGUE_WIZARD=1` env check | -| `md_*` platform shims | stdlib (`os`, `os/user`, `time`) or deleted | +| `vsprintf` message building | `fmt.Sprintf` | +| XOR-encrypted binary saves (state.c) | gob snapshot (§5.6) | +| DES wizard password | `ROGUE_WIZARD=1` env check | +| `md_*` platform shims | stdlib (`os`, `os/user`, `time`) or deleted | -**Exit discipline** deserves a note: the C code calls `exit()` from deep -inside call chains (death, victory, save). The port threads a -`g.gameOver(reason)` that sets `Playing=false` and unwinds via normal -returns to `Run` — the one structural change made everywhere, since -`os.Exit` would skip tcell teardown and defers. +**Exit discipline** deserves a note: the C code calls `exit()` from deep inside +call chains (death, victory, save). The port threads a `g.gameOver(reason)` that +sets `Playing=false` and unwinds via normal returns to `Run` — the one +structural change made everywhere, since `os.Exit` would skip tcell teardown and +defers. ## 7. Naming conventions -- C function `do_move` → method `(g *RogueGame) DoMove(dy, dx int)`; the C - name is preserved in CamelCase so cross-referencing Part 1 is mechanical. +- C function `do_move` → method `(g *RogueGame) DoMove(dy, dx int)`; the C name + is preserved in CamelCase so cross-referencing Part 1 is mechanical. Statics/internal helpers are lowerCamel (`g.doadd` → `g.addMsgV`). - Entity-intrinsic functions become entity methods where they read no game - state: `inv_name(obj, drop)` needs ItemLore → `(g *RogueGame) InvName(o - *Object, drop bool) string`; but `is_magic(obj)` → `(o *Object) IsMagic() - bool`. + state: `inv_name(obj, drop)` needs ItemLore → + `(g *RogueGame) InvName(o *Object, drop bool) string`; but `is_magic(obj)` → + `(o *Object) IsMagic() bool`. - Boolean C `bool`/`int` returns stay `bool`; `char` returns become `byte`. -- `coord*` out-params become `Coord` returns (or `(Coord, bool)` for - find-style functions like `fallpos`/`find_floor`). +- `coord*` out-params become `Coord` returns (or `(Coord, bool)` for find-style + functions like `fallpos`/`find_floor`). ## 8. Porting order (each step compiles, is committed, and is testable) -1. **Foundation:** `go.mod`, types.go, object.go, creature.go, level.go, - rng.go, tables.go, game.go skeleton. Unit tests: RNG sequence vs C - formula, table sanity (probabilities sum to 100). +1. **Foundation:** `go.mod`, types.go, object.go, creature.go, level.go, rng.go, + tables.go, game.go skeleton. Unit tests: RNG sequence vs C formula, table + sanity (probabilities sum to 100). 2. **Dungeon generation:** rooms.go, passages.go, newlevel.go + enough of - misc.go/monsters.go/things.go to place things. Test: fixed-seed level - render to text, eyeballed against C build. + misc.go/monsters.go/things.go to place things. Test: fixed-seed level render + to text, eyeballed against C build. 3. **Items:** things.go, pack.go, init.go + ItemLore. 4. **Creatures & combat:** monsters.go, chase.go, fight.go, daemon(s).go. 5. **UI:** screen.go, io.go, options.go, misc.go look(), command.go, move.go. @@ -1468,29 +1611,29 @@ returns to `Run` — the one structural change made everywhere, since ## 9. Dropped C functionality (deliberate) -| Dropped | Why | Replacement | -|---|---|---| -| `md_readchar` escape decoding | tcell decodes keys | key-event translation table | -| XOR save/score encryption | obscurity, not security | plain gob (file perms 0600) | -| save-file symlink/hardlink checks | single-user era anti-cheat | none | -| DES crypt wizard password | ditto | `ROGUE_WIZARD` env var | -| load-average / user-count gating (`too_much`, `ucount`, CHECKTIME) | 1980s timesharing courtesy | none | -| tty dsusp/ltc character juggling | tcell owns the tty | none | -| shell escape (`!`) setuid dance | no privileges to drop | plain `os/exec` shell | -| curses window save in save file | screen is derivable | redraw on restore | +| Dropped | Why | Replacement | +| ------------------------------------------------------------------ | -------------------------- | --------------------------- | +| `md_readchar` escape decoding | tcell decodes keys | key-event translation table | +| XOR save/score encryption | obscurity, not security | plain gob (file perms 0600) | +| save-file symlink/hardlink checks | single-user era anti-cheat | none | +| DES crypt wizard password | ditto | `ROGUE_WIZARD` env var | +| load-average / user-count gating (`too_much`, `ucount`, CHECKTIME) | 1980s timesharing courtesy | none | +| tty dsusp/ltc character juggling | tcell owns the tty | none | +| shell escape (`!`) setuid dance | no privileges to drop | plain `os/exec` shell | +| curses window save in save file | screen is derivable | redraw on restore | ## 10. Testing strategy - **RNG oracle:** golden test locking the LCG sequence for known seeds. -- **Generation goldens:** for a set of seeds, render generated levels - (map chars + room/passage metadata) to text fixtures; any diff = a porting - bug in rooms/passages/newlevel. Fixtures generated once from instrumented - C, or accepted from the first correct Go run and guarded thereafter. +- **Generation goldens:** for a set of seeds, render generated levels (map + chars + room/passage metadata) to text fixtures; any diff = a porting bug in + rooms/passages/newlevel. Fixtures generated once from instrumented C, or + accepted from the first correct Go run and guarded thereafter. - **Combat math tests:** `swing`/`rollEm` against hand-computed cases, str_plus/add_dam table spot checks. - **Determinism test:** scripted input sequence against a fixed seed must - produce identical final state across runs (guards hidden nondeterminism — - map iteration, time dependence). + produce identical final state across runs (guards hidden nondeterminism — map + iteration, time dependence). - **Save round-trip:** NewGame → play N scripted turns → save → restore → compare full state. - `go vet` + `gofmt` clean at every commit. diff --git a/MEMORY.md b/MEMORY.md index ece0780..f1b02b4 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -1,44 +1,41 @@ # Project Memory -Working notes for agents on this repo. Read this alongside TODO.md -(which holds the step queue and workflow) before starting work. +Working notes for agents on this repo. Read this alongside TODO.md (which holds +the step queue and workflow) before starting work. ## Error handling -Panicking on bad/unexpected errors is allowed and preferred over -threading unlikely error returns through game code — e.g. write-side -Close/encode failures where continuing would mean corrupt state. The -game already unwinds C's exit() calls via a gameEnd panic recovered in -Run. Return errors where a caller genuinely handles them (save-file -prompts, restore validation). Reserve deliberate `_ =` discards for -true best-effort paths (scorefile writes, signal-time autosave), always -with a comment saying why. +Panicking on bad/unexpected errors is allowed and preferred over threading +unlikely error returns through game code — e.g. write-side Close/encode failures +where continuing would mean corrupt state. The game already unwinds C's exit() +calls via a gameEnd panic recovered in Run. Return errors where a caller +genuinely handles them (save-file prompts, restore validation). Reserve +deliberate `_ =` discards for true best-effort paths (scorefile writes, +signal-time autosave), always with a comment saying why. ## Linting -The .golangci.yml is the house standard and may only be modified with -sneak's explicit permission. To disable a linter, ask, explaining what -the linter does; he approves specific exceptions, which are recorded -in the config's "Repo-specific exceptions" block with the approval -date. Approved so far: paralleltest (2026-07-06); testpackage, -exhaustive, and mnd (2026-07-07). The complexity linters (cyclop, -gocognit, nestif) are enabled and clean as of refactor step 7 -(2026-07-07): the whole golangci-lint run is 0 issues, so keep it that -way — decompose new hot spots rather than reaching for a nolint. -Line-level //nolint with a reason is used sparingly for C-faithfulness -(e.g. the authentic "missle" message spellings) and provably-safe gosec -conversions; each needs a justifying comment. +The .golangci.yml is the house standard and may only be modified with sneak's +explicit permission. To disable a linter, ask, explaining what the linter does; +he approves specific exceptions, which are recorded in the config's +"Repo-specific exceptions" block with the approval date. Approved so far: +paralleltest (2026-07-06); testpackage, exhaustive, and mnd (2026-07-07). The +complexity linters (cyclop, gocognit, nestif) are enabled and clean as of +refactor step 7 (2026-07-07): the whole golangci-lint run is 0 issues, so keep +it that way — decompose new hot spots rather than reaching for a nolint. +Line-level //nolint with a reason is used sparingly for C-faithfulness (e.g. the +authentic "missle" message spellings) and provably-safe gosec conversions; each +needs a justifying comment. ## Faithfulness -Behavior must not change during the idiomatic-Go refactor unless a -TODO step says so. The 80x24 seed-compatible gameplay, message text -(including original typos), RNG call order, and C quirks (documented -in tests like TestHoldScrollGreedyMonsterQuirk) are contract. Doc -comments keep their "(file.c func_name)" breadcrumbs. +Behavior must not change during the idiomatic-Go refactor unless a TODO step +says so. The 80x24 seed-compatible gameplay, message text (including original +typos), RNG call order, and C quirks (documented in tests like +TestHoldScrollGreedyMonsterQuirk) are contract. Doc comments keep their "(file.c +func_name)" breadcrumbs. ## Debugging -Write real, committed test files with t.Logf output and run plain -`go test -v`; no throwaway scratch scripts. Successful debug probes -become regression tests. +Write real, committed test files with t.Logf output and run plain `go test -v`; +no throwaway scratch scripts. Successful debug probes become regression tests. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4562048 --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +# Development convenience targets. This repo is exempt from the standard +# policy scaffold (no Dockerfile, CI, or REPO_POLICIES.md); this Makefile +# is only a thin wrapper around the Go toolchain, golangci-lint, and +# prettier so `make fmt` / `make check` behave the same as in sneak's +# other repos. + +GO_PKGS := ./... +MD_FILES := $(shell git ls-files '*.md') +PRETTIER := prettier --tab-width 4 --prose-wrap always + +.PHONY: check fmt fmt-check lint test + +# Format, lint, and test — the full local pre-commit gate. +check: fmt-check lint test + +# Format Go and Markdown in place. +fmt: + gofmt -w . + $(PRETTIER) --write $(MD_FILES) + +# Fail if any Go or Markdown file is not formatted. +fmt-check: + @unformatted="$$(gofmt -l .)"; \ + if [ -n "$$unformatted" ]; then \ + echo "gofmt needed on:"; echo "$$unformatted"; exit 1; \ + fi + $(PRETTIER) --check $(MD_FILES) + +# Run the house linter (config in .golangci.yml). +lint: + golangci-lint run $(GO_PKGS) + +# Run the test suite. +test: + go test $(GO_PKGS) diff --git a/README.md b/README.md index bdcf943..0e4e061 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,19 @@ [![License](https://img.shields.io/badge/license-BSD-blue.svg)](LICENSE.TXT) -**Rogue** is the original dungeon-crawling adventure game that spawned an -entire genre. This branch is a faithful Go port of Rogue 5.4.4: explore -procedurally generated dungeons, fight monsters, collect treasure, and -attempt to retrieve the Amulet of Yendor. +**Rogue** is the original dungeon-crawling adventure game that spawned an entire +genre. This branch is a faithful Go port of Rogue 5.4.4: explore procedurally +generated dungeons, fight monsters, collect treasure, and attempt to retrieve +the Amulet of Yendor. -**Original authors:** Michael Toy, Ken Arnold, and Glenn Wichman -(1980–1983, 1985, 1999). +**Original authors:** Michael Toy, Ken Arnold, and Glenn Wichman (1980–1983, +1985, 1999). The port is function-by-function faithful to the classic C sources — same -dungeon generation (seed-compatible RNG), same combat math, same item -tables, same messages. The C reference implementation lives on the -`master` and `modern-rogue` branches; [ARCHITECTURE.md](ARCHITECTURE.md) -documents both the original program structure and the design of this port. +dungeon generation (seed-compatible RNG), same combat math, same item tables, +same messages. The C reference implementation lives on the `master` and +`modern-rogue` branches; [ARCHITECTURE.md](ARCHITECTURE.md) documents both the +original program structure and the design of this port. ## Building and running @@ -40,13 +40,12 @@ go build ./cmd/rogue Press `?` in game for the full list. -- **arrows** or **h/j/k/l/y/u/b/n** — move (shift to run, ctrl to run - until adjacent) +- **arrows** or **h/j/k/l/y/u/b/n** — move (shift to run, ctrl to run until + adjacent) - **`.`** rest, **`s`** search for hidden doors and traps - **`i`** inventory, **`,`** pick up, **`d`** drop - **`q`** quaff potion, **`r`** read scroll, **`e`** eat food -- **`w`** wield weapon, **`W`** wear armor, **`P`**/**`R`** put on / - remove ring +- **`w`** wield weapon, **`W`** wear armor, **`P`**/**`R`** put on / remove ring - **`t`** throw, **`z`** zap a wand, **`f`**/**`F`** fight - **`>`**/**`<`** take the stairs - **`S`** save, **`Q`** quit @@ -61,8 +60,8 @@ export ROGUEOPTS="name=YourName,terse,jump,fruit=mango" ROGUE_WIZARD=1 SEED=12345 ./rogue ``` -The scoreboard is kept in `~/.rogue.scores`. Save files are Go gob -snapshots and, as in the original, are deleted when restored. +The scoreboard is kept in `~/.rogue.scores`. Save files are Go gob snapshots +and, as in the original, are deleted when restored. ## Code layout @@ -73,13 +72,13 @@ term/ tcell-backed terminal, replacing curses cmd/rogue/ the executable ``` -The engine package is fully headless-testable: `go test ./game/` runs -scripted game sessions, dungeon-generation golden checks, and an RNG -compatibility test against the original C generator. +The engine package is fully headless-testable: `go test ./game/` runs scripted +game sessions, dungeon-generation golden checks, and an RNG compatibility test +against the original C generator. ## License BSD-style; see [LICENSE.TXT](LICENSE.TXT). -Copyright (C) 1980-1983, 1985, 1999 Michael Toy, Ken Arnold and Glenn -Wichman. All rights reserved. +Copyright (C) 1980-1983, 1985, 1999 Michael Toy, Ken Arnold and Glenn Wichman. +All rights reserved. diff --git a/TODO.md b/TODO.md index 77ae6c7..0464d44 100644 --- a/TODO.md +++ b/TODO.md @@ -1,164 +1,156 @@ # Workflow -* branch (from `main`) -* do the work in Next Step -* move Next Step to the top of Completed Steps -* move the top item of Future Steps into Next Step -* commit (`TODO.md` changes in the same commit as the work) -* merge to `main` if the branch is not protected, otherwise open a PR -* push +- branch (from `main`) +- do the work in Next Step +- move Next Step to the top of Completed Steps +- move the top item of Future Steps into Next Step +- commit (`TODO.md` changes in the same commit as the work) +- merge to `main` if the branch is not protected, otherwise open a PR +- push # Status pre-1.0 -The port on main is complete and faithful (function-by-function from -Rogue 5.4.4 C; reference sources on c-master/modern-rogue). Current -phase: refactor from a transliterated port into idiomatic Go — one -feature branch per step below, descriptive naming, real types, house -style per ~/dev/prompts/prompts/CODE_STYLEGUIDE_GO.md. +The port on main is complete and faithful (function-by-function from Rogue 5.4.4 +C; reference sources on c-master/modern-rogue). Current phase: refactor from a +transliterated port into idiomatic Go — one feature branch per step below, +descriptive naming, real types, house style per +~/dev/prompts/prompts/CODE_STYLEGUIDE_GO.md. Refactor ground rules: -- Behavior must not change unless a step says so. The full test suite - (scripted sessions, generation invariants, C-compatible RNG goldens) - gates every step; 80x24 seed-compatible gameplay stays intact. -- Renames keep the C lineage greppable: doc comments retain their - "(file.c func_name)" breadcrumbs, and the docs refresh step adds a - C-name → Go-name table to ARCHITECTURE.md. +- Behavior must not change unless a step says so. The full test suite (scripted + sessions, generation invariants, C-compatible RNG goldens) gates every step; + 80x24 seed-compatible gameplay stays intact. +- Renames keep the C lineage greppable: doc comments retain their "(file.c + func_name)" breadcrumbs, and the docs refresh step adds a C-name → Go-name + table to ARCHITECTURE.md. # Next Step Refactor step 8: constructor and style pass per the house styleguide — -game.New(game.Params{...}) replacing NewGame(Config); replace the -gameEnd panic unwind with error-based turn results where feasible; -77-column wrap sweep. +game.New(game.Params{...}) replacing NewGame(Config); replace the gameEnd panic +unwind with error-based turn results where feasible; 77-column wrap sweep. # Completed Steps -- 2026-07-07 Refactor step 7 (refactor/effects-dispatch): effects - dispatch tables plus a full decomposition sweep — the quaff / - readScroll / doZap switches, the attack monster-power switch, the - be_trapped switch, the daemon d_func switch, and the command-key - switch all became handler tables on gameData (quaffHandlers, - readHandlers, zapHandlers, hitHandlers, trapHandlers, daemonHandlers, - commandHandlers), one small named method per case. Every remaining - cyclop/gocognit/nestif hot spot was split into named helpers across - fight, misc (look), command, chase, move, passages, options, pack, - things, save, daemons, rooms, score, monsters, rings, rip, io, - object, weapons, wizard, and term/tcell, plus three test functions. - Effect order and RNG call sequence preserved throughout; the whole - golangci-lint run is now 0 issues. +- 2026-07-07 Refactor step 7 (refactor/effects-dispatch): effects dispatch + tables plus a full decomposition sweep — the quaff / readScroll / doZap + switches, the attack monster-power switch, the be_trapped switch, the daemon + d_func switch, and the command-key switch all became handler tables on + gameData (quaffHandlers, readHandlers, zapHandlers, hitHandlers, trapHandlers, + daemonHandlers, commandHandlers), one small named method per case. Every + remaining cyclop/gocognit/nestif hot spot was split into named helpers across + fight, misc (look), command, chase, move, passages, options, pack, things, + save, daemons, rooms, score, monsters, rings, rip, io, object, weapons, + wizard, and term/tcell, plus three test functions. Effect order and RNG call + sequence preserved throughout; the whole golangci-lint run is now 0 issues. -- 2026-07-07 Refactor step 6 (refactor/god-object-extraction): - MessageLine (was MsgLine) owns the msg/addmsg/endmsg machinery, - wired to its screen/look/input needs via attach(); RogueGame keeps - one-line msg/addmsgf/endmsg shorthands so call sites are unchanged. - Player owns pack bookkeeping (nextPackChar, removeFromPack — the - state half of leave_pack; leavePack keeps only LastPick tracking). - Level owns object/monster list management and lookup (ObjectAt - replaces findObj; AddObject/RemoveObject/AddMonster/RemoveMonster - replace direct attachObj/detachObj/attachMon/detachMon on level - lists). Inventory/pickup UI flows stay on RogueGame deliberately: - they are display and turn orchestration, not state surgery. +- 2026-07-07 Refactor step 6 (refactor/god-object-extraction): MessageLine (was + MsgLine) owns the msg/addmsg/endmsg machinery, wired to its screen/look/input + needs via attach(); RogueGame keeps one-line msg/addmsgf/endmsg shorthands so + call sites are unchanged. Player owns pack bookkeeping (nextPackChar, + removeFromPack — the state half of leave_pack; leavePack keeps only LastPick + tracking). Level owns object/monster list management and lookup (ObjectAt + replaces findObj; AddObject/RemoveObject/AddMonster/RemoveMonster replace + direct attachObj/detachObj/attachMon/detachMon on level lists). + Inventory/pickup UI flows stay on RogueGame deliberately: they are display and + turn orchestration, not state surgery. -- 2026-07-07 Refactor step 5 (refactor/item-combat-ui-renames, three - commits, one subsystem each): items — getItem→promptPackItem now - returning (obj, ok), invName→inventoryName, doPot→applyPotionFuse; - combat — rollEm→rollAttacks, attack/moveMonster/chaseStep return - (removed bool) instead of C -1/0 int codes; UI — - getDir→promptDirection. C breadcrumbs kept; suite green. +- 2026-07-07 Refactor step 5 (refactor/item-combat-ui-renames, three commits, + one subsystem each): items — getItem→promptPackItem now returning (obj, ok), + invName→inventoryName, doPot→applyPotionFuse; combat — rollEm→rollAttacks, + attack/moveMonster/chaseStep return (removed bool) instead of C -1/0 int + codes; UI — getDir→promptDirection. C breadcrumbs kept; suite green. -- 2026-07-07 Refactor step 4 (refactor/movement-renames): movement/world - renames (doMove→moveHero, beTrapped→springTrap, rndmove→randomStep, - doRooms/doPassages/doMaze→digRooms/digPassages/digMaze, - chgStr→changeStrength, doRun→startRun, moveStuff→finishMove, - turnref→turnRefresh, moveMonst→moveMonster, doChase→chaseStep, - setOldch→setOldChar, cansee→canSee, roomin→roomIn, runto→runTo, - conn→connectRooms, putpass→putPassage, passnum→numberPassages, - numpass→numberPassage, rndPos→randomPos, rndRoom→randomRoom, - treasRoom→treasureRoom, accntMaze→accountMaze); all goto/label flows - replaced with loops (moveHero retry loop + extracted passageTurn, - dispatch re-dispatch loop, chaseStep passage loop, saveGame labeled - prompt loop); C breadcrumbs kept in doc comments. -- 2026-07-07 Lint adoption finished (refactor/no-package-globals): all - 37 package-level vars moved into `gameData` (built by `newGameData`, - hung on RogueGame as `g.data`, set in NewGame and Restore); ObjectKind +- 2026-07-07 Refactor step 4 (refactor/movement-renames): movement/world renames + (doMove→moveHero, beTrapped→springTrap, rndmove→randomStep, + doRooms/doPassages/doMaze→digRooms/digPassages/digMaze, chgStr→changeStrength, + doRun→startRun, moveStuff→finishMove, turnref→turnRefresh, + moveMonst→moveMonster, doChase→chaseStep, setOldch→setOldChar, cansee→canSee, + roomin→roomIn, runto→runTo, conn→connectRooms, putpass→putPassage, + passnum→numberPassages, numpass→numberPassage, rndPos→randomPos, + rndRoom→randomRoom, treasRoom→treasureRoom, accntMaze→accountMaze); all + goto/label flows replaced with loops (moveHero retry loop + extracted + passageTurn, dispatch re-dispatch loop, chaseStep passage loop, saveGame + labeled prompt loop); C breadcrumbs kept in doc comments. +- 2026-07-07 Lint adoption finished (refactor/no-package-globals): all 37 + package-level vars moved into `gameData` (built by `newGameData`, hung on + RogueGame as `g.data`, set in NewGame and Restore); ObjectKind Glyph()/objectKindForGlyph became switches; the table-reading subtype - Stringers were removed; isMagic became a RogueGame method; goconst - fixed with named word constants (potionName, goldName, staffName, - ripWall, ...); testpackage and exhaustive disabled in .golangci.yml - with sneak's approval (2026-07-07); misspell's corruption of the - "ther" scroll syllable reverted. mnd disabled with sneak's approval - (2026-07-07, follow-up commit). Remaining red: cyclop (36), nestif - (30), gocognit (23) stay until step 7 fixes them per sneak's ruling. -- 2026-07-06 Lint adoption bulk (refactor/lint-adoption, 5ba9fe8): - .golangci.yml copied verbatim from the prompts repo (plus the - sneak-approved paralleltest exception, 2026-07-06); ~1,500 findings - fixed (autofix formatting sweep, errcheck/err113/noinlineerr error - handling, forbidigo, funcorder, recvcheck pointer receivers, - goprintffuncname renames msg helpers to *f, revive doc comments, - gocritic switch rewrites, gosec real fixes plus justified nolints, - unparam signature tightening, C-faithful "missle" spellings restored - after misspell autofix changed game text). -- 2026-07-06 Module base path updated to git.eeqj.de/sneak/rgoue - (go.mod, term/ and cmd/ imports, ARCHITECTURE.md, version string). -- 2026-07-06 Refactor step 3 (refactor/object-fields): Object.Arm split - into ArmorClass/Charges/GoldValue/Bonus (rings); Stats.Arm → - ArmorClass; damage strings parsed once into DiceSpec at table - definition (ParseDice keeps C roll_em parse semantics, incl. "%%%x0" - and "000x0" edge cases, regression-tested); save format 5.4.4-go3. -- 2026-07-06 Refactor step 2 (refactor/typed-kinds, b940cfc): - ObjectKind separates item category from map glyph (Object.Type byte - → Kind ObjectKind with Glyph()); PotionKind/ScrollKind/RingKind/ - WandKind/WeaponKind/ArmorKind/TrapKind typed iota enums with - Stringer; typed accessors on Object; getItem/inventory/whatis - filters take ObjectKind (KindCallable/KindRingOrStick replace - CALLABLE/R_OR_S); save format bumped to 5.4.4-go2. Suite green. -- 2026-07-06 Refactor step 1 (refactor/descriptive-constants): renamed - all flag bits, trap types, item subtype constants, and Max* counts to - descriptive names (IsHuh→Confused, SeeMonst→SenseMonsters, - WsHasteM→WandHasteMonster, MaxSticks→NumWandTypes, ...); - Level.NTraps→TrapCount; C names kept as comment breadcrumbs. Pure - rename, suite green. + Stringers were removed; isMagic became a RogueGame method; goconst fixed with + named word constants (potionName, goldName, staffName, ripWall, ...); + testpackage and exhaustive disabled in .golangci.yml with sneak's approval + (2026-07-07); misspell's corruption of the "ther" scroll syllable reverted. + mnd disabled with sneak's approval (2026-07-07, follow-up commit). Remaining + red: cyclop (36), nestif (30), gocognit (23) stay until step 7 fixes them per + sneak's ruling. +- 2026-07-06 Lint adoption bulk (refactor/lint-adoption, 5ba9fe8): .golangci.yml + copied verbatim from the prompts repo (plus the sneak-approved paralleltest + exception, 2026-07-06); ~1,500 findings fixed (autofix formatting sweep, + errcheck/err113/noinlineerr error handling, forbidigo, funcorder, recvcheck + pointer receivers, goprintffuncname renames msg helpers to *f, revive doc + comments, gocritic switch rewrites, gosec real fixes plus justified nolints, + unparam signature tightening, C-faithful "missle" spellings restored after + misspell autofix changed game text). +- 2026-07-06 Module base path updated to git.eeqj.de/sneak/rgoue (go.mod, term/ + and cmd/ imports, ARCHITECTURE.md, version string). +- 2026-07-06 Refactor step 3 (refactor/object-fields): Object.Arm split into + ArmorClass/Charges/GoldValue/Bonus (rings); Stats.Arm → ArmorClass; damage + strings parsed once into DiceSpec at table definition (ParseDice keeps C + roll_em parse semantics, incl. "%%%x0" and "000x0" edge cases, + regression-tested); save format 5.4.4-go3. +- 2026-07-06 Refactor step 2 (refactor/typed-kinds, b940cfc): ObjectKind + separates item category from map glyph (Object.Type byte → Kind ObjectKind + with Glyph()); PotionKind/ScrollKind/RingKind/ + WandKind/WeaponKind/ArmorKind/TrapKind typed iota enums with Stringer; typed + accessors on Object; getItem/inventory/whatis filters take ObjectKind + (KindCallable/KindRingOrStick replace CALLABLE/R_OR_S); save format bumped to + 5.4.4-go2. Suite green. +- 2026-07-06 Refactor step 1 (refactor/descriptive-constants): renamed all flag + bits, trap types, item subtype constants, and Max* counts to descriptive names + (IsHuh→Confused, SeeMonst→SenseMonsters, WsHasteM→WandHasteMonster, + MaxSticks→NumWandTypes, ...); Level.NTraps→TrapCount; C names kept as comment + breadcrumbs. Pure rename, suite green. - 2026-07-06 Made the rgoue branch Go-only: removed C sources and the - autoconf/VS build system (they remain on master and modern-rogue), - ported the last wizard command (item-probability listing), rewrote - README.md for the Go port (c0b533e) -- 2026-07-06 Ported the command loop, save/restore, the tcell terminal - layer, and the playable binary at cmd/rogue (41fc104) -- 2026-07-06 Ported item effects: potions, scrolls, options, call_it - (cdf9bf7) -- 2026-07-06 Ported combat, the chase driver, traps, zapping, death and - scores (3c5add8) -- 2026-07-06 Ported dungeon generation, base items, the pack, and - monster creation (a69ef7d) -- 2026-07-06 Ported the foundation: types, seed-compatible RNG, item - tables, daemon scheduler (7fa2048) -- 2026-07-06 Wrote ARCHITECTURE.md Parts 1 and 2: complete map of the C - program and the Go port design (91eeee0, 45dba95) -- Fork base: Davidslv/rogue C 5.4.4 with modernization fixes (C23 - prototypes, ncurses compat), preserved on master/modern-rogue + autoconf/VS build system (they remain on master and modern-rogue), ported the + last wizard command (item-probability listing), rewrote README.md for the Go + port (c0b533e) +- 2026-07-06 Ported the command loop, save/restore, the tcell terminal layer, + and the playable binary at cmd/rogue (41fc104) +- 2026-07-06 Ported item effects: potions, scrolls, options, call_it (cdf9bf7) +- 2026-07-06 Ported combat, the chase driver, traps, zapping, death and scores + (3c5add8) +- 2026-07-06 Ported dungeon generation, base items, the pack, and monster + creation (a69ef7d) +- 2026-07-06 Ported the foundation: types, seed-compatible RNG, item tables, + daemon scheduler (7fa2048) +- 2026-07-06 Wrote ARCHITECTURE.md Parts 1 and 2: complete map of the C program + and the Go port design (91eeee0, 45dba95) +- Fork base: Davidslv/rogue C 5.4.4 with modernization fixes (C23 prototypes, + ncurses compat), preserved on master/modern-rogue # Future Steps 1. Docs refresh: update ARCHITECTURE.md Part 2 and README.md for the post-refactor names; add the C name → Go name rename table. -2. Playtest hardening pass: play several full games with the tcell - binary and extend run_test.go to script a deeper multi-level - playthrough (descend past level 5, use potions, scrolls, zapping, - save/restore). Fix any panics, message mismatches, or divergences - from the C behavior that this uncovers, with regression tests. -3. Verify the seed-compatibility claim against the C reference on - c-master: same seed, same dungeon, same item tables, for several - seeds. -4. Broaden unit test coverage where playtesting finds thin spots - (rings, sticks, wizard commands). -5. Tag a release once a full game (Amulet retrieval and score entry) - completes without defects. -6. Full-terminal-size support (deferred by explicit decision - 2026-07-06): per-game dungeon dimensions instead of the 80x24 - constants; open design questions are resize policy, gameplay - tuning at larger sizes, and a --classic 80x24 mode. -7. Note: this repo is exempt from the standard policy scaffold. Do not - add Makefile, Dockerfile, or REPO_POLICIES.md. +2. Playtest hardening pass: play several full games with the tcell binary and + extend run_test.go to script a deeper multi-level playthrough (descend past + level 5, use potions, scrolls, zapping, save/restore). Fix any panics, + message mismatches, or divergences from the C behavior that this uncovers, + with regression tests. +3. Verify the seed-compatibility claim against the C reference on c-master: same + seed, same dungeon, same item tables, for several seeds. +4. Broaden unit test coverage where playtesting finds thin spots (rings, sticks, + wizard commands). +5. Tag a release once a full game (Amulet retrieval and score entry) completes + without defects. +6. Full-terminal-size support (deferred by explicit decision 2026-07-06): + per-game dungeon dimensions instead of the 80x24 constants; open design + questions are resize policy, gameplay tuning at larger sizes, and a --classic + 80x24 mode. +7. Note: this repo is exempt from the standard policy scaffold. A minimal dev + Makefile (fmt/fmt-check/lint/test/check targets) exists per sneak's + 2026-07-07 request, but do not add a Dockerfile, CI config, or + REPO_POLICIES.md.