# AGENTS.md The House of Icarus — a Runescape-like sci-fi MUD written in Go. Data-driven via YAML files. No database, no ORM. Set on a terraformed asteroid where technology has regressed. "Technology" replaces Prayer, "Science" replaces Magic, Scavenging replaces Runecrafting. ## Build & Run ```bash make build # go build -o thoi ./cmd/mud make run # build + run with DATA_DIR=./data make test # DATA_DIR=$(PWD)/data go test ./... -timeout 60s make vet # go vet ./... ``` Binary accepts `--config ` (defaults to `config.yaml`). If no config found it creates one with defaults (telnet :4000, http :8888, tick 600ms). Dependencies: `gopkg.in/yaml.v3`, `github.com/gorilla/websocket`. ## Package Map ``` cmd/mud/main.go Entry point — config loading, multi-listener, tick subscriber registration internal/ config/ YAML config (telnet, telnet_tls, http, https listeners, color targets) net/ Conn interface (tcpConn, wsConn), Hub, Session, session states, IAC echo game/ Game struct, HandleSession state machine, command registry, all game logic hacking/ Minigame interface + 3 implementations (Wumpus, Mastermind, Liar's Dice) action/ Behavior structs (GatherConfig, TalkConfig, UseConfig), RecipeStore, SuccessChance player/ Player struct, skills, inventory, bank, equipment, accounts, XP, validation world/ World, RoomDef, MobDef, MobInstance, ObjState, ground items, wandering combat/ Combat state tracking, OSRS-style formulas (HitChance, MaxHit, AttackRoll) object/ ItemDef, ObjectDef, ItemStore, ObjectStore, EquipSlot, WeaponType, ItemStats engine/ Tick scheduler — configurable ms interval, subscriber pattern, ToTicks() color/ xterm-256 color system with auto ANSI downgrade, gradients, inline tags ``` `internal/game/` is organized by file name prefix for easy navigation: | Prefix | Purpose | Count | |---|---|---| | `game.go`, `cmd_registry.go`, `tick.go` | Core state machine, command dispatch, tick callbacks | 3 | | `core_*.go` | Infrastructure — types, utils, login, production engine, skill XP, flags, courses, equipment, stations, messages, startup validation | 13 | | `action_*.go` | Tick-based action lifecycles — gather, firemaking, talk, use, search, thieving, farming, agility, scavenging, assassin, fletching, cleaning, room scripts, targeting | 16 | | `cmd_*.go` | Command handlers — one file per command or command group | 49 | | `sys_*.go` | Game subsystems — technology, science, combat (death + aggro), assassin tasks, buffs, construction | 6 | | `ui_*.go` | Display/output — color, prompt, table, map, help | 5 | | `hacking/` | Hacking sub-package — Minigame interface, TerminalDef, CalcXP, WumpusGame, MastermindGame, LiarsDiceGame | 4 | **To add a new command:** create `cmd_.go` with the handler, add one line to `cmd_registry.go`. **To add a new skill:** create `action_.go` with `start*`/`advance*` methods dispatched from `action.go`. ## Key Conventions **YAML-driven.** Rooms, items, objects, mobs, drops, recipes, modules, courses are YAML files under `data/`. Read from disk on every access — edit and it takes effect immediately, no restart. Items, rooms, and recipes are organized into subdirectories by category (items: `equipment/`, `tools/`, etc.; rooms: `town/`, `wilderness/`, etc.; recipes: `smithing/`, `crafting/`, etc.). All IDs remain globally unique across subdirectories via path-indexed loading. **Live state.** Player data (characters, accounts) is written to YAML on every state change. Ground items, mob instances, and object states live in memory only and are lost on restart. **No database.** Everything is flat YAML files. Account passwords use sha256 + 16-byte random salt. **Transport-agnostic.** The `Conn` interface (`internal/net/conn.go`) abstracts the transport layer. `tcpConn` wraps a raw `net.Conn` for telnet. `wsConn` wraps `gorilla/websocket.Conn` for the web client. Game code works with `*Session` regardless of transport. **Input sanitization.** `HandleSession` strips Unicode control characters from every input line via `player.StripControlCharacters`. Account/character names validated to `[A-Za-z0-9 ]{1,30}`. Input truncated to 1024 bytes. Password echo suppressed via IAC ECHO (telnet) and OSC sequences (web). WebSocket origin checked against host header + loopback. **Tick-driven.** The world runs on a configurable tick (default 600ms, min 50ms). All game advancement happens per tick. **Fractional ticks.** All YAML timer fields accept `float64` values. `engine.ToTicks(base)` probabilistically rounds to an integer — a tool_speed of 4.5 means each action cycle has a 50% chance of 4 ticks and 50% of 5 ticks. **Concurrency.** Every state-bearing package uses its own `sync.Mutex`: - `world.World` — groundItems, objStates, seeded, objMoves - `world.MobStore` — defs, instances - `action.Store` — cache - `engine.Engine` — subscribers - `Game` — charsMu (loggedInChars map) - `combat` — combatants, mobTargets The pattern is: lock, snapshot/unmarshal, unlock, then process. The engine creates a subscriber snapshot each tick. ## Two State Systems - **World flags** (`set_flags` / checked with `flag`): Shared by all players. A door opened by one player is open for everyone. - **Player flags** (`set_player_flags` / checked with `player_flag`): Per-character, saved to character YAML. Quest progress, dialog history. ## Data Files ``` data/rooms/.yaml Room definitions (exits, objects, mobs, item_spawns, on_enter) data/rooms/player_housing/ Player house rooms data/items/.yaml Item definitions (stats, equip_slot, tool_type, burn_ticks, search_table, etc.) data/mobs/.yaml Mob definitions (combat stats, drops, behavior, steal config) data/objects/.yaml Object definitions (name, behavior, hidden, inroom_description, removal_item) data/drops/.yaml Shared drop tables (weighted item lists, sub-table references) data/help/.yaml Help topics data/recipes/.yaml Recipe definitions (cooking, smithing, crafting, fletching, etc.) data/modules/.yaml Science module definitions (id, level, max_hit, base_xp, junk_cost, category) data/courses/.yaml Agility course definitions (name, required_level, start_room, obstacles) data/players/accounts/ Account YAML (gitignored) data/players/characters/ Character YAML (gitignored) ``` Wander config (`wander_rooms`, `wander_interval`) is set per-instance in room YAML — mob defs are generic. Mobs wander via legal (unconditioned) room exits; objects teleport between rooms in their assigned list. See `worldbuilding_guide/` for full YAML format reference with examples. ## Startup Validation At startup, the server runs comprehensive integrity checks on all data files. Errors cause the server to exit; warnings are informational. **Duplicate ID checks** across all data directories (items, rooms, mobs, objects, drops, recipes, courses, modules, help). Detects collision from two files with the same ID in different subdirectories. **Referential integrity checks:** - Room exits → target rooms exist - Room mobs/objects/spawns → mob defs/object defs/items exist - Mob drops, steal tables, finishing blow items → referenced items/drop tables exist - Object removal items, steal tables, guard mobs, use_interaction items → exist - Object gather config drops → items/tables exist; bait items exist - Object talk config node actions (give_item, take_item, teleport, shop items) → exist - Item search tables, made_from items/byproducts, farm_product → exist - Recipe consume items, output, fail product → exist - Drop table sub-references → exist - Course start_room and obstacle rooms → exist **World wiring checks** (config-driven): - Orphan rooms (no exit from any room leads to them) ### Config: `startup_validation` ```yaml game: tick_length: 600 startup_validation: check_sources: [1] # rooms to start reachability walk from ignore_unreachable: [] # suppress "no exit leads here" for these rooms ``` Defaults: `check_sources: [1]`, ignore list empty. Set `check_sources` to include rooms accessible only via teleport (e.g., `[1, 1000]`) to suppress false orphan warnings. Use `ignore_unreachable_to` for rooms that are intentionally isolated. ## Inline Color Tags Room descriptions and prompts support inline color tags using `{spec}text{/}` syntax. ```yaml description: "On the table lies a {182 bold}mysterious vase{/} with a rose in it." ``` Tag spec format: `{<0-255> [bold] [dim] [underline]}text{/}`. Gradients: `{g:196,82}gradient text{/}`. Multi-stop: `{g:45,39,59}three stops{/}`. Item and object YAML `color` fields also support gradient syntax: `color: "g:196,208,226"`. Gradients interpolate in RGB space across the xterm-256 palette. In ANSI mode, each character maps to the nearest ANSI color. ## Command Dispatch `handleGameCommand()` in `game.go`: 1. Expands account aliases (aliases can shadow built-in commands) 2. Splits input, lowercases the command word 3. Special-cases `equip`/`wear`/`wield` with no args → display equipment directly 4. Calls `classifyCommand(cmd)` from `cmd_registry.go` → `ClassInstant` / `ClassFree` / `ClassActive` / `ClassUnknown` 5. ClassInstant executes `executeCommand()` immediately + writes prompt 6. ClassFree appends to `freeQueue[playerName]` for next tick 7. ClassActive replaces entry in `activeQueue[playerName]`, sorted by timestamp on execution 8. ClassUnknown falls through to `verbAliases` and `obstacleVerbs` maps, then prints error ## Tick-Based Action Queue `ProcessQueuedCommands()` runs each tick: 1. Clears stale `ActionState` from one-tick actions (move, get, drop) 2. Executes all free commands per player in queued order 3. Executes all active commands sorted globally by timestamp (first-to-queue wins conflicts) 4. Advances walk sequences (one step per tick) 5. Flushes pending shared depletions **Conflict resolution:** multi-player attack on same mob, get on same item, etc. — first to queue wins (timestamp-order). Shared depletion awards all successful gatherers on depletion tick. **ActionState** tracks player activity for `look` display. Timed actions persist until interrupted; one-tick actions (move, get, drop) clear after one tick. Queue feedback is suppressed by default via the `queue_silently` option. ## Action System Actions that interact with objects/mobs route through `startAction()` in `action.go`: 1. Normalizes verb via `verbAliases` map (mine/chop/fish/cut → gather, talk/speak/ask → talk) 2. Multiple object types → "That's ambiguous, which one?" 3. Searches object instances in room (numbered targeting via `N.name`) 4. Falls back to mob instances if no object matches 5. Loads the behavior from YAML 6. Routes to type-specific handler (startGather, startMobTalk, startTalk, startUse) The `verbSkill` map maps verb → skill name for default target resolution: - mine → mining, chop/cut → woodcutting, fish → fishing ### Action Types (from action_state.go) | ActionType | Trigger | Type | |---|---|---| | `gathering` | mine/chop/cut/fish | Active | | `combating` | attack/kill | Active | | `using` | use / pull / push | Active | | `talking` | talk/speak/ask | Active | | `burning` | burn | Active | | `stoking` | stoke | Active | | `searching` | search | Active | | `resting` | quit | Active | | `walking` | walk | Active | | `moving` | n/s/e/w/u/d | Active | | `picking_up` | get | Active | | `dropping` | drop | Active | | `producing` | cook/smelt/smith/craft/mix/construct | Active | | `fletching` | fletch | Free (background) | | `eating` | eat | Free | | `cleaning` | clean | Free (background) | | `identifying` | identify/id | Active | | `stealing` | steal/thieve | Active | | `planting` | plant | Active | | `harvesting_crop` | harvest | Active | | `raking` | rake | Active | | `watering` | water | Active | | `curing` | cure | Active | | `traversing` | obstacle verbs (scramble/jump/etc.) | Active | | `hacking` | jack/jackin | Active | ### Behavior Types (YAML-driven in behavior.go) | Type | Verbs | Description | |---|---|---| | `gather` | mine, chop, cut, fish | Resource gathering with skill checks, tool requirements, depletion/respawn | | `use` | use | Crafting stations — consume items, produce output, optional skill checks | | `talk` | talk, speak, ask | NPC conversation trees with choices, conditions, node actions | ### Hardcoded Actions (not YAML-driven) | Action | File | Description | |---|---|---| | burn/stoke | `action_burn.go` | 3-phase firemaking: drop log → try → skill check → create fire. Stoke adds logs. | | search | `action_search.go` | Search items with search_table, consumes one, rolls on drop table | | cook/smelt/smith/craft/mix | `production_core.go` | Unified recipe-driven production — station checks, timed loops, success/fail, XP, byproducts | | fletch | `action_fletch.go` | Background fletching — instant or timed, stackable output | | clean | `action_clean.go` | Background herb cleaning via recipe matching | | steal | `action_steal.go` | Thieving — pickpocket, stall stealing, guard watching, sneak mode | | agility | `action_agility.go` | 3-phase obstacle traversal with fail chance, course lap tracking | | farm | `action_farm.go` | Planting, growth stages (500-tick), watering, disease, harvesting | | identify | `action_identify.go` | Scavenging — scrap_metal → junk at altars | | finishing blow | `action_finishing_blow.go` | Assassin special item on mob at 1 HP | ### GatherConfig Fields | Field | Description | |---|---| | `skill` | Skill name for level check | | `level` | Required level | | `xp` | XP per successful gather | | `base_wait` | Base ticks per action cycle | | `tools` | Required tool_type list | | `bait` | Required bait item | | `success` | SuccessFormula (base + per_level * (level - required), capped) | | `drops` | Weighted drop entries (item_id, table ref, depletes, quantity, message, level, xp) | | `respawn_timer` | Ticks until depleted object respawns | | `deplete_timer` | Ticks for shared depletion (trees) | | `nest_chance` | 1/N chance for bird's nest alongside normal drop | | `gather_message` | Custom "You gather..." message | | `depleted_message` | Custom "The rock is depleted" message | | `exhausted_message` | Custom "The tree falls" message | | `fail_message` | Custom failure message | | `respawn_broadcast` | Broadcast message when object respawns | ### SuccessChance Formula ```go chance = cfg.Base + (level - requiredLevel) * cfg.PerLevel clamped to [0, cfg.Cap] ``` ### Node Actions (talk dialog) | Field | Effect | |---|---| | `set_flags` | Sets world flags (global) | | `set_player_flags` | Sets player-local flags (per-character, saved to YAML) | | `give_item` | Gives an item to inventory | | `take_item` | Removes an item from inventory | | `teleport` | Moves player to a room ID | | `heal` | Restores hitpoints | | `cost` | Deducts credits from player | | `shop` | Opens a buy/sell interface | | `assign_task` / `skip_task` / `extend_task` | Assassin task management | | `sawmill` | Opens sawmill interface (construction) | ### Condition System Used by exits, talk options, on-enter scripts, and use_interactions checks: | Field | Scope | Example | |---|---|---| | `flag` | World (shared by all) | `flag: gate_open` | | `player_flag` | Per-character | `player_flag: finished_tutorial` | | `has_item` | Player inventory | `has_item: bronze_key` | | `min_credits` | Player credits | `min_credits: 100` | | `all_of` | All sub-conditions pass | Nested list | | `any_of` | Any sub-condition passes | Nested list | | `not` | Invert the check | `not: true` | ## Two Depletion Mechanics **Per-drop depletion** (mining, regular trees): Set `depletes: true` on a drop entry. The resource depletes on first successful gather of that drop. **Shared depletion** (higher-tier trees): Set `deplete_timer: ` on the behavior. A shared timer counts down while anyone is chopping; when it hits 0, the next successful gather depletes the tree for all players. Timer regenerates when no one is chopping. Managed by `SharedDepletionTick`. ## Ground Item System - `DropDespawnTicks = 1000` — dropped items despawn after 1000 ticks - `ReserveTicks = 100` — mob loot is reserved for the killer for 100 ticks - Items with same ID in the same room are NOT merged — independent despawn timers - Spawn items respawn after their delay; nearby matching items are merged on respawn - Despawn timers visible via `despawn` option; reserve info via `reserve` option ## Combat System **Attack types.** Weapons have an `attack_type` field (stab/slash/crush/ranged/science). The attack type determines which player attack bonus and which mob defense bonus to use. **OSRS accuracy formula.** `HitChance(a, d)`: if `a > d` → `1 - (d+2)/(2*(a+1))`, else `a/(2*(d+1))`. Equal rolls ≈ 50%. **Ranged combat.** Weapon type "ranged" uses Ranged level for attack rolls, consumes 1 ammo per attack. If ammo runs out, combat ends. XP: 75% Ranged, 25% Hitpoints. **Agile mobs.** Mobs with `aggressive: true` auto-attack players entering their room (1-tick delay) if `playerLevel <= mobLevel * 2`. **Equipment requirements.** Items can have `requirements: { attack: 20, defense: 10 }` — checked before equipping. **Fleeing combat:** Movement during combat is a flee attempt. Mobs continue attacking during the movement countdown; a hit aborts the flee ("Can't escape!"). Cape of Agility makes fleeing instant. **Death mechanic:** Player drops credits to ground. If >3 items total (inventory + equipment), the 3 most valuable are kept, rest dropped. Player teleports to room 1 at full HP. All active techs deactivated. **XP formula:** `baseXP = dmg * 4`, distributed by attack style: - Accurate: +3 attack, 75% Attack XP / 25% HP - Aggressive: +3 strength, 75% Strength XP / 25% HP - Defensive: +3 defense, 75% Defense XP / 25% HP - Balanced: +1 each, 25% each Attack/Strength/Defense/HP - Ranged styles: varied bonus, 75% Ranged XP / 25% HP **Mob combat level:** `floor((def+hp)/4 + max((atk+str)/4, rng*3/8, sci*3/8))`. **Player combat level:** Ranged-or-melee dominant, includes Technology (0.25) and Science (0.125). **Mob DropTable:** `Remains` (guaranteed drop item), `Loot` (weighted DropEntry table). ## Technology System (tech.go) 25 hardcoded techs in 4 categories, unlocked by Technology level: - **Attack/Strength/Defense/Ranged/Science tiers:** I/II/III at 5%/10%/15% level boost - **Protection techs:** Kinetic Barrier (melee), Projectile Screen (ranged), Neural Firewall (science) — 40% damage reduction each - **Utility:** Nano Repair (2x HP regen), Rapid Repair (4x), Power Saver (20% drain reduction), Dead Man's Switch (25% max HP retribution on death) - **Combo:** Overclock (15% atk+str), Fortify (15% def + 2x regen) Battery = Technology level (float64). Each tech has a drain rate (0.05–0.25 per tick). Equipment TechnologyBonus reduces drain (capped at 50%). Active techs deactivate when battery hits 0. 1-tick "flicking" grace period supported. ## Science System (science.go) Mods loaded from `data/modules/` — categories: combat, utility, transport, enchant, processing. Each mod has a junk cost. Deck equipment (science weapons) provides junk and removes scrap_metal cost. **Triggering mods:** - `trigger ` — triggers a mod. Combat mods in non-deck combat queue as a one-shot attack. Transport/utility/enchant mods resolve immediately. Module matching searches by partial name (all words); if ambiguous, picks the highest-level module the player can cast (at or below their Science level). - `autotrigger ` — sets a combat mod to auto-fire each attack round when a deck is equipped. `autotrigger none` disables. Without a deck, autotrigger has no effect. **Deck combat:** When a deck weapon is equipped, attacks fire at the weapon's speed. If an autotrigger mod is set and the player has the required junk, the mod fires instead of a normal attack. If no autotrigger or out of junk, an error is shown and the player does an unarmed attack with no bonuses. **Non-deck manual triggers:** `trigger ` during combat without a deck queues one module to replace the next combat attack. Only one can be queued at a time. Mod list viewable with `mods`, `modules`, or `module` command. ## Hacking System (game/hacking/) Minigame interface: `Init(level) string`, `HandleInput(input) (output, done, won)`, `Name() string`. Three minigames at 3 terminal levels (1, 20, 40): - **WumpusGame** — Hunt the Rogue AI: 20-node dodecahedron, probes, hazards - **MastermindGame** — Code Breaker: 4-digit cipher, 10 guesses, feedback - **LiarsDiceGame** — Signal Bluff: hidden dice bidding vs AI, wilds, exact calls XP scales with Hacking level. ## Skills (23 total, 1-99, RSC XP table) | Category | Skills | |---|---| | Combat | Attack, Strength, Defense, Hitpoints, Ranged, Science, Technology, Assassin | | Gathering | Fishing, Woodcutting, Mining, Hacking, Scavenging, Farming, Thieving | | Production | Cooking, Smithing, Crafting, Fletching, Pharmacy, Construction, Firemaking | | Utility | Agility | All fully implemented. ## Option System Account-wide options set via `option `. Stored in account YAML, shared by all characters on the account. | Option | Type | Default | Description | |---|---|---|---| | `description` | bool | true | Long room descriptions when moving | | `tiny_map` | string | "right" | off/right/left — mini-map display | | `xp_drops` | bool | true | XP drop messages | | `exits` | bool | true | Long exit display in look | | `mob_enter` | bool | true | Messages when mobs enter | | `mob_leave` | bool | true | Messages when mobs leave | | `mob_spawn` | bool | true | Messages when mobs spawn | | `reserve` | bool | true | Show full reserved item details | | `depletion` | bool | false | Show depletion timers | | `despawn` | bool | false | Show ground item despawn timers | | `color` | string | "xterm256" | none/ansi/xterm256 — color output mode | | `mapwidth` | int | 30 | Map viewport width | | `mapheight` | int | 20 | Map viewport height | | `mappadding` | string | "none" | none/x/y/xy — blank-row stripping | | `automap` | bool | false | Auto-show map after moving | | `queue_silently` | bool | true | Suppress queue messages | | `room_desc_width` | int | 70 | Room desc line wrap width | | `unicode` | bool | true | Unicode box-drawing characters | | `run_countdown` | bool | false | Flee countdown messages | | `visual_ticks` | bool | false | Tick marker display | | `visual_tick_count` | int | 0 | Tick counter cycle length | | `visual_tick_text` | string | "Tick" | Text for visual tick marker | | `fletch_all` | bool | false | Auto-start fletching | | `smith_all` | bool | false | Auto-start smithing | | `cook_all` | bool | false | Auto-start cooking | | `smelt_all` | bool | false | Auto-start smelting | | `mix_all` | bool | false | Auto-start mixing | | `construct_all` | bool | false | Auto-start constructing | | `craft_all` | bool | false | Auto-start crafting | Prompt is NOT an option — per-character via the `prompt` command. Default: `"> "`. `prompt none` sets it to blank. Prompt variables: `%h` (HP), `%H` (max HP), `%b` (battery), `%B` (max battery), `%c` (credits), `%i` (free slots), `%s` (style short), `%S` (style long), `%m` (mob HP), `%M` (mob max HP), `%X_` (XP), `%x_` (XP to next). ## Session States (net/server.go) | State | Purpose | |---|---| | `StateAccountName` | Entering account name | | `StatePassword` | Entering password | | `StateNewAccountPass` | Setting password for new account | | `StateNewAccountConfirm` | Confirming password | | `StateColorChoice` | New account: "Should I disable color?" | | `StateMenu` | Main menu | | `StateNewCharName` | Naming new character or selecting for connection | | `StateRenameAccount` | Renaming account | | `StateRenameChar` | Selecting character to rename | | `StateRenameCharName` | Entering new character name | | `StateDeleteChar` | Confirming character deletion | | `StatePurgeAccount` | Confirming account purge | | `StateGame` | In-game command mode | | `StateChangeDescription` | Setting player description | | `StateTalk` | NPC conversation input | | `StateDropAllConfirm` | Confirming `drop all` | | `StateRecipeChoice` | Recipe/product selection (by number or name) | | `StateHowMany` | "How many?" production count | | `StateSmithProduct` / `StateFletchProduct` / `StateCraftProduct` / `StateProductChoice` | Product selection table menus | | `StateShop` | Shop interface (buy, sell, browse, leave) | | `StateBank` | Bank interface (deposit, withdraw, browse, leave) | | `StateHacking` | Jacked into a terminal running a minigame | ## ItemDef Fields (object/item.go) | Field | Description | |---|---| | `id` | Unique identifier | | `name` | Display name | | `aliases` | Alternate name prefixes for matching | | `description` | Item description | | `value` | Credit value | | `stackable` | Can stack in inventory | | `equip_slot` | head/neck/torso/legs/hands/feet/back/ammo/main_hand/off_hand/ring | | `weapon_type` | melee / ranged / science | | `attack_type` | stab / slash / crush / ranged / science | | `stats` | Per-type attack/defense bonuses, strength, ranged_strength, science_damage, technology_bonus | | `speed` | Attack speed in ticks | | `requirements` | Skill level requirements to equip | | `tool_type` | Tool type string for gather requirements | | `tool_speed` | Speed bonus for gathering | | `burn_ticks` | Burn duration for firemaking | | `fire_level` | Required firemaking level | | `fire_xp` | XP for burning | | `quality` / `max_quality` | Default and maximum quality | | `search_table` / `search_misc_table` | Drop tables for searching this item | | `search_ticks` | Ticks per search action | | `search_message` | Custom search message | | `made_from` | MadeFrom entries for item-on-item combinations (with byproducts) | | `ticks` | Ticks per production cycle for MadeFrom combines (default 2) | | `farming` | Farming-related fields (seed type, patch, yield, level, xp) | | `potion` | Potion properties (buff levels for attack/strength/defense/agility) | ## Places to Be Careful - `startAction` searches objects first, then mobs. A mob must have `behavior:` set on its YAML def to be interactable. - Exits changed from `map[ExitDir]int` to `map[ExitDir]ExitDef` — old `north: 2` still works via custom `UnmarshalYAML`. Same pattern for `RoomMob`. - `checkCondition` is the single condition evaluator — used by exits, talk options, on-enter scripts, and use_interactions. - Object `hidden: true` means the object doesn't appear in room listings but is still interactable. - Alias expansion happens before command dispatch and can shadow built-in commands. - `say` preserves case from raw input. - `get`, `drop`, and `quit` cancel the player's active action. - `findGroundMatches` and `findInventoryMatches` support bidirectional prefix matching — `"iron ax"` matches `"iron axe"`. - Mob wander config is per-instance in room YAML via `RoomMob`, not on `MobDef`. - Walk distance capped by agility level. - `wear all` equips the highest-value item per slot; `remove all` unequips everything. - Cape of Agility makes movement instant (1 tick). Graceful pieces each reduce by 0.25 ticks; full set +0.5 bonus for 2 ticks total. Cape overrides Graceful. - During combat, movement is a flee attempt. Mob hits during countdown abort the flee. - Level-up messages output `*** You are now level N ! ***` across all skills that gain XP. ## Adding a New Skill 1. Add `SkillName` constant + entry in `AllSkills` + `SkillAbbr` in `internal/player/player.go` 2. Create a gather behavior YAML in `data/behaviors/` 3. Create an object YAML referencing that behavior in `data/objects/` 4. Create an item for the tool in `data/items/` (with `tool_type`) 5. Place the object in a room YAML in `data/rooms/` 6. Create any resource items in `data/items/` ## Adding a New Production Skill Production skills share a unified system in `core_production.go`: 1. Create recipe YAMLs in `data/recipes/` with the new `type` 2. Create a station object in `data/objects/` if needed 3. Add a `cmd_.go` command handler following existing patterns 4. Add the command to `commandRegistry` in `cmd_registry.go` with the appropriate class 5. Wire up the `auto_start` option if desired The unified production cycle handles: HowMany prompt, start message, timed loops, success/fail rolls, XP, output quantity, stackable stacking, byproducts, and end message. Tool requirements are checked in the command handler before calling `promptHowMany`. ## Adding a New Command 1. Add a `cmd_*.go` file in `internal/game/` with the handler function `func (g *Game) executeXxx(sess *net.Session, args []string, rawInput string)` 2. Add the command to `commandRegistry` in `cmd_registry.go` with the appropriate `CommandClass` 3. Update `data/help/` YAML files