@yagejs-addons/dialogue 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/render/DialogueTextView.ts","../src/render/textEffects.ts","../src/render/BubbleTextView.ts","../src/render/BoxTextView.ts","../src/render/layers.ts","../src/actor/DialogueActor.ts","../src/actor/ActorRegistry.ts","../src/render/bubbleAnchor.ts","../src/render/bubbleSizing.ts","../src/render/BubbleLayout.ts","../src/render/BoxLayout.ts","../src/chrome/DialogueChrome.ts","../src/factory/theme.ts","../src/chrome/caret.ts","../src/chrome/textOptions.ts","../src/chrome/ChoiceListPresenter.ts","../src/chrome/choiceRow.ts","../src/chrome/BubbleChrome.ts","../src/chrome/BubbleChoicePresenter.ts","../src/chrome/RadialChoicePresenter.ts","../src/composite/route.ts","../src/composite/CompositeTextPresenter.ts","../src/composite/CompositeChrome.ts","../src/composite/CompositeChoicePresenter.ts","../src/composite/CompositeAvatarPresenter.ts","../src/avatar/AvatarPresenter.ts","../src/avatar/PortraitPresenter.ts","../src/avatar/SceneFigurePresenter.ts","../src/avatar/InBoxAvatarPresenter.ts","../src/avatar/BubbleAvatarPresenter.ts","../src/factory/defaultTheme.ts","../src/factory/themeFonts.ts","../src/factory/createBoxDialogue.ts","../src/factory/createBubbleDialogue.ts","../src/factory/createMixedDialogue.ts"],"sourcesContent":["/**\n * DialogueTextView — renders one parsed line as a single {@link SplitTextComponent}\n * (the engine wrapper for Pixi `SplitText`/`SplitBitmapText`) on a screen-space\n * layer, revealing it glyph-by-glyph (typewriter), honouring per-run colour/\n * bold/italic, and driving animated effects.\n *\n * Reveal *timing* — the grapheme cursor, inline `[pause=ms/]`, per-run/line\n * `[speed]`, the hold multiplier, and fired-once completion — is owned by the\n * headless {@link LineReveal} clock (pixi-free, reusable by a DOM presenter).\n * This view keeps only the pixi-`SplitText` concerns: mapping the clock's\n * grapheme cursor onto glyph visibility and fanning per-glyph styles out.\n *\n * Why one split per LINE (not per word): SplitText does its own tokenize /\n * measure / wrap / glyph-split, so we delegate layout to it instead of hand-\n * rolling it. We then reach into the per-glyph `chars` for everything rich:\n * - reveal → toggle `chars[i].visible` (no re-layout; split is done once)\n * - colour → `chars[i].tint` per run (independent per glyph)\n * - bold/italic → reassign that glyph's `style` to a baked variant atlas\n * (the chars SHARE one style object, so we assign a fresh one rather than\n * mutate — mutating would restyle the whole line)\n * - effects → per-glyph `position`/`scale`/`tint` (wave now ripples per letter)\n *\n * Pixi nests the split `root → line → word → char`, so a glyph's position in the\n * split's own space is the sum up its parent chain ({@link localInSplit}).\n *\n * All reveal bookkeeping counts GRAPHEMES (`splitGraphemes` — the same\n * segmentation SplitText uses to make one glyph node per user-perceived\n * character), so emoji / ZWJ sequences / combining marks stay aligned between\n * the cursor, `[pause]` offsets, per-glyph styles, and the rendered glyphs.\n * `SplitText.chars` additionally drops whitespace, but our runs / reveal\n * cursor / `[pause]` offsets count it, so we map \"global grapheme index (with\n * spaces) → non-space glyph index\" via a prefix table.\n *\n * This file is the only renderer-coupled part of text rendering; it takes the\n * scene + a font/layout config so nothing here is game-specific.\n */\n\nimport { MathUtils, Transform, type Entity, type Scene } from \"@yagejs/core\";\nimport { splitGraphemes } from \"../core/markup.js\";\nimport { LineReveal, type RevealBeat } from \"../core/LineReveal.js\";\nimport {\n SplitTextComponent,\n type DisplayContainer,\n type SplitTextComponentOptions,\n type TextStyle,\n} from \"@yagejs/renderer\";\nimport type { TextPresenter } from \"../chrome/DialogueUiAdapter.js\";\nimport type { FontConfig } from \"../chrome/textOptions.js\";\nimport type { PresentedLine } from \"../core/session.js\";\nimport type { ParsedText, RunStyle } from \"../core/types.js\";\nimport { evaluateEffect, effectDrivesTint, type EffectOutput } from \"./textEffects.js\";\n\nexport interface DialogueTextConfig extends FontConfig {\n /** Font size in px. */\n readonly textSize: number;\n /** Vertical advance between wrapped lines, in px. */\n readonly lineHeight: number;\n /** Colour for runs that don't override it (0xRRGGBB). */\n readonly textColor: number;\n /** Base reveal rate (graphemes/second). Scaled by per-run + hold speed. */\n readonly charsPerSec: number;\n /** Render layer name (screen-space). */\n readonly layer: string;\n /** Resting text region (screen px). Bubbles override per line via `setBox`. */\n readonly box?: { readonly x: number; readonly y: number; readonly width: number };\n}\n\n/**\n * Synthesised italic shear (radians, ~12°). We can't swap to the baked italic\n * atlas per glyph — each atlas has its own `baseLineOffset`, so a swapped glyph\n * sits at a different height than the regular run. Shearing the regular glyph in\n * place keeps the baseline. Negative leans the top to the right (forward italic)\n * about the glyph's top-left origin.\n */\nconst ITALIC_SKEW = -0.21;\n/** Faux-bold: horizontal offset (px) of the double-draw overlay glyph. */\nconst BOLD_OFFSET = 0.6;\n\n/** One per-glyph display object from the split (a `Text` or `BitmapText`). */\ntype CharNode = SplitTextComponent[\"chars\"][number];\n\n/** Per-frame animation bookkeeping for one EFFECT-BEARING glyph (static glyphs\n * need none — their tint/weight are applied once at build). */\ninterface EffectMeta {\n readonly node: CharNode;\n readonly effect: string;\n /** Resting position within the glyph's own parent (word) — effects offset from this. */\n readonly baseX: number;\n readonly baseY: number;\n /** Horizontal position in the split's own space (effect phase input). */\n readonly splitX: number;\n}\n\ninterface LineNodes {\n readonly entity: Entity;\n readonly comp: SplitTextComponent;\n readonly chars: CharNode[];\n readonly effectMetas: EffectMeta[];\n}\n\nexport class DialogueTextView implements TextPresenter {\n private scene?: Scene | undefined;\n private line?: LineNodes | undefined;\n private parsed?: ParsedText | undefined;\n private boxX = 0;\n private boxY = 0;\n private wrapWidth = 200;\n /** Top-left the split container sits at (box origin). */\n private layoutOriginX = 0;\n private layoutOriginY = 0;\n /** Optional per-frame origin (a moving NPC's head) for diegetic bubbles. */\n private originProvider?: (() => { x: number; y: number }) | undefined;\n\n /** `nonSpacePrefix[k]` = count of non-space graphemes among the first `k`. */\n private nonSpacePrefix = new Int32Array(1);\n /** Non-space glyphs currently visible. */\n private shownCount = -1;\n\n /** Headless reveal clock — owns the grapheme cursor, `[pause]` arming, hold +\n * per-line + per-run speed, and the fired-once completion. This view keeps\n * only the pixi-`SplitText` concerns (glyph prefix mapping + per-glyph style\n * fan-out) and maps the clock's grapheme cursor onto them. */\n private readonly reveal: LineReveal;\n /** Elapsed ms for animated per-glyph EFFECTS (wave/shake/…) — distinct from\n * the reveal cursor, which LineReveal owns. */\n private elapsedMs = 0;\n /** Scratch for {@link evaluateEffect} — one object reused across all glyphs. */\n private readonly effectScratch: EffectOutput = { dx: 0, dy: 0, scale: 1, tint: undefined };\n\n /** Reveal-completed listener, registered by the Session through\n * {@link setRevealListener} (a private seam, not a public field, so a\n * game can't clobber the session's wiring). */\n private revealListener?: (() => void) | undefined;\n /** Reveal-beat listener (ticks + inline markers), registered by the Session\n * through {@link setBeatListener} — same private-seam discipline. */\n private beatListener?: ((beat: RevealBeat) => void) | undefined;\n /** Master visibility gate ({@link setVisible}); hides the line WITHOUT\n * clearing it, so a hide/show round-trip resumes mid-typewriter. */\n private hidden = false;\n\n constructor(private readonly cfg: DialogueTextConfig) {\n this.reveal = new LineReveal(cfg.charsPerSec);\n // The reveal clock reports completion + beats through the view's\n // session-owned listeners — never public fields a game could clobber.\n this.reveal.setCompletionListener(() => this.revealListener?.());\n this.reveal.setBeatListener((beat) => this.beatListener?.(beat));\n if (cfg.box) this.setBox(cfg.box.x, cfg.box.y, cfg.box.width);\n }\n\n /** Attach to a scene (host lifecycle). Must run before the first `present`. */\n mount(scene: Scene): void {\n this.scene = scene;\n }\n\n /** Top-left of the text region, in screen px, plus the wrap width. */\n setBox(x: number, y: number, width: number): void {\n this.boxX = x;\n this.boxY = y;\n this.wrapWidth = width;\n }\n\n /**\n * Make the text track a per-frame origin (a diegetic bubble following an\n * NPC). The provider returns the top-left the laid-out box should sit at;\n * pass `undefined` to pin the text (the default box-dialogue behaviour).\n */\n setOrigin(provider: (() => { x: number; y: number }) | undefined): void {\n this.originProvider = provider;\n }\n\n /** TextChannel entry point: render + reveal a fully-resolved line. */\n present(line: PresentedLine): void {\n this.show(line.text, line.speed);\n }\n\n /** TextChannel: reveal everything now. */\n completeReveal(): void {\n this.skipToEnd();\n }\n\n /** TextChannel: true once the line is fully revealed. */\n isRevealComplete(): boolean {\n return this.reveal.isComplete();\n }\n\n /** Hold-to-speed multiplier (1 = normal, e.g. 3 while the skip key is held). */\n setSpeedMultiplier(m: number): void {\n this.reveal.setSpeedMultiplier(m);\n }\n\n isRevealing(): boolean {\n return this.reveal.isRevealing();\n }\n\n /**\n * Show or hide the body text WITHOUT disturbing reveal progress. Toggles\n * the laid-out split container's visibility; the per-glyph reveal cursor,\n * timers, and styling are untouched, so a cutscene can hide mid-typewriter and\n * show again to resume exactly where it left off.\n */\n setVisible(visible: boolean): void {\n this.hidden = !visible;\n this.applyHidden();\n }\n\n /** Register the reveal-completed listener. Session-owned (a private seam, not a\n * public field a game could clobber); pass `undefined` to clear. */\n setRevealListener(listener: (() => void) | undefined): void {\n this.revealListener = listener;\n }\n\n /** Register the reveal-beat listener (ticks + inline markers). Session-owned;\n * pass `undefined` to clear. The clock emits in char order as glyphs reveal. */\n setBeatListener(listener: ((beat: RevealBeat) => void) | undefined): void {\n this.beatListener = listener;\n }\n\n /** Build the split for a parsed line and start revealing. */\n show(parsed: ParsedText, lineSpeed = 1): void {\n this.clearLine();\n this.parsed = parsed;\n this.elapsedMs = 0;\n this.shownCount = -1;\n if (parsed.length > 0) this.buildLine(parsed);\n // Start the reveal clock — an empty line completes (and fires the\n // session-owned listener) synchronously here, the no-typewriter contract.\n // The glyph tree is already built above, so completion observes a\n // consistent view.\n this.reveal.begin(parsed, lineSpeed);\n this.applyReveal();\n this.reposition(); // place immediately (esp. bubble follow) before first update\n this.applyHidden(); // a new line inherits the current hide state\n }\n\n /** Apply the master visibility gate to the laid-out line — toggles the split\n * container, leaving the per-glyph reveal state intact. */\n private applyHidden(): void {\n if (this.line) this.line.comp.splitText.visible = !this.hidden;\n }\n\n /** Reveal everything immediately (jump-to-end on a click/tap). */\n skipToEnd(): void {\n this.reveal.complete();\n this.applyReveal();\n }\n\n update(dt: number): void {\n if (!this.parsed) return;\n this.elapsedMs += dt;\n // Advance the reveal cursor (fires completion exactly once when it lands),\n // then map the new cursor onto glyph visibility. applyReveal is idempotent,\n // so calling it after the line is done costs nothing.\n this.reveal.update(dt);\n this.applyReveal();\n this.reposition();\n }\n\n clear(): void {\n this.clearLine();\n // Channel-level clear ends the conversation's visuals — also drop the\n // origin closure, which captures the speaking actor's entity and would\n // otherwise keep a despawned NPC reachable until the next present().\n // (Per-line resets must NOT do this: a bubble view sets the next line's\n // origin BEFORE present() reaches show().)\n this.originProvider = undefined;\n }\n\n /** Per-line teardown (also the first step of `show()`). The reveal clock is\n * re-armed by the next `show()` via {@link LineReveal.begin}, so there is no\n * reveal state to reset here. */\n private clearLine(): void {\n this.line?.entity.destroy(); // destroys the split + glyphs\n this.line = undefined;\n this.parsed = undefined;\n this.shownCount = -1;\n }\n\n /** Permanent teardown. (No measurer nodes to free — SplitText owns layout.) */\n dispose(): void {\n this.clear();\n }\n\n // ── build ───────────────────────────────────────────────────────────────────\n\n private buildLine(parsed: ParsedText): void {\n if (!this.scene) return;\n const text = parsed.runs.map((r) => r.text).join(\"\");\n\n this.layoutOriginX = this.boxX;\n this.layoutOriginY = this.boxY;\n\n const entity = this.scene.spawn(\"dlg-line\");\n entity.add(new Transform()).setPosition(this.layoutOriginX, this.layoutOriginY);\n const comp = entity.add(new SplitTextComponent(this.lineSplitOptions(text)));\n const chars = comp.chars;\n const root = comp.splitText;\n\n // One run-style per non-space glyph, in reading order (1:1 with `chars`).\n const styles = this.buildRevealTables(parsed);\n\n // Static styling is applied once here; only effect-bearing glyphs need\n // per-frame bookkeeping, so reposition() never touches the rest.\n const effectMetas: EffectMeta[] = [];\n chars.forEach((node, i) => {\n const style = styles[i] ?? {};\n // Colour rides per-glyph `tint` (base fill is white); independent per glyph.\n node.tint = style.color ?? this.cfg.textColor;\n node.visible = false;\n this.applyWeight(node, style);\n if (style.effect) {\n effectMetas.push({\n node,\n effect: style.effect,\n baseX: node.position.x,\n baseY: node.position.y,\n splitX: localInSplit(node, root).x,\n });\n }\n });\n\n this.line = { entity, comp, chars, effectMetas };\n }\n\n /**\n * One grapheme-segmentation pass per line (build-time only — nothing\n * re-segments per frame), producing both reveal tables:\n * - `nonSpacePrefix`: grapheme cursor → count of non-space glyphs shown\n * - returned styles: the run-style for each NON-SPACE glyph, in reading\n * order (1:1 with SplitText's `chars`, which drops whitespace)\n * Segmenting per run matches how markup.ts counted `length`/`atChar`, so\n * the cursor, pauses, and styles all share one basis.\n */\n private buildRevealTables(parsed: ParsedText): RunStyle[] {\n const styles: RunStyle[] = [];\n const prefix: number[] = [];\n let shown = 0;\n for (const run of parsed.runs) {\n for (const g of splitGraphemes(run.text)) {\n prefix.push(shown);\n if (/\\s/.test(g)) continue;\n shown++;\n styles.push(run.style);\n }\n }\n prefix.push(shown);\n this.nonSpacePrefix = Int32Array.from(prefix);\n return styles;\n }\n\n /**\n * Apply a run's bold/italic to one glyph. On the bitmap path we must NOT swap\n * to the baked variant atlas — each atlas has its own `baseLineOffset`, which\n * lifts/drops a swapped glyph out of line with the regular runs. So we keep the\n * regular-atlas glyph (baseline intact) and synthesise:\n * - italic → shear it in place (`skew.x`)\n * - bold → overlay a 1px-offset copy as a CHILD, so it rides the parent's\n * reveal/visibility, tint cascade, skew, and per-frame effects for\n * free (no separate bookkeeping).\n * On the canvas path, real bold/italic of the same family is baseline-safe, so\n * we just set the style flags.\n */\n private applyWeight(node: CharNode, style: RunStyle): void {\n if (this.cfg.bitmapFont) {\n if (style.italic) node.skew.x = ITALIC_SKEW;\n if (style.bold) {\n // Same class as the glyph (Text or BitmapText) without importing pixi\n // values — `constructor` is typed `Function`, so one cast is needed.\n const Ctor = node.constructor as new (o: {\n text: string;\n style: CharNode[\"style\"];\n }) => CharNode;\n const dup = new Ctor({ text: node.text, style: node.style });\n dup.position.set(BOLD_OFFSET, 0);\n dup.tint = 0xffffff; // the real colour comes from the parent glyph's tint (cascades)\n node.addChild(dup);\n }\n return;\n }\n if (style.bold || style.italic) {\n const s: TextStyle = { fontSize: this.cfg.textSize, fill: 0xffffff, lineHeight: this.cfg.lineHeight };\n if (this.cfg.fontFamily) s.fontFamily = this.cfg.fontFamily;\n if (style.bold) s.fontWeight = \"bold\";\n if (style.italic) s.fontStyle = \"italic\";\n node.style = s;\n }\n }\n\n // ── reveal + effects ─────────────────────────────────────────────────────────\n\n private applyReveal(): void {\n if (!this.line) return;\n const cursor = MathUtils.clamp(Math.floor(this.reveal.revealed), 0, this.nonSpacePrefix.length - 1);\n const shown = this.nonSpacePrefix[cursor]!;\n if (shown === this.shownCount) return;\n // Toggle only the glyphs whose visibility changed since the last step —\n // buildLine hides every glyph, so the initial -1 state equals \"0 shown\".\n const prev = this.shownCount < 0 ? 0 : this.shownCount;\n const chars = this.line.chars;\n const lo = Math.min(prev, shown);\n const hi = Math.max(prev, shown);\n for (let i = lo; i < hi; i++) chars[i]!.visible = i < shown;\n this.shownCount = shown;\n }\n\n /**\n * Per-frame placement: follow a moving origin (bubble mode) by moving the\n * split container's `Transform` — every glyph inherits it — then apply\n * per-glyph animated effects on top, in the glyph's own (parent) space.\n * Only effect-bearing glyphs are walked; a static pinned line costs nothing.\n */\n private reposition(): void {\n const line = this.line;\n if (!line) return;\n if (this.originProvider) {\n const o = this.originProvider();\n line.entity.get(Transform).setPosition(o.x, o.y);\n }\n for (const m of line.effectMetas) {\n if (!m.node.visible) continue;\n const out = evaluateEffect(m.effect, this.elapsedMs, m.splitX, this.effectScratch);\n m.node.position.set(m.baseX + out.dx, m.baseY + out.dy);\n if (out.scale !== 1) m.node.scale.set(out.scale, out.scale);\n if (effectDrivesTint(m.effect) && out.tint !== undefined) m.node.tint = out.tint;\n }\n }\n\n // ── node construction ─────────────────────────────────────────────────────────\n\n /** Options for the per-line split: regular atlas, wrap to the box, white fill\n * (per-glyph `tint` carries the real colour). */\n private lineSplitOptions(text: string): SplitTextComponentOptions {\n const style: TextStyle = {\n fontSize: this.cfg.textSize,\n fill: 0xffffff,\n wordWrap: true,\n wordWrapWidth: this.wrapWidth,\n lineHeight: this.cfg.lineHeight,\n };\n const font = this.cfg.bitmapFont ?? this.cfg.fontFamily;\n if (font) style.fontFamily = font;\n const base: SplitTextComponentOptions = {\n text,\n style,\n layer: this.cfg.layer,\n visible: true,\n };\n if (this.cfg.bitmapFont) base.bitmap = true;\n return base;\n }\n}\n\n/** Sum a glyph's position up its parent chain (char → word → line → root). */\nfunction localInSplit(node: CharNode, root: unknown): { x: number; y: number } {\n let x = 0;\n let y = 0;\n let n: DisplayContainer | undefined = node;\n while (n && n !== root) {\n x += n.position.x;\n y += n.position.y;\n n = n.parent ?? undefined;\n }\n return { x, y };\n}\n","/**\n * Per-glyph animated text effects. The line is one `SplitTextComponent`; we\n * animate each glyph node (`chars[i]`) individually, phased by its position\n * along the line — so `[wave]` ripples letter-by-letter, `[shake]` jitters each\n * glyph, `[rainbow]` cycles per glyph. Effects are pure functions of time + the\n * glyph's resting x (the phase), so they're deterministic and snapshot-safe.\n *\n * The effect name is an OPEN vocabulary (any `[name]` markup tag). This bundled\n * evaluator animates the four built-ins (wave / shake / pulse / rainbow — the\n * `BuiltinEffectId`s) and treats any other name — one a custom text channel owns,\n * or a typo — as a no-op (identity transform), so the run renders as plain styled\n * text.\n */\n\n/** Mutable so a per-frame caller can reuse one scratch instance (see `out`). */\nexport interface EffectOutput {\n /** Offset from the run's resting position, in px. */\n dx: number;\n dy: number;\n /** Uniform scale multiplier (1 = none). */\n scale: number;\n /** Tint override (0xRRGGBB), or undefined to keep the run's base colour. */\n tint: number | undefined;\n}\n\n/**\n * @param effect which effect by name (undefined or an unrecognized name → no motion)\n * @param timeMs elapsed time the run has been on screen\n * @param phase a per-run phase seed (use the run's resting x) so adjacent\n * runs animate out of sync instead of in lockstep\n * @param out optional scratch object, reset and returned — pass one per\n * caller to avoid an allocation per animated glyph per frame\n */\nexport function evaluateEffect(\n effect: string | undefined,\n timeMs: number,\n phase: number,\n out: EffectOutput = { dx: 0, dy: 0, scale: 1, tint: undefined },\n): EffectOutput {\n out.dx = 0;\n out.dy = 0;\n out.scale = 1;\n out.tint = undefined;\n switch (effect) {\n case \"wave\":\n out.dy = Math.sin(timeMs / 260 + phase / 14) * 1.6;\n break;\n case \"shake\":\n // Time-quantised jitter so it reads as a buzz, not per-frame noise.\n out.dx = pseudoNoise(timeMs, phase) * 1.3;\n out.dy = pseudoNoise(timeMs, phase + 99) * 1.3;\n break;\n case \"pulse\":\n out.scale = 1 + 0.09 * Math.sin(timeMs / 220 + phase / 18);\n break;\n case \"rainbow\":\n out.tint = hsv((timeMs / 18 + phase * 4) % 360, 0.55, 1);\n break;\n }\n return out;\n}\n\n/** True if the effect needs a tint each frame (so the view skips static tint).\n * False for an unrecognized name (it animates nothing). */\nexport function effectDrivesTint(effect: string | undefined): boolean {\n return effect === \"rainbow\";\n}\n\n/** Cheap deterministic [-0.5, 0.5] noise quantised to ~30 Hz. */\nfunction pseudoNoise(timeMs: number, seed: number): number {\n const t = Math.floor(timeMs / 33);\n const x = Math.sin(t * 12.9898 + seed * 78.233) * 43758.5453;\n return (x - Math.floor(x)) - 0.5;\n}\n\nfunction hsv(h: number, s: number, v: number): number {\n const c = v * s;\n const hp = h / 60;\n const x = c * (1 - Math.abs((hp % 2) - 1));\n let rgb: [number, number, number];\n if (hp < 1) rgb = [c, x, 0];\n else if (hp < 2) rgb = [x, c, 0];\n else if (hp < 3) rgb = [0, c, x];\n else if (hp < 4) rgb = [0, x, c];\n else if (hp < 5) rgb = [x, 0, c];\n else rgb = [c, 0, x];\n const [r, g, b] = rgb;\n const m = v - c;\n return (\n (Math.round((r + m) * 255) << 16) |\n (Math.round((g + m) * 255) << 8) |\n Math.round((b + m) * 255)\n );\n}\n","/**\n * A {@link DialogueTextView} that lays its body text inside a diegetic bubble\n * and follows the speaking actor. Per line it asks the shared {@link BubbleLayout}\n * for the bubble size + the speaker anchor, and points the view's origin\n * provider at the bubble's inner top-left. Because the size and anchor come from\n * the SAME owner the companion {@link BubbleChrome} reads, the text always sits\n * inside its frame — no per-presenter sizing copies to drift. All the\n * typewriter / effect / markup machinery is inherited unchanged.\n */\n\nimport type { Scene } from \"@yagejs/core\";\nimport type { DiagnosticSink } from \"../chrome/DialogueUiAdapter.js\";\nimport type { PresentedLine } from \"../core/session.js\";\nimport type { BubbleLayout } from \"./BubbleLayout.js\";\nimport { DialogueTextView, type DialogueTextConfig } from \"./DialogueTextView.js\";\n\nexport class BubbleTextView extends DialogueTextView {\n private sceneRef?: Scene;\n\n constructor(\n cfg: Omit<DialogueTextConfig, \"box\">,\n private readonly layout: BubbleLayout,\n ) {\n super({\n ...cfg,\n // Initial wrap width; updated per line in present() as the bubble widens.\n box: { x: 0, y: 0, width: 0 },\n });\n }\n\n override mount(scene: Scene): void {\n super.mount(scene);\n this.sceneRef = scene;\n }\n\n /** Route the missing-actor warning to the engine Logger (the layout owns the\n * shared anchor resolver). The base view has no diagnostics of its own. */\n setDiagnostics(warn: DiagnosticSink): void {\n this.layout.setDiagnostics(warn);\n }\n\n override present(line: PresentedLine): void {\n // Size to the same width + height the chrome draws this line at (one shared\n // measurement), so the text sits inside the content-sized bubble — wrapping\n // to the column left of any in-bubble portrait inset.\n const size = this.layout.sizeFor(line);\n this.setBox(0, 0, this.layout.textWrapWidth(size));\n const speakerId = line.speaker?.id;\n // Always anchor: a missing actor resolves to the last-known / fallback\n // position via the shared owner, never pinned at world origin.\n this.setOrigin(() => {\n const anchor = this.sceneRef\n ? this.layout.anchorFor(this.sceneRef, speakerId)\n : { x: 0, y: 0 };\n return this.layout.originFor(anchor, size);\n });\n super.present(line);\n }\n}\n","/**\n * A {@link DialogueTextView} that reads its body-text region from the shared\n * {@link BoxLayout} instead of a fixed box. Per line it wraps to the owner's\n * current text region (which the owner narrows for a registered avatar inset, so\n * the text reflows around it) and tracks the region's top-left via the origin\n * provider, so when `meta.position` moves the frame or a choice grows it, the\n * body follows. This mirrors {@link BubbleTextView}, which follows the speaker\n * anchor the same way.\n */\n\nimport type { PresentedLine } from \"../core/session.js\";\nimport type { BoxLayout } from \"./BoxLayout.js\";\nimport { DialogueTextView, type DialogueTextConfig } from \"./DialogueTextView.js\";\n\nexport class BoxTextView extends DialogueTextView {\n constructor(\n cfg: Omit<DialogueTextConfig, \"box\">,\n private readonly layout: BoxLayout,\n ) {\n super({ ...cfg, box: { x: 0, y: 0, width: 0 } });\n }\n\n override present(line: PresentedLine): void {\n // Wrap to the owner's current region width (insets already applied — the\n // avatar registers its column before the session presents the text), and\n // follow the region's top-left so a moved/grown frame carries the text.\n const region = this.layout.textRegion();\n this.setBox(0, 0, region.width);\n this.setOrigin(() => {\n const r = this.layout.textRegion();\n return { x: r.x, y: r.y };\n });\n super.present(line);\n }\n}\n","import type { LayerDef } from \"@yagejs/renderer\";\n\n/**\n * Screen-space render layers the dialogue system draws into. They sit ABOVE\n * the auto-provisioned ui-react layer (order 1000) so a conversation overlays\n * any in-scene React chrome, and `space: \"screen\"` pins the box to the\n * viewport (it doesn't scroll/zoom with the world camera).\n *\n * A host scene opts in by spreading `DIALOGUE_LAYERS` into its `layers` field:\n * readonly layers = [...DIALOGUE_LAYERS];\n */\nexport const DIALOGUE_LAYER_FRAME = \"dialogue-frame\";\nexport const DIALOGUE_LAYER_TEXT = \"dialogue-text\";\nexport const DIALOGUE_LAYER_AVATAR = \"dialogue-avatar\";\n\nexport const DIALOGUE_LAYERS: readonly LayerDef[] = [\n { name: DIALOGUE_LAYER_FRAME, order: 1100, space: \"screen\" },\n // Avatar between frame and text so a portrait can tuck behind the box edge.\n { name: DIALOGUE_LAYER_AVATAR, order: 1105, space: \"screen\" },\n { name: DIALOGUE_LAYER_TEXT, order: 1110, space: \"screen\" },\n];\n","/**\n * A component you drop on any world entity to make it a dialogue *actor*: it\n * self-registers under a logical `speaker` id (see {@link ActorRegistry}) so\n * presenters can find \"where is whoever is speaking\" without an external map.\n * It owns the per-entity translation of dialogue intent — expression + speaking\n * — into whatever the entity's character system supports (swap an animation\n * clip, tint, toggle a talk loop) via callbacks, and exposes a head/anchor\n * point for diegetic bubbles to position against.\n */\n\nimport { Component, Transform, type Entity } from \"@yagejs/core\";\nimport { actorRegistryFor } from \"./ActorRegistry.js\";\n\nexport interface DialogueActorOptions {\n /** Logical speaker id this entity answers to (matches the script). */\n readonly speaker: string;\n /** Offset from the entity transform to the bubble anchor (head), in px. */\n readonly anchor?: { readonly x: number; readonly y: number };\n /** Map a script expression id onto this entity's character system. */\n readonly onExpression?: (entity: Entity, expression: string | undefined) => void;\n /** Toggle a talk animation / mouth flap. */\n readonly onSpeaking?: (entity: Entity, speaking: boolean) => void;\n}\n\nexport class DialogueActor extends Component {\n constructor(private readonly opts: DialogueActorOptions) {\n super();\n }\n\n get speaker(): string {\n return this.opts.speaker;\n }\n\n onAdd(): void {\n actorRegistryFor(this.scene).register(this.opts.speaker, this);\n }\n\n onDestroy(): void {\n actorRegistryFor(this.scene).unregister(this.opts.speaker, this);\n }\n\n /** Bubble anchor in world space: the entity position plus the configured offset. */\n anchorWorld(): { x: number; y: number } {\n const t = this.entity.tryGet(Transform);\n const p = t?.position ?? { x: 0, y: 0 };\n const a = this.opts.anchor ?? { x: 0, y: 0 };\n return { x: p.x + a.x, y: p.y + a.y };\n }\n\n setExpression(expression: string | undefined): void {\n this.opts.onExpression?.(this.entity, expression);\n }\n\n setSpeaking(speaking: boolean): void {\n this.opts.onSpeaking?.(this.entity, speaking);\n }\n}\n","/**\n * Scene-scoped speaker → live entity index. A {@link DialogueActor} self-\n * registers under its logical speaker id; presenters resolve a speaker to a\n * world entity through here instead of carrying an external map. Scripts always\n * speak in *logical* ids (invariant) — instance selection happens here, never\n * in the script.\n *\n * The registry is keyed off the `Scene` via a WeakMap, so it needs no service\n * registration and is torn down with the scene. Multiple live entities for one\n * speaker id is unsupported: last registration wins.\n */\n\nimport type { Scene } from \"@yagejs/core\";\nimport type { DialogueActor } from \"./DialogueActor.js\";\n\nexport class ActorRegistry {\n private readonly actors = new Map<string, DialogueActor>();\n\n register(speaker: string, actor: DialogueActor): void {\n this.actors.set(speaker, actor);\n }\n\n unregister(speaker: string, actor: DialogueActor): void {\n if (this.actors.get(speaker) === actor) this.actors.delete(speaker);\n }\n\n resolve(speaker: string | undefined): DialogueActor | undefined {\n return speaker ? this.actors.get(speaker) : undefined;\n }\n}\n\nconst registries = new WeakMap<Scene, ActorRegistry>();\n\n/** The (lazily-created) registry for a scene. Shared by actors + presenters. */\nexport function actorRegistryFor(scene: Scene): ActorRegistry {\n let registry = registries.get(scene);\n if (!registry) {\n registry = new ActorRegistry();\n registries.set(scene, registry);\n }\n return registry;\n}\n","/**\n * The ONE shared \"where does this speaker's bubble go\" resolver.\n *\n * The three bubble presenters (`BubbleChrome`, `BubbleTextView`,\n * `BubbleChoicePresenter`) all need the same answer for \"anchor the bubble for\n * speaker X\", including the failure cases: a\n * despawned NPC, a typo'd speaker, or a speakerless narrator line routed to a\n * pure-bubble bundle. Three independent copies of that logic is exactly the bug\n * class — so it lives here once. (A future layout owner can absorb this.)\n *\n * Policy:\n * - A **live actor** wins: use its head anchor, and refresh the caches.\n * - A **missing declared speaker** (despawn / typo) falls back to that\n * speaker's last-known position, else the most recent any-speaker anchor,\n * else a configurable anchor — and warns (at most once per speaker id per\n * resolver instance) through the diagnostics sink (→ engine Logger), never\n * `console.warn`.\n * - A **speakerless narrator** line (no id) uses the same fallback chain but\n * never warns — it is authored intent, not a failure.\n *\n * The bubble stays VISIBLE in every case — anchored to a sane fallback rather\n * than hidden — so a line is always readable somewhere with its continue caret.\n */\n\nimport type { Scene } from \"@yagejs/core\";\nimport { actorRegistryFor } from \"../actor/index.js\";\nimport type { DiagnosticSink } from \"../chrome/DialogueUiAdapter.js\";\n\nexport interface AnchorPoint {\n readonly x: number;\n readonly y: number;\n}\n\n/** Resolves a speaker id to a world anchor, with last-known caching + a\n * fallback for missing/absent actors. Each bubble presenter owns its own\n * instance; the position caches converge frame-to-frame (every resolver sees\n * the same live actors), but the missing-actor warning dedups *per instance* —\n * a missing speaker warns at most once per presenter that anchors it (so up to\n * ~2–3× across a bubble bundle), not once globally. A future single layout\n * owner could collapse these into one shared instance. */\nexport class BubbleAnchorResolver {\n private readonly lastKnown = new Map<string, AnchorPoint>();\n private lastAnchor: AnchorPoint | undefined;\n private readonly warned = new Set<string>();\n private warn: DiagnosticSink | undefined;\n\n /**\n * @param fallback Ultimate anchor when nothing better is known (a speaker\n * never seen and no prior bubble). Defaults to the world origin; a\n * pure-bubble bundle that shows narrator lines should point this at its\n * camera centre so a speakerless line lands on screen.\n */\n constructor(private readonly fallback: () => AnchorPoint = () => ({ x: 0, y: 0 })) {}\n\n /** Wire the diagnostics sink (the controller injects the engine-Logger one). */\n setDiagnostics(warn: DiagnosticSink): void {\n this.warn = warn;\n }\n\n /**\n * World anchor for `speakerId`. Live actor → its anchor (caches refreshed).\n * Missing → last-known for that speaker, else the most recent any-speaker\n * anchor, else {@link fallback}. Warns at most once per declared speaker id\n * (per resolver instance); a speakerless line never warns.\n */\n resolve(scene: Scene, speakerId: string | undefined): AnchorPoint {\n const actor = actorRegistryFor(scene).resolve(speakerId);\n if (actor) {\n const anchor = actor.anchorWorld();\n if (speakerId !== undefined) this.lastKnown.set(speakerId, anchor);\n this.lastAnchor = anchor;\n return anchor;\n }\n // A declared speaker with no live actor is a runtime failure — warn once so\n // the dev knows, but still render the bubble at a sane spot. A speakerless\n // narrator line is authored intent, so it routes through the same fallback\n // chain silently.\n if (speakerId !== undefined && !this.warned.has(speakerId)) {\n this.warned.add(speakerId);\n this.warn?.(\n `no DialogueActor is registered for speaker \"${speakerId}\"; anchoring its ` +\n `bubble at the last-known / fallback position. Register a DialogueActor ` +\n `(even on an invisible entity) to place it deliberately.`,\n );\n }\n const known = speakerId !== undefined ? this.lastKnown.get(speakerId) : undefined;\n return known ?? this.lastAnchor ?? this.fallback();\n }\n}\n","/**\n * Shared speech-bubble sizing. The bubble chrome (`BubbleChrome`) and the bubble\n * body text (`BubbleTextView`) are independent presenters that the session drives\n * separately — and the chrome sizes *before* the text (`session.handleSay` calls\n * `chrome.present` then `text.present`). So they can't read a size off each other;\n * instead both compute it from the SAME inputs here, guaranteeing they agree and\n * the text sits inside its frame.\n *\n * Bubbles grow **width first**: a short line gets a snug bubble that widens up to\n * `maxWidth`; only past that does the text wrap and the bubble grow taller. That\n * keeps bubbles short (less likely to run off the top of the screen) and reads\n * more like a real speech bubble. Bitmap fonts size the same way — the renderer's\n * `measureWrappedText` is wrap-aware on both paths.\n */\n\nimport { measureWrappedText } from \"@yagejs/renderer\";\n\nexport interface BubbleSizeInput {\n /** Snuggest width (px). The bubble never gets narrower than this. */\n readonly minWidth: number;\n /** Widest the bubble grows before the text wraps to more lines. */\n readonly maxWidth: number;\n readonly padding: number;\n /** Minimum content height. */\n readonly minHeight: number;\n /** Body-text size + line advance (must match the text view's). */\n readonly textSize: number;\n readonly lineHeight: number;\n readonly fontFamily?: string | undefined;\n /** Set → measure via this baked bitmap-font atlas instead of `fontFamily`. */\n readonly bitmapFont?: string | undefined;\n}\n\nexport interface BubbleSize {\n readonly width: number;\n readonly height: number;\n}\n\n/**\n * Outer bubble size to fit `plainText` (markup already stripped): widen to the\n * text up to `maxWidth`, then wrap and grow height. Both clamped to the configured\n * minimums.\n */\nexport function bubbleSize(plainText: string, cfg: BubbleSizeInput): BubbleSize {\n const oneLine = cfg.lineHeight + 2 * cfg.padding;\n const font = cfg.bitmapFont ?? cfg.fontFamily;\n const fontOpt = font !== undefined ? { fontFamily: font } : {};\n const bitmapOpt = cfg.bitmapFont !== undefined ? { bitmap: true } : {};\n\n // Natural single-line width — does it fit under maxWidth?\n const natural = measureWrappedText(plainText, {\n fontSize: cfg.textSize,\n lineHeight: cfg.lineHeight,\n ...fontOpt,\n ...bitmapOpt,\n });\n const wantWidth = natural.width + 2 * cfg.padding;\n if (wantWidth <= cfg.maxWidth) {\n return {\n width: Math.max(cfg.minWidth, wantWidth),\n height: Math.max(cfg.minHeight, oneLine),\n };\n }\n\n // Too wide: cap the width, wrap, and grow the height to the line count.\n const inner = cfg.maxWidth - 2 * cfg.padding;\n const wrapped = measureWrappedText(plainText, {\n fontSize: cfg.textSize,\n lineHeight: cfg.lineHeight,\n wordWrapWidth: inner,\n ...fontOpt,\n ...bitmapOpt,\n });\n return {\n width: cfg.maxWidth,\n height: Math.max(cfg.minHeight, wrapped.lineCount * cfg.lineHeight + 2 * cfg.padding),\n };\n}\n","/**\n * BubbleLayout — the single per-line geometry owner for the speech-bubble\n * coordinate model (the bubble half of the \"layout owner\"). One instance is\n * injected into `BubbleChrome`, `BubbleTextView`, and `BubbleChoicePresenter` so\n * they can no longer drift: the bubble outer size is measured **once** per line\n * (memoized — the session always calls `chrome.present` then `text.present` with\n * the same line object, so the second read is free), the missing-actor anchor\n * policy lives in ONE {@link BubbleAnchorResolver}, and the anchor→inner-top-left\n * origin formula exists once.\n *\n * It owns the bubble *geometry* (sizing inputs + padding/offsetY); the\n * presenters keep only their drawing config (colours, tail, caret).\n */\n\nimport type { Scene } from \"@yagejs/core\";\nimport { BubbleAnchorResolver, type AnchorPoint } from \"./bubbleAnchor.js\";\nimport { bubbleSize, type BubbleSize } from \"./bubbleSizing.js\";\nimport type { DiagnosticSink } from \"../chrome/DialogueUiAdapter.js\";\nimport type { PresentedLine } from \"../core/session.js\";\n\nexport interface BubbleLayoutConfig {\n /** Snuggest width; the bubble widens to its text up to {@link maxWidth}. */\n readonly minWidth: number;\n /** Widest the bubble grows before its text wraps to more lines. */\n readonly maxWidth: number;\n /** Minimum bubble height (px); grows past this to fit wrapped text. */\n readonly height: number;\n readonly padding: number;\n /** Gap between the actor's head anchor and the bubble's bottom edge. */\n readonly offsetY: number;\n /** Body-text metrics the size measures with (the text view wraps to the same). */\n readonly textSize: number;\n readonly lineHeight: number;\n readonly fontFamily?: string | undefined;\n readonly bitmapFont?: string | undefined;\n /** Anchor for a missing/absent speaker with no last-known position. Default\n * world origin; point it at the camera centre for a pure-bubble bundle that\n * shows narrator lines. */\n readonly fallbackAnchor?: (() => AnchorPoint) | undefined;\n}\n\n/** A reserved portrait column INSIDE the bubble: the bubble grows to contain it\n * and the body text reflows past it (the in-bubble avatar registers one). */\nexport interface BubblePortraitInset {\n readonly side: \"left\" | \"right\";\n /** Full reserved column width (portrait + gap), px. */\n readonly width: number;\n /** Min content height the bubble clears for the portrait, px. */\n readonly height: number;\n}\n\nexport class BubbleLayout {\n private readonly anchors: BubbleAnchorResolver;\n /** One-line memo: the session presents one line to chrome then text, so a\n * one-deep cache makes the second `sizeFor` free (no redundant measure pass). */\n private memoLine: PresentedLine | undefined;\n private memoSize: BubbleSize | undefined;\n /** Reserved portrait column for the current line (set by the in-bubble avatar\n * before the chrome/text present). */\n private inset: BubblePortraitInset | undefined;\n /** The current bubble content size — the say bubble (from {@link sizeFor}) or a\n * choice panel (from {@link setChoicePanelSize}). The in-bubble avatar centres\n * in this, so it follows whichever is on screen. */\n private active: BubbleSize | undefined;\n private readonly listeners: Array<() => void> = [];\n\n constructor(private readonly cfg: BubbleLayoutConfig) {\n this.anchors = new BubbleAnchorResolver(cfg.fallbackAnchor);\n }\n\n /** Reserve (or clear with `undefined`) a portrait column inside the bubble.\n * The bubble (and a bubble choice panel) then grows to contain it and the\n * text/rows reflow to the narrowed column; the avatar sets this per line\n * before the chrome/text/choices present. */\n setPortraitInset(inset: BubblePortraitInset | undefined): void {\n this.inset = inset;\n this.memoLine = undefined; // re-measure with the new reserve\n }\n\n /** The reserved portrait column (or undefined) — a bubble choice presenter\n * reads it to reflow its panel around the portrait. */\n portraitInset(): BubblePortraitInset | undefined {\n return this.inset;\n }\n\n /** Register a callback fired when the active bubble content size changes (a\n * say line sizes its bubble, or a choice commits its panel) — the in-bubble\n * avatar re-places. */\n onChange(listener: () => void): void {\n this.listeners.push(listener);\n }\n\n /** The current bubble content size the avatar centres in (say bubble or choice\n * panel). */\n activeSize(): BubbleSize | undefined {\n return this.active;\n }\n\n /** A bubble choice presenter commits its (inset-grown) panel size here so the\n * in-bubble avatar follows the panel, not the say bubble. */\n setChoicePanelSize(size: BubbleSize): void {\n this.setActive(size);\n }\n\n private setActive(size: BubbleSize): void {\n if (this.active && this.active.width === size.width && this.active.height === size.height) return;\n this.active = size;\n for (const fn of this.listeners) fn();\n }\n\n /** Inner padding (px) — the presenters position the name/caret/text by it. */\n get padding(): number {\n return this.cfg.padding;\n }\n\n /** Gap between the speaker anchor and the bubble's bottom edge (px). */\n get offsetY(): number {\n return this.cfg.offsetY;\n }\n\n /** Wire the missing-actor warning to the engine Logger (the controller's sink). */\n setDiagnostics(warn: DiagnosticSink): void {\n this.anchors.setDiagnostics(warn);\n }\n\n /** Outer bubble size to fit this line's text (+ a reserved portrait column,\n * if one is registered) — measured once, then memoized for the companion\n * presenter's read of the same line. */\n sizeFor(line: PresentedLine): BubbleSize {\n if (line === this.memoLine && this.memoSize) return this.memoSize;\n const plain = line.text.runs.map((r) => r.text).join(\"\");\n const reserve = this.inset?.width ?? 0;\n // Measure the text in the column left of the portrait, so the FULL bubble\n // (text + portrait column) still caps at maxWidth.\n const textSize = bubbleSize(plain, {\n minWidth: this.cfg.minWidth,\n maxWidth: Math.max(this.cfg.minWidth, this.cfg.maxWidth - reserve),\n padding: this.cfg.padding,\n minHeight: this.cfg.height,\n textSize: this.cfg.textSize,\n lineHeight: this.cfg.lineHeight,\n fontFamily: this.cfg.fontFamily,\n bitmapFont: this.cfg.bitmapFont,\n });\n const size: BubbleSize = {\n width: textSize.width + reserve,\n height: Math.max(textSize.height, (this.inset?.height ?? 0) + 2 * this.cfg.padding),\n };\n this.memoLine = line;\n this.memoSize = size;\n this.setActive(size); // a say line is the active bubble the avatar follows\n return size;\n }\n\n /** Body-text wrap width inside the bubble — the inner width minus the\n * reserved portrait column (so the text reflows past an in-bubble avatar). */\n textWrapWidth(size: BubbleSize): number {\n return size.width - 2 * this.cfg.padding - (this.inset?.width ?? 0);\n }\n\n /** World anchor for a speaker: a live {@link DialogueActor}'s head, else the\n * last-known / fallback position (and a once-per-speaker warning). Shared by\n * all three bubble presenters so they track the same actor. */\n anchorFor(scene: Scene, speakerId: string | undefined): AnchorPoint {\n return this.anchors.resolve(scene, speakerId);\n }\n\n /** Inner top-left a content-sized bubble's body sits at, from the speaker\n * anchor + the bubble size (the once-derived origin formula). Shifts past a\n * left-side portrait column so the text reflows beside it. */\n originFor(anchor: AnchorPoint, size: BubbleSize): { x: number; y: number } {\n let x = anchor.x - size.width / 2 + this.cfg.padding;\n if (this.inset?.side === \"left\") x += this.inset.width;\n return { x, y: anchor.y - (this.cfg.offsetY + size.height) + this.cfg.padding };\n }\n}\n","/**\n * BoxLayout — the single per-line geometry owner for the bottom-box coordinate\n * model (the box half of the \"layout owner\"). One instance is shared by the box\n * chrome, the box text view, the box choice list, and an in-box avatar\n * presenter, so the **frame, nameplate, prompt, and choice rows move and grow as\n * ONE coherent panel** instead of each presenter holding its own copy.\n *\n * It owns three things the presenters would otherwise compute independently:\n *\n * - **Per-line position** — `meta.position` (`top|center|bottom`) places the\n * frame within the design viewport (the renderer's `virtualSize`, bound at\n * mount via {@link setViewport}); the frame AND the text region move together.\n * The box is a full-width bottom bar resolved from viewport-relative margins,\n * so the default presenter works at any resolution with no override.\n * - **Unified panel grow** — for a choice, the frame grows to fit the nameplate\n * + prompt + rows; the row rects are stacked inside it (see\n * `stackChoiceRows`). Growing is bottom-anchored at \"bottom\", so the frame top\n * rises and the chrome/nameplate/prompt follow.\n * - **Inset registry** — a presenter reserves a left/right column; the text\n * region subtracts it so the body text reflows around it (the reference\n * in-box avatar registers one).\n *\n * Presenters call {@link onChange} to re-place when the committed frame changes\n * (a choice grows the frame after the chrome/text already presented).\n */\n\nimport { measureWrappedText } from \"@yagejs/renderer\";\nimport type { BoxBounds } from \"../factory/theme.js\";\nimport type { PresentedLine } from \"../core/session.js\";\n\n/** RPG-Maker-style vertical placement of the box within the field. */\nexport type BoxPosition = \"top\" | \"center\" | \"bottom\";\n\n/** A reserved column the body text reflows around (the avatar-reflow seam). */\nexport interface TextInset {\n readonly side: \"left\" | \"right\";\n readonly width: number;\n}\n\n/** A laid-out rectangle (screen px). */\nexport interface Rect {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n}\n\n/** A choice row's screen rect — shared by placement, highlight, and hit-test. */\nexport interface ChoiceRowRect {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n}\n\nexport interface BoxLayoutConfig {\n /** Viewport-relative box bounds (margins + height); the frame is resolved\n * against the design viewport set by {@link BoxLayout.setViewport} at mount. */\n readonly box: BoxBounds;\n readonly padding: number;\n /** Nameplate band height (theme.nameSize) — body text starts below it. */\n readonly nameSize: number;\n /** Body text metrics — for measuring an optional choice prompt's height. */\n readonly textSize: number;\n readonly lineHeight: number;\n /** Vertical gap between choice rows (for the grown panel). */\n readonly choiceGap: number;\n readonly fontFamily?: string | undefined;\n readonly bitmapFont?: string | undefined;\n}\n\n/** Gap (px) between the nameplate band and the body text — matches the box text\n * region the box dialogue used before the owner existed. */\nconst TEXT_GAP = 4;\n\n/**\n * Stack choice-row slots bottom-up inside `box`, growing **upward** from the\n * bottom edge. `rowHeights` are full slot heights (wrapped text height + gap).\n * Rows are always contiguous and non-overlapping. Inside a frame the owner grew\n * to fit, the topmost row lands right below the prompt; in a too-tall menu the\n * excess spills off the top (the soft-cap advisory flags that). The single\n * source of row geometry: placement, highlight, and hit-test all consume it.\n */\nexport function stackChoiceRows(\n rowHeights: readonly number[],\n box: Rect,\n padding: number,\n): ChoiceRowRect[] {\n const x = box.x + padding;\n const width = box.width - 2 * padding;\n const rects: ChoiceRowRect[] = [];\n let bottom = box.y + box.height - padding;\n for (let i = rowHeights.length - 1; i >= 0; i--) {\n const h = rowHeights[i] ?? 0;\n bottom -= h;\n rects[i] = { x, y: bottom, width, height: h };\n }\n return rects;\n}\n\nexport class BoxLayout {\n /** Design viewport (the renderer's `virtualSize`) the box is placed within —\n * bound at mount via {@link setViewport}. Defaults to a sane size so headless\n * use (no renderer) and pre-mount calls still produce a valid frame. */\n private viewW = 800;\n private viewH = 600;\n private readonly insets = new Map<string, TextInset>();\n private readonly listeners: Array<() => void> = [];\n /** The committed frame — moved by `meta.position`, grown for a choice. */\n private frame: Rect;\n /** The line currently laid out (for the choice panel's prompt + nameplate). */\n private line: PresentedLine | undefined;\n\n constructor(private readonly cfg: BoxLayoutConfig) {\n this.frame = this.frameAt(\"bottom\", cfg.box.height);\n }\n\n /**\n * Bind the design viewport (the renderer's `virtualSize`), read at mount, so\n * the box is a full-width bottom bar at any resolution and `meta.position`\n * places the frame against the true screen. Recomputes the resting frame.\n */\n setViewport(width: number, height: number): void {\n this.viewW = width;\n this.viewH = height;\n this.commit(this.frameAt(positionOf(this.line), this.cfg.box.height));\n }\n\n /** Register a callback fired when the committed frame changes (a choice grows\n * it, or an inset reflows the text) — the chrome redraws, the text re-places. */\n onChange(listener: () => void): void {\n this.listeners.push(listener);\n }\n\n /** The frame rect for the current line (read by the chrome to draw + place). */\n frameRect(): Rect {\n return this.frame;\n }\n\n /** Inner content width — choice rows wrap to this. Frame minus padding minus\n * any registered insets, so choices reflow around an in-box avatar the same\n * way the body text does. */\n contentWidth(): number {\n return (\n this.viewW -\n 2 * this.cfg.box.marginX -\n 2 * this.cfg.padding -\n this.insetWidth(\"left\") -\n this.insetWidth(\"right\")\n );\n }\n\n /** Inner padding between the frame and its contents — an in-box presenter\n * aligns its column to this so it sits inside the border, like the text. */\n padding(): number {\n return this.cfg.padding;\n }\n\n /** Lay out a say/prompt line: place the base-height frame at its\n * `meta.position`. Commits the frame (firing {@link onChange} if it moved). */\n layoutLine(line: PresentedLine | undefined): Rect {\n this.line = line;\n this.commit(this.frameAt(positionOf(line), this.cfg.box.height));\n return this.frame;\n }\n\n /**\n * Grow the frame to fit a choice: nameplate band + optional prompt + the\n * rows, capped at the field. Commits the grown frame (firing {@link onChange}\n * so the chrome/nameplate/prompt follow) and returns the row rects stacked\n * inside it.\n */\n layoutChoicePanel(rowHeights: readonly number[]): ChoiceRowRect[] {\n const promptH = this.promptHeight();\n const headH = promptH > 0 ? promptH + this.cfg.choiceGap : 0;\n const rows = rowHeights.reduce((a, h) => a + h, 0);\n const content =\n this.cfg.padding + this.bodyOffset() + headH + rows + this.cfg.padding;\n const maxH = this.viewH - 2 * this.cfg.box.marginY; // cap at the screen (minus margins)\n const height = Math.min(Math.max(this.cfg.box.height, content), maxH);\n this.commit(this.frameAt(positionOf(this.line), height));\n // Stack the rows inside the inset-narrowed region, so they reflow around an\n // in-box avatar exactly like the prompt + body text above them.\n const insetL = this.insetWidth(\"left\");\n const insetR = this.insetWidth(\"right\");\n const inner: Rect = {\n x: this.frame.x + insetL,\n y: this.frame.y,\n width: this.frame.width - insetL - insetR,\n height: this.frame.height,\n };\n return stackChoiceRows(rowHeights, inner, this.cfg.padding);\n }\n\n /** Body-text region inside the current frame: below the nameplate band, inset\n * by padding, minus any registered insets (so text reflows around an avatar).\n * For a choice, this is the prompt region above the rows. */\n textRegion(): { x: number; y: number; width: number } {\n let x = this.frame.x + this.cfg.padding;\n let width = this.frame.width - 2 * this.cfg.padding;\n for (const inset of this.insets.values()) {\n width -= inset.width;\n if (inset.side === \"left\") x += inset.width;\n }\n return { x, y: this.frame.y + this.cfg.padding + this.bodyOffset(), width };\n }\n\n /** Top-left of the nameplate inside the current frame. */\n nameplatePos(): { x: number; y: number } {\n return { x: this.frame.x + this.cfg.padding, y: this.frame.y + this.cfg.padding - 1 };\n }\n\n /** Bottom-right continue-caret position inside the current frame. */\n caretPos(size: { width: number; height: number }): { x: number; y: number } {\n return {\n x: this.frame.x + this.frame.width - this.cfg.padding - size.width,\n y: this.frame.y + this.frame.height - this.cfg.padding - size.height - 1,\n };\n }\n\n /**\n * Reserve (or clear with `undefined`) a left/right column the body text\n * reflows around — the avatar-reflow seam. The reference in-box avatar\n * presenter registers one keyed by its own id. Fires {@link onChange} so a\n * text view already showing this line reflows.\n */\n setInset(key: string, inset: TextInset | undefined): void {\n const prev = this.insets.get(key);\n if (inset) this.insets.set(key, inset);\n else this.insets.delete(key);\n if (!sameInset(prev, inset)) this.notify();\n }\n\n /** The reserved width on a side (0 if none) — an avatar presenter reads it to\n * place itself in the column it reserved. */\n insetWidth(side: \"left\" | \"right\"): number {\n let w = 0;\n for (const inset of this.insets.values()) if (inset.side === side) w += inset.width;\n return w;\n }\n\n /** Distance from the frame top to the body text (nameplate band + gap). */\n private bodyOffset(): number {\n return this.cfg.nameSize + TEXT_GAP;\n }\n\n /** Measure the current choice line's prompt (0 when there is none). */\n private promptHeight(): number {\n const text = this.line?.text;\n if (!text || text.length === 0) return 0;\n const plain = text.runs.map((r) => r.text).join(\"\");\n const font = this.cfg.bitmapFont ?? this.cfg.fontFamily;\n const measured = measureWrappedText(plain, {\n fontSize: this.cfg.textSize,\n lineHeight: this.cfg.lineHeight,\n wordWrapWidth: this.textRegion().width,\n ...(font !== undefined ? { fontFamily: font } : {}),\n ...(this.cfg.bitmapFont !== undefined ? { bitmap: true } : {}),\n });\n return measured.height;\n }\n\n /** Place a full-width frame of `height` at `position` within the design\n * viewport: `bottom` anchors `marginY` from the bottom edge, `top` mirrors it\n * to the top, `center` centres. The width is the viewport minus side margins. */\n private frameAt(position: BoxPosition, height: number): Rect {\n const { marginX, marginY } = this.cfg.box;\n let y: number;\n if (position === \"top\") y = marginY;\n else if (position === \"center\") y = (this.viewH - height) / 2;\n else y = this.viewH - marginY - height; // bottom (resting)\n return { x: marginX, y, width: this.viewW - 2 * marginX, height };\n }\n\n private commit(frame: Rect): void {\n if (sameRect(this.frame, frame)) return;\n this.frame = frame;\n this.notify();\n }\n\n private notify(): void {\n for (const fn of this.listeners) fn();\n }\n}\n\n/** A line's `meta.position`, defaulting to `bottom` (the resting box). */\nfunction positionOf(line: PresentedLine | undefined): BoxPosition {\n const p = line?.meta?.[\"position\"];\n return p === \"top\" || p === \"center\" ? p : \"bottom\";\n}\n\nfunction sameRect(a: Rect, b: Rect): boolean {\n return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;\n}\n\nfunction sameInset(a: TextInset | undefined, b: TextInset | undefined): boolean {\n if (a === undefined || b === undefined) return a === b;\n return a.side === b.side && a.width === b.width;\n}\n","/**\n * Default chrome — draws the dialogue box frame, the name plate, and the\n * blinking \"continue\" caret with the renderer (Graphics + Text on screen-space\n * layers), so this addon needs only renderer + core, not ui-react. The choice\n * list lives in its own {@link ChoiceListPresenter}; the body text lives in\n * {@link DialogueTextView}. This class owns only the frame + nameplate + caret,\n * which makes z-order deterministic and the seams swappable independently.\n *\n * Geometry comes from the shared {@link BoxLayout}: the frame rect (moved per\n * line by `meta.position`, grown to fit a choice's rows), the nameplate spot,\n * and the caret spot. The chrome subscribes to the owner so when a choice grows\n * the frame after the chrome already presented, it redraws + repositions — the\n * frame, nameplate, prompt, and rows stay ONE coherent panel.\n *\n * The frame renders one of three ways per line, chosen by the line's\n * `meta.chrome` key (box only): a named {@link NineSliceFrame} from\n * {@link DialogueChromeConfig.frameStyles}, the built-in `\"none\"` (no frame), or\n * the drawn Graphics rounded rect (the default).\n */\n\nimport { Transform, type Entity, type Scene } from \"@yagejs/core\";\nimport {\n createNineSlice,\n GraphicsComponent,\n RendererKey,\n TextComponent,\n type NineSliceSprite,\n} from \"@yagejs/renderer\";\nimport { caretAlpha, drawCaret } from \"./caret.js\";\nimport type { ChromePresenter, DiagnosticSink } from \"./DialogueUiAdapter.js\";\nimport { makeTextOptions, type FontConfig } from \"./textOptions.js\";\nimport {\n CHROME_STYLE_DEFAULT,\n CHROME_STYLE_NONE,\n DEFAULT_CARET_SIZE,\n type CaretTheme,\n type NineSliceFrame,\n} from \"../factory/theme.js\";\nimport type { BoxLayout } from \"../render/BoxLayout.js\";\nimport type { PresentedLine } from \"../core/session.js\";\n\nexport interface DialogueChromeConfig extends FontConfig {\n readonly frameColor: number;\n readonly frameAlpha: number;\n readonly borderColor: number;\n readonly cornerRadius: number;\n readonly nameColor: number;\n readonly nameSize: number;\n readonly indicatorColor: number;\n /** Continue-caret blink + size (built-in defaults when omitted). */\n readonly caret?: CaretTheme | undefined;\n /** Named nine-slice box-frame styles, keyed to match `meta.chrome`. The\n * reserved `\"default\"` entry is the no-meta look; `\"none\"` is built-in and\n * needs no entry. Omit the whole field for the Graphics-only default. */\n readonly frameStyles?: Readonly<Record<string, NineSliceFrame>> | undefined;\n /** Frame + continue indicator. */\n readonly layerFrame: string;\n /** Name plate (drawn above the frame layer). */\n readonly layerText: string;\n}\n\n/** Which frame the active line draws: the Graphics rect, a named nine-slice, or\n * nothing. */\nexport type ActiveFrame =\n | { readonly kind: \"graphics\" }\n | { readonly kind: \"nineSlice\"; readonly key: string }\n | { readonly kind: \"none\" };\n\n/**\n * Resolve a line's `meta.chrome` key against the configured textured styles.\n * `\"none\"` → no frame; a known style → that nine-slice; missing or unknown →\n * the `\"default\"` textured style if present, else the drawn Graphics rect. Pure\n * so the policy is unit-testable without the renderer.\n */\nexport function resolveActiveFrame(\n styleKey: string | undefined,\n styles: ReadonlyMap<string, unknown>,\n): ActiveFrame {\n if (styleKey === CHROME_STYLE_NONE) return { kind: \"none\" };\n if (styleKey !== undefined && styles.has(styleKey)) return { kind: \"nineSlice\", key: styleKey };\n if (styles.has(CHROME_STYLE_DEFAULT)) return { kind: \"nineSlice\", key: CHROME_STYLE_DEFAULT };\n return { kind: \"graphics\" };\n}\n\nexport class DialogueChrome implements ChromePresenter {\n private frame?: Entity | undefined;\n private frameGfx?: GraphicsComponent | undefined;\n /** Separate entity hosting the nine-slice sprites (one per textured style);\n * only spawned when {@link DialogueChromeConfig.frameStyles} has entries. Its\n * Transform tracks the per-line frame origin so the sprites draw at local 0. */\n private frameTex?: Entity | undefined;\n private frameTexTransform?: Transform | undefined;\n private nineSliceHost?: GraphicsComponent | undefined;\n private readonly nineSlices = new Map<string, NineSliceSprite>();\n private name?: { entity: Entity; transform: Transform; comp: TextComponent } | undefined;\n private indicator?: { entity: Entity; transform: Transform; gfx: GraphicsComponent } | undefined;\n private indicatorTime = 0;\n /** Selected textured-style name from the line's `meta.chrome`, or undefined\n * when the line names none. */\n private styleKey: string | undefined;\n private warn?: DiagnosticSink | undefined;\n private readonly warnedKeys = new Set<string>();\n /** Master gate (from {@link setVisible}); the Session drives it. Hidden at\n * mount until a line shows. The name/caret also need their own content\n * sub-state — each renders only when shown AND its content is present. */\n private visible = false;\n private nameShown = false;\n private caretShown = false;\n\n constructor(\n private readonly cfg: DialogueChromeConfig,\n private readonly layout: BoxLayout,\n ) {\n // A choice grows the frame AFTER this chrome presented its prompt line — so\n // re-place the frame + nameplate + caret when the owner's geometry changes.\n this.layout.onChange(() => this.applyGeometry());\n }\n\n /** Route the unknown-`meta.chrome` warning to the engine Logger. */\n setDiagnostics(warn: DiagnosticSink): void {\n this.warn = warn;\n }\n\n mount(scene: Scene): void {\n const cfg = this.cfg;\n\n // Bind the design viewport so the box is a full-width bottom bar at any\n // resolution and meta.position places against the true screen. Resolved here\n // (the chrome owns the frame); a custom box chrome should do the same. Falls\n // back to the layout's default size if no renderer is present (headless).\n const renderer = scene.context.tryResolve(RendererKey);\n if (renderer) this.layout.setViewport(renderer.virtualSize.width, renderer.virtualSize.height);\n\n // Frame: the drawn Graphics rounded rect (the default look). Drawn per line\n // in applyGeometry — the rect moves with `meta.position` and grows for a\n // choice, so it can't be a one-shot mount draw.\n const frame = scene.spawn(\"dlg-frame\");\n frame.add(new Transform()).setPosition(0, 0);\n this.frameGfx = frame.add(new GraphicsComponent({ layer: cfg.layerFrame }));\n this.frameGfx.graphics.visible = false;\n this.frame = frame;\n\n // Textured frame styles: a nine-slice sprite per named style, parented into\n // a host GraphicsComponent (Pixi Graphics is a Container) on its OWN entity\n // — the DisplaySystem drives a GraphicsComponent's position from its entity\n // Transform, so the host Transform tracks the per-line frame origin and the\n // sprites draw at local (0,0). Reuses renderer's layer path; no pixi.js import.\n const styles = cfg.frameStyles;\n if (styles && Object.keys(styles).length > 0) {\n const texEntity = scene.spawn(\"dlg-frame-tex\");\n this.frameTexTransform = texEntity.add(new Transform());\n const host = texEntity.add(new GraphicsComponent({ layer: cfg.layerFrame }));\n for (const [key, spec] of Object.entries(styles)) {\n const sprite = createNineSlice({\n texture: spec.texture,\n leftWidth: spec.insets.left,\n topHeight: spec.insets.top,\n rightWidth: spec.insets.right,\n bottomHeight: spec.insets.bottom,\n width: this.layout.frameRect().width,\n height: this.layout.frameRect().height,\n });\n sprite.visible = false;\n host.graphics.addChild(sprite);\n this.nineSlices.set(key, sprite);\n }\n this.frameTex = texEntity;\n this.nineSliceHost = host;\n }\n\n // Name plate.\n const nameEntity = scene.spawn(\"dlg-name\");\n const nameTransform = nameEntity.add(new Transform());\n const nameComp = nameEntity.add(\n new TextComponent(makeTextOptions(cfg, \"\", cfg.nameSize, cfg.nameColor, cfg.layerText)),\n );\n this.name = { entity: nameEntity, transform: nameTransform, comp: nameComp };\n\n // Continue indicator (blinking caret), sized by the theme. Drawn once in\n // local coords; positioned per line via its transform.\n const caretSize = cfg.caret?.size ?? DEFAULT_CARET_SIZE;\n const ind = scene.spawn(\"dlg-indicator\");\n const indTransform = ind.add(new Transform());\n const indGfx = ind.add(new GraphicsComponent({ layer: cfg.layerFrame }));\n indGfx.draw((g) => drawCaret(g, cfg.indicatorColor, caretSize));\n indGfx.graphics.visible = false;\n this.indicator = { entity: ind, transform: indTransform, gfx: indGfx };\n\n this.applyGeometry();\n }\n\n setNameplate(name: string | undefined, color?: number): void {\n if (!this.name) return;\n // `undefined` means \"no name\" — only the nameplate text, NOT a covert\n // hide-all (that overload died; the Session hides via setVisible).\n this.nameShown = name !== undefined;\n if (name !== undefined) {\n // Mutate fill in place — replacing the whole style would drop the bitmap\n // font (BitmapText resolves its font from style.fontFamily).\n this.name.comp.text.style.fill = color ?? this.cfg.nameColor;\n this.name.comp.setText(name);\n }\n this.apply();\n }\n\n setContinueVisible(visible: boolean): void {\n this.caretShown = visible;\n this.indicatorTime = 0;\n this.apply();\n }\n\n /** Place this line's frame at its `meta.position` and pick its `meta.chrome`\n * style. Box only; the bubble ignores both. `undefined` (no line) resets to\n * the default look at the resting position. */\n present(line: PresentedLine | undefined): void {\n const metaChrome = line?.meta?.[\"chrome\"];\n this.styleKey = typeof metaChrome === \"string\" ? metaChrome : undefined;\n // Warn once on a named-but-unresolvable style (a typo'd `#chrome:` lands\n // here and silently falls back, which is easy to miss).\n if (\n this.styleKey !== undefined &&\n this.styleKey !== CHROME_STYLE_NONE &&\n this.nineSlices.get(this.styleKey) === undefined &&\n !this.warnedKeys.has(this.styleKey)\n ) {\n this.warnedKeys.add(this.styleKey);\n this.warn?.(`unknown meta.chrome style \"${this.styleKey}\" — using the default frame`);\n }\n this.layout.layoutLine(line); // place the frame at meta.position\n this.applyGeometry();\n this.apply();\n }\n\n /** Show or hide the whole box. State-preserving — the name/caret content\n * sub-state survives, so showing again restores exactly what was up. */\n setVisible(visible: boolean): void {\n this.visible = visible;\n this.apply();\n }\n\n /** Redraw the frame + reposition the nameplate and caret from the owner's\n * current geometry (per line, and when a choice grows the frame). */\n private applyGeometry(): void {\n const r = this.layout.frameRect();\n this.frameGfx?.draw((g) => {\n g.clear();\n g.roundRect(r.x, r.y, r.width, r.height, this.cfg.cornerRadius)\n .fill({ color: this.cfg.frameColor, alpha: this.cfg.frameAlpha })\n .stroke({ color: this.cfg.borderColor, alpha: 1, width: 2 });\n });\n if (this.frameTexTransform) this.frameTexTransform.setPosition(r.x, r.y);\n for (const sprite of this.nineSlices.values()) {\n sprite.width = r.width;\n sprite.height = r.height;\n }\n if (this.name) {\n const p = this.layout.nameplatePos();\n this.name.transform.setPosition(p.x, p.y);\n }\n if (this.indicator) {\n const p = this.layout.caretPos(this.cfg.caret?.size ?? DEFAULT_CARET_SIZE);\n this.indicator.transform.setPosition(p.x, p.y);\n }\n }\n\n /** Render each piece = master-visible AND its own content present. */\n private apply(): void {\n const active = resolveActiveFrame(this.styleKey, this.nineSlices);\n if (this.frameGfx) {\n this.frameGfx.graphics.visible = this.visible && active.kind === \"graphics\";\n }\n if (this.nineSliceHost) {\n // The host shows only while a nine-slice is the active frame (so the\n // \"graphics\"/\"none\" cases leave nothing textured on screen).\n this.nineSliceHost.graphics.visible = this.visible && active.kind === \"nineSlice\";\n for (const [key, sprite] of this.nineSlices) {\n sprite.visible = active.kind === \"nineSlice\" && active.key === key;\n }\n }\n if (this.name) this.name.comp.text.visible = this.visible && this.nameShown;\n if (this.indicator) {\n this.indicator.gfx.graphics.visible = this.visible && this.caretShown;\n }\n }\n\n update(dt: number): void {\n // Read pixi's `visible` directly — a parallel boolean could desync (e.g.\n // keep animating a caret that `setVisible(false)` hid).\n const gfx = this.indicator?.gfx.graphics;\n if (gfx?.visible) {\n this.indicatorTime += dt;\n gfx.alpha = caretAlpha(this.indicatorTime, this.cfg.caret?.blinkMs);\n }\n }\n\n dispose(): void {\n this.frame?.destroy();\n this.frameTex?.destroy();\n this.name?.entity.destroy();\n this.indicator?.entity.destroy();\n this.nineSlices.clear();\n this.frame = undefined;\n this.frameTex = undefined;\n this.frameTexTransform = undefined;\n this.frameGfx = undefined;\n this.nineSliceHost = undefined;\n this.name = undefined;\n this.indicator = undefined;\n }\n}\n","/**\n * DialogueTheme — the single flat visual-config object the dialogue factories\n * consume. A theme is a plain data object (no behaviour) so it can be authored\n * inline, imported from a preset module, or serialized.\n *\n * The factories ({@link createBoxDialogue}, {@link createBubbleDialogue},\n * {@link createMixedDialogue}) map every field below onto the chrome / text /\n * choice presenter configs. The mapping is mechanical: a presenter-config field\n * has the SAME name as the theme field it comes from (e.g. theme `frameColor` →\n * config `frameColor`), so drift is visible and a `dialogue.exhaustiveness`\n * test asserts every field reaches a presenter.\n *\n * {@link defaultTheme} returns a zero-asset instance (Graphics chrome + canvas\n * text, no `bitmapFont`/`textured`), so the factories work with no\n * caller-supplied theme.\n *\n * Bitmap fonts (`bitmapFont`) are an OPT-IN crisp-pixel path. Textured\n * nine-slice chrome is a separate opt-in re-theming path driven by the\n * {@link textured} field.\n */\n\nimport type { TextureInput } from \"@yagejs/renderer\";\n\n/**\n * Viewport-relative bounds for the dialogue box (virtual px). The box is a\n * full-width bottom bar resolved against the renderer's design size at mount, so\n * the default presenter works at ANY virtual resolution with no override: the\n * width is `viewport.width - 2*marginX`, and the frame anchors `marginY` from the\n * screen edge. `meta.position` reuses these: `bottom` (default) anchors at the\n * bottom edge, `top` mirrors `marginY` to the top, `center` ignores it.\n */\nexport interface BoxBounds {\n /** Horizontal margin from the left and right screen edges (virtual px). */\n readonly marginX: number;\n /** Vertical margin from the anchored screen edge — the bottom by default, the\n * top for `meta.position: top` (the centred position ignores it). */\n readonly marginY: number;\n /** Box height (virtual px) — sized to hold the body text, not the screen, so\n * it holds the same number of lines at any resolution. */\n readonly height: number;\n}\n\n/** Continue-caret styling. The caret is the blinking \"press to advance\"\n * triangle every chrome draws at its bottom-right; both fields are optional —\n * omit them for the built-in defaults ({@link DEFAULT_CARET_BLINK_MS} /\n * {@link DEFAULT_CARET_SIZE}). The nested-group shape is the convention the\n * (cut) glossary `term` styling returns into. */\nexport interface CaretTheme {\n /** Blink time constant (ms) in `0.35 + 0.65·(0.5 + 0.5·sin(t/blinkMs))`.\n * Larger = slower pulse. Default {@link DEFAULT_CARET_BLINK_MS}. */\n readonly blinkMs?: number;\n /** Triangle size (px). Default {@link DEFAULT_CARET_SIZE} (7×5, pointing down). */\n readonly size?: { readonly width: number; readonly height: number };\n}\n\nexport interface DialogueTheme {\n /** Box geometry as viewport-relative margins + height — a full-width bottom\n * bar resolved against the renderer's design size, so it works at any\n * resolution with no override. See {@link BoxBounds}. */\n readonly box: BoxBounds;\n /** Inner padding between the frame and its contents. */\n readonly padding: number;\n\n // --- Frame / bubble background ---\n readonly frameColor: number;\n readonly frameAlpha: number;\n readonly borderColor: number;\n readonly cornerRadius: number;\n\n // --- Name plate + continue caret ---\n readonly nameColor: number;\n readonly nameSize: number;\n /** Blinking \"continue\" caret colour. */\n readonly indicatorColor: number;\n /** Continue-caret blink + size (optional; built-in defaults otherwise). */\n readonly caret?: CaretTheme;\n\n // --- Body text ---\n readonly textSize: number;\n readonly lineHeight: number;\n readonly textColor: number;\n /** Base reveal rate (characters/second). */\n readonly charsPerSec: number;\n\n // --- Choices ---\n readonly choiceSize: number;\n readonly choiceColor: number;\n readonly choiceSelectedColor: number;\n readonly highlightColor: number;\n /** Vertical gap (px) between choice rows. One value for box and bubble lists\n * (a per-bundle override stays possible via the presenter config). Optional;\n * default {@link DEFAULT_CHOICE_GAP}. */\n readonly choiceGap?: number;\n\n // --- Speech bubble ---\n /** Bubble tail tip offset from the speaker anchor (px), the little\n * asymmetric \"lean\" of the pointer. Optional; default {@link DEFAULT_TAIL_LEAN}.\n * Bubble *size* (min/max width, height, tail height) is geometry, set via\n * `createBubbleDialogue`'s `bubble` option, not the theme. */\n readonly tailLean?: { readonly x: number; readonly y: number };\n\n // --- Fonts (omit the bitmap field for canvas text) ---\n /**\n * Baked bitmap-font name (OPT-IN). Omit for canvas text. When set, the\n * presenters render with the crisp pixel atlas instead of canvas SplitText.\n * Bold/italic are synthesised on the regular atlas (skew + double-draw);\n * variant-atlas fields will return if a baseline-compensating crisp path\n * lands in the renderer.\n */\n readonly bitmapFont?: string;\n /** Canvas font family (used when {@link bitmapFont} is omitted). */\n readonly fontFamily?: string;\n /** Canvas render resolution (used when not bitmap). */\n readonly resolution?: number;\n\n // --- Render layers (screen-space) ---\n /** Layer for the frame + selection highlight + continue caret. */\n readonly layerFrame: string;\n /** Layer for all text (name, body, choice labels). */\n readonly layerText: string;\n\n // --- Behaviour ---\n /** Hold-to-fast-forward multiplier. Default 4 (applied by the session). */\n readonly skipMultiplier?: number;\n\n /**\n * Optional textured nine-slice chrome (OPT-IN) — a MAP of named\n * {@link ChromeStyle}s. A box line picks one by name through its\n * `meta.chrome` key; the box chrome renders that style's `frame` as a\n * stretchable nine-slice instead of the drawn rounded rect, and the bubble\n * renders the `\"default\"` style's `bubble` (if any). Two style names are\n * reserved:\n * - `\"default\"` — the look used for box lines with no (or an unknown)\n * `meta.chrome`. Omit it to keep the drawn Graphics frame as the default.\n * - `\"none\"` — built-in (needs no entry); hides the box frame entirely for\n * that line (e.g. a full-bleed narration line).\n *\n * Leave `textured` undefined for the Graphics-only default path.\n */\n readonly textured?: Readonly<Record<string, ChromeStyle>>;\n}\n\n/**\n * NineSliceInsets — the four immutable border widths (in source-texture pixels)\n * that define a nine-slice frame. The center + edges stretch; the corners stay\n * fixed. Matches Pixi's NineSliceSprite `leftWidth`/`topHeight`/etc.\n */\nexport interface NineSliceInsets {\n /** Fixed-width left border in texture pixels. */\n readonly left: number;\n /** Fixed-height top border in texture pixels. */\n readonly top: number;\n /** Fixed-width right border in texture pixels. */\n readonly right: number;\n /** Fixed-height bottom border in texture pixels. */\n readonly bottom: number;\n}\n\n/**\n * A single nine-slice texture frame: the source texture plus its border insets.\n * Textures are referenced by {@link TextureInput} (string key or Texture) so the\n * theme stays serializable. Used for both box frames and speech bubbles.\n */\nexport interface NineSliceFrame {\n /** Nine-slice texture (string asset key or a resolved Texture). */\n readonly texture: TextureInput;\n /** Border insets for {@link texture}, in source-texture pixels. */\n readonly insets: NineSliceInsets;\n}\n\n/**\n * A named chrome style: a box-frame nine-slice and, optionally, a matching\n * speech-bubble nine-slice. A box line selects a style by name via its\n * `meta.chrome` key (see {@link DialogueTheme.textured}); the speech bubble uses\n * the `\"default\"` style's `bubble` for every bubble line (per-line bubble\n * variants are not a thing — bubbles are diegetic, anchored to an actor).\n */\nexport interface ChromeStyle {\n /** Box-frame nine-slice for this style. */\n readonly frame: NineSliceFrame;\n /** Speech-bubble nine-slice (only the `\"default\"` style's `bubble` is read).\n * Omit to keep the drawn Graphics bubble. */\n readonly bubble?: NineSliceFrame;\n}\n\n/** Default continue-caret blink time constant (ms). */\nexport const DEFAULT_CARET_BLINK_MS = 260;\n/** Default continue-caret triangle size (px), pointing down. */\nexport const DEFAULT_CARET_SIZE: { readonly width: number; readonly height: number } = {\n width: 7,\n height: 5,\n};\n/** Default vertical gap (px) between choice rows (box + bubble). */\nexport const DEFAULT_CHOICE_GAP = 6;\n/** Default bubble tail tip offset from the speaker anchor (px). */\nexport const DEFAULT_TAIL_LEAN: { readonly x: number; readonly y: number } = { x: -3, y: -2 };\n\n/** Reserved `meta.chrome` / {@link DialogueTheme.textured} key: the box look\n * used when a line carries no (or an unknown) `meta.chrome`. */\nexport const CHROME_STYLE_DEFAULT = \"default\";\n/** Reserved `meta.chrome` value (built-in, needs no `textured` entry): hide the\n * box frame for that line. */\nexport const CHROME_STYLE_NONE = \"none\";\n\n/** The box-frame nine-slice for each named style, shaped for the box chrome\n * config (`meta.chrome` → frame). Undefined when the theme has no `textured`. */\nexport function boxFrameStyles(\n textured: DialogueTheme[\"textured\"],\n): Readonly<Record<string, NineSliceFrame>> | undefined {\n if (!textured) return undefined;\n const out: Record<string, NineSliceFrame> = {};\n for (const [name, style] of Object.entries(textured)) out[name] = style.frame;\n return out;\n}\n\n/** The speech-bubble nine-slice from the `\"default\"` style, for the bubble\n * chrome config. Undefined when there is no textured `\"default\"` bubble. */\nexport function defaultBubbleFrame(textured: DialogueTheme[\"textured\"]): NineSliceFrame | undefined {\n return textured?.[CHROME_STYLE_DEFAULT]?.bubble;\n}\n","/**\n * The blinking \"continue\" caret every chrome draws — one triangle + one blink\n * formula, shared so the box and bubble chromes can't drift apart (their\n * polygons had already diverged before this was extracted), and both\n * parameterized by the theme's {@link CaretTheme} so a game can restyle the\n * caret without forking a presenter.\n */\n\nimport type { GraphicsContext } from \"@yagejs/renderer\";\nimport { DEFAULT_CARET_BLINK_MS, DEFAULT_CARET_SIZE, type CaretTheme } from \"../factory/theme.js\";\n\n/**\n * Blink alpha for the continue caret, `timeMs` since it was (re)shown.\n * `blinkMs` is the time constant in `0.35 + 0.65·(0.5 + 0.5·sin(t/blinkMs))`.\n */\nexport function caretAlpha(timeMs: number, blinkMs: number = DEFAULT_CARET_BLINK_MS): number {\n return 0.35 + 0.65 * (0.5 + 0.5 * Math.sin(timeMs / blinkMs));\n}\n\n/** The continue-caret triangle (pointing down) at the local origin, sized by\n * `size` (default {@link DEFAULT_CARET_SIZE}). Position the owning entity's\n * `Transform` to place it. */\nexport function drawCaret(\n g: GraphicsContext,\n color: number,\n size: CaretTheme[\"size\"] = DEFAULT_CARET_SIZE,\n): void {\n const w = size.width;\n const h = size.height;\n g.poly([0, 0, w, 0, w / 2, h]).fill({ color, alpha: 1 });\n}\n","/**\n * Font plumbing shared by the renderer-backed presenters: every chrome / choice\n * presenter renders incidental text (nameplates, choice labels) the same way — an\n * optional baked bitmap font wins, else the canvas family + resolution. One\n * {@link FontConfig} triplet + one {@link makeTextOptions} builder keep the\n * presenter configs and their `TextComponent` construction in lockstep.\n *\n * (Choice-row helpers — labelling, disabled-row dimming, highlight — live in\n * `./choiceRow.js`.)\n */\n\nimport type { TextComponentOptions, TextStyle } from \"@yagejs/renderer\";\n\n/** The font triplet every presenter config carries (canvas by default). */\nexport interface FontConfig {\n /** Baked bitmap-font name (OPT-IN crisp-pixel path). Omit for canvas text. */\n readonly bitmapFont?: string | undefined;\n /** Canvas font family (used when {@link bitmapFont} is omitted). */\n readonly fontFamily?: string | undefined;\n /** Canvas render resolution (used when not bitmap). */\n readonly resolution?: number | undefined;\n}\n\n/**\n * Build the `TextComponent` options for a presenter text node. Colour rides\n * `style.fill`; the bitmap font name lives in `style.fontFamily` (that's where\n * `BitmapText` resolves its font from) and wins over the canvas family.\n */\nexport function makeTextOptions(\n fonts: FontConfig,\n text: string,\n size: number,\n color: number,\n layer: string,\n anchor: { readonly x: number; readonly y: number } = { x: 0, y: 0 },\n): TextComponentOptions & { style: TextStyle } {\n const style: TextStyle = { fontSize: size, fill: color };\n if (fonts.bitmapFont) style.fontFamily = fonts.bitmapFont;\n else if (fonts.fontFamily) style.fontFamily = fonts.fontFamily;\n const base: TextComponentOptions & { style: TextStyle } = { text, style, layer, anchor };\n if (fonts.bitmapFont) base.bitmap = true;\n else if (fonts.resolution !== undefined) base.resolution = fonts.resolution;\n return base;\n}\n","/**\n * Default choice presenter — a vertical list inside the box, with a highlight\n * bar behind the selected row. Split out of `DialogueChrome` so the choice UI is\n * swappable (a radial / Mass-Effect wheel, a separate panel, a touch list)\n * without touching the frame or body text. Selection *navigation* lives in the\n * Session; this presenter only paints what it's told and reports pointer commits\n * back through {@link ChoiceChannel.onChoiceChosen}.\n *\n * Overflow + unified panel: labels word-wrap (a row may be several lines), and\n * the row stack is laid out by the shared {@link BoxLayout}, which **grows the\n * surrounding frame** to fit the rows + prompt + nameplate as one panel (a list\n * too tall for the screen spills off the bottom, non-overlapping). Row\n * placement, the selection highlight, and pointer hit-testing all derive from\n * the ONE set of row rects the owner returns, so a wrapped/overflowing row can't\n * desync them. A list longer than `softMaxChoices` logs a soft-cap advisory.\n */\n\nimport { Transform, type Entity, type Scene } from \"@yagejs/core\";\nimport { GraphicsComponent, TextComponent, type TextComponentOptions } from \"@yagejs/renderer\";\nimport type { PresentedChoice } from \"../core/session.js\";\nimport { type BoxLayout, type ChoiceRowRect } from \"../render/BoxLayout.js\";\nimport type { ChoicePresenter, DiagnosticSink } from \"./DialogueUiAdapter.js\";\nimport { makeTextOptions, type FontConfig } from \"./textOptions.js\";\nimport { applyChoiceTint, choiceRowLabel, clampSelection, firstEnabledIndex } from \"./choiceRow.js\";\nimport { DEFAULT_CHOICE_GAP } from \"../factory/theme.js\";\n\nexport interface ChoiceListConfig extends FontConfig {\n readonly choiceSize: number;\n readonly choiceColor: number;\n readonly choiceSelectedColor: number;\n readonly highlightColor: number;\n /** Vertical gap (px) between choice rows. Default {@link DEFAULT_CHOICE_GAP}. */\n readonly choiceGap?: number | undefined;\n /** Soft cap on option count: more than this logs an advisory (the list still\n * grows to fit). Default {@link DEFAULT_SOFT_MAX_CHOICES}. */\n readonly softMaxChoices?: number | undefined;\n /** Selection highlight bar. */\n readonly layerFrame: string;\n /** Choice labels (drawn above the frame layer). */\n readonly layerText: string;\n}\n\ninterface ChoiceRow {\n readonly entity: Entity;\n readonly comp: TextComponent;\n readonly disabled: boolean;\n readonly rect: ChoiceRowRect;\n}\n\n/** Default soft cap on the option count before {@link ChoiceListPresenter} logs\n * an advisory. A single menu much longer than this reads better paginated or\n * as a sub-menu; the list still renders. */\nexport const DEFAULT_SOFT_MAX_CHOICES = 8;\n\n/** Left indent (px) of a row's label inside its rect, leaving room for the\n * highlight bar's rounded edge. */\nconst ROW_TEXT_INDENT = 6;\n\nexport class ChoiceListPresenter implements ChoicePresenter {\n private scene?: Scene | undefined;\n private highlightBar?: { entity: Entity; gfx: GraphicsComponent } | undefined;\n private rows: ChoiceRow[] = [];\n private selected = -1;\n private warn?: DiagnosticSink | undefined;\n /** Master visibility gate — hides the list WITHOUT clearing it, so a\n * hide/show round-trip keeps the rows + selection. */\n private hidden = false;\n\n onChoiceChosen?: (position: number) => void;\n\n constructor(\n private readonly cfg: ChoiceListConfig,\n private readonly layout: BoxLayout,\n ) {}\n\n /** Route the soft-cap advisory to the engine Logger. */\n setDiagnostics(warn: DiagnosticSink): void {\n this.warn = warn;\n }\n\n mount(scene: Scene): void {\n this.scene = scene;\n const hl = scene.spawn(\"dlg-highlight\");\n hl.add(new Transform()).setPosition(0, 0);\n const hlGfx = hl.add(new GraphicsComponent({ layer: this.cfg.layerFrame }));\n this.highlightBar = { entity: hl, gfx: hlGfx };\n }\n\n // Screen-space box list — ignores the choice context (the box frame is its\n // backing; routing is the composite's job).\n present(choices: readonly PresentedChoice[]): void {\n this.clear();\n if (!this.scene) return;\n const cfg = this.cfg;\n const gap = cfg.choiceGap ?? DEFAULT_CHOICE_GAP;\n const softMax = cfg.softMaxChoices ?? DEFAULT_SOFT_MAX_CHOICES;\n if (choices.length > softMax) {\n this.warn?.(\n `choice list: ${choices.length} options exceeds the soft max of ${softMax} — ` +\n `the list grows to fit, but a shorter menu (or a sub-menu) reads better`,\n );\n }\n\n const innerWidth = this.layout.contentWidth();\n const wrapWidth = innerWidth - ROW_TEXT_INDENT - 2;\n\n // Pass 1: build each row's wrapped label and measure its slot height\n // (wrapped text + gap). Word-wrap means a row can be several lines tall.\n const built = choices.map((choice) => {\n const entity = this.scene!.spawn(\"dlg-choice\");\n entity.add(new Transform());\n const comp = entity.add(new TextComponent(this.textOptions(choiceRowLabel(choice), wrapWidth)));\n comp.text.visible = true;\n const slotHeight = Math.ceil(comp.text.height) + gap;\n return { entity, comp, disabled: choice.disabled ?? false, slotHeight };\n });\n\n // Pass 2: hand the row heights to the owner, which grows the surrounding\n // frame to fit them (+ prompt + nameplate) and returns the stacked rects —\n // the ONE geometry source for placement, highlight, and hit-test. Then place\n // each label at its rect (indented for the highlight bar's rounded edge).\n const rects = this.layout.layoutChoicePanel(built.map((b) => b.slotHeight));\n this.rows = built.map((b, i) => {\n const rect = rects[i] ?? { x: 0, y: 0, width: innerWidth, height: b.slotHeight };\n b.entity.get(Transform).setPosition(rect.x + ROW_TEXT_INDENT, rect.y);\n return { entity: b.entity, comp: b.comp, disabled: b.disabled, rect };\n });\n\n // Land the initial highlight on the first ENABLED row (the Session re-asserts\n // this right after; doing it here keeps a stand-alone present() consistent).\n this.highlightAt(firstEnabledIndex(this.rows));\n this.applyHidden();\n }\n\n highlight(position: number): void {\n this.highlightAt(position);\n }\n\n /** Show or hide the list without clearing it — state-preserving. */\n setVisible(visible: boolean): void {\n this.hidden = !visible;\n this.applyHidden();\n }\n\n /** Render = rows present AND not hidden. Disabled rows still show (greyed). */\n private applyHidden(): void {\n for (const row of this.rows) row.comp.text.visible = !this.hidden;\n if (this.highlightBar) {\n const onEnabled = this.selected >= 0 && !this.rows[this.selected]?.disabled;\n this.highlightBar.gfx.graphics.visible = !this.hidden && onEnabled;\n }\n }\n\n /** {@link PointerChoiceTarget}: which row (if any) a screen point falls in —\n * from the same grown rects the rows are drawn at. */\n choiceAtPoint(x: number, y: number): number | undefined {\n for (let i = 0; i < this.rows.length; i++) {\n const r = this.rows[i]?.rect;\n if (r && x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height) return i;\n }\n return undefined;\n }\n\n clear(): void {\n for (const row of this.rows) row.entity.destroy();\n this.rows = [];\n this.selected = -1;\n this.highlightBar?.gfx.draw((g) => g.clear());\n }\n\n dispose(): void {\n this.clear();\n this.highlightBar?.entity.destroy();\n this.highlightBar = undefined;\n }\n\n private highlightAt(position: number): void {\n if (this.rows.length === 0) return;\n this.selected = clampSelection(position, this.rows.length);\n applyChoiceTint(this.rows, this.selected, this.cfg.choiceColor, this.cfg.choiceSelectedColor);\n this.drawHighlight();\n }\n\n private drawHighlight(): void {\n if (!this.highlightBar || this.selected < 0) return;\n const row = this.rows[this.selected];\n // A disabled row is never highlighted (nav skips it); clear any stale bar.\n if (!row || row.disabled) {\n this.highlightBar.gfx.draw((g) => g.clear());\n return;\n }\n const r = row.rect;\n this.highlightBar.gfx.draw((g) => {\n g.clear();\n g.roundRect(r.x, r.y, r.width, r.height - 1, 3).fill({\n color: this.cfg.highlightColor,\n alpha: 0.3,\n });\n });\n }\n\n private textOptions(text: string, wrapWidth: number): TextComponentOptions {\n const opts = makeTextOptions(\n this.cfg,\n text,\n this.cfg.choiceSize,\n this.cfg.choiceColor,\n this.cfg.layerText,\n );\n opts.style.wordWrap = true;\n opts.style.wordWrapWidth = wrapWidth;\n return opts;\n }\n}\n","/**\n * Shared choice-row helpers for the renderer-backed choice presenters\n * (list / bubble / radial). Disabled-row dimming, labelling, initial-highlight\n * selection, and the clamp + active-tint highlight loop live here once so the\n * three presenters stay consistent.\n */\n\nimport type { TextComponent } from \"@yagejs/renderer\";\n\n/** Opacity applied to a disabled (non-selectable) choice row, so the default\n * presenters grey it out without needing a dedicated theme colour. */\nexport const DISABLED_CHOICE_ALPHA = 0.4;\n\n/**\n * A choice row's display text: the label, plus its `disabledReason` in\n * parentheses when the row is disabled and carries one. Parens (not an em-dash)\n * keep it renderable on baked bitmap-font atlases that may omit punctuation\n * glyphs. Used by the single-line list/bubble presenters; the radial wheel has\n * no room for a reason and shows the bare label.\n */\nexport function choiceRowLabel(choice: {\n readonly label: string;\n readonly disabled?: boolean | undefined;\n readonly disabledReason?: string | undefined;\n}): string {\n return choice.disabled && choice.disabledReason\n ? `${choice.label} (${choice.disabledReason})`\n : choice.label;\n}\n\n/** Display position of the first selectable (non-disabled) row, or 0 when every\n * row is disabled — a case the runner prevents by skipping a zero-enabled step,\n * so the 0 is only a defensive fallback. Shared by all three choice presenters\n * to seed the initial highlight. */\nexport function firstEnabledIndex(\n rows: readonly { readonly disabled?: boolean | undefined }[],\n): number {\n const i = rows.findIndex((r) => !r.disabled);\n return i < 0 ? 0 : i;\n}\n\n/** Clamp a requested highlight position into `[0, count)` (callers guard the\n * empty list before calling). */\nexport function clampSelection(position: number, count: number): number {\n return Math.min(Math.max(position, 0), count - 1);\n}\n\n/**\n * Paint the per-row fill + disabled dimming for a choice presenter's highlight\n * loop: the row at `selected` (when enabled) takes `selectedColor`, every other\n * enabled row `color`, and a disabled row dims to {@link DISABLED_CHOICE_ALPHA}.\n * The shared body of each presenter's `highlight()`; the presenter still draws\n * its own highlight bar / hub (and the radial scales its selected spoke).\n */\nexport function applyChoiceTint(\n rows: readonly { readonly comp: TextComponent; readonly disabled: boolean }[],\n selected: number,\n color: number,\n selectedColor: number,\n): void {\n rows.forEach((row, i) => {\n const active = i === selected && !row.disabled;\n row.comp.text.style.fill = active ? selectedColor : color;\n row.comp.text.alpha = row.disabled ? DISABLED_CHOICE_ALPHA : 1;\n });\n}\n","/**\n * Diegetic speech-bubble chrome: a rounded bubble + downward tail drawn on a\n * *world* layer, repositioned every frame to sit above the speaking actor's\n * head. The per-line size, the speaker→world anchor (incl. the missing-actor\n * fallback), and the inner-top-left origin all come from the shared\n * {@link BubbleLayout} — the single owner the companion {@link BubbleTextView}\n * and {@link BubbleChoicePresenter} read too, so they can never drift. This\n * class keeps only the bubble's *drawing* config (colours, tail, caret, name).\n *\n * The bubble is content-sized per line (see {@link BubbleLayout.sizeFor}); the\n * companion text view wraps to the same inner width so they stay aligned. With a\n * textured {@link BubbleChromeConfig.frame}, the body renders as a nine-slice\n * stretched to that same per-line size (the tail stays a drawn triangle).\n */\n\nimport { Transform, type Entity, type Scene } from \"@yagejs/core\";\nimport {\n createNineSlice,\n GraphicsComponent,\n TextComponent,\n type NineSliceSprite,\n} from \"@yagejs/renderer\";\nimport type { PresentedLine } from \"../core/session.js\";\nimport type { BubbleLayout } from \"../render/BubbleLayout.js\";\nimport { caretAlpha, drawCaret } from \"./caret.js\";\nimport type { ChromePresenter, DiagnosticSink } from \"./DialogueUiAdapter.js\";\nimport { makeTextOptions, type FontConfig } from \"./textOptions.js\";\nimport {\n DEFAULT_CARET_SIZE,\n DEFAULT_TAIL_LEAN,\n type CaretTheme,\n type NineSliceFrame,\n} from \"../factory/theme.js\";\n\nexport interface BubbleChromeConfig extends FontConfig {\n /** World-space render layer. */\n readonly layer: string;\n /** Tail height (the little pointer toward the speaker). */\n readonly tail: number;\n /** Tail tip offset from the speaker anchor (the asymmetric \"lean\"). */\n readonly tailLean?: { readonly x: number; readonly y: number } | undefined;\n readonly frameColor: number;\n readonly frameAlpha: number;\n readonly borderColor: number;\n readonly cornerRadius: number;\n readonly nameColor: number;\n readonly nameSize: number;\n readonly indicatorColor: number;\n /** Continue-caret blink + size (built-in defaults when omitted). */\n readonly caret?: CaretTheme | undefined;\n /** Optional textured nine-slice for the bubble body (the `\"default\"` style's\n * `bubble`). Resized per line; omit for the drawn Graphics bubble. */\n readonly frame?: NineSliceFrame | undefined;\n}\n\nexport class BubbleChrome implements ChromePresenter {\n private scene?: Scene | undefined;\n private root?: Entity | undefined;\n private gfx?: GraphicsComponent | undefined;\n private transform?: Transform | undefined;\n /** Nine-slice body sprite (child of {@link gfx}) when textured; resized per\n * line. The tail stays a drawn triangle on {@link gfx}. */\n private bubbleSlice?: NineSliceSprite | undefined;\n private name?: TextComponent | undefined;\n private nameTransform?: Transform | undefined;\n private caret?: GraphicsComponent | undefined;\n private caretTransform?: Transform | undefined;\n private caretTime = 0;\n /** Current (content-sized) bubble size; recomputed per line from the layout. */\n private currentWidth: number;\n private currentHeight: number;\n // Master visibility + content sub-state; rendered = visible && hasLine.\n private visible = false; // master (from setVisible)\n private hasLine = false; // a line is up (from present)\n private nameShown = false; // the speaker has a name to show\n private caretShown = false; // continue caret requested\n /** Speaker id of the line on screen — re-resolved each frame by `follow()` so\n * the bubble tracks a live actor and falls back when one is missing. */\n private speakerId: string | undefined;\n\n constructor(\n private readonly cfg: BubbleChromeConfig,\n private readonly layout: BubbleLayout,\n ) {\n this.currentWidth = 0;\n this.currentHeight = 0;\n }\n\n /** Route the missing-actor warning to the engine Logger (the layout owns the\n * shared anchor resolver). */\n setDiagnostics(warn: DiagnosticSink): void {\n this.layout.setDiagnostics(warn);\n }\n\n mount(scene: Scene): void {\n this.scene = scene;\n const c = this.cfg;\n const root = scene.spawn(\"dlg-bubble\");\n this.transform = root.add(new Transform());\n this.gfx = root.add(new GraphicsComponent({ layer: c.layer }));\n if (c.frame) {\n // Textured body: a nine-slice child of the bubble graphics, resized +\n // positioned per line in drawBubble. createNineSlice keeps the addon off a\n // direct pixi.js import.\n const slice = createNineSlice({\n texture: c.frame.texture,\n leftWidth: c.frame.insets.left,\n topHeight: c.frame.insets.top,\n rightWidth: c.frame.insets.right,\n bottomHeight: c.frame.insets.bottom,\n width: this.currentWidth,\n height: this.currentHeight,\n });\n this.gfx.graphics.addChild(slice);\n this.bubbleSlice = slice;\n }\n this.drawBubble();\n this.gfx.graphics.visible = false;\n\n // Name floats just above the bubble; left-aligned to the inner edge.\n const nameEntity = scene.spawn(\"dlg-bubble-name\");\n this.nameTransform = nameEntity.add(new Transform());\n this.name = nameEntity.add(\n new TextComponent(makeTextOptions(c, \"\", c.nameSize, c.nameColor, c.layer)),\n );\n this.name.text.visible = false;\n\n const caretEntity = scene.spawn(\"dlg-bubble-caret\");\n this.caretTransform = caretEntity.add(new Transform());\n this.caret = caretEntity.add(new GraphicsComponent({ layer: c.layer }));\n // Drawn once in local coords; positioned each frame via the transform.\n this.caret.draw((g) => drawCaret(g, c.indicatorColor, c.caret?.size));\n this.caret.graphics.visible = false;\n\n this.root = root;\n }\n\n /** Re-anchor to the line's speaker, grow to fit the text, and reveal. The\n * bubble stays visible even when the speaker has no live actor — the layout\n * anchors it at the last-known / fallback position instead of vanishing. */\n present(line: PresentedLine | undefined): void {\n this.hasLine = line !== undefined;\n this.speakerId = line?.speaker?.id;\n if (line) {\n const size = this.layout.sizeFor(line);\n this.currentWidth = size.width;\n this.currentHeight = size.height;\n this.drawBubble();\n const label = line.speaker?.name;\n this.nameShown = label !== undefined && label.length > 0;\n if (label && this.name) {\n this.name.text.style.fill = line.speaker?.color ?? this.cfg.nameColor;\n this.name.setText(label);\n }\n } else {\n this.nameShown = false;\n }\n this.apply();\n this.follow();\n }\n\n setNameplate(name: string | undefined): void {\n // The bubble owns its own nameplate (set from present's speaker); this\n // handles only the \"no name\" (undefined) clear. Visibility is governed by\n // setVisible, never by the nameplate.\n if (name === undefined) {\n this.nameShown = false;\n this.apply();\n }\n }\n\n setContinueVisible(visible: boolean): void {\n // no `actor !== undefined` gate — the caret shows for a missing-actor\n // line too (the bubble is at the fallback anchor, not hidden).\n this.caretShown = visible;\n this.caretTime = 0;\n this.apply();\n }\n\n /** Show or hide the whole bubble — state-preserving: the line, name, and\n * caret content survive a hide, so showing again restores them in place\n * (used by a composite chrome to hide the bubble while a box line plays). */\n setVisible(visible: boolean): void {\n this.visible = visible;\n this.apply();\n }\n\n /** Render each piece = master-visible AND a line is up AND its content present. */\n private apply(): void {\n if (this.gfx) this.gfx.graphics.visible = this.visible && this.hasLine;\n if (this.name) {\n this.name.text.visible = this.visible && this.hasLine && this.nameShown;\n }\n if (this.caret) {\n this.caret.graphics.visible = this.visible && this.hasLine && this.caretShown;\n }\n }\n\n update(dt: number): void {\n this.follow();\n // Read pixi's `visible` directly — a parallel boolean could desync (e.g.\n // keep animating a caret that `setVisible(false)` hid).\n const gfx = this.caret?.graphics;\n if (gfx?.visible) {\n this.caretTime += dt;\n gfx.alpha = caretAlpha(this.caretTime, this.cfg.caret?.blinkMs);\n }\n }\n\n dispose(): void {\n this.root?.destroy();\n this.name?.entity.destroy();\n this.caret?.entity.destroy();\n this.root = undefined;\n this.gfx = undefined;\n this.transform = undefined;\n this.bubbleSlice = undefined;\n this.name = undefined;\n this.nameTransform = undefined;\n this.caret = undefined;\n this.caretTransform = undefined;\n }\n\n /** Move the bubble + name + caret to sit above the speaker — its live actor,\n * or the last-known / fallback anchor when the actor is missing. */\n private follow(): void {\n if (!this.scene || !this.hasLine) return;\n const a = this.layout.anchorFor(this.scene, this.speakerId);\n const c = this.cfg;\n const padding = this.layout.padding;\n const offsetY = this.layout.offsetY;\n const w = this.currentWidth;\n const h = this.currentHeight;\n const caretSize = c.caret?.size ?? DEFAULT_CARET_SIZE;\n this.transform?.setPosition(a.x, a.y);\n // Name: top-left corner of the bubble, lifted by the (grown) bubble height.\n this.nameTransform?.setPosition(a.x - w / 2 + padding, a.y - (offsetY + h) - c.nameSize - 1);\n // Caret: bottom-right interior of the bubble (anchored near the bottom edge).\n this.caretTransform?.setPosition(\n a.x + w / 2 - padding - caretSize.width,\n a.y - offsetY - padding - caretSize.height + 3,\n );\n }\n\n private drawBubble(): void {\n const c = this.cfg;\n const offsetY = this.layout.offsetY;\n const w = this.currentWidth;\n const h = this.currentHeight;\n const L = -w / 2;\n const R = w / 2;\n const T = -(offsetY + h); // top edge\n const B = -offsetY; // bottom edge (the tail hangs below it to the speaker)\n const half = c.tail; // tail base half-width\n const lean = c.tailLean ?? DEFAULT_TAIL_LEAN;\n const tipX = lean.x; // slight lean\n const tipY = lean.y; // just above the actor's head anchor (local 0,0)\n this.gfx?.graphics.clear(); // re-drawn per line at a new size — don't accumulate\n\n if (this.bubbleSlice) {\n // Textured body: stretch the nine-slice to the content size and place it\n // at the bubble's top-left; draw only the tail triangle (the nine-slice is\n // a rectangle and can't carry the pointer).\n this.bubbleSlice.position.set(L, T);\n this.bubbleSlice.width = w;\n this.bubbleSlice.height = h;\n this.gfx?.draw((g) => {\n g.poly([-half, B, half, B, tipX, tipY]).fill({ color: c.frameColor, alpha: c.frameAlpha });\n });\n return;\n }\n\n const r = Math.max(0, Math.min(c.cornerRadius, w / 2 - 1, h / 2 - 1));\n // One closed silhouette (rounded rect + a tail notch on the bottom edge), so\n // the border flows around the tail instead of cutting across it.\n this.gfx?.draw((g) => {\n g.moveTo(L + r, T)\n .lineTo(R - r, T)\n .arcTo(R, T, R, T + r, r) // top-right\n .lineTo(R, B - r)\n .arcTo(R, B, R - r, B, r) // bottom-right\n .lineTo(half, B) // along the bottom edge to the tail base\n .lineTo(tipX, tipY) // down to the tail tip\n .lineTo(-half, B) // back up to the tail base\n .lineTo(L + r, B)\n .arcTo(L, B, L, B - r, r) // bottom-left\n .lineTo(L, T + r)\n .arcTo(L, T, L + r, T, r) // top-left\n .closePath()\n .fill({ color: c.frameColor, alpha: c.frameAlpha })\n .stroke({ color: c.borderColor, alpha: 1, width: 2 });\n });\n }\n}\n","/**\n * A diegetic choice list: the options float in their own bubble panel over the\n * speaking actor (resolved via the {@link ActorRegistry} from the choice's\n * `context.speaker`), with their own background — so a bubble choice never\n * leans on the box frame (the source of the \"frameless options\" glitch). Pairs\n * with {@link BubbleChrome}/{@link BubbleTextView}; keyboard nav is Session-\n * driven, pointer hover/click come back via `onChoiceChosen` (world coords).\n *\n * The actor is static while a focused choice is up (the player is frozen), so\n * the panel is placed once on `present` rather than followed each frame.\n */\n\nimport { Transform, type Entity, type Scene } from \"@yagejs/core\";\nimport {\n GraphicsComponent,\n TextComponent,\n type TextComponentOptions,\n} from \"@yagejs/renderer\";\nimport type { ChoiceContext, PresentedChoice } from \"../core/session.js\";\nimport type { BubbleLayout } from \"../render/BubbleLayout.js\";\nimport type { ChoicePresenter, DiagnosticSink } from \"./DialogueUiAdapter.js\";\nimport { makeTextOptions, type FontConfig } from \"./textOptions.js\";\nimport { applyChoiceTint, choiceRowLabel, clampSelection, firstEnabledIndex } from \"./choiceRow.js\";\nimport { DEFAULT_CHOICE_GAP } from \"../factory/theme.js\";\n\nexport interface BubbleChoiceConfig extends FontConfig {\n /** World-space render layer (same as the bubble). */\n readonly layer: string;\n readonly width: number;\n readonly padding: number;\n /** Gap between the actor's head anchor and the panel's bottom edge. */\n readonly offsetY: number;\n readonly tail: number;\n readonly choiceSize: number;\n readonly choiceColor: number;\n readonly choiceSelectedColor: number;\n readonly highlightColor: number;\n /** Vertical gap (px) between choice rows. Default {@link DEFAULT_CHOICE_GAP}. */\n readonly choiceGap?: number | undefined;\n /** Colour for the optional prompt header rendered inside the panel — the\n * theme's body `textColor`. */\n readonly textColor: number;\n readonly frameColor: number;\n readonly frameAlpha: number;\n readonly borderColor: number;\n readonly cornerRadius: number;\n}\n\ninterface Row {\n readonly entity: Entity;\n readonly comp: TextComponent;\n readonly x0: number;\n readonly x1: number;\n readonly y0: number;\n readonly y1: number;\n readonly disabled: boolean;\n}\n\nexport class BubbleChoicePresenter implements ChoicePresenter {\n readonly pointerSpace = \"world\" as const;\n\n private scene?: Scene | undefined;\n private bg?: { entity: Entity; gfx: GraphicsComponent } | undefined;\n private highlightBar?: { entity: Entity; gfx: GraphicsComponent } | undefined;\n private prompt?: { entity: Entity; comp: TextComponent } | undefined;\n private rows: Row[] = [];\n private selected = -1;\n /** Master visibility gate — state-preserving hide/show. */\n private hidden = false;\n\n onChoiceChosen?: (position: number) => void;\n\n constructor(\n private readonly cfg: BubbleChoiceConfig,\n private readonly layout: BubbleLayout,\n ) {}\n\n /** Route the missing-actor warning to the engine Logger (the layout owns the\n * shared anchor resolver). */\n setDiagnostics(warn: DiagnosticSink): void {\n this.layout.setDiagnostics(warn);\n }\n\n /** This panel is self-contained (own bg + prompt header), so it owns the\n * prompt — the Session hides the chrome/body prompt for these choices. */\n ownsPrompt(): boolean {\n return true;\n }\n\n mount(scene: Scene): void {\n this.scene = scene;\n const bg = scene.spawn(\"dlg-bchoice-bg\");\n bg.add(new Transform()).setPosition(0, 0);\n this.bg = { entity: bg, gfx: bg.add(new GraphicsComponent({ layer: this.cfg.layer })) };\n const hl = scene.spawn(\"dlg-bchoice-hl\");\n hl.add(new Transform()).setPosition(0, 0);\n this.highlightBar = { entity: hl, gfx: hl.add(new GraphicsComponent({ layer: this.cfg.layer })) };\n }\n\n present(choices: readonly PresentedChoice[], context?: ChoiceContext): void {\n this.clear();\n if (!this.scene) return;\n const c = this.cfg;\n // a missing speaker resolves to the last-known / fallback anchor (with a\n // once-per-speaker warning), never the world origin — via the shared owner.\n const a = this.layout.anchorFor(this.scene, context?.speaker?.id);\n const innerW = c.width - 2 * c.padding; // content column width\n // An in-bubble avatar reserves a portrait column: the panel grows to contain\n // it and the prompt/rows reflow past it, like the say bubble does.\n const inset = this.layout.portraitInset();\n const reserve = inset?.width ?? 0;\n const panelWidth = c.width + reserve;\n\n // Optional prompt header inside the same panel (one bubble, not two).\n const promptStr =\n context?.prompt && context.prompt.length > 0\n ? context.prompt.runs.map((r) => r.text).join(\"\")\n : \"\";\n let promptH = 0;\n if (promptStr) {\n const e = this.scene.spawn(\"dlg-bchoice-prompt\");\n e.add(new Transform());\n const comp = e.add(new TextComponent(this.textOptions(promptStr, c.textColor, innerW)));\n comp.text.visible = true;\n promptH = Math.ceil(comp.text.height);\n this.prompt = { entity: e, comp };\n }\n\n const n = choices.length;\n const gap = c.choiceGap ?? DEFAULT_CHOICE_GAP;\n const lineH = c.choiceSize + gap;\n const headH = promptH > 0 ? promptH + gap : 0;\n const contentH = c.padding + headH + n * lineH + c.padding;\n const panelH = Math.max(contentH, (inset?.height ?? 0) + 2 * c.padding);\n const left = a.x - panelWidth / 2;\n const bottom = a.y - c.offsetY;\n const top = bottom - panelH;\n // Content starts past a left-side portrait column; the tail stays under `a.x`.\n const contentX = left + c.padding + (inset?.side === \"left\" ? reserve : 0);\n\n this.drawPanel(left, top, panelWidth, panelH, a.x, bottom);\n this.prompt?.entity.get(Transform).setPosition(contentX, top + c.padding);\n\n const optionsTop = top + c.padding + headH;\n choices.forEach((choice, i) => {\n const rowY = optionsTop + i * lineH;\n const entity = this.scene!.spawn(\"dlg-bchoice\");\n entity.add(new Transform()).setPosition(contentX + 4, rowY);\n const comp = entity.add(new TextComponent(this.textOptions(choiceRowLabel(choice), c.choiceColor)));\n comp.text.visible = true;\n this.rows.push({\n entity,\n comp,\n x0: contentX,\n x1: contentX + innerW,\n y0: rowY,\n y1: rowY + lineH,\n disabled: choice.disabled ?? false,\n });\n });\n this.highlight(firstEnabledIndex(this.rows));\n this.applyHidden();\n // Commit the panel as the active bubble so an in-bubble avatar centres in it.\n this.layout.setChoicePanelSize({ width: panelWidth, height: panelH });\n }\n\n /** Show or hide the whole panel without clearing it — state-preserving. */\n setVisible(visible: boolean): void {\n this.hidden = !visible;\n this.applyHidden();\n }\n\n /** Render every piece (bg, prompt, rows, highlight) gated by the master.\n * Disabled rows still show (greyed); only the highlight bar is suppressed. */\n private applyHidden(): void {\n const shown = !this.hidden;\n if (this.bg) this.bg.gfx.graphics.visible = shown && this.rows.length > 0;\n if (this.highlightBar) {\n const onEnabled = this.selected >= 0 && !this.rows[this.selected]?.disabled;\n this.highlightBar.gfx.graphics.visible = shown && onEnabled;\n }\n if (this.prompt) this.prompt.comp.text.visible = shown;\n for (const row of this.rows) row.comp.text.visible = shown;\n }\n\n highlight(position: number): void {\n if (this.rows.length === 0) return;\n this.selected = clampSelection(position, this.rows.length);\n applyChoiceTint(this.rows, this.selected, this.cfg.choiceColor, this.cfg.choiceSelectedColor);\n this.drawHighlight();\n }\n\n /** {@link ChoicePresenter}: which option a *world* point falls in. */\n choiceAtPoint(x: number, y: number): number | undefined {\n for (let i = 0; i < this.rows.length; i++) {\n const r = this.rows[i]!;\n if (x >= r.x0 && x <= r.x1 && y >= r.y0 && y <= r.y1) return i;\n }\n return undefined;\n }\n\n clear(): void {\n for (const r of this.rows) r.entity.destroy();\n this.rows = [];\n this.prompt?.entity.destroy();\n this.prompt = undefined;\n this.selected = -1;\n this.bg?.gfx.draw((g) => g.clear());\n this.highlightBar?.gfx.draw((g) => g.clear());\n }\n\n dispose(): void {\n this.clear();\n this.bg?.entity.destroy();\n this.highlightBar?.entity.destroy();\n this.bg = undefined;\n this.highlightBar = undefined;\n }\n\n private drawPanel(\n left: number,\n top: number,\n width: number,\n h: number,\n tailX: number,\n bottom: number,\n ): void {\n const c = this.cfg;\n this.bg?.gfx.draw((g) => {\n g.clear();\n g.roundRect(left, top, width, h, c.cornerRadius)\n .fill({ color: c.frameColor, alpha: c.frameAlpha })\n .stroke({ color: c.borderColor, alpha: 1, width: 2 });\n g.poly([tailX - c.tail, bottom, tailX + c.tail, bottom, tailX, bottom + c.tail]).fill({\n color: c.frameColor,\n alpha: c.frameAlpha,\n });\n });\n }\n\n private drawHighlight(): void {\n const row = this.rows[this.selected];\n if (!this.highlightBar || !row) return;\n // A disabled row is never highlighted (nav skips it); clear any stale bar.\n if (row.disabled) {\n this.highlightBar.gfx.draw((g) => g.clear());\n return;\n }\n this.highlightBar.gfx.draw((g) => {\n g.clear();\n g.roundRect(row.x0, row.y0, row.x1 - row.x0, row.y1 - row.y0 - 1, 3).fill({\n color: this.cfg.highlightColor,\n alpha: 0.3,\n });\n });\n }\n\n private textOptions(text: string, color: number, wrapWidth?: number): TextComponentOptions {\n const opts = makeTextOptions(this.cfg, text, this.cfg.choiceSize, color, this.cfg.layer);\n if (wrapWidth != null) {\n opts.style.wordWrap = true;\n opts.style.wordWrapWidth = wrapWidth;\n }\n return opts;\n }\n}\n","/**\n * A Mass-Effect-style radial choice wheel — an alternative {@link ChoicePresenter}\n * that proves the choice seam is swappable without touching the Session. Options\n * are placed evenly around a centre hub; the Session's up/down nav still cycles\n * them, and pointer hover/click works via {@link ChoicePresenter.choiceAtPoint}.\n *\n * @experimental This presenter is an unpolished spike. Geometry, hit radius, and\n * styling are intentionally minimal; the API may change. Reach it via the\n * `./presenters` subpath; it is not part of the default factory bundles.\n */\n\nimport { Transform, type Entity, type Scene } from \"@yagejs/core\";\nimport { GraphicsComponent, TextComponent } from \"@yagejs/renderer\";\nimport type { PresentedChoice } from \"../core/session.js\";\nimport type { ChoicePresenter } from \"./DialogueUiAdapter.js\";\nimport { makeTextOptions, type FontConfig } from \"./textOptions.js\";\nimport { applyChoiceTint, clampSelection, firstEnabledIndex } from \"./choiceRow.js\";\n\nexport interface RadialChoiceConfig extends FontConfig {\n readonly center: { readonly x: number; readonly y: number };\n readonly radius: number;\n readonly choiceColor: number;\n readonly choiceSelectedColor: number;\n readonly hubColor: number;\n /** Choice label size (px) — matches the theme's `choiceSize`. */\n readonly choiceSize: number;\n readonly layerFrame: string;\n readonly layerText: string;\n}\n\ninterface Spoke {\n readonly entity: Entity;\n readonly comp: TextComponent;\n readonly x: number;\n readonly y: number;\n readonly disabled: boolean;\n}\n\n/** @experimental Unpolished radial choice wheel; see file header. */\nexport class RadialChoicePresenter implements ChoicePresenter {\n private scene?: Scene | undefined;\n private hub?: { entity: Entity; gfx: GraphicsComponent } | undefined;\n private spokes: Spoke[] = [];\n private selected = -1;\n /** Master visibility gate — state-preserving hide/show. */\n private hidden = false;\n\n onChoiceChosen?: (position: number) => void;\n\n constructor(private readonly cfg: RadialChoiceConfig) {}\n\n mount(scene: Scene): void {\n this.scene = scene;\n const hub = scene.spawn(\"dlg-radial-hub\");\n hub.add(new Transform()).setPosition(0, 0);\n this.hub = { entity: hub, gfx: hub.add(new GraphicsComponent({ layer: this.cfg.layerFrame })) };\n }\n\n present(choices: readonly PresentedChoice[]): void {\n this.clear();\n if (!this.scene) return;\n const { center: c, radius } = this.cfg;\n const n = choices.length;\n choices.forEach((choice, i) => {\n // Evenly spaced, starting at the top and going clockwise.\n const angle = -Math.PI / 2 + (i / n) * Math.PI * 2;\n const x = c.x + Math.cos(angle) * radius;\n const y = c.y + Math.sin(angle) * radius;\n const entity = this.scene!.spawn(\"dlg-radial\");\n entity.add(new Transform()).setPosition(x, y);\n const comp = entity.add(\n new TextComponent(\n makeTextOptions(\n this.cfg,\n choice.label,\n this.cfg.choiceSize,\n this.cfg.choiceColor,\n this.cfg.layerText,\n { x: 0.5, y: 0.5 },\n ),\n ),\n );\n comp.text.visible = true;\n this.spokes.push({ entity, comp, x, y, disabled: choice.disabled ?? false });\n });\n this.highlight(firstEnabledIndex(this.spokes));\n this.applyHidden();\n }\n\n highlight(position: number): void {\n if (this.spokes.length === 0) return;\n this.selected = clampSelection(position, this.spokes.length);\n applyChoiceTint(this.spokes, this.selected, this.cfg.choiceColor, this.cfg.choiceSelectedColor);\n // Radial-only: scale the selected spoke up.\n this.spokes.forEach((s, i) => {\n const scale = i === this.selected && !s.disabled ? 1.15 : 1;\n s.entity.get(Transform).setScale(scale, scale);\n });\n this.drawHub();\n }\n\n /** Show or hide the wheel without clearing it — state-preserving. */\n setVisible(visible: boolean): void {\n this.hidden = !visible;\n this.applyHidden();\n }\n\n private applyHidden(): void {\n for (const s of this.spokes) s.comp.text.visible = !this.hidden;\n if (this.hub) this.hub.gfx.graphics.visible = !this.hidden && this.spokes.length > 0;\n }\n\n /** {@link ChoicePresenter}: nearest spoke within a small radius. */\n choiceAtPoint(x: number, y: number): number | undefined {\n let best: number | undefined;\n let bestD = 22; // px hit radius\n this.spokes.forEach((s, i) => {\n const d = Math.hypot(s.x - x, s.y - y);\n if (d < bestD) {\n bestD = d;\n best = i;\n }\n });\n return best;\n }\n\n clear(): void {\n for (const s of this.spokes) s.entity.destroy();\n this.spokes = [];\n this.selected = -1;\n this.hub?.gfx.draw((g) => g.clear());\n }\n\n dispose(): void {\n this.clear();\n this.hub?.entity.destroy();\n this.hub = undefined;\n }\n\n private drawHub(): void {\n if (!this.hub) return;\n const { center: c } = this.cfg;\n const sel = this.spokes[this.selected];\n // No spoke line to a disabled row (selection never lands there anyway).\n const active = sel && !sel.disabled ? sel : undefined;\n this.hub.gfx.draw((g) => {\n g.clear();\n if (active) {\n g.moveTo(c.x, c.y).lineTo(active.x, active.y).stroke({ color: this.cfg.choiceSelectedColor, width: 2, alpha: 0.7 });\n }\n g.circle(c.x, c.y, 4).fill({ color: this.cfg.hubColor, alpha: 1 });\n });\n }\n}\n","/**\n * Box-vs-bubble routing for the three composite presenters. A route maps a\n * {@link PresentedLine} (or a choice's context, adapted to one) to `\"box\"` or\n * `\"bubble\"`. All three composites in a mixed bundle MUST consult the SAME\n * route, or a line could send its chrome to the bubble and its text to the box.\n *\n * The default policy is **speaker-aware**: a narrator goes to the box (no head\n * to float a bubble over), an explicit `view` hint wins for a real speaker, and\n * otherwise a speaker with a registered {@link DialogueActor} floats in a bubble\n * while one without falls to the box. Because the registry is scene-scoped, the\n * default is built as a {@link MountRoute} whose `hasActor` lookup is bound at\n * mount; `createMixedDialogue` shares one instance across the three composites\n * and exposes a `route` override for a custom policy (\"all of NPC X in bubbles\").\n */\n\nimport type { Scene } from \"@yagejs/core\";\nimport { actorRegistryFor } from \"../actor/index.js\";\nimport { EMPTY_PARSED } from \"../core/markup.js\";\nimport type { PresentedLine, ChoiceContext } from \"../core/session.js\";\n\n/** Decides which variant a line renders in. Reads `view`/`speaker`/`meta`; a\n * custom route can key off any of them (e.g. `meta.aside → bubble`). */\nexport type CompositeRoute = (line: PresentedLine | undefined) => \"box\" | \"bubble\";\n\n/**\n * A route paired with a scene-bind hook the composites call at `mount`. The\n * default route needs the scene to resolve registered actors; a caller-supplied\n * route needs nothing, so its {@link bind} is a no-op. The composites store ONE\n * of these (shared, for a mixed bundle) and route every line/choice through it.\n */\nexport interface MountRoute {\n readonly route: CompositeRoute;\n /** Bind the scene at mount (idempotent across the three composites). */\n bind(scene: Scene): void;\n}\n\n/**\n * The default route decision as a pure function of the line + an actor lookup\n * (testable without a scene). Precedence:\n * 1. **narrator** (no `speaker`) → **box** — the genre convention; a bubble\n * has no head to float over. A *positioned* narrator is the invisible-anchor\n * recipe (give it a speaker whose actor sits on an invisible entity).\n * 2. an explicit **`view`** hint wins for a real speaker (`\"bubble\"`/`\"box\"`) —\n * a `view:\"bubble\"` whose actor is missing still routes to the bubble path,\n * which renders at the fallback anchor rather than vanishing.\n * 3. otherwise a speaker with a **registered actor** → **bubble**; else **box**.\n */\nexport function routeWithActor(\n line: PresentedLine | undefined,\n hasActor: (speakerId: string) => boolean,\n): \"box\" | \"bubble\" {\n const speaker = line?.speaker;\n if (speaker === undefined) return \"box\";\n const view = line?.view;\n if (view === \"bubble\") return \"bubble\";\n if (view === \"box\") return \"box\";\n return hasActor(speaker.id) ? \"bubble\" : \"box\";\n}\n\n/**\n * Build the default {@link MountRoute}. `hasActor` resolves the speaker through\n * the scene's {@link ActorRegistry}, rebound at mount (before then it answers\n * \"no actor\", so a pre-mount route call still resolves via the narrator/`view`\n * rules).\n */\nexport function makeDefaultRoute(): MountRoute {\n let hasActor: (speakerId: string) => boolean = () => false;\n return {\n bind(scene: Scene): void {\n const registry = actorRegistryFor(scene);\n hasActor = (id) => registry.resolve(id) !== undefined;\n },\n route: (line) => routeWithActor(line, hasActor),\n };\n}\n\n/** Wrap a caller-supplied route as a {@link MountRoute} (no scene needed). */\nexport function fixedRoute(route: CompositeRoute): MountRoute {\n return { route, bind() {} };\n}\n\n/** A choice's routing inputs as a (partial) line — the route only reads\n * `view`/`speaker`/`meta`, all of which a {@link ChoiceContext} carries. */\nexport function choiceAsLine(context: ChoiceContext | undefined): PresentedLine {\n return {\n view: context?.view,\n speaker: context?.speaker,\n meta: context?.meta,\n text: context?.prompt ?? EMPTY_PARSED,\n speed: 1,\n };\n}\n\n/** Whether a presented line routes to the bubble under `route`. */\nexport function lineRoutesToBubble(\n route: CompositeRoute,\n line: PresentedLine | undefined,\n): boolean {\n return route(line) === \"bubble\";\n}\n\n/** Whether a choice (by its context) routes to the bubble under `route`. */\nexport function choiceRoutesToBubble(\n route: CompositeRoute,\n context: ChoiceContext | undefined,\n): boolean {\n return route(choiceAsLine(context)) === \"bubble\";\n}\n","/**\n * Routes each line to one of two text presenters by its `view` hint and whether\n * it has a speaker — e.g. a narrator's speakerless line types in the bottom box\n * while NPC `view:\"bubble\"` lines float over their heads, all in one\n * conversation. The inactive presenter is cleared so only one shows at a time.\n * Reveal state + skip/fast-forward proxy to whichever is active; both are ticked\n * each frame.\n */\n\nimport type { Scene } from \"@yagejs/core\";\nimport type { PresentedLine } from \"../core/session.js\";\nimport type { RevealBeat } from \"../core/LineReveal.js\";\nimport type { TextPresenter } from \"../chrome/DialogueUiAdapter.js\";\nimport { makeDefaultRoute, lineRoutesToBubble, type MountRoute } from \"./route.js\";\n\nexport class CompositeTextPresenter implements TextPresenter {\n private active?: TextPresenter | undefined;\n private revealListener?: (() => void) | undefined;\n private beatListener?: ((beat: RevealBeat) => void) | undefined;\n /** Master visibility gate from the Session's setVisible. */\n private visible = false;\n\n constructor(\n private readonly box: TextPresenter,\n private readonly bubble: TextPresenter,\n private readonly routing: MountRoute = makeDefaultRoute(),\n ) {\n // Each sub-view's reveal/beats forward to the Session's listeners ONLY when\n // that sub-view is the active one (the composite wrinkle): the inactive view\n // can't fire a stale reveal-completed or a marker/tick for a line it isn't\n // showing.\n this.box.setRevealListener(() => this.fireReveal(this.box));\n this.bubble.setRevealListener(() => this.fireReveal(this.bubble));\n this.box.setBeatListener((beat) => this.fireBeat(this.box, beat));\n this.bubble.setBeatListener((beat) => this.fireBeat(this.bubble, beat));\n }\n\n /** Register the Session's reveal-completed listener. */\n setRevealListener(listener: (() => void) | undefined): void {\n this.revealListener = listener;\n }\n\n /** Register the Session's reveal-beat listener (ticks + inline markers). */\n setBeatListener(listener: ((beat: RevealBeat) => void) | undefined): void {\n this.beatListener = listener;\n }\n\n private fireReveal(view: TextPresenter): void {\n if (this.active === view) this.revealListener?.();\n }\n\n private fireBeat(view: TextPresenter, beat: RevealBeat): void {\n if (this.active === view) this.beatListener?.(beat);\n }\n\n setDiagnostics(warn: (message: string) => void): void {\n this.box.setDiagnostics?.(warn);\n this.bubble.setDiagnostics?.(warn);\n }\n\n mount(scene: Scene): void {\n this.routing.bind(scene); // resolve the default route's actor lookup\n this.box.mount(scene);\n this.bubble.mount(scene);\n }\n\n present(line: PresentedLine): void {\n const target = lineRoutesToBubble(this.routing.route, line) ? this.bubble : this.box;\n const other = target === this.box ? this.bubble : this.box;\n other.clear();\n this.active = target;\n target.present(line);\n target.setVisible(this.visible); // reflect the master gate immediately\n }\n\n /** Show/hide the body text — forwarded to both views (the inactive one is\n * cleared, so its setVisible is a no-op); state-preserving on the active. */\n setVisible(visible: boolean): void {\n this.visible = visible;\n this.box.setVisible(visible);\n this.bubble.setVisible(visible);\n }\n\n completeReveal(): void {\n this.active?.completeReveal();\n }\n\n isRevealComplete(): boolean {\n return this.active ? this.active.isRevealComplete() : true;\n }\n\n isRevealing(): boolean {\n return this.active ? this.active.isRevealing() : false;\n }\n\n setSpeedMultiplier(multiplier: number): void {\n // Both, not just the active view: the inactive one would keep a stale\n // multiplier into its next line (both setters are trivially cheap).\n this.box.setSpeedMultiplier(multiplier);\n this.bubble.setSpeedMultiplier(multiplier);\n }\n\n update(dt: number): void {\n this.box.update(dt);\n this.bubble.update(dt);\n }\n\n clear(): void {\n this.box.clear();\n this.bubble.clear();\n this.active = undefined;\n }\n\n dispose(): void {\n this.box.dispose();\n this.bubble.dispose();\n }\n}\n","/**\n * Chrome counterpart to {@link CompositeTextPresenter}: shows the box frame for\n * box lines and the bubble for bubble lines, hiding the other. The Session\n * calls `setNameplate`/`setContinueVisible` *before* `present`, so those are\n * buffered and applied to whichever variant `present` then selects.\n *\n * Visibility is owned by the Session: it calls `setVisible(bool)`\n * after each transition, and this composite restores **only the active variant**\n * on show. `active` is therefore RETAINED across a hide (a cutscene\n * mid-bubble-line shows the bubble again, not an empty box).\n */\n\nimport type { Scene } from \"@yagejs/core\";\nimport type { PresentedLine } from \"../core/session.js\";\nimport type { ChromePresenter } from \"../chrome/DialogueUiAdapter.js\";\nimport { makeDefaultRoute, lineRoutesToBubble, type MountRoute } from \"./route.js\";\n\nexport class CompositeChrome implements ChromePresenter {\n private active?: ChromePresenter | undefined;\n private pendingName: { name?: string | undefined; color?: number | undefined } = {};\n private pendingContinue = false;\n /** Master gate from the Session's setVisible — composed with the active\n * variant's own content state. Hidden at mount. */\n private visible = false;\n\n constructor(\n private readonly box: ChromePresenter,\n private readonly bubble: ChromePresenter,\n private readonly routing: MountRoute = makeDefaultRoute(),\n ) {}\n\n mount(scene: Scene): void {\n this.routing.bind(scene); // resolve the default route's actor lookup\n this.box.mount(scene);\n this.bubble.mount(scene);\n this.box.setVisible(false);\n this.bubble.setVisible(false);\n }\n\n setDiagnostics(warn: (message: string) => void): void {\n this.box.setDiagnostics?.(warn);\n this.bubble.setDiagnostics?.(warn);\n }\n\n setNameplate(name: string | undefined, color?: number): void {\n // Buffer for whichever variant `present` selects, and forward to the active\n // one. `undefined` means \"no name\" — NOT a hide-all (that overload died).\n this.pendingName = { name, color };\n this.active?.setNameplate(name, color);\n }\n\n setContinueVisible(visible: boolean): void {\n // Remember the latest caret intent so a hide/show round-trip can re-apply it\n // to the restored variant (the buffered-caret path), and forward it now.\n this.pendingContinue = visible;\n this.active?.setContinueVisible(visible);\n }\n\n present(line: PresentedLine | undefined): void {\n if (line === undefined) {\n // \"No line\" — clear the active variant's content; the Session hides via\n // setVisible. Keep `active` so a later show restores the right variant.\n this.active?.present?.(undefined);\n return;\n }\n const target = lineRoutesToBubble(this.routing.route, line) ? this.bubble : this.box;\n const other = target === this.box ? this.bubble : this.box;\n other.setVisible(false);\n\n this.active = target;\n target.setNameplate(this.pendingName.name, this.pendingName.color);\n target.setContinueVisible(this.pendingContinue);\n target.present?.(line);\n // Reflect the current master gate immediately (the Session also calls\n // setVisible right after — idempotent).\n target.setVisible(this.visible);\n }\n\n /** Show/hide the chrome. On show, restore ONLY the active variant\n * and re-apply the buffered caret; the other stays hidden. On hide, hide both\n * but RETAIN `active` so the next show brings back the right one. */\n setVisible(visible: boolean): void {\n this.visible = visible;\n if (visible) {\n if (this.active) {\n const other = this.active === this.box ? this.bubble : this.box;\n other.setVisible(false);\n this.active.setContinueVisible(this.pendingContinue);\n this.active.setVisible(true);\n }\n } else {\n this.box.setVisible(false);\n this.bubble.setVisible(false);\n }\n }\n\n update(dt: number): void {\n this.box.update(dt);\n this.bubble.update(dt);\n }\n\n dispose(): void {\n this.box.dispose();\n this.bubble.dispose();\n }\n}\n","/**\n * Routes a choice list to the box list or a bubble panel by its\n * `context.view` — the choice counterpart to {@link CompositeTextPresenter} /\n * {@link CompositeChrome}. A box choice keeps the framed bottom list; a\n * `view:\"bubble\"` choice gets its own panel over the actor (so it never relies\n * on the box frame, which the composite chrome hides for bubble lines).\n */\n\nimport type { Scene } from \"@yagejs/core\";\nimport type { ChoiceContext, PresentedChoice } from \"../core/session.js\";\nimport type { ChoicePresenter } from \"../chrome/DialogueUiAdapter.js\";\nimport { choiceRoutesToBubble, makeDefaultRoute, type MountRoute } from \"./route.js\";\n\nexport class CompositeChoicePresenter implements ChoicePresenter {\n private active?: ChoicePresenter | undefined;\n /** Master visibility gate from the Session's setVisible. */\n private visible = false;\n\n onChoiceChosen?: (position: number) => void;\n\n constructor(\n private readonly box: ChoicePresenter,\n private readonly bubble: ChoicePresenter,\n private readonly routing: MountRoute = makeDefaultRoute(),\n ) {\n this.box.onChoiceChosen = (p) => this.onChoiceChosen?.(p);\n this.bubble.onChoiceChosen = (p) => this.onChoiceChosen?.(p);\n }\n\n /** The active list's pointer space (so the binding hit-tests correctly). */\n get pointerSpace(): \"screen\" | \"world\" {\n return this.active?.pointerSpace ?? \"screen\";\n }\n\n setDiagnostics(warn: (message: string) => void): void {\n this.box.setDiagnostics?.(warn);\n this.bubble.setDiagnostics?.(warn);\n }\n\n /** Routes to the variant this choice will use, so the Session knows whether\n * to suppress its chrome/body prompt before `present` picks the active one. */\n ownsPrompt(context?: ChoiceContext): boolean {\n const target = choiceRoutesToBubble(this.routing.route, context) ? this.bubble : this.box;\n return target.ownsPrompt?.(context) ?? false;\n }\n\n mount(scene: Scene): void {\n this.routing.bind(scene); // resolve the default route's actor lookup\n this.box.mount(scene);\n this.bubble.mount(scene);\n }\n\n present(choices: readonly PresentedChoice[], context?: ChoiceContext): void {\n // Disabled rows (and the choice `meta`) ride through untouched — the active\n // leaf presenter is the one that greys/skips them.\n const target = choiceRoutesToBubble(this.routing.route, context) ? this.bubble : this.box;\n const other = target === this.box ? this.bubble : this.box;\n other.clear();\n this.active = target;\n target.present(choices, context);\n target.setVisible(this.visible); // reflect the master gate immediately\n }\n\n /** Show/hide the choices — forwarded to both (the inactive one is\n * cleared, so its setVisible is a no-op); state-preserving on the active. */\n setVisible(visible: boolean): void {\n this.visible = visible;\n this.box.setVisible(visible);\n this.bubble.setVisible(visible);\n }\n\n highlight(position: number): void {\n this.active?.highlight(position);\n }\n\n choiceAtPoint(x: number, y: number): number | undefined {\n return this.active?.choiceAtPoint?.(x, y);\n }\n\n clear(): void {\n this.box.clear();\n this.bubble.clear();\n this.active = undefined;\n }\n\n dispose(): void {\n this.box.dispose();\n this.bubble.dispose();\n }\n}\n","/**\n * Routes the avatar to a box-side or bubble-side presenter per line, the avatar\n * counterpart to {@link CompositeChrome} / {@link CompositeTextPresenter} /\n * {@link CompositeChoicePresenter}. It consults the SAME {@link MountRoute} the\n * other composites do, so a line's avatar lands on the same side as its chrome\n * and text. The non-routed presenter is cleared (`present(undefined)`), so only\n * one portrait shows at a time. The speaker-def + visibility verbs forward to\n * both (they are cheap and idempotent).\n */\n\nimport type { Scene } from \"@yagejs/core\";\nimport type { AvatarPresenter } from \"../avatar/AvatarPresenter.js\";\nimport type { PresentedLine } from \"../core/session.js\";\nimport type { MarkerToken, SpeakerDef } from \"../core/types.js\";\nimport { lineRoutesToBubble, type MountRoute } from \"./route.js\";\n\nexport class CompositeAvatarPresenter implements AvatarPresenter {\n constructor(\n private readonly box: AvatarPresenter,\n private readonly bubble: AvatarPresenter,\n private readonly routing: MountRoute,\n ) {}\n\n mount(scene: Scene): void {\n this.routing.bind(scene);\n this.box.mount(scene);\n this.bubble.mount(scene);\n }\n\n setSpeaker(speaker: SpeakerDef | undefined): void {\n this.box.setSpeaker(speaker);\n this.bubble.setSpeaker(speaker);\n }\n\n setExpression(expression: string | undefined): void {\n this.box.setExpression(expression);\n this.bubble.setExpression(expression);\n }\n\n /** Forward an inline reveal marker to both (cheap + idempotent, like\n * setExpression — the inactive side's setExpression no-ops with no speaker). */\n marker(marker: MarkerToken): void {\n this.box.marker?.(marker);\n this.bubble.marker?.(marker);\n }\n\n setSpeaking(speaking: boolean): void {\n this.box.setSpeaking(speaking);\n this.bubble.setSpeaking(speaking);\n }\n\n present(line: PresentedLine | undefined): void {\n if (line === undefined) {\n this.box.present?.(undefined);\n this.bubble.present?.(undefined);\n return;\n }\n const toBubble = lineRoutesToBubble(this.routing.route, line);\n const active = toBubble ? this.bubble : this.box;\n const other = toBubble ? this.box : this.bubble;\n other.present?.(undefined); // clear the variant this line doesn't use\n active.present?.(line);\n }\n\n setVisible(visible: boolean): void {\n this.box.setVisible?.(visible);\n this.bubble.setVisible?.(visible);\n }\n\n update(dt: number): void {\n this.box.update(dt);\n this.bubble.update(dt);\n }\n\n dispose(): void {\n this.box.dispose();\n this.bubble.dispose();\n }\n}\n","/**\n * The avatar layer is deliberately decoupled from the box: the controller only\n * ever talks to this interface, so a speaker can be shown as a portrait beside\n * the box, as a full figure already standing in the scene, or not at all —\n * without the dialogue runtime knowing which. Implementations live alongside\n * (PortraitPresenter, SceneFigurePresenter); swap freely or compose your own.\n */\n\nimport type { Scene } from \"@yagejs/core\";\nimport type { AvatarChannel } from \"../core/session.js\";\nimport type { MarkerToken } from \"../core/types.js\";\n\n/**\n * The adapter-level avatar presenter: the headless {@link AvatarChannel}\n * (setSpeaker / setExpression / setSpeaking / marker / update) plus the YAGE\n * lifecycle the host drives (mount / dispose).\n */\nexport interface AvatarPresenter extends AvatarChannel {\n /** Called once when the controller mounts. */\n mount(scene: Scene): void;\n dispose(): void;\n}\n\n/**\n * The bundled avatar presenters' shared `[expression=…/]` convention, in one\n * place: an inline reveal marker named `expression` drives the avatar's own\n * `setExpression` (the self-named prop is the new face; absent → reset to the\n * default). The Session name-matches NO marker — each presenter decides what it\n * owns, so any other marker name is ignored here.\n */\nexport function applyExpressionMarker(\n avatar: Pick<AvatarChannel, \"setExpression\">,\n marker: MarkerToken,\n): void {\n if (marker.name === \"expression\") avatar.setExpression(marker.props[\"expression\"]);\n}\n\n/** No-op presenter — the default when a script has no avatars. */\nexport class NullAvatarPresenter implements AvatarPresenter {\n mount(): void {}\n setSpeaker(): void {}\n setExpression(): void {}\n setSpeaking(): void {}\n update(): void {}\n dispose(): void {}\n}\n","/**\n * Portrait avatar: a sprite that sits beside the box on the left or right.\n * Expression variants are just different textures (from the speaker's\n * `avatar.expressions` map); \"speaking\" adds a gentle talk bob. Textures must\n * be preloaded by the host scene — the presenter only resolves handles.\n */\n\nimport { Transform, type Entity, type Scene } from \"@yagejs/core\";\nimport { SpriteComponent, texture, type TextureHandle } from \"@yagejs/renderer\";\nimport type { AvatarRef, MarkerToken, SpeakerDef } from \"../core/types.js\";\nimport { applyExpressionMarker, type AvatarPresenter } from \"./AvatarPresenter.js\";\n\nexport interface PortraitPresenterConfig {\n readonly layer: string;\n /** Centre X for a left-side portrait (screen px). */\n readonly leftX: number;\n /** Centre X for a right-side portrait (screen px). */\n readonly rightX: number;\n /** Centre Y (screen px). */\n readonly y: number;\n /** Uniform sprite scale. */\n readonly scale: number;\n}\n\nexport class PortraitPresenter implements AvatarPresenter {\n // Explicit `| undefined` (not `?`) so reassigning `undefined` in dispose() /\n // setSpeaker() is legal under the repo's exactOptionalPropertyTypes.\n private scene: Scene | undefined;\n private entity: Entity | undefined;\n private sprite: SpriteComponent | undefined;\n private transform: Transform | undefined;\n private current: AvatarRef | undefined;\n private speaking = false;\n private bobMs = 0;\n private baseX = 0;\n private baseY = 0;\n /** Host-hidden gate — a cutscene hides the portrait with the rest of the\n * UI. Composes with \"is a portrait speaker active\": shown = current && !hidden. */\n private hidden = false;\n private readonly handles = new Map<string, TextureHandle>();\n\n constructor(private readonly cfg: PortraitPresenterConfig) {}\n\n mount(scene: Scene): void {\n this.scene = scene;\n }\n\n setSpeaker(speaker: SpeakerDef | undefined): void {\n const av = speaker?.avatar;\n if (!av || av.kind !== \"portrait\") {\n this.hide();\n this.current = undefined;\n return;\n }\n this.current = av;\n this.ensureSprite(av.ref);\n this.baseX = av.side === \"right\" ? this.cfg.rightX : this.cfg.leftX;\n this.baseY = this.cfg.y;\n this.transform?.setPosition(this.baseX, this.baseY);\n this.applyTexture(av.ref);\n this.applyVisibility();\n }\n\n /** Host-hidden gate — hide the portrait during a cutscene, restore on\n * show. Composes with the active-speaker state, so showing again only\n * re-reveals the portrait if a portrait speaker is still current. */\n setVisible(visible: boolean): void {\n this.hidden = !visible;\n this.applyVisibility();\n }\n\n private applyVisibility(): void {\n if (this.sprite) {\n this.sprite.sprite.visible = this.current !== undefined && !this.hidden;\n }\n }\n\n setExpression(expression: string | undefined): void {\n if (!this.current) return;\n const variant = expression ? this.current.expressions?.[expression] : undefined;\n this.applyTexture(variant ?? this.current.ref);\n }\n\n /** Interpret a mid-line `[expression=…/]` reveal marker as a face swap (the\n * Session name-matches nothing — the presenter owns the convention). */\n marker(marker: MarkerToken): void {\n applyExpressionMarker(this, marker);\n }\n\n setSpeaking(speaking: boolean): void {\n this.speaking = speaking;\n if (!speaking) {\n this.bobMs = 0;\n this.transform?.setPosition(this.baseX, this.baseY);\n }\n }\n\n update(dt: number): void {\n if (!this.speaking || !this.transform) return;\n this.bobMs += dt;\n this.transform.setPosition(this.baseX, this.baseY + Math.sin(this.bobMs / 110) * 1.5);\n }\n\n dispose(): void {\n this.entity?.destroy();\n this.entity = undefined;\n this.sprite = undefined;\n this.transform = undefined;\n }\n\n private ensureSprite(initialPath: string): void {\n if (this.sprite || !this.scene) return;\n const entity = this.scene.spawn(\"dlg-portrait\");\n this.transform = entity.add(new Transform());\n this.transform.setScale(this.cfg.scale, this.cfg.scale);\n this.sprite = entity.add(\n new SpriteComponent({\n texture: this.handle(initialPath),\n layer: this.cfg.layer,\n anchor: { x: 0.5, y: 0.5 },\n visible: false,\n }),\n );\n this.entity = entity;\n }\n\n private applyTexture(path: string): void {\n this.sprite?.setTexture(this.handle(path));\n }\n\n private handle(path: string): TextureHandle {\n let h = this.handles.get(path);\n if (!h) {\n h = texture(path);\n this.handles.set(path, h);\n }\n return h;\n }\n\n private hide(): void {\n if (this.sprite) this.sprite.sprite.visible = false;\n }\n}\n","/**\n * Scene-figure avatar: instead of a portrait, the \"avatar\" is an entity that\n * already exists in the world (an NPC standing in the shop, say). The speaker's\n * `avatar.ref` is that entity's name. This presenter is the communication seam\n * the design calls for — it doesn't know about your character system, so it\n * takes callbacks to translate expression/speaking into whatever the figure\n * supports (swap an AnimatedSprite clip, tint, toggle a talk loop). Out of the\n * box it does a subtle talk bob on the figure's Transform.\n */\n\nimport { Transform, type Entity, type Scene } from \"@yagejs/core\";\nimport type { MarkerToken, SpeakerDef } from \"../core/types.js\";\nimport { actorRegistryFor, type DialogueActor } from \"../actor/index.js\";\nimport { applyExpressionMarker, type AvatarPresenter } from \"./AvatarPresenter.js\";\n\nexport interface SceneFigurePresenterConfig {\n /** Map a script expression id onto your character system. */\n readonly onExpression?: (figure: Entity, expression: string | undefined) => void;\n /** Toggle a talk animation / mouth flap. */\n readonly onSpeaking?: (figure: Entity, speaking: boolean) => void;\n /** Apply the built-in talk bob (default true). */\n readonly bob?: boolean;\n}\n\nexport class SceneFigurePresenter implements AvatarPresenter {\n // Explicit `| undefined` (not `?`) so reassigning `undefined` in dispose() /\n // setSpeaker() is legal under the repo's exactOptionalPropertyTypes.\n private scene: Scene | undefined;\n private figure: Entity | undefined;\n private actor: DialogueActor | undefined;\n private transform: Transform | undefined;\n private speaking = false;\n private bobMs = 0;\n /** Bob displacement currently applied to the figure's Transform. The bob is\n * a *relative* offset (delta-translated each frame), so external movement —\n * an NPC walking mid-line — is preserved instead of being pinned back to a\n * position captured at setSpeaker time. */\n private bobOffset = 0;\n\n constructor(private readonly cfg: SceneFigurePresenterConfig = {}) {}\n\n mount(scene: Scene): void {\n this.scene = scene;\n }\n\n setSpeaker(speaker: SpeakerDef | undefined): void {\n this.releaseBob();\n const av = speaker?.avatar;\n if (!av || av.kind !== \"scene\" || !this.scene) {\n this.figure = undefined;\n this.actor = undefined;\n this.transform = undefined;\n return;\n }\n // Prefer a registered DialogueActor for the speaker; fall back to looking\n // the entity up by name (the script's `avatar.ref`).\n this.actor = actorRegistryFor(this.scene).resolve(speaker?.id);\n this.figure = this.actor?.entity ?? this.scene.findEntity(av.ref);\n this.transform = this.figure?.tryGet(Transform);\n }\n\n setExpression(expression: string | undefined): void {\n if (this.actor) this.actor.setExpression(expression);\n else if (this.figure) this.cfg.onExpression?.(this.figure, expression);\n }\n\n /** Mid-line `[expression=…/]` reveal marker → the figure's own expression\n * (actor or the `onExpression` callback). The Session name-matches nothing. */\n marker(marker: MarkerToken): void {\n applyExpressionMarker(this, marker);\n }\n\n setSpeaking(speaking: boolean): void {\n this.speaking = speaking;\n if (this.actor) this.actor.setSpeaking(speaking);\n else if (this.figure) this.cfg.onSpeaking?.(this.figure, speaking);\n if (!speaking) this.releaseBob();\n }\n\n update(dt: number): void {\n if (!this.speaking || !this.transform || this.cfg.bob === false) return;\n this.bobMs += dt;\n const next = Math.sin(this.bobMs / 130) * 1.2;\n // Apply only the delta on top of wherever the figure is NOW, so movement\n // systems (walking NPCs, knockback…) keep full ownership of the position.\n this.transform.translate(0, next - this.bobOffset);\n this.bobOffset = next;\n }\n\n dispose(): void {\n this.releaseBob();\n this.figure = undefined;\n this.transform = undefined;\n }\n\n /** Remove only the residual bob displacement — never teleport to a captured\n * base, which would undo legitimate movement since speaking began. */\n private releaseBob(): void {\n if (this.transform && this.bobOffset !== 0) {\n this.transform.translate(0, -this.bobOffset);\n }\n this.bobOffset = 0;\n this.bobMs = 0;\n }\n}\n","/**\n * InBoxAvatarPresenter — the reference **line-driven, reflowing in-box avatar**.\n * It is built ONLY from the documented presenter contract:\n *\n * - {@link AvatarChannel.present} gives it the line, so it reads `meta.portrait`\n * (texture key), `meta.side` (`left`/`right`, default left), and `meta.presence`\n * (set `false` to speak from off-screen — portrait hidden, no inset);\n * - {@link BoxLayout.setInset} reserves a column, so the box body text **reflows**\n * around the portrait, and {@link BoxLayout.frameRect} places the sprite.\n *\n * It needs NO addon internals — that's the point: it doubles as the proof the\n * contract is sufficient to write a custom presenter (if building it ever needed\n * an internal, the contract — not the presenter — would be wrong). It is\n * opt-in: wire it through `createBoxDialogue(theme, { avatar })`, which hands\n * it the box's shared layout owner. With no avatar wired (or no `meta.portrait`),\n * behavior is unchanged.\n *\n * Portrait textures must be **preloaded** by the host (the presenter only\n * resolves `meta.portrait` to a handle), like `PortraitPresenter`.\n */\n\nimport { Transform, type Entity, type Scene } from \"@yagejs/core\";\nimport {\n GraphicsComponent,\n SpriteComponent,\n texture,\n type TextureHandle,\n} from \"@yagejs/renderer\";\nimport { applyExpressionMarker, type AvatarPresenter } from \"./AvatarPresenter.js\";\nimport type { PresentedLine } from \"../core/session.js\";\nimport type { MarkerToken } from \"../core/types.js\";\nimport type { BoxLayout } from \"../render/BoxLayout.js\";\n\nexport interface InBoxAvatarConfig {\n /** Render layer (screen-space) — e.g. `DIALOGUE_LAYER_AVATAR`, which sits\n * between the frame and the text so the portrait tucks behind the box edge. */\n readonly layer: string;\n /** Width (px) of the reserved avatar column; the body text reflows past it. */\n readonly width: number;\n /** Gap (px) between the avatar column and the reflowed text. Default 8. */\n readonly gap?: number;\n /** Uniform sprite scale (textures must be preloaded by the host). Default 1. */\n readonly scale?: number;\n /** Optional rounded-rect panel drawn behind the portrait (a framed look),\n * sized to the {@link width} column. Omit for a bare sprite. */\n readonly background?: {\n readonly color: number;\n readonly alpha?: number;\n /** Corner radius (px). Default 8. */\n readonly radius?: number;\n };\n /** Vertical alignment in the box: `top` (level with the body text) or\n * `center` (default — centred in the frame, so it sinks in a grown choice box). */\n readonly align?: \"top\" | \"center\";\n}\n\n/** Distinct inset key per instance so two in-box avatars can coexist. */\nlet nextId = 0;\n\nexport class InBoxAvatarPresenter implements AvatarPresenter {\n private readonly insetKey = `avatar:${nextId++}`;\n // Explicit `| undefined` (not `?`) so reassigning `undefined` is legal under\n // the repo's exactOptionalPropertyTypes.\n private scene: Scene | undefined;\n private entity: Entity | undefined;\n private sprite: SpriteComponent | undefined;\n private transform: Transform | undefined;\n /** Optional background panel (behind the portrait), its own entity so it draws\n * under the sprite on the same layer. */\n private bgEntity: Entity | undefined;\n private bg: GraphicsComponent | undefined;\n private bgTransform: Transform | undefined;\n private side: \"left\" | \"right\" = \"left\";\n /** A portrait is up for the current line (from `meta.portrait` + presence). */\n private shown = false;\n /** Host-hidden gate (a cutscene hides the avatar with the rest of the UI). */\n private hidden = false;\n private readonly handles = new Map<string, TextureHandle>();\n\n constructor(\n private readonly layout: BoxLayout,\n private readonly cfg: InBoxAvatarConfig,\n ) {\n // Reposition whenever the box frame commits or grows. The session calls\n // avatar.present() BEFORE the chrome commits this line's frame (and a choice\n // grows it later still), so place() in present() alone would read a stale\n // rect — this follows every commit, like DialogueChrome's applyGeometry.\n this.layout.onChange(() => this.place());\n }\n\n mount(scene: Scene): void {\n this.scene = scene;\n }\n\n // Image/side/presence are line-driven via `present`, not the speaker def — so\n // setSpeaker / setExpression / setSpeaking are intentionally inert here.\n setSpeaker(): void {}\n setExpression(): void {}\n setSpeaking(): void {}\n\n /** Routes a mid-line `[expression=…/]` marker to its own setExpression (inert\n * here — this avatar is portrait-by-`meta`, not expression-mapped — so it's\n * the uniform contract, not a visible face swap). */\n marker(marker: MarkerToken): void {\n applyExpressionMarker(this, marker);\n }\n\n /** Read the line's `meta` to show/hide the portrait and reserve (or clear) the\n * text-reflow inset. Called before the body text presents, so the text wraps\n * to the narrowed region. */\n present(line: PresentedLine | undefined): void {\n const meta = line?.meta;\n const portrait = typeof meta?.[\"portrait\"] === \"string\" ? (meta[\"portrait\"] as string) : undefined;\n const visible = portrait !== undefined && meta?.[\"presence\"] !== false;\n this.side = meta?.[\"side\"] === \"right\" ? \"right\" : \"left\";\n if (visible && portrait !== undefined) {\n this.ensureSprite(portrait);\n this.applyTexture(portrait);\n this.shown = true;\n // Reserve the column (+ gap) so the body text reflows past the portrait.\n this.layout.setInset(this.insetKey, { side: this.side, width: this.cfg.width + (this.cfg.gap ?? 8) });\n } else {\n this.shown = false;\n this.layout.setInset(this.insetKey, undefined); // text reclaims the full width\n }\n this.place();\n this.applyVisibility();\n }\n\n /** Host-hidden gate — hide with a cutscene, restore on show (only if a\n * portrait is still current). */\n setVisible(visible: boolean): void {\n this.hidden = !visible;\n this.applyVisibility();\n }\n\n update(): void {}\n\n dispose(): void {\n this.layout.setInset(this.insetKey, undefined);\n this.entity?.destroy();\n this.bgEntity?.destroy();\n this.entity = undefined;\n this.bgEntity = undefined;\n this.sprite = undefined;\n this.bg = undefined;\n this.transform = undefined;\n this.bgTransform = undefined;\n }\n\n /** Centre the portrait (+ its panel) in its reserved column, inset by the box\n * padding so it sits inside the border like the text — at the frame's current\n * rect, so it follows `meta.position` and a grown choice panel. */\n private place(): void {\n if (!this.transform) return;\n const frame = this.layout.frameRect();\n const pad = this.layout.padding();\n const half = this.cfg.width / 2;\n const x =\n this.side === \"left\"\n ? frame.x + pad + half\n : frame.x + frame.width - pad - half;\n // top: align the portrait's top with the body text top (below the nameplate);\n // center: centre it in the frame (default).\n const y =\n this.cfg.align === \"top\"\n ? this.layout.textRegion().y + half\n : frame.y + frame.height / 2;\n this.transform.setPosition(x, y);\n this.bgTransform?.setPosition(x, y);\n }\n\n private applyVisibility(): void {\n const shown = this.shown && !this.hidden;\n if (this.sprite) this.sprite.sprite.visible = shown;\n if (this.bg) this.bg.graphics.visible = shown;\n }\n\n private ensureSprite(initialKey: string): void {\n if (this.sprite || !this.scene) return;\n // Background panel first (same layer), so the portrait spawned next draws\n // on top of it.\n const bgCfg = this.cfg.background;\n if (bgCfg) {\n const bgEntity = this.scene.spawn(\"dlg-inbox-avatar-bg\");\n this.bgTransform = bgEntity.add(new Transform());\n const w = this.cfg.width;\n const bg = bgEntity.add(new GraphicsComponent({ layer: this.cfg.layer }));\n bg.draw((g) =>\n g\n .roundRect(-w / 2, -w / 2, w, w, bgCfg.radius ?? 8)\n .fill({ color: bgCfg.color, alpha: bgCfg.alpha ?? 1 }),\n );\n bg.graphics.visible = false;\n this.bg = bg;\n this.bgEntity = bgEntity;\n }\n const entity = this.scene.spawn(\"dlg-inbox-avatar\");\n this.transform = entity.add(new Transform());\n const scale = this.cfg.scale ?? 1;\n this.transform.setScale(scale, scale);\n this.sprite = entity.add(\n new SpriteComponent({\n texture: this.handle(initialKey),\n layer: this.cfg.layer,\n anchor: { x: 0.5, y: 0.5 },\n visible: false,\n }),\n );\n this.entity = entity;\n }\n\n private applyTexture(key: string): void {\n this.sprite?.setTexture(this.handle(key));\n }\n\n private handle(key: string): TextureHandle {\n let h = this.handles.get(key);\n if (!h) {\n h = texture(key);\n this.handles.set(key, h);\n }\n return h;\n }\n}\n","/**\n * BubbleAvatarPresenter — the reference **line-driven portrait INSIDE the speech\n * bubble**, the bubble counterpart to {@link InBoxAvatarPresenter}. Built only\n * from the documented contract: {@link AvatarChannel.present} gives it the line\n * (so it reads `meta.portrait` / `meta.side` / `meta.presence`), and it reserves\n * a portrait column on the shared {@link BubbleLayout} (`setPortraitInset`) — so\n * the bubble **grows** to contain the portrait and its body text **reflows** past\n * it, and the whole thing follows the speaker's actor.\n *\n * Portrait textures must be **preloaded** by the host. Wire it through\n * `createMixedDialogue(theme, { avatar: { bubble } })`.\n */\n\nimport { Transform, type Entity, type Scene } from \"@yagejs/core\";\nimport {\n GraphicsComponent,\n SpriteComponent,\n texture,\n type TextureHandle,\n} from \"@yagejs/renderer\";\nimport { applyExpressionMarker, type AvatarPresenter } from \"./AvatarPresenter.js\";\nimport type { PresentedLine } from \"../core/session.js\";\nimport type { MarkerToken } from \"../core/types.js\";\nimport type { BubbleLayout } from \"../render/BubbleLayout.js\";\n\nexport interface BubbleAvatarConfig {\n /** World-space render layer (same as the bubble). */\n readonly layer: string;\n /** Portrait column size (px) reserved beside the bubble. */\n readonly size: number;\n /** Gap (px) between the portrait and the bubble edge. Default 8. */\n readonly gap?: number;\n /** Uniform sprite scale. Default 1. */\n readonly scale?: number;\n /** Optional rounded-rect panel behind the portrait. */\n readonly background?: {\n readonly color: number;\n readonly alpha?: number;\n readonly radius?: number;\n };\n /** Vertical alignment in the bubble: `top` (level with the body text) or\n * `center` (default). */\n readonly align?: \"top\" | \"center\";\n}\n\nexport class BubbleAvatarPresenter implements AvatarPresenter {\n // Explicit `| undefined` (not `?`) for exactOptionalPropertyTypes reassignment.\n private scene: Scene | undefined;\n private entity: Entity | undefined;\n private sprite: SpriteComponent | undefined;\n private transform: Transform | undefined;\n private bgEntity: Entity | undefined;\n private bg: GraphicsComponent | undefined;\n private bgTransform: Transform | undefined;\n private side: \"left\" | \"right\" = \"left\";\n private speakerId: string | undefined;\n private shown = false;\n private hidden = false;\n private readonly handles = new Map<string, TextureHandle>();\n\n constructor(\n private readonly layout: BubbleLayout,\n private readonly cfg: BubbleAvatarConfig,\n ) {\n // Follow the active bubble content rect — a say bubble (sized after this\n // avatar presents) or a choice panel (committed later still). Both notify.\n this.layout.onChange(() => this.follow());\n }\n\n mount(scene: Scene): void {\n this.scene = scene;\n }\n\n // Line-driven (via present), so the speaker-def hooks are inert here.\n setSpeaker(): void {}\n setExpression(): void {}\n setSpeaking(): void {}\n\n /** Uniform marker contract: routes `[expression=…/]` to its own setExpression\n * (inert here — this avatar is portrait-by-`meta`). */\n marker(marker: MarkerToken): void {\n applyExpressionMarker(this, marker);\n }\n\n present(line: PresentedLine | undefined): void {\n const meta = line?.meta;\n const portrait = typeof meta?.[\"portrait\"] === \"string\" ? (meta[\"portrait\"] as string) : undefined;\n const visible = portrait !== undefined && meta?.[\"presence\"] !== false;\n this.side = meta?.[\"side\"] === \"right\" ? \"right\" : \"left\";\n this.speakerId = line?.speaker?.id;\n if (visible && portrait !== undefined && line) {\n // Reserve the column BEFORE the chrome/text/choices size the bubble, so it\n // grows to contain the portrait and the text/rows reflow past it.\n const gap = this.cfg.gap ?? 8;\n this.layout.setPortraitInset({ side: this.side, width: this.cfg.size + gap, height: this.cfg.size });\n this.ensureSprite(portrait);\n this.applyTexture(portrait);\n this.shown = true;\n } else {\n this.layout.setPortraitInset(undefined); // bubble reclaims its full width\n this.shown = false;\n }\n this.follow(); // re-runs via onChange once the bubble/panel size is committed\n this.applyVisibility();\n }\n\n setVisible(visible: boolean): void {\n this.hidden = !visible;\n this.applyVisibility();\n }\n\n update(): void {\n this.follow(); // the bubble follows a moving actor; the portrait tracks it\n }\n\n dispose(): void {\n this.entity?.destroy();\n this.bgEntity?.destroy();\n this.entity = undefined;\n this.bgEntity = undefined;\n this.sprite = undefined;\n this.bg = undefined;\n this.transform = undefined;\n this.bgTransform = undefined;\n }\n\n /** Place the portrait in its reserved column INSIDE the active bubble (say\n * bubble or choice panel), vertically centred on the body, tracking the\n * speaker's (moving) anchor. */\n private follow(): void {\n const size = this.layout.activeSize();\n if (!this.transform || !this.scene || !size || !this.shown) return;\n const a = this.layout.anchorFor(this.scene, this.speakerId);\n const pad = this.layout.padding;\n const half = this.cfg.size / 2;\n const x =\n this.side === \"left\"\n ? a.x - size.width / 2 + pad + half\n : a.x + size.width / 2 - pad - half;\n // Bubble body spans [anchor.y - offsetY - height, anchor.y - offsetY].\n const bodyTop = a.y - this.layout.offsetY - size.height;\n const y =\n this.cfg.align === \"top\"\n ? bodyTop + pad + half // level with the body text top\n : a.y - this.layout.offsetY - size.height / 2; // centre (default)\n this.transform.setPosition(x, y);\n this.bgTransform?.setPosition(x, y);\n }\n\n private applyVisibility(): void {\n const shown = this.shown && !this.hidden;\n if (this.sprite) this.sprite.sprite.visible = shown;\n if (this.bg) this.bg.graphics.visible = shown;\n }\n\n private ensureSprite(initialKey: string): void {\n if (this.sprite || !this.scene) return;\n const bgCfg = this.cfg.background;\n if (bgCfg) {\n const bgEntity = this.scene.spawn(\"dlg-bubble-avatar-bg\");\n this.bgTransform = bgEntity.add(new Transform());\n const w = this.cfg.size;\n const bg = bgEntity.add(new GraphicsComponent({ layer: this.cfg.layer }));\n bg.draw((g) =>\n g\n .roundRect(-w / 2, -w / 2, w, w, bgCfg.radius ?? 8)\n .fill({ color: bgCfg.color, alpha: bgCfg.alpha ?? 1 }),\n );\n bg.graphics.visible = false;\n this.bg = bg;\n this.bgEntity = bgEntity;\n }\n const entity = this.scene.spawn(\"dlg-bubble-avatar\");\n this.transform = entity.add(new Transform());\n const scale = this.cfg.scale ?? 1;\n this.transform.setScale(scale, scale);\n this.sprite = entity.add(\n new SpriteComponent({\n texture: this.handle(initialKey),\n layer: this.cfg.layer,\n anchor: { x: 0.5, y: 0.5 },\n visible: false,\n }),\n );\n this.entity = entity;\n }\n\n private applyTexture(key: string): void {\n this.sprite?.setTexture(this.handle(key));\n }\n\n private handle(key: string): TextureHandle {\n let h = this.handles.get(key);\n if (!h) {\n h = texture(key);\n this.handles.set(key, h);\n }\n return h;\n }\n}\n","import type { DialogueTheme } from \"./theme.js\";\nimport { DIALOGUE_LAYER_FRAME, DIALOGUE_LAYER_TEXT } from \"../render/layers.js\";\n\n/**\n * defaultTheme — a zero-config, zero-asset {@link DialogueTheme}.\n *\n * Renders entirely with Graphics chrome (rounded rectangles + strokes) and\n * canvas text (SplitText/Text). No bitmap fonts, no textures, no bundled\n * assets — so `createBoxDialogue()` / `createBubbleDialogue(undefined, opts)`\n * work with no caller-supplied theme.\n *\n * Returns a fresh object each call so callers can spread-and-tweak without\n * mutating a shared singleton:\n *\n * ```ts\n * const theme = { ...defaultTheme(), textColor: 0xff0000 };\n * ```\n *\n * The `box` is viewport-relative (margins + height), so it's a full-width bottom\n * bar at ANY virtual resolution with no override. Bitmap fonts (`bitmapFont`) and\n * textured nine-slice chrome (the `textured` field) are OPT-IN re-theming paths,\n * intentionally absent here.\n */\nexport function defaultTheme(): DialogueTheme {\n return {\n // Viewport-relative: a full-width bottom bar resolved against the renderer's\n // design size at mount, so this works at any resolution with no override.\n box: { marginX: 32, marginY: 24, height: 160 },\n padding: 16,\n\n frameColor: 0x1a1a2e,\n frameAlpha: 0.92,\n borderColor: 0x4a4a8a,\n cornerRadius: 8,\n\n nameColor: 0xffd866,\n nameSize: 16,\n indicatorColor: 0xffffff,\n\n textSize: 18,\n lineHeight: 24,\n textColor: 0xf0f0f0,\n charsPerSec: 45,\n\n choiceSize: 16,\n choiceColor: 0xaaaaaa,\n choiceSelectedColor: 0xffffff,\n highlightColor: 0x4a4a8a,\n\n // No bitmapFont → canvas SplitText/Text path (zero assets).\n fontFamily: \"sans-serif\",\n\n layerFrame: DIALOGUE_LAYER_FRAME,\n layerText: DIALOGUE_LAYER_TEXT,\n };\n}\n","/**\n * `themeFonts(theme)` lifts a theme's font triplet into the shared\n * {@link FontConfig} shape every presenter config extends — one block instead\n * of the same literal cloned into each factory (which had already started to\n * drift before this was extracted).\n */\n\nimport type { FontConfig } from \"../chrome/textOptions.js\";\nimport type { DialogueTheme } from \"./theme.js\";\n\n/** The theme's font triplet, shaped for the presenter configs. */\nexport function themeFonts(theme: DialogueTheme): FontConfig {\n return {\n bitmapFont: theme.bitmapFont,\n fontFamily: theme.fontFamily,\n resolution: theme.resolution,\n };\n}\n","/**\n * `createBoxDialogue(theme)` is the easy on-ramp: hand it one {@link DialogueTheme}\n * and get back the wired presenter bundle for a classic bottom-of-screen box\n * (frame + nameplate + caret, a typewriter body, a vertical choice list). Spread\n * it into a controller and override any one piece:\n *\n * new DialogueController({ ...createBoxDialogue(theme), avatar, storage });\n *\n * It only assembles configs + presenters from the theme — no scene, no input —\n * so the host stays in charge of lifecycle. All four box presenters share ONE\n * {@link BoxLayout} so the frame, nameplate, body text, and choice rows move and\n * grow as one panel (per-line `meta.position`, choice-grow, avatar reflow).\n *\n * `theme` defaults to {@link defaultTheme} so a zero-config call works out of\n * the box (Graphics chrome + canvas text, no bundled assets).\n */\n\nimport { DialogueChrome } from \"../chrome/DialogueChrome.js\";\nimport { ChoiceListPresenter } from \"../chrome/ChoiceListPresenter.js\";\nimport { BoxTextView } from \"../render/BoxTextView.js\";\nimport { BoxLayout } from \"../render/BoxLayout.js\";\nimport type { AvatarPresenter } from \"../avatar/AvatarPresenter.js\";\nimport type { DialogueBundle } from \"../DialogueController.js\";\nimport { boxFrameStyles, DEFAULT_CHOICE_GAP, type DialogueTheme } from \"./theme.js\";\nimport { defaultTheme } from \"./defaultTheme.js\";\nimport { themeFonts } from \"./themeFonts.js\";\n\nexport interface BoxDialogueOptions {\n /**\n * Build an avatar presenter wired to the box's shared {@link BoxLayout} — so\n * a line-driven, reflowing in-box avatar (the reference `InBoxAvatarPresenter`)\n * can reserve a text-reflow inset on it. Receives the layout the box\n * chrome/text/choices share. Omit for no avatar (the default).\n *\n * createBoxDialogue(theme, {\n * avatar: (layout) =>\n * new InBoxAvatarPresenter(layout, { layer: DIALOGUE_LAYER_AVATAR, width: 96 }),\n * })\n */\n readonly avatar?: (layout: BoxLayout) => AvatarPresenter;\n}\n\nexport function createBoxDialogue(\n theme: DialogueTheme = defaultTheme(),\n opts: BoxDialogueOptions = {},\n): DialogueBundle {\n const fonts = themeFonts(theme);\n\n // The single per-line geometry owner for the box: frame position (meta.position),\n // the unified panel grow (choices grow the frame), and the avatar-reflow inset\n // registry — shared by the chrome, body text, and choice list below.\n const layout = new BoxLayout({\n box: theme.box,\n padding: theme.padding,\n nameSize: theme.nameSize,\n textSize: theme.textSize,\n lineHeight: theme.lineHeight,\n choiceGap: theme.choiceGap ?? DEFAULT_CHOICE_GAP,\n ...fonts,\n });\n\n const chrome = new DialogueChrome(\n {\n frameColor: theme.frameColor,\n frameAlpha: theme.frameAlpha,\n borderColor: theme.borderColor,\n cornerRadius: theme.cornerRadius,\n nameColor: theme.nameColor,\n nameSize: theme.nameSize,\n indicatorColor: theme.indicatorColor,\n caret: theme.caret,\n frameStyles: boxFrameStyles(theme.textured),\n layerFrame: theme.layerFrame,\n layerText: theme.layerText,\n ...fonts,\n },\n layout,\n );\n\n const choices = new ChoiceListPresenter(\n {\n choiceSize: theme.choiceSize,\n choiceColor: theme.choiceColor,\n choiceSelectedColor: theme.choiceSelectedColor,\n highlightColor: theme.highlightColor,\n choiceGap: theme.choiceGap,\n layerFrame: theme.layerFrame,\n layerText: theme.layerText,\n ...fonts,\n },\n layout,\n );\n\n // Body text region comes from the owner (below the nameplate band, reflowing\n // around any registered avatar inset, moving with meta.position).\n const text = new BoxTextView(\n {\n textSize: theme.textSize,\n lineHeight: theme.lineHeight,\n textColor: theme.textColor,\n charsPerSec: theme.charsPerSec,\n layer: theme.layerText,\n ...fonts,\n },\n layout,\n );\n\n // Opt-in avatar, wired to the same layout owner so it can reserve a text inset.\n const avatar = opts.avatar?.(layout);\n\n return {\n chrome,\n text,\n choices,\n ...(avatar ? { avatar } : {}),\n skipMultiplier: theme.skipMultiplier,\n };\n}\n","/**\n * `createBubbleDialogue(theme, opts)` wires a diegetic variant: the body text\n * floats in a world-space speech bubble over the speaking NPC (resolved via its\n * {@link DialogueActor}), while choices float in their own bubble panel over the\n * actor. Reuses the {@link DialogueTheme} for colours/fonts so a game's box and\n * bubble dialogues look consistent.\n *\n * new DialogueController({ ...createBubbleDialogue(theme, { worldLayer }), avatar });\n *\n * The actors must already carry a {@link DialogueActor} (speaker id + head\n * anchor); the bubble follows that anchor every frame. Register exactly ONE\n * actor per speaker id — the bubble chrome and the bubble text each resolve the\n * speaker independently, so two actors sharing an id would let them track\n * different entities and the text would drift off the bubble.\n *\n * `theme` defaults to {@link defaultTheme} so a zero-config call works out of\n * the box (Graphics chrome + canvas text, no bundled assets).\n */\n\nimport { BubbleChrome } from \"../chrome/BubbleChrome.js\";\nimport { BubbleChoicePresenter } from \"../chrome/BubbleChoicePresenter.js\";\nimport { BubbleTextView } from \"../render/BubbleTextView.js\";\nimport { BubbleLayout } from \"../render/BubbleLayout.js\";\nimport type { AvatarPresenter } from \"../avatar/AvatarPresenter.js\";\nimport type { DialogueBundle } from \"../DialogueController.js\";\nimport { defaultBubbleFrame, type DialogueTheme } from \"./theme.js\";\nimport { defaultTheme } from \"./defaultTheme.js\";\nimport { themeFonts } from \"./themeFonts.js\";\n\nexport interface BubbleGeometry {\n /** Snuggest width; the bubble widens to its text up to {@link maxWidth}. */\n readonly minWidth: number;\n /** Widest the bubble grows before its text wraps to more lines. */\n readonly maxWidth: number;\n /** Minimum height; grows to fit wrapped text once `maxWidth` is reached. */\n readonly height: number;\n readonly padding: number;\n /** Gap between the actor's head anchor and the bubble's bottom edge. */\n readonly offsetY: number;\n /** Tail (pointer) height. */\n readonly tail: number;\n}\n\nexport const DEFAULT_BUBBLE: BubbleGeometry = {\n minWidth: 90,\n maxWidth: 260,\n height: 40,\n padding: 8,\n offsetY: 24,\n tail: 6,\n};\n\nexport interface BubbleDialogueOptions {\n /** World-space render layer the bubble + text draw into. */\n readonly worldLayer: string;\n readonly bubble?: Partial<BubbleGeometry>;\n /**\n * Where a bubble anchors when its speaker has no live actor and no last-known\n * position (a never-seen speaker / a narrator in a pure-bubble bundle).\n * Defaults to the world origin; point it at your camera centre so a\n * speakerless line lands on screen. A despawned actor uses its last-known\n * position regardless. Shared by the chrome, text, and choice presenters.\n */\n readonly fallbackAnchor?: () => { x: number; y: number };\n /** Build an avatar presenter wired to the bubble's shared {@link BubbleLayout}\n * — e.g. the reference `BubbleAvatarPresenter`, a line-driven portrait beside\n * the bubble. Receives the layout the bubble chrome/text/choices share. */\n readonly avatar?: (layout: BubbleLayout) => AvatarPresenter;\n}\n\nexport function createBubbleDialogue(\n theme: DialogueTheme = defaultTheme(),\n opts: BubbleDialogueOptions,\n): DialogueBundle {\n const geo: BubbleGeometry = { ...DEFAULT_BUBBLE, ...opts.bubble };\n const fonts = themeFonts(theme);\n\n // The single per-line geometry owner for the bubble coordinate model: one\n // size measurement, one anchor resolver, one origin formula — shared by all\n // three bubble presenters so they can't drift.\n const layout = new BubbleLayout({\n minWidth: geo.minWidth,\n maxWidth: geo.maxWidth,\n height: geo.height,\n padding: geo.padding,\n offsetY: geo.offsetY,\n textSize: theme.textSize,\n lineHeight: theme.lineHeight,\n fallbackAnchor: opts.fallbackAnchor,\n ...fonts,\n });\n\n const chrome = new BubbleChrome(\n {\n layer: opts.worldLayer,\n tail: geo.tail,\n tailLean: theme.tailLean,\n frameColor: theme.frameColor,\n frameAlpha: theme.frameAlpha,\n borderColor: theme.borderColor,\n cornerRadius: theme.cornerRadius,\n nameColor: theme.nameColor,\n nameSize: theme.nameSize,\n indicatorColor: theme.indicatorColor,\n caret: theme.caret,\n frame: defaultBubbleFrame(theme.textured),\n ...fonts,\n },\n layout,\n );\n\n const text = new BubbleTextView(\n {\n textSize: theme.textSize,\n lineHeight: theme.lineHeight,\n textColor: theme.textColor,\n charsPerSec: theme.charsPerSec,\n layer: opts.worldLayer,\n ...fonts,\n },\n layout,\n );\n\n // Choices float in their own self-contained bubble panel over the actor\n // (prompt header + options, its own bg) — so they never depend on the box\n // frame and the prompt lives in the same bubble as the options.\n const choices = new BubbleChoicePresenter(\n {\n layer: opts.worldLayer,\n width: geo.maxWidth,\n padding: geo.padding,\n offsetY: geo.offsetY,\n tail: geo.tail,\n choiceSize: theme.choiceSize,\n choiceColor: theme.choiceColor,\n choiceSelectedColor: theme.choiceSelectedColor,\n highlightColor: theme.highlightColor,\n choiceGap: theme.choiceGap,\n textColor: theme.textColor,\n frameColor: theme.frameColor,\n frameAlpha: theme.frameAlpha,\n borderColor: theme.borderColor,\n cornerRadius: theme.cornerRadius,\n ...fonts,\n },\n layout,\n );\n\n // Opt-in bubble avatar, wired to the same layout owner (for the bubble size +\n // speaker anchor it follows).\n const avatar = opts.avatar?.(layout);\n\n return {\n chrome,\n text,\n choices,\n ...(avatar ? { avatar } : {}),\n skipMultiplier: theme.skipMultiplier,\n };\n}\n","/**\n * `createMixedDialogue` composes the box and bubble factories so one\n * conversation can place each line — and each choice — in either presentation,\n * chosen per step by its `view` hint (\"box\" — default — vs \"bubble\"). Text,\n * chrome, choices, and the avatar all route the same way, so a box choice keeps\n * the framed bottom list while a bubble choice gets its own panel over the actor.\n *\n * `theme` defaults to {@link defaultTheme} so a zero-config call works out of\n * the box (Graphics chrome + canvas text, no bundled assets).\n */\n\nimport { CompositeChrome } from \"../composite/CompositeChrome.js\";\nimport { CompositeChoicePresenter } from \"../composite/CompositeChoicePresenter.js\";\nimport { CompositeTextPresenter } from \"../composite/CompositeTextPresenter.js\";\nimport { CompositeAvatarPresenter } from \"../composite/CompositeAvatarPresenter.js\";\nimport { makeDefaultRoute, fixedRoute, type CompositeRoute } from \"../composite/route.js\";\nimport type { AvatarPresenter } from \"../avatar/AvatarPresenter.js\";\nimport type { DialogueBundle } from \"../DialogueController.js\";\nimport { createBoxDialogue, type BoxDialogueOptions } from \"./createBoxDialogue.js\";\nimport { createBubbleDialogue, type BubbleDialogueOptions } from \"./createBubbleDialogue.js\";\nimport type { DialogueTheme } from \"./theme.js\";\nimport { defaultTheme } from \"./defaultTheme.js\";\n\nexport interface MixedDialogueOptions extends Omit<BubbleDialogueOptions, \"avatar\"> {\n /**\n * Override the box-vs-bubble routing policy for this bundle. The default is\n * speaker-aware (narrator → box; explicit `view` wins; else a registered\n * actor → bubble, otherwise box). Supply a route to key off anything on the\n * line — e.g. `(line) => line?.speaker?.id === \"boss\" ? \"bubble\" : \"box\"`.\n * All composites (and the avatar) consult this one route, so chrome, text,\n * choices, and avatar always agree per line.\n */\n readonly route?: CompositeRoute;\n /**\n * Wire avatar presenters per side. `box` gets the box's {@link BoxLayout} (an\n * in-box reflowing avatar); `bubble` gets the bubble's {@link BubbleLayout} (a\n * portrait beside the bubble). With both, a {@link CompositeAvatarPresenter}\n * routes each line to the matching side; with one, only that side shows.\n */\n readonly avatar?: {\n readonly box?: BoxDialogueOptions[\"avatar\"];\n readonly bubble?: BubbleDialogueOptions[\"avatar\"];\n };\n}\n\nexport function createMixedDialogue(\n theme: DialogueTheme = defaultTheme(),\n opts: MixedDialogueOptions,\n): DialogueBundle {\n const box = createBoxDialogue(theme, opts.avatar?.box ? { avatar: opts.avatar.box } : {});\n const bubble = createBubbleDialogue(theme, {\n worldLayer: opts.worldLayer,\n ...(opts.bubble !== undefined ? { bubble: opts.bubble } : {}),\n ...(opts.fallbackAnchor !== undefined ? { fallbackAnchor: opts.fallbackAnchor } : {}),\n ...(opts.avatar?.bubble ? { avatar: opts.avatar.bubble } : {}),\n });\n // ONE route shared across the composites (+ the avatar) — per-presenter\n // divergence would route a line's chrome to the bubble and its text to the box.\n const routing = opts.route ? fixedRoute(opts.route) : makeDefaultRoute();\n\n // Compose the avatars: both sides → a routing composite; one side → that one.\n let avatar: AvatarPresenter | undefined;\n if (box.avatar && bubble.avatar) {\n avatar = new CompositeAvatarPresenter(box.avatar, bubble.avatar, routing);\n } else {\n avatar = box.avatar ?? bubble.avatar;\n }\n\n return {\n text: new CompositeTextPresenter(box.text, bubble.text, routing),\n chrome: new CompositeChrome(box.chrome, bubble.chrome, routing),\n choices: new CompositeChoicePresenter(box.choices, bubble.choices, routing),\n ...(avatar ? { avatar } : {}),\n skipMultiplier: theme.skipMultiplier,\n };\n}\n"],"mappings":"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","names":["Transform","Transform","measureWrappedText","measureWrappedText","Transform","Transform","Transform","GraphicsComponent","TextComponent","Transform","GraphicsComponent","TextComponent","Transform","createNineSlice","GraphicsComponent","TextComponent","Transform","GraphicsComponent","createNineSlice","TextComponent","Transform","GraphicsComponent","TextComponent","Transform","GraphicsComponent","TextComponent","Transform","GraphicsComponent","TextComponent","Transform","GraphicsComponent","TextComponent","Transform","Transform","Transform","Transform","Transform","GraphicsComponent","SpriteComponent","texture","Transform","GraphicsComponent","SpriteComponent","texture","Transform","GraphicsComponent","SpriteComponent","texture","Transform","GraphicsComponent","SpriteComponent","texture"]}