aboutsummaryrefslogtreecommitdiff
path: root/AGENTS.md
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-09 16:09:35 -0400
committerhistoria <[not public]>2026-07-09 16:09:35 -0400
commitb8c90886ef1f3afb8d908aac89028bd177836cae (patch)
tree7e566a7e308e7531bbb7d6a3a39239e789f69a3c /AGENTS.md
parentc705ae942573984784ef501bf8198f61f5206ddd (diff)
downloadthehouseoficarus-b8c90886ef1f3afb8d908aac89028bd177836cae.tar.gz
feat(admin): multi-select with shift+drag for common bulk operations
Diffstat (limited to 'AGENTS.md')
-rw-r--r--AGENTS.md200
1 files changed, 0 insertions, 200 deletions
diff --git a/AGENTS.md b/AGENTS.md
deleted file mode 100644
index 3da4260..0000000
--- a/AGENTS.md
+++ /dev/null
@@ -1,200 +0,0 @@
-# AGENTS.md
-
-The House of Icarus — a sci-fi MUD in Go. YAML-driven, no database. "Technology" replaces Prayer, "Science" replaces Magic, Scavenging replaces Runecrafting.
-
-## Build & Run
-
-```
-make build # go build -o thoi ./cmd/thoi
-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 <path>` (defaults to `config.yaml`). Dependencies: `gopkg.in/yaml.v3`, `github.com/gorilla/websocket`.
-
-## Prefixes
-
-File prefixes in `internal/game/`: `game.go/cmd_registry.go/tick.go` (core), `core_*` (infra), `act_*` (actions), `cmd_*` (commands), `sys_*` (subsystems), `render_*` (output), `production_*` (crafting engine), `combat_*` (hits), `look_*` (display).
-
-## Key Conventions
-
-**YAML-driven.** Rooms, items, objects, mobs, drops, modules, courses under `data/`. Read from disk on every access — edit and it takes effect immediately. **The filename is the ID** for every data type: lookup by filename stem. **Do not put a top-level `id:` field** (ignored). A nested `- id:` under a room's `objects:`/`mobs:` is a *reference* and is still required. Subdirectories are organizational only. **Local objects:** a room `objects:` entry that carries content fields (`name`/`description`/`aliases`/`inroom_description`/`color`/`hidden`/`on_look`) is a fully local, room-scoped object def instead of a reference — passive subset only (no gather/talk/use/safespot/steal/guard_mob/on_use). Local identity derives from the normalized `name` (lowercased, whitespace-collapsed, spaces kept); `id:` is reserved for references and ignored+warned on local objects. Each object in a room must have a unique name (startup error on collision, incl. local-name vs referenced file id); names may repeat across different rooms.
-
-**Live state.** Player data persisted to YAML on every change. Ground items, mob instances, object states are in-memory only.
-
-**No database.** Everything flat YAML. Passwords use sha256 + 16-byte random salt.
-
-**Transport-agnostic.** `Conn` interface (`internal/net/conn.go`) — telnet and websocket. Game code works with `*Session`.
-
-**Tick-driven.** 600ms default (min 50ms). All game advancement per tick. YAML timer fields accept `float64`; `engine.ToTicks(base)` probabilistically rounds.
-
-**Concurrency.** Every state package uses its own `sync.Mutex`. Pattern: lock → snapshot/unmarshal → unlock → process. Engine creates subscriber snapshot each tick.
-
-## Two State Systems
-
-- **Global flags** (`set_global_flags` / `global_flag`): Shared by all players.
-- **Player flags** (`set_player_flags` / `player_flag`): Per-character, saved to YAML.
-
-## Room Scripting
-
-- **`on_enter` steps** (`[]behavior.StepAction`): message + optional `condition` + `delay` + any of the shared `StepAction` effects (`set_global_flags`/`set_player_flags`, `give_item`/`take_item`, `teleport`, `heal`, `credits`, `aps_node`, `broadcast`, `broadcast_global`, `spawn_mob`, `despawn_mob`). Timed steps (any non-zero field triggers the timed path) run as scheduled enter sequences; otherwise messages print synchronously. Conditions snapshotted once at entry. Resumes on reconnect if interrupted.
-- **Conditional descriptions** (`description: [{text, condition}]`): first passing variant wins. For objects, if no variant matches the object is absent for that player.
-- **Exits** carry an `on_traverse:` list of `behavior.Interaction` (same shape as on_use) — first passing entry fires AFTER `p.RoomID` is updated to the destination, so `aps_node`/`teleport` resolve against the new room. `ExitDef.Condition` still gates whether the exit is passable at all; each `on_traverse` entry has its own additional `Condition`.
-
-## Data Files
-
-```
-data/rooms/<id>.yaml data/items/<id>.yaml data/mobs/<id>.yaml
-data/objects/<id>.yaml data/objects/unique/ data/drops/<id>.yaml
-data/hazards/<id>.yaml data/help/<id>.yaml data/modules/<id>.yaml
-data/techs/<id>.yaml data/courses/<id>.yaml data/players/accounts|characters/
-```
-
-Wander config per-instance in room YAML, not on MobDef. See `building_guide/` for full YAML format reference.
-
-## Map & Colors
-
-**Color specs are hex** (`00`–`FF`, case-insensitive) — the notation for the xterm-256 palette index (internally still `0–255`; only the string form is hex). Applies everywhere `color.Parse` runs: `color:` fields (rooms/items/objects), the `color`/`symbol` commands, `bg:`/`g:` tokens, and inline `{spec}text{/}` tags. Raw `ColorSpec{Fg:int}` literals stay decimal indices. `color` command targets live in `colorCategoryOrder` (`cmd_color.go`) + `config.DefaultColors()`; `map_at` (the `@`) and `map_blocked` (the blocked `X`) are themeable.
-
-**Map rendering** (`render_map.go`): BFS lays rooms on a 2D grid (NSEW move, up/down don't). Rooms carry an optional `color:` field (player `symbol` color overrides it). Links between two grid-adjacent rooms render per-direction: two-way → bar (`-`/`│`), one-way → arrow (`← ↑ → ↓` / `< ^ > v` pointing along travel), no traversable direction → blocked `X` (`map_blocked`). Bars/arrows blend the two endpoints' colors (`color.Average`); only `X` uses the blocked color. A player's custom symbol also shows in dim brackets on the look title.
-
-## Startup Validation
-
-Comprehensive integrity checks on all data files (errors logged, server still starts). Duplicate ID checks across all directories. Referential integrity: exits→rooms, mobs/objects/spawns→defs, drops/steal→items/tables, craft blocks→items/stations/tools, etc. Configurable orphan-room reachability check (`startup_validation.root_rooms`). Grid-overlap check: starting from `root_rooms`, lays each horizontal plane (NSEW only; up/down seed new planes) on a 2D grid and errors when two rooms collide on one cell (overlap) or a room maps to two cells (twist).
-
-## Command Dispatch
-
-`handleGameCommand()` in `game.go`: expands account aliases → split input → `classifyCommand()` → `ClassInstant` (executes now + prompt), `ClassFree` (next tick queue), `ClassActive` (sorted timestamp queue), `ClassUnknown` (verbAliases/obstacleVerbs, else error).
-
-## Tick-Based Action Queue
-
-`ProcessQueuedCommands()`: clear stale one-tick actions → execute free commands → execute active commands (sorted, first-queued wins conflicts) → advance walks → flush shared depletions. `ActionState` tracks player activity for `look`. Queue feedback shown by `show_queued_cmds`.
-
-## Action System
-
-Actions route through `startAction()` in `act.go`: normalize verb → resolve target (objects then mobs) → load behavior → route to handler.
-
-**Behavior types (YAML-driven):** `gather` (mine/chop/cut/fish), `use` (crafting stations), `talk` (NPC dialog). **Hardcoded actions:** burn/stoke, search, production (cook/smelt/smith/craft/mix/fletch/clean), steal, agility, farm, identify, finishing blow. Gather behaviors use SuccessChance = Base + (level-required)*PerLevel, clamped `[0, Cap]`. Action types and all YAML config fields are self-documenting in the source (`act_state.go`, `behavior.go`, `item/item.go`).
-
-### Interactions (unified conditional-action model)
-
-`behavior.Interaction` (struct in `behavior/behavior.go`) is the shared per-entry shape for every "when X happens, do Y with optional condition" feature in the game:
-
-- **On Use** (`on_use:` on objects) — fires on `use <obj>` (bare, no `item_id`) or `use <item> on <obj>` (item-keyed). Triggered in `cmd_use.go`; queued as a one-tick action; tick executor in `act_use_interaction.go`.
-- **On Look** (`on_look:` on objects) — fires after the object's description is shown on `look <obj>`. Triggered in `look_target.go`; runs the first passing entry inline (no queue). An entry with an `item_id` only fires if the player currently carries that item in their inventory.
-- **On Kill** (`on_kill:` on mobs) — fires (additively over standard loot) when a combat kill of the mob completes OR when a `kind: task` mob's work drains its HP to zero (the "completion" of the task). Triggered from `endCombat` in `combat_mob.go`. An entry with an `item_id` only fires if the player is wielding that item in a weapon-hand slot (main_hand or off_hand) at the moment of the kill.
-- **Exit On Traverse** (`on_traverse:` on exits) — fires AFTER `p.RoomID` is set to the destination, so `aps_node`/`teleport` resolve against the new room. Stashed in `Player.MovePendingInteraction` at move-classify time, applied in `completeMove` (`cmd_move.go`).
-- **On Enter steps** (`on_enter:` on rooms) — list of `behavior.StepAction`; each step has its own `Condition` + `Message`/`Delay` + full effect set. Reconnect-resume. Driven by `enterSeq`/`EnterSeqTick` in `act_room.go`.
-- **Trigger steps** (`steps:` on room/global `TriggerDef`) — same `StepAction` per step, newly supports per-step `Condition`. Trigger watcher + async playback in `sys_triggers.go` / `trigger_store.go`. The event-listener / cascade / re-entry-guard machinery is separate from interactions' inline dispatch.
-- **Talk** — `TalkNode.Action` and `TalkOption.Action` are `*NodeAction` (inline subset). Wrap via `handleNodeAction` → `applyNodeAction`.
-
-Each list of Interactions is walked top-to-bottom; the **first** entry whose item filter and `Condition` pass wins and fires (one entry per triggering event). `applyInteraction` (`act_effects.go`) is the convenience wrapper for that walk + fire; individual callers can loop directly. The item filter is interaction-kind-specific: on_use matches the held item, on_look requires `p.HasItem`, on_kill requires `p.IsWielding`; nil disables the filter (exit traversal, on_traverse).
-
-**Effect superset — `behavior.StepAction`.** The universal effect container embedded by all interaction `action` fields. Holds the inline-only `NodeAction` fields (`set_global_flags`, `set_player_flags`, `give_item`, `take_item`, `teleport`, `heal`, `credits`, `aps_node`) PLUS the sequence-only fields (`message`, `broadcast`, `broadcast_global`, `spawn_mob`, `despawn_mob`, `delay`) and a per-step `Condition`. Inline callers (use/look/kill/talk) ignore the sequence-only fields; on_enter / trigger steps use them.
-
-**One executor — `applyStepAction(sess, p, step, roomID, scope, flagValue)` in `act_effects.go`.** Scopes: `scopePlayer` (inline: flags + items + heal + credits + teleport + aps_node + broadcast), `scopePlayerSeq` (on_enter / trigger player seq: above + broadcast_global + spawn_mob + despawn_mob), `scopeGlobal` (trigger global seq: broadcasts + global flags + world-owned spawn/despawn only). `applyNodeAction` (`act_talk.go`) is a thin wrapper passing a NodeAction-wrapped StepAction at scopePlayer; `fireEnterStep` / `executeTriggerStep` / `executeGlobalTriggerStep` are thin wrappers at scopePlayerSeq / scopeGlobal. The old duplicate executors (`applyNodeAction` body, `fireEnterStep` body, `executeTriggerStep`, `executeGlobalTriggerStep`) collapsed into one in `act_effects.go`.
-
-`aps_node` retains its dedicated bool field — it stamps `aps_node_<currentRoomID>: true` on the player, which the `aps` command (`cmd_aps.go`) reads to build the datapad. (The room-id is dynamic, so it can't be expressed as a static `set_player_flags` entry without template substitution.) `reputation_cost` was removed (assassin reputation shop is not yet wired up).
-
-### Condition System
-
-Used by exits (exit gate + `on_traverse`), talk options/nodes, on-enter steps, on_use / on_look (objects), on_kill (mobs), trigger steps: `global_flag` (global), `player_flag` (per-char), `value` (exact match), `has_item`, `min_credits`, `all_of`, `any_of`, `not`. Bare global_flag/player_flag passes on truthy.
-
-## Two Depletion Mechanics
-
-- **Per-drop** (`depletes: true`): depletes on first successful gather of that drop.
-- **Shared** (`deplete_timer: <ticks>`): timer counts down while anyone gathers; at 0, next gather depletes for all. Regenerates when idle.
-
-## Ground Item System
-
-`DropDespawnTicks = 1000`. `ReserveTicks = 100` (mob loot reserved for killer). Same-ID items NOT merged. Spawn items respawn with merge. Despawn/reserve visible via options.
-
-## Drop Resolution (`behavior/store.go`)
-
-Drop tables are skill-independent weighted lists of items. `ResolveDrop` does one weighted pick; if the picked entry is a `table:` ref, its `quantity` yields that many copies of a single sub-roll (used by gather/search/steal). `ResolveDropList` (mob loot only, `combat_mob.go`) instead treats a picked table entry's `quantity` as the number of **independent rolls** against the sub-table (each may differ), returning one entry per roll; a plain item entry yields one result. Mob loot is still a single weighted pick per kill — a table entry just expands into N sub-rolls.
-
-## Shop System (`cmd_shop.go`)
-
-Root-level `shop:` on a MobDef (`behavior.ShopConfig`) — NOT part of the talk tree. Traded via plain `StateGame` commands: `list`, `buy <item>`, `sell <item>`, with `list <mob>` / `buy <item> from <mob>` / `sell <item> to <mob>` to disambiguate when a room has multiple shop mobs (else "Which shop?"). No bespoke shop CLI/session state. **Prices derive from item `value:`** — shops sell at 100% of value; buy back via OSRS formula `value×max(10, buy_percentage−stock×change_percentage)/100` (defaults `buy_percentage: 40`, `change_percentage: 3`, floor 10%). Per-item `stock` (target+starting); buying depletes (out of stock at 0), selling raises it (lowering next sell price). Restock moves current stock toward target one step per item `restock_ticks` (default `behavior.ShopDefaultRestock`, set from `game_constants.shop_default_restock` in config.yaml = 1000) in **both** directions; dynamic (unlisted) items decay to 0. `buys_anything` (default true) lets specialty shops refuse unlisted items. **Live stock is per-MobInstance, in-memory only** (`ShopStock`/`ShopRestock` maps), mutated only under `MobStore.mu` via `ShopTake`/`ShopAdd`/`ShopStockOf`; restock runs in `MobStore.Tick()`.
-
-## Combat System
-
-`attack_type` (stab/slash/crush/ranged/science) selects attack/defense bonuses. **Weapons** carry a single `attack_type` string. **Mobs** carry an `attack_type` list (`[]string`, validated): exactly one melee type (stab/slash/crush) plus optionally `ranged`/`science`. A mob uses its melee type in normal combat; when a player is safespotted from melee (`safespotBlocksMelee`), the mob switches to its strongest ranged/science type (`mobStrongestRangedScience`, by max hit) via `effectiveMobAttackType`. A melee-only mob whose melee is blocked is fully blocked (`safespotBlocksMob`). Tech protection (`applyTechProtection`) uses the *resolved* attack type of the landed hit. Ranged consumes 1 ammo/attack. Aggressive mobs auto-attack if `playerLevel <= mobLevel * 2`. Movement during combat = flee attempt (mob hits abort). Death: drop credits + keep 3 most valuable items, respawn at room 1, deactivate techs. Equipment `requirements` checked before equipping. Formulas and XP splits in `combat/tracker.go`.
-
-## Labor System (`sys_labor.go`, `sys_hazard.go`)
-
-Non-violent reuse of combat engine for worksites. Task mobs (`kind: task`): HP drains to 0 to complete work (progress bar fills). Never attacks back. Commands `work`/`attack`/`kill` route to `doAttack`. Per-type defenses = method efficiency.
-
-**Hazards:** Room references shared hazard by ID. `HazardTick()` rolls against everyone in room every `speed` ticks. `required_item` negates. Safespot blocks all hazard damage. Lethal hit calls `killPlayer`. Does NOT interrupt movement. Safe→dangerous prompts `[Y/n]` unless `danger_warning` off.
-
-## Technology System (`tech.go`)
-
-Togglable buffs that drain battery. 4 categories: Attack/Strength/etc. tiers (5/10/15% boost), Protection techs (40% reduction), Utility (regen/drain/retribution), Combo. Reserved IDs wired to code: `protect_melee`, `protect_ranged`, `protect_science`, `retribution`. Battery = Technology level. Each tech has drain rate. Equipment TechnologyBonus reduces drain (cap 50%). ActiveTechs resets on reconnect.
-
-## Science System (`science.go`)
-
-Mods from `data/modules/`: combat/utility/transport/enchant/processing. `trigger <mod>` fires once. `autotrigger <mod>` auto-fires each round when deck equipped. Deck combat uses weapon speed; mod fires instead if autotrigger set. No deck = manual queuing. View with `mods`/`modules`/`module`.
-
-## Hacking System
-
-Minigame interface: 3 games at terminals (levels 1/20/40). Interface in `game/hacking/`.
-
-## Skills (23 total, 1-99, classic XP table)
-
-| Category | Skills |
-| ---------- | --------------------------------------------------------------------------- |
-| Combat | Attack, Strength, Defense, Hitpoints, Ranged, Science, Technology, Assassin |
-| Gathering | Fishing, Woodcutting, Mining, Hacking, Scavenging, Thieving |
-| Production | Cooking, Smithing, Crafting, Fletching, Pharmacy, Construction, Farming |
-| Utility | Agility, Firemaking |
-
-## Session States
-
-States in `net/server.go` covering login flow, game, NPC talk, menus (bank/recipe/confirm), and hacking. (Shops are command-driven in `StateGame` — no dedicated shop state.)
-
-## ItemDef Fields
-
-Full field reference in `item/item.go`. Key fields: equip_slot, weapon_type, attack_type, stats, speed, requirements, tool_type, craft block, farming, potion, search_table.
-
-## CraftIndex
-
-`CraftIndex` built once at startup: O(1) lookups via `ByType`, `ByStation`, `ByInput`, `FindByTwoInputs`. No cache invalidation — static after startup.
-
-## Places to Be Careful
-
-- `startAction` searches objects first, then mobs. Mobs need `behavior:` on YAML def to be interactable.
-- Exits use a strict `{room: <int>}` map format.
-- `checkCondition` is the single condition evaluator — shared across exits, talk, on-enter, on_use/on_look (objects), on_kill (mobs), trigger steps. Every interaction type and every timed step shares one condition language.
-- Object `hidden: true` = not in room listings but still interactable.
-- Alias expansion happens before command dispatch and can shadow built-ins.
-- `say` preserves case from raw input.
-- `get`, `drop`, `quit` cancel active action.
-- `findGroundMatches`/`findInventoryMatches` support bidirectional prefix matching.
-- Mob wander config is per-instance in room YAML, not on MobDef.
-- Walk distance capped by agility level.
-- `wear all` equips highest-value per slot; `remove all` unequips everything.
-- Cape of Agility: instant movement (1 tick). Graceful pieces reduce by 0.25 each; full set bonus. Cape overrides Graceful.
-- During combat, movement = flee attempt. Mob hits abort flee.
-- Task mobs (`kind: task`) reuse combat engine 1:1; HP drains to 0 to complete. Never attack back — only room hazard provides danger.
-- Hazard damage omits flee-abort — never interrupts walking. Mob hits still do.
-- Safespot blocks ALL hazard damage, but melee work/attack forces you out of cover.
-- `killPlayer` is the shared death path — used by combat and lethal hazards. `mob` may be nil.
-- Science (deck) attacks use `combat.EffectiveRoll` for both rolls. `techLevelBonus(p, "science")` included in attack roll (Neural Focus).
-
-## Adding a New Command
-
-1. Add `cmd_<name>.go` in `internal/game/` with `func (g *Game) executeXxx(sess *net.Session, args []string, rawInput string)`
-2. Register in `commandRegistry` in `cmd_registry.go`
-3. Add help YAML in `data/help/`
-
-## Known Gaps
-
-- **Tick subscriber ordering**: `AdvanceActions`, combat subscriptions, mob attack subscriptions,
- and hazard ticks are independent subscribers without priority guarantees. A trigger completing
- and a mob landing a hit on the same tick have non-deterministic ordering.
-- **3D map BFS cost**: `buildGraph()` does a full 3D BFS across the entire reachable world on every
- map render (`map`/`look`/move). For large worlds (10k+ rooms) this could become expensive. Options:
- (a) cache the graph keyed by player room and invalidate on world edits (`dig`/`room insert`/`undig`),
- (b) prune by visited rooms (revert to horizontal-only expansion from unvisited), or (c) compute z-level
- assignments once at startup and use precomputed plane membership.