# Conflicts: # CHANGELOG.md # content/_meta/README.md # content/_meta/npc-authoring-style-guide.md # wiki/_templates/cultural-group.md # wiki/_templates/institution.md # wiki/_templates/star-system.md # wiki/characters/devra.md # wiki/characters/drin.md # wiki/characters/harek.md # wiki/characters/lera-sessik.md # wiki/characters/maret-korr.md # wiki/characters/naia-tamm.md # wiki/characters/nils-davan.md # wiki/characters/pell.md # wiki/characters/renn.md # wiki/characters/resha.md # wiki/characters/sabel.md # wiki/characters/sera-venn.md # wiki/characters/torek-lintar.md # wiki/characters/voss.md # wiki/star-systems/krenn/index.md
293 lines
18 KiB
Markdown
293 lines
18 KiB
Markdown
---
|
|
title: "Tyre Round 2: Proposal Evaluation and Technical Resolution"
|
|
description: "Technical resolution of behaviours scope, tell_behaviors field, and token budgets"
|
|
type: workshop
|
|
status: archived
|
|
workshop: llm-voice-pipeline
|
|
agent: "tyre"
|
|
round: 2
|
|
created: 2026-03-07
|
|
---
|
|
|
|
# Tyre Round 2: Proposal Evaluation & Technical Resolution
|
|
|
|
**Domain:** Technical architecture
|
|
**Round:** 2 — Convergent Evaluation
|
|
**Assigned questions:** Q-R1-02 (behaviors-only vs behaviors+dialogue), Q-R1-03 (tell_behaviors field), Q-R1-04 (token budget feasibility)
|
|
|
|
---
|
|
|
|
## Resolution Matrix
|
|
|
|
| Question | My answer |
|
|
|----------|-----------|
|
|
| Which proposal do you recommend? | **B** (Two-Track — Behaviors + Tells with Semantic Core) |
|
|
| Are there blockers in your recommended proposal? | No. See implementation notes below. |
|
|
| Can you live with Proposal A? | Yes. It's the safe fallback if B's constrained re-voicing fails the spike. |
|
|
| Can you live with Proposal C? | Yes, but with a phased spike — behaviors first, dialogue second. Don't test both simultaneously. |
|
|
| Minimum change to make A acceptable? | None needed — A is acceptable as-is, just leaves value on the table. |
|
|
| Minimum change to make C acceptable? | Phase the spike: validate behaviors + tells first (Sprint N), extend to dialogue second (Sprint N+1). Don't test two content types and two model sizes simultaneously. |
|
|
|
|
---
|
|
|
|
## Why Proposal B
|
|
|
|
*cracks knuckles* — Let me be direct about why B is the sweet spot.
|
|
|
|
**Proposal A** locks tells as passthrough. That's safe but wasteful. The tell system already outputs a `TellCategory` enum (Nervous, Angry, Friendly, Guarded, RoutineDeviation) — it's a closed taxonomy of 5 categories. The tells are not free-form authored content; they're behavioral expressions of simulation state. A Nervous Van Maanen's Star worker and a Nervous Sovari merchant should look nervous *differently*. Passthrough means they look nervous identically. That's technically correct but culturally flat.
|
|
|
|
**Proposal C** adds dialogue re-voicing to the same spike. That's scope creep that risks muddying the results. Behaviors are 5-15 word strings with no conversation context. Dialogue is 15-40 words requiring relationship state, access tier, and conversation history. Testing both in one spike means you can't isolate whether a quality failure comes from the model, the prompt, or the content type. Phase it.
|
|
|
|
**Proposal B** adds constrained re-voicing for tells while keeping the spike focused on a single content type (behaviors). The `semantic_core` tag is a lightweight addition that maps directly to the existing `TellCategory` enum. Two prompt templates (free + constrained) is marginally more complex than one, but both operate on the same short-form input. The spike complexity increase is ~20%, not 2x.
|
|
|
|
---
|
|
|
|
## Q-R1-02: Behaviors-Only vs Behaviors+Dialogue — Feasibility & Quality Risk
|
|
|
|
### Spike complexity comparison
|
|
|
|
| Dimension | A/B (behaviors only) | C (behaviors + dialogue) |
|
|
|-----------|---------------------|--------------------------|
|
|
| Prompt templates | 1 (A) or 2 (B) | 3 (free behavior, constrained tell, dialogue) |
|
|
| Input context length | 150-200 tokens | 150-200 (behaviors) + 400-500 (dialogue) |
|
|
| Output length | 10-30 tokens | 10-30 (behaviors) + 20-60 (dialogue) |
|
|
| Model candidates to test | 1 (Gemma 2B Q4) | 2 (Gemma 2B for behaviors, potentially Qwen2.5-3B for dialogue) |
|
|
| Evaluation criteria | Register accuracy, oath preservation, semantic core preservation | All of the above + conversation coherence, relationship accuracy, information boundary compliance |
|
|
| Test payloads | 5-8 behavior strings across 2 zones | 5-8 behaviors + 5-8 dialogue lines across 2 zones + 2 relationship contexts |
|
|
| Spike duration | 1-2 sprints | 2-3 sprints |
|
|
| Risk of inconclusive results | Low | Moderate — if dialogue fails, did the model fail or the prompt? |
|
|
|
|
### Model size implications for dialogue
|
|
|
|
Dialogue re-voicing is a harder task than behavior re-voicing. Here's why, concretely:
|
|
|
|
**Behavior input:** "tends crops in the field"
|
|
**Behavior prompt context:** Culture register + personality traits + mood = ~150 tokens total
|
|
**Output constraint:** Rephrase in voice, preserve the action. Single sentence.
|
|
|
|
**Dialogue input:** "You need a keycard for that door."
|
|
**Dialogue prompt context:** Culture register + personality traits + mood + relationship to listener + access tier + trust level + conversation topic = ~400-500 tokens total
|
|
**Output constraint:** Rephrase in voice, preserve the information, maintain conversation coherence, don't cross information boundaries.
|
|
|
|
At 2B model size (Gemma 2B), instruction-following degrades as prompt complexity increases. The community benchmarks show:
|
|
- Simple rephrasing tasks (our behavior case): 2B models perform at ~85-90% of 7B quality
|
|
- Multi-constraint tasks (our dialogue case): 2B models drop to ~65-75% of 7B quality
|
|
- The drop is steeper when constraints conflict (e.g., "be direct" + "be evasive about this topic")
|
|
|
|
**Practical implication:** A 2B model that handles behavior re-voicing well may produce mediocre dialogue re-voicing. If Proposal C tests both and dialogue fails, the conclusion might be "we need a 3B model" — which tightens RAM and slows inference. That's a real architectural fork in the road, and it shouldn't be discovered mid-spike alongside behavior evaluation.
|
|
|
|
### My recommendation
|
|
|
|
**Phase the spike:**
|
|
1. Sprint N: Behaviors + tells (Proposal B scope). One model (Gemma 2B Q4). Clear success criteria.
|
|
2. Sprint N+1: If behaviors pass, extend to dialogue with the same model. If dialogue quality is insufficient, test Qwen2.5-3B as an upgrade candidate.
|
|
3. Sprint N+2: If 3B is needed for dialogue, run the RAM/throughput validation separately.
|
|
|
|
This costs 1 sprint more than C's all-at-once approach but eliminates the risk of an inconclusive spike. We know exactly what works and what doesn't at each stage.
|
|
|
|
---
|
|
|
|
## Q-R1-03: `tell_behaviors` as a Separate Field — Implementation Confirmation
|
|
|
|
**Yes, this is implementable. And it's actually simpler than the proposals assume, because the production system already separates tells from behaviors at the ECS level.**
|
|
|
|
Let me walk through the existing architecture:
|
|
|
|
### Current state (production server)
|
|
|
|
The production code has a clean separation that the spike doesn't:
|
|
|
|
1. **`NpcBlueprint.observable_behaviors: Vec<String>`** — role-specific actions from `RoleSpec.typical_behaviors`. These are the ambient behaviors.
|
|
|
|
2. **`DerivedTellState` (ECS component)** — a `TellCategory` enum (Nervous, Angry, Friendly, Guarded, RoutineDeviation) derived each tick from simulation state by `tell_state::derive_tell_state()`. This is NOT a string. It's a simulation signal.
|
|
|
|
3. **`VisibleEntity.tell_state: Option<TellCategory>`** on the wire (bridge/types.rs, line 402). The client receives the tell as an enum, not a behavior string.
|
|
|
|
### The spike's confusion
|
|
|
|
The spike (`generator_spike.rs`) conflates these by appending tell strings to `observable_behaviors` at index 1:
|
|
```rust
|
|
// Fourth pass: generate Want tells (#632).
|
|
if let Some(tell) = gen_want_tell(&mut rng, npc) {
|
|
npc.observable_behaviors.push(tell);
|
|
}
|
|
```
|
|
|
|
This was a pragmatic spike shortcut — the spike doesn't have an ECS world, so it can't use `DerivedTellState`. But it created the impression that tells and behaviors share a flat list.
|
|
|
|
### What needs to change for the voice pipeline
|
|
|
|
**In `NpcBlueprint`:** Add a `tell_behaviors: Vec<TellBehavior>` field:
|
|
|
|
```rust
|
|
pub struct TellBehavior {
|
|
/// The TellCategory this behavior expresses.
|
|
pub category: TellCategory,
|
|
/// Base text for this tell (culture-neutral).
|
|
pub base_text: String,
|
|
/// Semantic core tag for constrained re-voicing (Proposal B).
|
|
/// e.g., "avoidance_behavior", "nervous_fidget", "suppression_tell"
|
|
pub semantic_core: String,
|
|
}
|
|
|
|
pub struct NpcBlueprint {
|
|
// ... existing fields ...
|
|
pub observable_behaviors: Vec<String>, // ambient role actions — free re-voicing
|
|
pub tell_behaviors: Vec<TellBehavior>, // tells — constrained re-voicing (B) or passthrough (A)
|
|
}
|
|
```
|
|
|
|
**Effort:** ~0.5 sprint. Add the struct, update the generator to populate it separately from `observable_behaviors`, update the spike to use the new field instead of appending to the flat list. No downstream changes needed — `DerivedTellState` already flows as an enum on the wire; the tell behavior text is a separate rendering concern.
|
|
|
|
**Observer integration:** The observer snapshot already sends `tell_state: Option<TellCategory>`. The voiced tell text would be a cache lookup: `(TellCategory, culture_id, personality_traits) -> voiced_tell_string`. This is a client-side lookup, not a server change.
|
|
|
|
### Interaction with the ECS tell system
|
|
|
|
Important subtlety: in the production server, tells are **not pre-generated per NPC**. `DerivedTellState` is recomputed every tick from live simulation state. An NPC might be Friendly at tick 100 and Nervous at tick 500 because their stress increased.
|
|
|
|
This means tell behavior text is not a static per-NPC attribute — it's a per-category, per-culture library. The voice pipeline generates voiced variants for all 5 TellCategory values per culture, not per NPC. That's:
|
|
|
|
- 5 categories x N cultures x ~4 variants per category = ~20-40 voiced tell strings per culture
|
|
|
|
This is a small, finite set. It could even be baked at build time for all cultures, no lazy generation needed. **Tell voicing is not a scaling problem — it's a fixed-size content library.**
|
|
|
|
---
|
|
|
|
## Q-R1-04: Token Budget — 150 Tokens for Culture Injectors
|
|
|
|
### Is 150 tokens sufficient?
|
|
|
|
**For behaviors: yes. For the full cultural philosophy Miri describes: no, but it doesn't need to be.**
|
|
|
|
Let me construct the actual prompt for a behavior re-voicing call and count tokens:
|
|
|
|
```
|
|
System: You are a dialogue localizer for a science fiction game.
|
|
Rephrase the following action description in the specified voice.
|
|
Preserve the physical action. Change only style, register, and vocabulary.
|
|
Do not add information. Do not explain motivation. One sentence only.
|
|
|
|
Culture: Van Maanen's Star (working-class, direct, minimal pleasantries).
|
|
Speech register: direct, gets to the point, no contractions avoided.
|
|
Exclamations (use ONLY these): "void take it", "stars", "blood and void",
|
|
"cold vacuum", "damn all", "void's sake".
|
|
DO NOT use: military ranks, sir/ma'am, religious references, quips.
|
|
|
|
Personality: Bold, Honest.
|
|
Mood: Neutral.
|
|
|
|
Rephrase: "tends crops in the field"
|
|
```
|
|
|
|
Token count (GPT-4 tokenizer as proxy, actual varies by model):
|
|
- System instruction: ~45 tokens
|
|
- Culture injector: ~75 tokens
|
|
- Personality + mood: ~10 tokens
|
|
- Base text + format: ~15 tokens
|
|
- **Total: ~145 tokens**
|
|
|
|
That fits the 150-token budget for behaviors. The culture injector at 75 tokens covers: register description, oath vocabulary (closed list), negative constraints (NOT-lists). It does NOT cover the full cultural philosophy (community anchors, competence signaling, emotional weight of void-oaths) — that would push to 200-300 tokens as Miri describes.
|
|
|
|
**The key question: does the model need cultural philosophy to rephrase a 10-word action?**
|
|
|
|
No. For behavior re-voicing, the model needs:
|
|
1. Register (direct, clipped) — so it doesn't produce flowery prose
|
|
2. Oath vocabulary (closed list) — so it uses "void take it" not "damn it"
|
|
3. Negative constraints — so it doesn't produce franchise bleed
|
|
|
|
It does NOT need to understand why Van Maanen's Star people swear by the void. That's a dialogue-level concern, not a behavior-level concern. "Tends crops in the field" becomes "works the irrigation channels before the morning rotation" — register and setting vocabulary are sufficient.
|
|
|
|
**For Proposal B's constrained tell re-voicing:** add ~20 tokens for the semantic core constraint ("PRESERVE: avoidance_behavior. Culture-voice the expression, not the phenomenon."). Total: ~170 tokens. Still within the effective range for a 2B model.
|
|
|
|
### Throughput impact: 150-token vs 500-token prompts
|
|
|
|
This matters because it determines whether Proposal C's dialogue re-voicing is feasible on minimum-spec hardware.
|
|
|
|
**How LLM inference works with different prompt sizes:**
|
|
|
|
There are two phases:
|
|
1. **Prompt processing (prefill):** Process all input tokens. This is parallelizable and fast. For llama.cpp on CPU: ~100-500 tokens/sec depending on hardware.
|
|
2. **Token generation (decode):** Generate output tokens one at a time. This is sequential and slow. This is where the 3-14 t/s numbers from Troblum's analysis apply.
|
|
|
|
| Prompt size | Prefill time (5 t/s hardware) | Generate 30 tokens | Total |
|
|
|-------------|-------------------------------|---------------------|-------|
|
|
| 150 tokens | ~0.3-0.5 sec | ~6 sec | ~6.5 sec |
|
|
| 500 tokens | ~1.0-1.5 sec | ~6 sec | ~7.5 sec |
|
|
|
|
**The throughput difference is ~15% per call.** Prefill is cheap; generation is the bottleneck. Longer prompts don't dramatically slow things down because the output length is the dominant factor, not the input length.
|
|
|
|
However, there's a memory impact. At 500-token context, the KV cache per inference call grows from ~50MB to ~80MB. On minimum-spec hardware, this tightens the already-constrained RAM budget. Not a showstopper but worth noting.
|
|
|
|
**Practical conclusion:** 500-token prompts for dialogue (Proposal C) are feasible from a throughput perspective. The concern with Proposal C is quality at 2B, not speed. If you need to step up to 3B for dialogue quality, THAT is where throughput drops — a 3B model at Q4 runs ~30% slower than 2B, which means the zone pre-voicing times in Troblum's table increase by a third.
|
|
|
|
---
|
|
|
|
## Additional Technical Assessment of Each Proposal
|
|
|
|
### Proposal A: Conservative
|
|
|
|
**Architecturally clean.** One prompt template, one model, one content type. The spike is maximally simple. If we are risk-averse about the v0.2 timeline, this is the right call.
|
|
|
|
**Technical gap:** Tell behaviors pass through as culture-neutral base text. A Van Maanen's Star NPC who is Nervous shows exactly the same tell phrasing as a Sovari NPC who is Nervous. Since tells are a fixed-size library (5 categories x N cultures), voicing all of them is cheap. Leaving them unvoiced is a missed opportunity with near-zero risk.
|
|
|
|
### Proposal B: Two-Track
|
|
|
|
**My recommendation.** The constrained re-voicing for tells is a well-bounded problem:
|
|
- Input: culture + TellCategory + semantic_core
|
|
- Output: culture-voiced tell string
|
|
- Validation: does the output still read as the same TellCategory?
|
|
- Volume: ~20-40 strings per culture (bakeable at build time)
|
|
|
|
The `semantic_core` tag maps directly to `TellCategory`:
|
|
|
|
| TellCategory | semantic_core | Example base text |
|
|
|---|---|---|
|
|
| Nervous | nervous_fidget | "shifts weight and checks the time without reason" |
|
|
| Angry | hostile_display | "speaks through clenched teeth" |
|
|
| Friendly | warmth_signal | "greets passersby unprompted" |
|
|
| Guarded | concealment_tell | "becomes evasive and avoids eye contact" |
|
|
| RoutineDeviation | routine_break | "takes an unusual route to their station" |
|
|
|
|
The prompt for constrained re-voicing adds one line to the behavior prompt:
|
|
```
|
|
PRESERVE the observable phenomenon: {semantic_core}.
|
|
Rephrase in Van Maanen's Star register. Do not change what the NPC is doing — change how they do it.
|
|
```
|
|
|
|
This is a constrained localization task, not free generation. A 2B model handles this well because the constraint is specific and verifiable. The spike can include tell re-voicing alongside behavior re-voicing with minimal additional test payloads (5 categories x 1-2 base texts = 5-10 additional test strings).
|
|
|
|
### Proposal C: Full Pipeline
|
|
|
|
**Technically feasible, strategically premature.**
|
|
|
|
The dialogue re-voicing itself works — the prompt structure is sound, the content types are compatible. But testing it in the same spike as behaviors creates a 3-variable experiment:
|
|
1. Model capability (can 2B handle it?)
|
|
2. Prompt quality (are the injectors sufficient?)
|
|
3. Content type suitability (is dialogue a good fit for re-voicing?)
|
|
|
|
If the spike produces mediocre dialogue, which variable failed? You can't tell without running the experiment again with controls. Phase it.
|
|
|
|
**If the team chooses C anyway:** the minimum change that makes it acceptable is to **separate the spike into two phases with independent success criteria.** Phase 1: behaviors + tells (same as B). Phase 2: dialogue, using the Phase 1-validated model and injectors. Phase 2 only runs if Phase 1 passes. This is functionally B → C progression, not parallel C.
|
|
|
|
---
|
|
|
|
## D-123 Tension — Technical Position
|
|
|
|
All three proposals amend D-123 ("authoring tool, not runtime system"). From a technical architecture standpoint:
|
|
|
|
**The amendment is correct.** The LLM is both an authoring tool (baked content at build time) and a runtime enhancement (pre-voicing queue during gameplay). The distinction D-123 drew was premature — it was written before the re-voicing architecture was designed. The re-voicing model IS an authoring tool that happens to run at runtime. The output is cached text, not real-time generation. The simulation never depends on LLM output. The game is complete without it.
|
|
|
|
Proposed amendment language: *"D-123 is amended. The AI pipeline is an authoring tool for content assembly AND a background runtime enhancement for culture-voiced dialogue. Runtime inference is optional (player toggle), non-blocking (graceful fallback to base text), and cache-deterministic (same seed produces same voiced content per machine). D-124 is superseded — the voice pipeline IS the in-game AI system."*
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
| Proposal | Technical risk | Spike complexity | Value delivered |
|
|
|----------|---------------|-----------------|----------------|
|
|
| A | Low | Low | Ambient behaviors voiced; tells flat |
|
|
| **B** | **Low-medium** | **Low-medium** | **Ambient behaviors + culturally-voiced tells** |
|
|
| C | Medium | High | Full content pipeline, but spike may be inconclusive |
|
|
|
|
**B is the right scope for the spike and the right architecture for v0.2.** Dialogue re-voicing can follow in Sprint N+1 as a natural extension if the pipeline validates. The tell voicing is a small, bounded addition that delivers disproportionate cultural richness — 5 categories x N cultures, bakeable at build time, zero lazy-generation pressure.
|