# `@combos-fun/plugin-renderer-3d-text` — Agent notes

In-world text backed by `troika-three-text`, suitable for labels, dialogue, signs, and scoreboards.

Prerequisite: follow the parent `@combos-fun/plugin-renderer-3d` skill for system order, pose seeding, hierarchy, and rendering lifecycle.

## Public API

```ts
import { Text3D, Text3DSystem, type Text3DParams } from '@combos-fun/plugin-renderer-3d-text';
```

`componentName = 'Text3D'`; `systemName = 'Text3DSystem'`.

For dynamic, translatable text, keep values in named `replacements`; do not
concatenate them into `text` and do not invent translation IDs:

```ts
score.addComponent(new Text3D({
  text: '{player} scored {score} points',
  replacements: { player: 'Alice', score: 100 },
  fontSize: 0.4,
}));
```

**MUST** set `Text3DSystem` `sourceLocale` to the game's written language as a
BCP 47 tag (`en`, `en-US`, `zh-CN`, `ja-JP`, …). Match the `Text3D` literals,
not the player device locale. Omitting it disables host i18n.

```ts
new Text3DSystem({
  sourceLocale: 'zh-CN',
  sourceTexts: ['开始游戏', '下一关已解锁'],
});
```

Pass **every** later-screen template in `sourceTexts` so the first
`requestI18n` translates the full catalog before `ready`. The system keeps one
growing session catalog of source templates and translations: new labels are
added to it, and going back to earlier copy looks up that catalog instead of
rebuilding from the current screen. You can also call
`textSystem.declareSourceTexts([...])` during `onSystemsBootstrapComplete`.

The source template itself is the translation lookup key. After labels exist,
the system checks that `window.combos.requestI18n` exists. The first
`requestI18n({ sourceLocale, sourceTexts })` runs in `beforeReady` and holds
game `ready` / `start` until the host replies. Labels stay empty until that
first reply so the source language does not flash on the canvas. Apply the
result only when `needed: true`. If that reply is `needed: true` but a current template is
missing from `translations`, send only the still-untranslated templates and
`requestI18n` once more before `ready`. After ready, a new label whose
translation is still missing stays empty, sends only those missing templates,
waits for one more `requestI18n`, then renders. Already-translated templates
are omitted from later `requestI18n` payloads and do not trigger another call
when the same labels go back to earlier copy.
`needed: false` keeps source text and does not hold later labels. Do not post `i18n-ready` / `set-i18n` from
this plugin. If no source locale or method is present, placeholder replacement
still works and source text stays as-is.

## Parameters

| Field | Type | Default |
|-------|------|---------|
| `text` | `string` | `''` |
| `replacements` | `Record<string, string \| number>` | `{}` |
| `fontSize` | `number` | `0.5` |
| `color` | `number` | `0xffffff` |
| `anchorX` | `string` | `'center'` |
| `anchorY` | `string` | `'middle'` |
| `maxWidth` | `number` | `0`; applied only when `> 0` |
| `font` | `string` | `''`; Troika URL/path, not an engine resource name |
| `positionX/Y/Z` | `number` | `0` |
| `rotationX/Y/Z` | `number` | `0` radians |

There are no scale fields; use `fontSize` for size.

## Runtime and lifecycle

- ADD creates and attaches a `TroikaText`, then calls its asynchronous `sync()`. Property changes copy values and call `sync()` again.
- Font loading and glyph layout complete asynchronously inside Troika. Do not assume final geometry or metrics immediately after ADD.
- Removal detaches and disposes the Troika object; there is no engine named-resource lifecycle for `font`.
- Pose synchronizes per frame.

## Package-specific pitfalls

- `font` must be a browser-reachable URL/path.
- Setting `maxWidth` back to `0` does not clear a previously applied positive width in the current implementation; likewise, setting `font` back to `''` does not restore the prior/default font.
- Empty text or a background-matching color can look like a render failure.

## Minimal example

```ts
const sign = new GameObject('sign');
sign.addComponent(new Text3D({
  text: 'Welcome',
  fontSize: 0.6,
  color: 0xffffff,
  positionY: 1.5,
}));
```

## Verification

`pnpm --filter @combos-fun/plugin-renderer-3d-text run build`
