← 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).