aboutsummaryrefslogtreecommitdiff
path: root/AGENTS.md
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-20 23:43:35 -0400
committerhistoria <[not public]>2026-06-20 23:43:35 -0400
commit470bc5bd99b793e46305310b5fbeb3068eba45f1 (patch)
treeff268fab1a8e7699cd6404e6e86c2d4bd1c6f2f6 /AGENTS.md
parent010fa439399581391fcd509d2058191ff4fa74b3 (diff)
downloadthehouseoficarus-470bc5bd99b793e46305310b5fbeb3068eba45f1.tar.gz
refactor: removed separate crafting recipes and combined them into item YAML
Diffstat (limited to 'AGENTS.md')
-rw-r--r--AGENTS.md59
1 files changed, 19 insertions, 40 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 461c235..d3196cd 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -26,7 +26,7 @@ internal/
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
+ action/ Behavior structs (GatherConfig, TalkConfig, UseConfig), 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)
@@ -52,7 +52,7 @@ internal/
## 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. Recipes are walked from disk on first access then cached in-memory with an `O(1)` ID index (see GetByID below). 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.
+**YAML-driven.** Rooms, items, objects, mobs, drops, modules, courses are YAML files under `data/`. Read from disk on every access — edit and it takes effect immediately, no restart. Crafting information lives in `craft:` blocks on item YAML files — no separate recipe directory. Items are organized into subdirectories by category (items: `equipment/`, `tools/`, etc.; rooms: `town/`, `wilderness/`, 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.
@@ -86,12 +86,11 @@ The pattern is: lock, snapshot/unmarshal, unlock, then process. The engine creat
```
data/rooms/<id>.yaml Room definitions (exits, objects, mobs, item_spawns, on_enter)
data/rooms/player_housing/ Player house rooms
-data/items/<id>.yaml Item definitions (stats, equip_slot, tool_type, burn_ticks, search_table, etc.)
+data/items/<id>.yaml Item definitions (stats, equip_slot, tool_type, craft, burn_ticks, search_table, etc.)
data/mobs/<id>.yaml Mob definitions (combat stats, drops, behavior, steal config)
data/objects/<id>.yaml Object definitions (name, behavior, hidden, inroom_description, removal_item)
data/drops/<id>.yaml Shared drop tables (weighted item lists, sub-table references)
data/help/<id>.yaml Help topics
-data/recipes/<id>.yaml Recipe definitions — individual or consolidated format (see worldbuilding_guide/recipes.md)
data/modules/<id>.yaml Science module definitions (id, level, max_hit, base_xp, junk_cost, category)
data/courses/<id>.yaml Agility course definitions (name, required_level, start_room, obstacles)
data/players/accounts/ Account YAML (gitignored)
@@ -106,7 +105,7 @@ See `worldbuilding_guide/` for full YAML format reference with examples.
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.
+**Duplicate ID checks** across all data directories (items, rooms, mobs, objects, drops, courses, modules, help). Detects collision from two files with the same ID in different subdirectories.
**Referential integrity checks:**
- Room exits → target rooms exist
@@ -115,8 +114,8 @@ At startup, the server runs comprehensive integrity checks on all data files. Er
- 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
+- Item search tables, farm_product → exist
+- Item craft blocks → consume items, fail products, station objects, tools → exist
- Drop table sub-references → exist
- Course start_room and obstacle rooms → exist
@@ -481,8 +480,7 @@ Prompt variables: `%h` (HP), `%H` (max HP), `%b` (battery), `%B` (max battery),
| `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) |
+| `craft` | Craft block — type, skill, level, XP, station, tool, consume, output_qty, fail, message, success, steps |
| `farming` | Farming-related fields (seed type, patch, yield, level, xp) |
| `potion` | Potion properties (buff levels for attack/strength/defense/agility) |
@@ -516,47 +514,28 @@ Prompt variables: `%h` (HP), `%H` (max HP), `%b` (battery), `%B` (max battery),
Production skills share a unified system in `core_production.go`:
-1. Create recipe YAMLs in `data/recipes/<type>/` — either individual (one per product) or consolidated (one per material group, with shared fields at top level and a `products:` list). See `worldbuilding_guide/recipes.md`.
+1. Create the output item YAML in `data/items/` with a `craft:` block. See `worldbuilding_guide/recipes.md`.
2. Create a station object in `data/objects/` if needed
3. Add a `cmd_<skill>.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`.
+The unified production cycle handles: HowMany prompt, start message, timed loops, success/fail rolls, XP, output quantity, stackable stacking, byproducts, multi-step messages, and end message. Tool requirements are checked in the command handler before calling `promptHowMany`.
-## Recipe Loading and Lookup
+## CraftIndex
-Recipes have two YAML formats — **individual** (one file per recipe) and **consolidated** (shared `type`/`station`/`wait`/`consume` at top level, products listed under `products:`). The loader expands consolidated files into individual `RecipeDef` structs transparently. The production engine (`core_production.go`) sees only `RecipeDef` — no code changes needed for format.
+`CraftIndex` (`internal/game/craft_index.go`) is built once at startup from all item YAMLs that have a `craft:` block. It provides `O(1)` lookups:
-**RecipeStore** (`internal/action/recipe.go`) manages recipe access:
-
-### GetByID — Step by Step
-
-`GetByID(recipeID)` is an `O(1)` direct recipe lookup. Here's the full chain:
-
-1. **First call anywhere in the game** — a command like `cook`, `smith`, or the tick advancing an active production action calls `GetByID("smith_bronze_dagger")`.
-
-2. **Index check.** `GetByID` checks the `byID` map (`map[string]*RecipeDef`). On first call, it's empty, so the method calls `ensureLoaded()`.
-
-3. **`ensureLoaded()`** checks if the full recipe slice is already cached under key `"_all"` in `s.cache`. If not (cold cache), it calls `loadAllRaw()`.
-
-4. **`loadAllRaw()`** walks `data/recipes/` recursively. For each YAML file:
- - First attempts to unmarshal as a plain `RecipeDef` (individual format). If `type` is non-empty, the recipe is appended.
- - If that fails, attempts to unmarshal as `consolidatedRecipeDef`. If `type` is set and `products` is non-empty, it expands each product entry via `expandProduct()` into a full `RecipeDef` (inheriting top-level `type`, `station`, `wait`, `consume`; overriding with product-level fields where specified).
- - **YAML parse errors are logged to stderr** — no more silent failures.
-
-5. **Index building.** `ensureLoaded()` caches the full recipe slice as `s.cache["_all"]` (permanent backing store), then populates `s.byID` with pointers into that slice: `byID["smith_bronze_dagger"] = &all[i]`. The cached slice keeps the backing array alive, so byID pointers never dangle.
-
-6. **GetByID returns.** The `*RecipeDef` pointer is returned and used by the caller (e.g., `loadProductionRecipe()` or `menuEntryName()`).
-
-7. **All subsequent calls** to `GetByID`, `LoadAll()`, or `LoadByType()` hit the in-memory cache — no filesystem walk. `LoadByType("smithing")` filters the cached `"_all"` slice and caches the filtered result.
+- **`ByType("pharmacy")`** — all pharmacy-craftable items (used by `mix` command)
+- **`ByStation("anvil")`** — all items craftable at that station
+- **`ByInput("guam")`** — all items that consume that ingredient
+- **`FindByTwoInputs(a, b)`** — items consuming both inputs in different consume entries (used by `use` command)
**Key properties:**
-- `GetByID` — `O(1)` map lookup, zero allocations
-- `LoadByType` — `O(n)` filter pass on first call per type, `O(1)` copy on cache hits
-- `LoadAll` — same as `LoadByType("")`, returns copy of `"_all"` cache
-- All public methods return copies — callers can't mutate cached data
-- No cache invalidation — recipes are static after startup. Server restart picks up changes.
+- Built once at startup — `O(n)` one-pass scan over all items
+- All indexes are `O(1)` lookups with zero allocations
+- The CraftIndex stores `*ItemDef` pointers — no RecipeDef copies
+- No cache invalidation — craft data is static after startup
## Adding a New Command