← all games

adventure-fable-unknown — FRICTION.md

# Friction log — adventure-fable-unknown

Logged in the moment while building. Categories: API friction / missing primitive /
recipe gap / docs ambiguity / asset gap / test-infra gap.

## 1. Addon docs exist but are invisible from the docs index

- **Trying to:** find consumer docs for `@yagejs-addons/interaction`, `/quests`,
  `/steering` before writing NPC/quest code. The genre design checklist maps
  directly to these packages.
- **What happened:** `https://yage.dev/llms.txt` lists only three addons
  (dialogue, inventory, abilities). The interaction/quests/steering docs ARE
  published (`https://yage.dev/llms/addons/interaction.md` etc. return 200) but
  nothing links to them — an agent following the index would conclude the
  addons are undocumented or don't exist, and hand-roll proximity prompts and a
  quest log. I only found them because I could read the monorepo's
  `packages/addons/*/docs/llms/` tree directly (via the addon *authoring* guide,
  which is aimed at authors, not consumers).
- **Category:** docs ambiguity (stale `llms.txt` index vs published files).

## 2. No procedural/runtime audio path in @yagejs/audio

- **Trying to:** ship fully synthesized audio (bell strikes with inharmonic
  partials, wind/wave ambience, UI blips) with zero asset files, as this run
  requires.
- **What happened:** `@yagejs/audio` is alias-based playback over `@pixi/sound`:
  the only documented way in is the `sound("file.ext")` asset factory +
  preload. There is no way to register a runtime-generated `AudioBuffer` under
  an alias — no analogue of the renderer's `registerTexture(key, tex)`, which
  solves exactly this problem for visuals (that symmetry gap is what makes it
  feel like a hole rather than a scope choice). Consequence: the whole audio
  layer for this game bypasses `@yagejs/audio` — hand-rolled WebAudio synth +
  mixer, re-implementing channel volumes, unlock-on-gesture, and mute-on-blur
  that the engine package already has.
- **Category:** missing primitive (`registerSound(alias, buffer)` or any
  synthesis-friendly entry point).

## 3. patterns.md example calls `Transform.translate` with a Vec2; the API takes (dx, dy)

- **Trying to:** move the wandering cat with `tr.translate(new Vec2(dx, dy))`,
  copied from the docs.
- **What happened:** compile error — `translate(dx: number, dy: number)` per
  `@yagejs/core`'s d.ts. The LLM docs' `patterns.md` (Testing Patterns →
  "Unit testing a component") shows `t.translate(new Vec2(0, 9.8 * dt))`, and
  core-concepts.md says "Transform has mutating methods (`setPosition`,
  `translate`)" without signatures, so the doc example is the only signature
  an agent sees — and it's wrong. One-minute fix, but only because tsc caught
  it; in a JS project it would have been a silent runtime `NaN`.
- **Category:** docs ambiguity (doc example contradicts the typed API).

## 4. Entities have `.scene` but no `.context` (components have both)

- **Trying to:** resolve services (dialogue, HUD) from inside `Entity`
  subclass methods (`GreatBell.interacted`, an `Interactable.onInteract`
  closure created in `setup()`).
- **What happened:** `this.context` doesn't exist on `Entity` — components get
  `this.context`/`this.service()`, entities only `this.scene`. The fix is
  `this.scene.context.tryResolve(...)`, easy once seen, but the docs never
  state which accessors exist at the entity level, and the asymmetry with
  Component (which documents `this.service`/`this.use`/`this.context`
  prominently) invites exactly this mistake. Six call sites hit it in one file.
- **Category:** docs ambiguity (entity-level service access is undocumented).

## 5. quick-start presents Inspector time/input control as debug:true-only; it needs DebugPlugin

- **Trying to:** verify the game the way the docs (and this repo's skill)
  prescribe: freeze the clock with `window.__yage__.inspector.time.freeze()` +
  `step(n)` and drive synthetic input, because the game does not advance in a
  backgrounded tab (rAF never fires — exactly the failure mode the skill's
  gotcha list predicts).
- **What happened:** `Error: Inspector.time requires DebugPlugin to be
  active.` quick-start.md's "Inspector (runtime queries)" section shows
  `time.freeze()`, `time.step(1)`, and `input.keyDown(...)` right after
  "engine constructed with `debug: true` installs an introspection API" —
  nothing marks the time/input namespaces as DebugPlugin-gated (the section
  explicitly frames extensions like `debug` as the plugin-gated part).
  Snapshot/query calls worked, so the failure only shows up at the exact
  moment an agent starts driving frames.
- **Category:** docs ambiguity (which Inspector namespaces need DebugPlugin).

## 6. Frame-stepped verification can't drive dialogue: advance is async inside

- **Trying to:** drive a full conversation (reveal → advance → choice) with the
  engine's own recommended agent-verification loop — `inspector.time.freeze()`
  + `step(n)` + synthetic input — inside one synchronous script.
- **What happened:** only the first advance per script registered. The
  dialogue session's `advance()` refuses re-entry while `advancing` is held,
  and `advanceLine()` is async — its continuation (which clears the latch and
  presents the next line) sits in the microtask queue, which never drains
  while a synchronous `step()` loop is running. Symptom from the outside:
  taps look randomly swallowed, conversations look stuck, and nothing errors.
  Burned well over an hour on wrong theories (reveal speed, rate limits,
  disabled components) before reading the addon source. Workaround: yield a
  macrotask (`await setTimeout(0)`) between taps so the runner's continuation
  can land — after which one script can drive whole conversations.
- **Category:** test-infra gap (step-driven verification vs async runner
  internals; nothing in the docs warns about it, and the skill's prescribed
  freeze+step pattern walks straight into it).