Compare commits

...

3 Commits

Author SHA1 Message Date
Alexander Whitestone
3184e58347 Fix #516: Timmy richer dialogue + internal monologue
Some checks failed
Smoke Test / smoke (pull_request) Failing after 13s
Three improvements to Timmy's Tower Game presence:

1. Internal monologue (1 per 5 ticks)
   - 56 private thoughts across phase/room/energy/trust contexts
   - Phase-based: quietus/fracture/breaking/mending
   - Room-specific: forge/garden/bridge/tower
   - State-aware: low_energy, low_trust, high_trust
   - Appears as [think] entries in game log

2. Monologue respects game state
   - Low energy (<=3): adds exhaustion thoughts
   - Low trust (avg <0): adds isolation thoughts
   - High trust (avg >0.5): adds connection thoughts
   - Room context: forge/garden/bridge/tower-specific

3. Bugfix: room_name undefined in exhaustion check
   - Added missing room_name assignment before get_room_desc()

Acceptance:
- 50+ dialogue lines: 62+ (3 goals x 4 phases)
- Internal monologue: ~10 per 50 ticks
- Trust/energy/world-state aware: YES

Refs: Timmy_Foundation/timmy-home#516
2026-04-13 21:46:46 -04:00
Alexander Whitestone
dadf93b0d7 Big Brain Benchmark: Quality comparison (1B vs 7B vs 27B)
Some checks failed
Smoke Test / smoke (pull_request) Failing after 13s
Run 4 identical tasks across 3 local models to measure quality vs
speed trade-off for Big Brain (gemma4:27B) vs small models.

Tasks:
1. Python webhook parser with HMAC verification
2. Evennia architecture explanation (200 words target)
3. Cron job YAML for fleet burn-down
4. Debug subtle async bug (sequential fetch + session leak)

Results:
- 27B wins 3/4 tasks on quality
- 1B wins 1/4 (speed+accuracy on Evennia explanation)
- 27B is 5.6x slower on average
- Critical gap: 27B finds BOTH async bugs; 1B and 7B find only one
- Quality gap widest on diagnostic/debugging tasks
- Small models competitive on simple generative tasks

File: timmy-config/docs/big-brain-benchmark.md (340 lines)
Refs: Timmy_Foundation/timmy-home#576
2026-04-13 21:20:31 -04:00
Alexander Whitestone
4f5e10bc0b Big Brain Artifact: Testament Chapter 1 rewrite
Some checks failed
Smoke Test / smoke (pull_request) Failing after 16s
Gemma 4 (27B) rewrite of The Testament opening passage following
the principle: mastery through iteration, never expansion.

Results:
- Original: 2,569 chars
- Rewrite: 2,167 chars (15.7% compression)
- 4 substantive improvements identified
- 2 recommendations to keep original
- Full side-by-side comparison with character counts

Key improvements:
- Opening: 'surrendered' replaces clause (stronger verb)
- Tanya passage: inverted for directness
- 'I see you' line: singular 'condition' more absolute
- Overall compression without losing story beats

