RELEASE
planning/2.6.0/designs/CRAFT_MENU_DESIGN.md
sha256 066cabd8f07708bc · 16043 bytes ·
original held in the private archive
# CRAFT MENU — Design Doc (v2.6.0)
> Design-only. No code changes. Targets `origin/main` @ `eb6a8bb` (v2.5.0-r29, 981 tests).
> Author: Opus design pass. Status: awaiting owner decisions (§H) before build.
## Architecture findings (verified against current code)
- **Inventory** — `state.inventory = [{ name, qty, wgt, val, type }]` (`state.js:343`); `type ∈ {weapon, armor, aid, mod, misc}`. **Ammo is separate**: `state.ammo = { "<caliber>": count }` (the `addItem` ammo-routing path at `ui-render.js:20` sends `type:'ammo'` items there). `state.skills = { <skill>: 0-100 }` (`state.js:327`).
- **Inventory mutation** — `addItem()` dedups by name (increments `qty`), auto-fills `wgt/val/type` via `lookupItemInDb(name)` (`db_*.js:786`); `delItem(idx)` splices. Persistence = debounced `saveState()` (local localStorage write) + `beforeunload` flush. **No auto cloud push** (cloud is manual-button only).
- **`[CRAFT]` today** — NOT a client feature. It's only an **AI directive keyword** (`api.js:92`, token-triage list `api.js:1071`) + a help-menu entry (`ui-core.js:1151`). Crafting is currently 100% AI-handled (the AI returns an updated inventory). There is **no structured recipe data and no client crafting logic** — this design adds both.
- **RECIPES.CSV** — raw AI-text only, inside `databaseCSVs` (sent to the model), **not parsed into any cache**. Schema `Recipe_Name, Skill_Req, Components, Output, Quarantine_Cap`; **FNV = 10 recipes, FO3 = 8**. `Components` is free-text (`"1 Scrap Electronics / 1 Scrap Metal"`) — too fragile to parse reliably.
- **Registry** — `FALLOUT_REGISTRY` (`reg_nv.js` / `reg_fo3.js`) is **read-only** (Protocol 23), and is **already per-game**: the boot loader loads exactly one reg file by `activeContext`, so `FALLOUT_REGISTRY.<anything>` is automatically the active game's data. `registrySearch(category)` reads `FALLOUT_REGISTRY[category]`. No `GAME_DEFS` flag is needed for recipes (both games craft).
- **Panel convention** — top-level `<details class="panel" data-tab="inv|stat|...">`; nested `<details class="sub-panel" data-sub-id="...">` whose collapse state persists in `localStorage['robco_panel_state']` (`ui-core.js:295`, default-collapsed on first load). `_updatePanelBadges()` maps an `h2` text → count badge (`ui-core.js:797`); `expandPanelForCategory(key)` auto-opens a panel via `tabMap` (`ui-core.js:920`). The craft panel must match this compact/collapsible/persisted style (owner is strict on UI consistency).
- **Destructive-op convention** — `confirm()`-gated (e.g. `wipeTerminal`).
---
## A. Recommended recipe data model + location
**Add a structured `recipes` array to `FALLOUT_REGISTRY` in BOTH `reg_nv.js` and `reg_fo3.js`** (read-only registry, auto-per-game). **Do NOT parse RECIPES.CSV** — keep it as the AI-text reference; the registry array is the structured source of truth for the menu.
```js
// FALLOUT_REGISTRY.recipes (per game)
{
name: 'Weapon Repair Kit', // unique recipe id + default output display
station: 'workbench', // 'workbench' | 'reloading' | 'campfire' | 'sink'
output: { item: 'Weapon Repair Kit', qty: 1 }, // type/wgt/val resolved via lookupItemInDb at craft time
ingredients: [
{ item: 'Scrap Electronics', qty: 1 },
{ item: 'Scrap Metal', qty: 1 },
],
skillReq: { skill: 'repair', level: 50 }, // or null
perkReq: null, // e.g. 'Hand Loader' | null
dlc: null, // e.g. 'Old World Blues' | null (display tag only)
}
```
Concrete examples:
```js
{ name: 'Hand-Loaded .308', station: 'reloading',
output: { item: '.308', qty: 10, ammo: true }, // ammo:true → routes to state.ammo, not inventory
ingredients: [ {item:'.308 Casing', qty:1}, {item:'Pistol Powder', qty:1}, {item:'Lead', qty:1} ],
skillReq: { skill:'repair', level:55 }, perkReq:'Hand Loader', dlc:null },
{ name: 'Wasteland Omelet', station: 'campfire',
output: { item: 'Wasteland Omelet', qty: 1 },
ingredients: [ {item:'Brahmin Egg', qty:2} ],
skillReq: { skill:'survival', level:50 }, perkReq:null, dlc:null },
```
Notes: `output.ammo:true` flags ammo-producing recipes so the craft handler routes the yield to `state.ammo` (reusing the existing ammo path) instead of `state.inventory`. `wgt/val/type` are NOT stored on the recipe — they come from `lookupItemInDb(output.item)` at craft time, so the produced item matches the DB exactly (single source of truth).
**Scrap/breakdown data** — add a parallel read-only array (or reuse recipes that are cleanly reversible). Recommended explicit table so yields are deterministic and testable:
```js
// FALLOUT_REGISTRY.breakdowns (per game)
{ item: 'Weapon Repair Kit', yields: [ {item:'Scrap Metal', qty:1} ], skillReq:null }
// (yields may be reduced vs the craft cost — owner decides canon vs simplified, §H-3)
```
---
## B. Sourcing (orchestrator → builder)
Structured recipes come from **fallout.wiki crafting pages** — FNV: Workbench, Reloading Bench, Campfire, **Sink/Hot Plate (OWB)**; FO3: Workbench (Schematics). The **orchestrator sources the structured list via Chrome** and hands the builder the `recipes`/`breakdowns` arrays in the schema above (the builder does not free-source).
**Reasonable v1 size** (curated, not full parity — FNV has 100+ recipes incl. every reloading variant): **~30–40 FNV recipes + ~12–18 FO3 recipes**, covering the notable/useful items across stations; **~15–25 breakdown entries** for clearly-scrappable items. Full reloading-bench parity (every ammo variant) is a v2 candidate.
---
## C. Craft panel UI
A top-level **`<details class="panel" data-tab="inv">`** titled `> CRAFTING`, placed in the **INV tab** alongside the inventory cluster, matching the compact/collapsible/persisted style (collapse state in `robco_panel_state`).
Layout (per recipe row, compact):
```
> CRAFTING [badge: N craftable]
[ Station ▾ All | Workbench | Reloading | Campfire | Sink ] ← optional filter
── Weapon Repair Kit (Repair 50 ✓) [ qty: 1 ][MAX] [ CRAFT ]
Scrap Electronics 2/1 ✓ · Scrap Metal 0/1 ✗ ← have/need, green/red
── Wasteland Omelet (Survival 50 ✗) [ qty: 1 ][MAX] [ CRAFT ]
Brahmin Egg 1/2 ✗
```
- **Have/need per ingredient**: `have = state.inventory.find(name).qty` (or `state.ammo` for ammo ingredients) vs `need = ingredient.qty × batchQty`; green if `have ≥ need`, red otherwise. Show `have/need`.
- **CRAFT button**: enabled only when all ingredients met (and skill met, if gating is enforced — §H-2). Disabled state shows the blocker ("MISSING: Scrap Metal").
- **Batch control**: a small numeric `qty` input (1..cap) + a **`MAX`** button that computes the largest craftable N from current ingredients (and the cap).
- **Skill/perk indicator**: `(Repair 50 ✓/✗)` next to the recipe, color-coded against `state.skills[skill]`; perk/DLC shown as a tag.
- **Scrap/Breakdown sub-flow**: a nested **`<details class="sub-panel" data-sub-id="scrap_breakdown">`** "SCRAP / BREAKDOWN" listing inventory items that have a `breakdowns` entry, each with `qty` + a **`SCRAP`** button showing the yield ("→ 1 Scrap Metal"). Same compact style.
---
## D. Crafting mechanics
**Craft (consume → produce), recommended functional model:**
1. On `CRAFT` (batch N): re-validate skill/perk (per §H-2) and ingredients (`have ≥ need×N` for every ingredient).
2. **Confirm-gate** the consume (destructive): `confirm("Craft 3× Weapon Repair Kit? Consumes: 3 Scrap Electronics, 3 Scrap Metal.")`.
3. Decrement each ingredient: find in `state.inventory` (or `state.ammo`), subtract `need×N`; if an item's qty hits 0, splice it out. **Never go negative** (clamped; re-checked before write).
4. Produce output: `addItem`-style — `state.inventory` entry `qty += output.qty×N` (auto `wgt/val/type` via `lookupItemInDb`), OR `state.ammo[output.item] += output.qty×N` if `output.ammo`.
5. `renderInventory()` + `renderAmmo()` + `renderCraft()` + `updateMath()`; **`saveState()` (local debounced write only)**; append a chat line `> [CRAFT] Built 3× Weapon Repair Kit`. **No cloud push.**
**Batch validation** — `MAX` = `min(cap, floor(min over ingredients of have/need_per_unit))`; the qty input clamps to `[1, MAX]`. Crafting N requires `have ≥ need×N` for all ingredients (no partial batches — all-or-nothing per click).
**Scrap/breakdown** — reverse: confirm-gate, consume 1×(or N×) the source item, add the `yields` to inventory (auto-stats). If `yields` < craft cost (simplified), that's intended (scrapping isn't lossless). Skill-gated only if `breakdown.skillReq`.
**Edge cases**: missing ingredient → CRAFT disabled + "MISSING: X"; `MAX` caps at available; item not in inventory → SCRAP disabled; per-game availability is automatic (`FALLOUT_REGISTRY.recipes` is the active game's); skill-not-met handled per §H-2; ammo ingredients/outputs routed via `state.ammo`.
---
## E. Protocol impact checklist
| Protocol | Impact |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **P1 (cache bump)** | Served files touched: `index.html` (panel), `js/ui-render.js` (renderCraft + craft/scrap fns), `js/reg_nv.js`/`js/reg_fo3.js` (recipes/breakdowns). → **bump `CACHE_NAME`** each push. |
| **P4 (new state field)** | **NOT required for v1.** Craft only mutates existing `state.inventory`/`state.ammo` and reads `state.skills`. _Optional_: `state.stats.craftedCount` — if added, full 4-file wiring (state default+migrate, autoImportState map, getSystemDirective schema, render). **Recommend: no new field for v1.** |
| **P5 (new panel)** | `<details>` block in index.html · `renderCraft()` in ui-render.js · call from `loadUI()` · `_updatePanelBadges()` entry (craftable-count badge) · `expandPanelForCategory` map key `'craft'` → inv tab · scrap sub-panel gets `data-sub-id` (persisted collapse). |
| **P6 (autocomplete)** | **Not needed** — the panel is registry-driven (no free-text item add). If a recipe-search filter input is added, it's client-filter only (no `wireInput`). |
| **P23 / P24** | Recipes/breakdowns live in read-only `FALLOUT_REGISTRY` (never mutated). Craft operates only on curated registry recipes (no AI/user-injected recipes). Batch qty validated to positive int ≤ cap; ingredient/output names are registry-fixed. |
| **P34 (cloud safety)** | Craft mutates **local** inventory + `saveState()` (debounced local write). **No auto cloud push** (manual button only). Destructive consume/scrap is **confirm-gated**. ✓ |
| **P2 / P2a** | Update ARCHITECTURE/README/CHANGELOG; sync test count in both runners + all doc locations. |
| **P13** | Each behavioral fix/feature gets regression tests (below) in the same commit. |
---
## F. Test plan (both runners, parity)
**Structural (recipe DATA push):** `FALLOUT_REGISTRY.recipes` exists per game, non-empty, count floor; every recipe has `name/station/output.item/ingredients[]`; every ingredient/output `item` resolves to a known item name (registry/DB) — no typos; no duplicate recipe names; `station` ∈ allowed set; `skillReq.skill ∈ getSkillKeys()` (or null); `breakdowns[].item` resolves; FO3 has no FNV-only stations (no `sink` in FO3).
**Behavioral (craft UI push) — sandbox-eval the craft/scrap fns:**
- Craft consumes ingredients **and** adds output: start `inventory=[Scrap Electronics×2, Scrap Metal×1]`, craft 1× WRK → ingredients decremented to `[Scrap Electronics×1]` (Scrap Metal removed at 0), `Weapon Repair Kit×1` added.
- **Batch**: craft 2× when only enough for 1× → **rejected, inventory unchanged**.
- **No negative inventory** ever (consume clamps / pre-checks).
- **Insufficient ingredients** → CRAFT no-op, inventory unchanged.
- **Scrap** consumes the item + adds its `yields`; scrapping an item not in inventory → no-op.
- **Ammo recipe** routes output to `state.ammo`, not `state.inventory`.
- **Data-safety**: the craft path contains **no `pushToCloud`/`addDoc`/`setDoc`** (no auto cloud sync); a `confirm(` gate exists on the consume path.
- **Per-game**: with FO3 registry loaded, FNV-only recipes are absent.
- Skill-gating behaves per the chosen mode (§H-2).
---
## G. Build sequencing
**Two pushes (recommended):**
1. **Recipe DATA** — `FALLOUT_REGISTRY.recipes` + `breakdowns` in both reg files + structural guards. Additive, low-risk, independently auditable. (~r30)
2. **Craft UI + mechanics** — panel, `renderCraft`, craft/batch/scrap handlers, behavioral + data-safety tests, P5 wiring. (~r31)
(If scrap/breakdown data is large, it can be a 3rd push; otherwise fold into #1.) Each push: full gate green, count synced, audited before the next.
---
## H. KEY DECISIONS FOR THE OWNER (confirm before build)
1. **Crafting effect** — **(a) MUTATES real inventory** (consume ingredients, add output, confirm-gated) _[recommended — a functional craft menu]_ **vs (b) advisory-only** (show recipes/have-need, don't touch inventory; AI/manual does the consume).
2. **Skill/perk gating** — **(a) SHOWN-ONLY / soft** (display the requirement, allow crafting anyway) _[recommended — matches the app's "warn, don't block" philosophy]_ **vs (b) ENFORCED** (block CRAFT until skill/perk met).
3. **Scrap/breakdown yields** — **(a) SIMPLIFIED** (curated breakdown table, possibly reduced yield, fewer items) _[recommended for v1]_ **vs (b) CANON** (exact reverse components from the wiki for everything — larger data effort).
4. **Batch-craft cap** — **(a) cap at available ingredients only** (MAX = what you can make) _[recommended]_ **vs (b) a fixed UI max** (e.g. 99) **vs (c) the recipe's Quarantine_Cap value**.
5. **Reloading-bench ammo crafting in v1** — **(a) include** (ammo output routes to `state.ammo` via the existing path) _[recommended, curated subset]_ **vs (b) defer to v2** (workbench/consumables only first).
6. **Stations in v1** — **(a) all FNV stations** (workbench + reloading + campfire + Sink/Hot Plate), grouped/filterable _[recommended]_ **vs (b) workbench only** first.
---
## Version
**2.6.0 is correct.** This is a new user-facing feature panel = MINOR bump (Protocol 2 semver). It adds a feature additively, breaks nothing, and is not a rewrite — so it stays **well below 3.0.0**. APP_VERSION moves 2.5.0 → 2.6.0 when Phase 6 / this feature set ships (not before).
STAMP · generated for RELEASE v2.8.5 commit 06e5180 (06e51801b38a) · archive input-tree hash c07fbfbdd2e1ddeb · 754 files · no wall-clock timestamp (regenerates identically when nothing changed).