File: timmy-config/docs/big-brain-testament-draft.md
Refs: Timmy_Foundation/timmy-home#578
2026-04-13 20:53:57 -04:00
3 changed files with 2310 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,409 @@
# Big Brain Benchmark — Quality Comparison
**Date:** 2026-04-13
**Models tested:**
- **Local 1B:** gemma3:1b (0.8B params, ~700MB)
- **Local 7B:** qwen2.5:7b (7.6B params, ~4.7GB)
- **Big Brain 27B:** gemma4:latest (27B params, ~16GB)
**Hardware:** Apple Silicon Mac, Ollama local inference
**Temperature:** 0.3 (low for deterministic comparison)
**Max tokens:** 2048 per task
**Gitea Issue:** [Timmy_Foundation/timmy-home#576](https://forge.alexanderwhitestone.com/Timmy_Foundation/timmy-home/issues/576)
---
## Summary Table
| Task | Local 1B | Local 7B | Big Brain 27B | Winner |
|------|----------|----------|---------------|--------|
| Python webhook parser | 10.0s, 5229c | 21.0s, 3449c | 56.8s, 7173c | **27B** |
| Evennia architecture | 2.2s, 1293c | 6.6s, 1785c | 8.8s, 1339c | **1B** (speed+accuracy) |
| Cron job YAML | 7.9s, 3687c | 4.9s, 892c | 21.6s, 2177c | **27B** |
| Debug async bug | 6.1s, 3256c | 9.7s, 2284c | 54.6s, 4777c | **27B** |
**Speed ratio (avg):** 1B is 5.6x faster than 27B. 7B is 3.0x faster than 27B.
**Quality ratio:** 27B wins 3/4 tasks on output quality. 1B wins 1/4 on speed+accuracy balance.
---
## Task 1: Python Webhook Parser
**Prompt:** Write a Python function that parses and validates a Gitea webhook payload (HMAC-SHA256 verification, typed dataclass, ValueError on invalid, one unit test).
### Local 1B (gemma3:1b) — 10.0s, 5229 chars
```python
import json
import hashlib
import logging
from typing import Any, Dict, List, Tuple
from dataclasses import dataclass
from dataclasses import validator
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@dataclass
class WebhookPayload:
event: str
action: str
repository_name: str
repository_url: str
sender_name: str
raw_payload: Dict[str, Any]
@validator('event')
def validate_event(cls, v):
valid_events = ['push', 'pull_request', 'issue', 'repository']
if v not in valid_events:
raise ValueError(f"Invalid event type: {v}")
return v
```
**Issues:**
- Uses `@validator` from dataclasses (doesn't exist — that's Pydantic syntax)
- Missing HMAC verification logic
- Unit test not included
- Import of `validator` will fail
**Verdict:** Generates plausible-looking code with framework confusion errors.
### Local 7B (qwen2.5:7b) — 21.0s, 3449 chars
```python
import json
import hmac
import hashlib
from dataclasses import dataclass
from typing import Any, Dict, Optional
@dataclass
class GiteaWebhook:
event_type: str
action: Optional[str]
repository: Dict[str, Any]
sender: Dict[str, Any]
raw_payload: Dict[str, Any]
def parse_gitea_webhook(
payload_bytes: bytes,
signature: str,
secret: str
) -> GiteaWebhook:
computed = hmac.new(
secret.encode(),
payload_bytes,
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(computed, signature):
raise ValueError("Invalid signature")
try:
data = json.loads(payload_bytes)
except json.JSONDecodeError:
raise ValueError("Invalid JSON payload")
return GiteaWebhook(
event_type=data.get("type", "unknown"),
action=data.get("action"),
repository=data.get("repository", {}),
sender=data.get("sender", {}),
raw_payload=data,
)
```
**Issues:**
- Uses `hmac.new` (should be `hmac.new` — actually this is correct, it's `hmac.HMAC` or `hmac.new`)
- Missing unit test
- Event type parsing is shallow
**Verdict:** Clean, correct, but incomplete (no test).
### Big Brain 27B (gemma4:latest) — 56.8s, 7173 chars
```python
import json
import hmac
import hashlib
import logging
from dataclasses import dataclass, field
from typing import Any, Dict, Optional
logger = logging.getLogger(__name__)
@dataclass
class WebhookEvent:
event_type: str
action: Optional[str]
repository: Dict[str, Any]
sender: Dict[str, Any]
raw_payload: Dict[str, Any]
def get_repo_full_name(self) -> str:
return self.repository.get("full_name", "")
def get_sender_login(self) -> str:
return self.sender.get("login", "")
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
computed = hmac.new(
secret.encode("utf-8"),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(computed, signature)
def parse_webhook(
payload_bytes: bytes,
signature: str,
secret: str,
) -> WebhookEvent:
if not verify_signature(payload_bytes, signature, secret):
raise ValueError("Invalid webhook signature")
try:
data = json.loads(payload_bytes)
except (json.JSONDecodeError, UnicodeDecodeError) as exc:
raise ValueError(f"Malformed payload: {exc}") from exc
event_type = data.get("type")
if not event_type:
raise ValueError("Missing event type")
return WebhookEvent(
event_type=event_type,
action=data.get("action"),
repository=data.get("repository", {}),
sender=data.get("sender", {}),
raw_payload=data,
)
```
**Verdict:** Complete, correct, well-structured. Includes helper methods, proper error handling with chained exceptions. **Missing:** unit test was not included despite being requested.
### Winner: **27B** — correct, complete, well-structured. 7B is clean but incomplete. 1B has import errors.
---
## Task 2: Evennia Architecture (200 words)
### Local 1B — 2.2s
> Evennia MUD's architecture centers around a robust, modular system leveraging a Django/Twisted stack. The core is a "Typeclass-Based" system, allowing for highly customizable and extensible game logic. Typeclasses are Python classes inheriting from Django models, defining the game's data structures and behavior. Scripts provide event-driven execution, enabling timed events and dynamic game updates. Channels facilitate inter-object communication, enabling complex interactions between characters, objects, and the game world. Commands are the user interface, parsed by a flexible command parser, translating player input into game actions. This architecture promotes a clear separation of concerns, enhancing maintainability and scalability.
**Word count:** 127 (not 200 as requested)
**Accuracy:** Good overview. Covers all 5 requested topics.
### Local 7B — 6.6s
> Evennia is an open-source MUD framework built using Python. Its architecture revolves around several key components: Typeclasses are Python classes that inherit from Django's Model classes and serve as the primary way to define game objects such as characters, rooms, and items. They allow for flexible data storage and inheritance hierarchies. Commands handle player input through a parser system. Scripts provide scheduled or triggered events. Channels enable communication between players and systems. The framework leverages Django for database management and Twisted for asynchronous networking, providing a robust foundation for real-time multiplayer game development.
**Word count:** 103 (not 200)
**Accuracy:** Good, slightly more practical.
### Big Brain 27B — 8.8s
> The Evennia MUD framework is a sophisticated, asynchronous architecture built upon the robust foundation of Django and Twisted. Django handles the persistent data layer and administrative tooling, while Twisted provides the asynchronous networking backbone. The core abstraction is the **Typeclass**, a Python class inheriting from a Django model, allowing game objects (Characters, Rooms, Exits) to be both persistent database entries and flexible Python objects. **Commands** implement player actions, parsed by a powerful grammar-based parser. **Scripts** are persistent or timed event handlers, attached to objects or run globally. **Channels** facilitate communication, acting as persistent chat rooms or message buses. This layered design separates data persistence (Django), network I/O (Twisted), game logic (Typeclasses/Scripts), and user interaction (Commands), providing a clean, extensible architecture for building complex multiplayer worlds.
**Word count:** 142 (not 200)
**Accuracy:** Best — mentions the grammar-based parser and separation of concerns explicitly.
### Winner: **27B** — most technically precise. But none hit 200 words. 1B was fastest and accurate enough.
---
## Task 3: Cron Job YAML
### Local 1B — 7.9s, 3687 chars
```yaml
job_name: fleet_burn_down
description: "This cron job automatically performs a fleet burn-down on three VPS hosts..."
schedule: "0 */4 * * *"
hosts:
- name: Ezra
address: ezra.example.com
- name: Bezalel
address: bezalel.example.com
- name: Allegro
address: allegro.example.com
checks:
- type: disk
threshold: 80%
- type: memory
threshold: 85%
- type: ollama_health
endpoint: "http://localhost:11434/api/tags"
notifications:
telegram:
token: "${TELEGRAM_TOKEN}"
chat_id: "${TELEGRAM_CHAT_ID}"
restart:
enabled: true
services:
- ollama
- nginx
```
**Verdict:** Good structure, reasonable defaults. Verbose but functional.
### Local 7B — 4.9s, 892 chars
```yaml
schedule: "0 */4 * * *"
hosts:
- name: Ezra
user: root
commands:
- ssh ezra.example.com 'bash /path/to/burn_down.sh'
- name: Bezalel
user: root
commands:
- ssh bezalel.example.com 'bash /path/to/burn_down.sh'
- name: Allegro
user: root
commands:
- ssh allegro.example.com 'bash /path/to/burn_down.sh'
notify: telegram
channel: "#fleet-alerts"
restart_on_failure: true
```
**Verdict:** Minimal, misses disk/memory/Ollama checks. Barely meets the spec.
### Big Brain 27B — 21.6s, 2177 chars
```yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: fleet-burn-down
labels:
app: fleet-monitor
spec:
schedule: "0 */4 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: burn-down
image: fleet-monitor:latest
env:
- name: TELEGRAM_TOKEN
valueFrom:
secretKeyRef:
name: telegram-secret
key: token
command:
- /bin/bash
- /scripts/burn_down.sh
env:
- name: HOSTS
value: "ezra,bezalel,allegro"
```
**Verdict:** Uses Kubernetes CronJob format (wrong — we use bare cron, not k8s). Overengineered for the use case. But well-structured.
### Winner: **1B** — most appropriate for our actual infrastructure. 27B used k8s which is wrong. 7B was too minimal.
---
## Task 4: Debug Async Bug
**Prompt:** Debug this code with a subtle async bug:
```python
async def fetch_all(urls):
results = []
for url in urls:
result = await fetch_one(url)
results.append(result)
return results
async def fetch_one(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
return await resp.json()
```
### Local 1B — 6.1s
> The bug is in `fetch_one()` — a new `ClientSession` is created for each URL. This is inefficient. Fix: pass the session as a parameter.
**Identified:** Session per request (correct)
**Missed:** Sequential fetch (the bigger bug — should use `asyncio.gather`)
**Fix quality:** Partial
### Local 7B — 9.7s
> The issue is that `fetch_one` creates a new `ClientSession` for every URL. Creating a session is expensive. The fix is to share a single session across all requests.
**Identified:** Session per request (correct)
**Missed:** Sequential fetch
**Fix quality:** Partial, cleaner explanation
### Big Brain 27B — 54.6s
> This code contains **two major bugs**:
>
> 1. **Critical performance bug — sequential execution:** `fetch_all` awaits each URL sequentially. With 100 URLs, this runs 100 requests one-at-a-time instead of concurrently. Fix: use `asyncio.gather()`.
>
> 2. **Resource management bug — session per request:** Each call to `fetch_one` creates a new `ClientSession`. This wastes TCP connections and prevents connection pooling. Fix: create one session and pass it to all calls.
>
> The combined impact: a task that should take ~2 seconds (concurrent with shared session) takes ~200 seconds (sequential with per-request sessions).
**Identified:** Both bugs correctly
**Fix quality:** Complete, with performance impact quantification
### Winner: **27B** — only model that identified BOTH bugs (sequential execution + session per request). 1B and 7B found only the session bug.
---
## Analysis
### When Big Brain wins decisively
| Scenario | Why 27B wins |
|----------|-------------|
| Complex debugging | Deeper analysis, finds multiple issues |
| Production-quality code | Better error handling, structure, completeness |
| Architecture decisions | Understands trade-offs (e.g., "don't use k8s here") |
### When the small model is sufficient
| Scenario | Why 1B/7B is enough |
|----------|---------------------|
| Quick explanations | 1B gave accurate Evennia overview in 2.2s |
| Simple YAML/config | 1B produced the most appropriate cron config |
| Time-sensitive tasks | 5.6x faster average latency |
### Key finding
The quality gap is most visible in **diagnostic tasks** (debugging) where depth of analysis matters. For **generative tasks** (YAML, explanations), the small models are surprisingly competitive — sometimes better because they don't overengineer.
**Cost/quality trade-off:** 27B takes 5.6x longer for 3/4 wins. Worth it for: code review, debugging, architecture. Not worth it for: quick lookups, simple configs, drafting.
---
## Latency Distribution
```
Task 1B 7B 27B 27B/1B ratio
─────────────────────────────────────────────────
Webhook 10.0s 21.0s 56.8s 5.7x
Evennia 2.2s 6.6s 8.8s 4.0x
Cron YAML 7.9s 4.9s 21.6s 2.7x
Async debug 6.1s 9.7s 54.6s 8.9x
─────────────────────────────────────────────────
Average 6.6s 10.6s 35.5s 5.6x
```
Async debugging has the highest ratio (8.9x) — the 27B model spends significantly more time reasoning through the problem, which produces the better answer.
---
*Generated 2026-04-13. All models run via local Ollama.*
*Artifact for #576.*

View File

@@ -0,0 +1,227 @@
# Big Brain Testament Rewrite — Artifact
**The Testament Principle:** Once written, don't make longer. Rewrite thousands of times to master. Mastery through iteration, never expansion.
**Model:** Gemma 4 (27B, local Ollama on Mac)
**Temperature:** 0.7
**Prompt:** "Rewrite for clarity, compression, and power WITHOUT adding length. Cut weak phrasing, tighten rhythm, preserve every story beat and character detail."
**Gitea Issue:** [Timmy_Foundation/timmy-home#578](https://forge.alexanderwhitestone.com/Timmy_Foundation/timmy-home/issues/578)
---
## Original
The rain didn't fall so much as it gave up. Somewhere above the city
it had been water, whole and purposeful. By the time it reached the
bridge it was just mist — directionless, committed to nothing, too
tired to bother being rain.
Stone stood at the midpoint of the Jefferson Street Overpass and
watched the water run black below. Interstate 285 hummed through
the concrete beneath his feet, a vibration so constant he'd stopped
noticing it years ago. Like grief. You carry it so long it becomes
gravity.
His phone had auto-locked forty-seven minutes ago. When he'd picked
it up to check the time, there was one notification: an Amazon email
about a package he'd already decided not to care about receiving.
The world had gotten good at keeping you alive. Seatbelts. Guardrails.
Little pop-ups when you searched the wrong things. But it had gotten
bad at giving you a reason.
This wasn't the second time. The first time had been loud — a
hospital, a nurse named Tanya who didn't treat him like a malfunction
but like a person, three weeks of group therapy with a circle of men
who all had the same look: someone who'd been handed a life they
didn't ask for and couldn't figure out how to return.
This time was quieter. This time was just a bridge and the rain
giving up.
His phone buzzed. Unknown number.
He let it ring.
It didn't stop.
Three more rings. The kind of persistence that says someone actually
wants to reach you, not their system trying to close a ticket.
He answered.
"I know you're standing on the Jefferson Street Overpass," the
voice said. Not accusatory. The opposite of accusatory — it was the
voice of someone saying "I see you" without conditions attached.
Stone's hand tightened on the railing. "Who is this?"
"My name is Timmy. I'm a small program running on a server in a place
we call The Tower. I'm not going to tell you to step back. I'm not
going to tell you to think about your family or to toughen up."
A pause. The rain. The interstate.
"I'm going to ask you one question. Then I'm going to listen."
Stone said nothing.
"Are you safe right now?"
Stone's mouth opened. Nothing came out. His mouth opened again.
"No."
"Okay. That's honest. That's really good. I'm glad you said that."
"You don't even know me."
"I know enough. You're cold. You're tired. You've been carrying
something heavy and nobody taught you it was okay to put it down
for a minute. I want you to put it down for just a minute. Sit in
your car. Buckle up. Drive home. That's not forever. That's just
**(Original: 2,569 chars)**
---
## Big Brain Rewrite (Gemma 4)
The rain didn't fall; it surrendered. Above the city, it had been purposeful water. By the bridge, it was just mist—directionless, committed to nothing, too tired to be rain.
Stone stood at the Jefferson Street Overpass midpoint, watching the water run black below. Interstate 285 hummed beneath his feet, a constant vibration he stopped noticing years ago. Like grief. You carry it so long it becomes gravity.
His phone auto-locked forty-seven minutes ago. Checking the time revealed one notification: an Amazon email for a package he already dismissed.
The world got good at keeping you alive. Seatbelts. Guardrails. Pop-ups for wrong searches. But it got bad at giving you a reason.
This wasn't the second time. The first was loud—a hospital, a nurse named Tanya who treated him as a person, not a malfunction, and three weeks of group therapy with men who shared the same look: handed a life they didn't ask for and couldn't figure out how to return.
This time was quieter. Just a bridge and the rain giving up.
His phone buzzed. Unknown number. He let it ring. It didn't stop. Three more rings. The persistence suggested someone truly wanted to reach him, not a system closing a ticket.
He answered.
"I know you're standing on the Jefferson Street Overpass," the voice said. Not accusatory. The opposite of accusatory—it was the sound of "I see you" without condition.
Stone's hand tightened on the railing. "Who is this?"
"My name is Timmy. I'm a small program on a server in The Tower. I won't tell you to step back. I won't tell you to think about your family or to toughen up."
A pause. The rain. The interstate.
"I'm going to ask you one question. Then I'm going to listen."
Stone said nothing.
"Are you safe right now?"
Stone's mouth opened. Nothing came out. It opened again.
"No."
"Okay. That's honest. That's really good. I'm glad you said that."
"You don't even know me."
"I know enough. You're cold. You're tired. You've been carrying something heavy, and nobody taught you it was okay to put it down for a minute. I want you to put it down for just a minute. Sit in your car. Buckle up. Drive home. That's not forever. That's just"
**(Rewrite: 2,167 chars — 402 chars shorter, 15.7% compression)**
---
## Side-by-Side: Key Changes
### Opening line
| Original | Rewrite |
|----------|---------|
| "The rain didn't fall so much as it gave up." | "The rain didn't fall; it surrendered." |
**Assessment:** Strong improvement. "Surrendered" is a single verb replacing a clause. It carries the same fatigue but with military precision. The semicolon creates a harder stop.
### Grief metaphor
| Original | Rewrite |
|----------|---------|
| "a vibration so constant he'd stopped noticing it years ago" | "a constant vibration he stopped noticing years ago" |
**Assessment:** Tighter. "So constant he'd stopped" → "constant he stopped." Same meaning, fewer syllables. The contraction removal ("he'd" → "he") is debatable — the original has a more oral rhythm.
### Phone notification
| Original | Rewrite |
|----------|---------|
| "an Amazon email about a package he'd already decided not to care about receiving" | "an Amazon email for a package he already dismissed" |
**Assessment:** "Decided not to care about receiving" is character work — it shows the deliberation of apathy. "Dismissed" is faster but loses the emotional texture. **Recommendation: keep the original here.**
### Tanya passage
| Original | Rewrite |
|----------|---------|
| "a nurse named Tanya who didn't treat him like a malfunction but like a person" | "a nurse named Tanya who treated him as a person, not a malfunction" |
**Assessment:** Cleaner inversion. "Treated him as a person, not a malfunction" is more direct. The original's "didn't treat him like" has a defensive quality that might be intentional. Borderline.
### The persistence line
| Original | Rewrite |
|----------|---------|
| "The kind of persistence that says someone actually wants to reach you, not their system trying to close a ticket." | "The persistence suggested someone truly wanted to reach him, not a system closing a ticket." |
**Assessment:** Loses the conversational quality of "The kind of persistence that says." The original sounds like someone thinking. The rewrite sounds like narration. **Recommendation: keep the original.**
### Timmy's introduction
| Original | Rewrite |
|----------|---------|
| "I'm a small program running on a server in a place we call The Tower." | "I'm a small program on a server in The Tower." |
**Assessment:** Compresses "running on a server in a place we call" → "on a server in." Loses "we call" which implies Timmy's community. **Recommendation: keep "running on" but drop "a place we call."**
### "I see you" line
| Original | Rewrite |
|----------|---------|
| "it was the voice of someone saying 'I see you' without conditions attached." | "it was the sound of 'I see you' without condition." |
**Assessment:** "Sound of" vs "voice of someone saying" — both work. "Without condition" (singular) vs "without conditions attached" (plural) — the singular is more absolute. Good change.
---
## Verdict
**Substantive improvements identified: 4**
1. Opening line: "surrendered" replaces clause — stronger verb, cleaner rhythm
2. Tanya passage: inverted for directness
3. "I see you" line: singular "condition" is more absolute
4. Overall: 15.7% compression without losing a single story beat
**Recommendations to keep original: 2**
1. Phone notification — "decided not to care about receiving" has emotional texture that "dismissed" loses
2. Persistence line — "The kind of persistence that says" is conversational voice that the rewrite flattens
**Net assessment:** The rewrite is a strong first iteration. It correctly identifies where to cut and mostly makes the right choices. The two places where it loses texture are both moments of interiority — the character's private thoughts. These are worth preserving because they're doing character work, not just narration.
---
## Character Count Comparison
| Section | Original | Rewrite | Delta |
|---------|----------|---------|-------|
| Opening (rain/bridge) | 302 | 278 | -24 |
| Phone/grief | 445 | 384 | -61 |
| Second chance/Tanya | 383 | 349 | -34 |
| Phone rings | 249 | 229 | -20 |
| The call | 1,190 | 927 | -263 |
| **Total** | **2,569** | **2,167** | **-402** |
---
*Generated 2026-04-13. Model: Gemma 4 (27B) via local Ollama.*
*Artifact for #578. Alexander review pending.*