Assistant
big-pickle | agent=general | finish=stop
▶ Reasoning
Now I have all the information needed. Let me compile a thorough summary.
## Complete API Research for `/opt/bagger-api/`
---
### 1. All Endpoints (Routes)
This is a **raw HTTP server** (not Flask — it uses `http.server.BaseHTTPRequestHandler`). Routes are defined in `app.py:61-139` via string matching on `self.path`.
**GET endpoints:**
| Path | Handler | Response |
|---|---|---|
| `GET /api/health` | `app.py:65-66` | `{"ok": bool, "model": str, "routeErrors": list}` |
| `GET /api/game-data` | `app.py:68-69` | Returns `public_game_data()` — full game data object |
| `GET /api/save/<token>` | `app.py:71-73` | `{"ok": true, "token": str, "state": {...game_state}}` |
| `GET /api/debug/routes` | `app.py:79-80` | `{"errors": list, "data": ROUTES}` (loopback only) |
| `GET /api/debug/state/<token>` | `app.py:82-84` | Same as `/api/save/<token>` (loopback only) |
**POST endpoints:**
| Path | Handler | Request Body | Response |
|---|---|---|---|
| `POST /api/interact` | `app.py:97-98` | `{"state": {...}, "intent": {...}}` | Full interact response (see section 4) |
| `POST /api/new-game` | `app.py:100-101` | `{"player": {"name","address","style"}}` | `{"ok": true, "state": {...default_state}}` |
| `POST /api/save` | `app.py:103-105` | `{"state": {...}, "token": str?}` | `{"ok": true, "token": str}` |
| `POST /api/restore` | `app.py:107-109` | `{"token": str}` | `{"ok": true, "token": str, "state": {...}}` |
| `POST /api/debug/simulate` | `app.py:115-116` | `{"route": str, "strategy": str, "steps": int}` | `{"state": {...}, "history": [...], "ending": {...}}` |
| `POST /api/debug/resolve-ending` | `app.py:118-121` | `{"state": {...}, "route": str}` | `{"ending": {...}}` |
| `POST /api/debug/explain-scene` | `app.py:123-128` | `{"state": {...}, "route": str, "intent": str}` | `{"selected": node, "calendarEvent": event, "rejected": [...]}` |
---
### 2. The `resolve_interaction` function (the core interact handler)
**Signature** (`game_engine.py:644`):
```python
def resolve_interaction(payload):
state = normalize_state(payload.get("state") or payload)
intent = payload.get("intent") or {...}
```
**Returns** (`game_engine.py:689-705`):
```python
{
"state": state, # full updated game_state
"scene": node, # the selected scene node
"calendarEvent": event | None, # matching calendar event if any
"reply": ai["reply"], # AI-generated dialogue string
"emotionalRead": ai["emotionalRead"],# short interpretation string
"deltas": { # relationship changes this step
"bondDelta": int, "trustDelta": int,
"warmthDelta": int, "depthDelta": int, "courageDelta": int
},
"routePressure": { # pressure deltas per pressure type
"toward_romance": int, "toward_friendship": int,
"needs_repair": int, "opens_secret": int,
"toward_crisis": int, "toward_lockin": int
},
"memory": str, # new memory line
"visual": str, # visual tone (listening|shy|proud|guarded|digging|crisis|confession)
"ending": {...} | None, # ending object if resolved
"endingProse": str | None, # ending prose text
"locationFit": str, # "good"|"neutral"|"poor"
"commitmentScore": {route: int}, # per-route commitment
"activePromises": [...], # unkept/unbroken promises
"traceId": str, # trace hex id
}
```
---
### 3. `game_state` Structure
Created in `default_state()` (`game_engine.py:51-97`):
```python
{
"schemaVersion": 2,
"player": {"name": str, "address": "du", "style": str},
"day": int, # 1-30
"periodIndex": int, # 0=Morgen, 1=Nachmittag, 2=Abend, 3=Nacht
"currentRoute": str, # "aurora"|"brummbert"|"mira"
"lockedRoute": str | None,
"relationships": {
route: {
"bond": int, # 0-100
"trust": int,
"warmth": int,
"depth": int,
"courage": int,
"mood": str, # emotional tone descriptor
"memories": [str], # up to 24 memory strings
"dates": int,
"neglect": int, # 0-99
}
},
"playerStats": {
"mechanics": int, "charm": int, "patience": int,
"courage": int, "focus": int, "fatigue": int
},
"flags": [str], # story/gameplay flags
"inventory": {item_id: count},
"currency": int, # starts at 20
"backlog": [...], # dialogue log (last 300 entries)
"eventsSeen": [str], # event/scene IDs seen
"calendarState": {"missed": [str]},
"routePressure": {
route: {
"toward_romance": int, "toward_friendship": int,
"needs_repair": int, "opens_secret": int,
"toward_crisis": int, "toward_lockin": int
}
},
"commitmentScore": {route: int},
"promises": [
{"id": str, "route": str, "label": str, "createdDay": int,
"dueDay": int|None, "duePeriod": str|None, "sceneId": str|None,
"kept": bool, "broken": bool}
],
"endingState": None | {"route": str, "kind": str, "label": str, "resolvedAt": int, "prose": str},
"settings": {
"textSpeed": 35, "autoSpeed": 3000,
"skipMode": "read", "nvlMode": "auto", "showFreeText": True
},
"debugMeta": {"lastTraceId": str | None}
}
```
---
### 4. Dialogue/Event Response Format
The **scene node** structure (from `make_node` in `game_data.py:244-263`):
```python
{
"id": str, # e.g. "aurora_intro_puddle"
"route": str,
"category": str, # intro|daily|date|threshold|romance|crisis|repair|friendship|secret|finale
"chapter": str, # e.g. "Prolog", "Daily Lamp"
"premise": str, # short narrative premise
"location": str, # location ID
"periodAffinity": [str], # which periods this can appear in
"requiredFlags": [{"flag": str, ...}], # conditions to unlock
"blockedByFlags": [str], # flags that block this
"statHints": {stat: int}, # suggested min stats
"motifs": [str], # narrative motifs
"choiceSet": [str], # which choice set to show (default: ["sincere","careful","bold"])
"routePressureEffects": {pressure: int}, # pressure deltas from this node
"setsFlags": [str], # flags set by completing this scene
"translationStyleNotes": {...}
}
```
The **calendar event** structure (`game_data.py:139-148`):
```python
{
"id": str, # e.g. "rain_day_3"
"day": int, # 3
"period": str, # "Morgen"|"Nachmittag"|"Abend"|"Nacht"
"label": str, # "Rain at the maintenance gate"
"location": str, # location ID
"tags": [str], # ["rain", "quiet"]
"known": bool, # visible to player?
"route": str | None, # optional route filter
"repeatable": bool | None, # can repeat?
}
```
The **choice set** structure (`game_data.py:306-362`):
```python
{
"id": str, # e.g. "sincere"
"label": str, # "Aufrichtig antworten"
"message": str, # the actual player line text
}
```
---
### 5. Settings Schema
From `default_state` in `game_engine.py:89-95`:
```python
{
"textSpeed": int, # 35 (ms per char, presumably)
"autoSpeed": int, # 3000 (ms auto-advance)
"skipMode": str, # "read" (skip only read text)
"nvlMode": str, # "auto"
"showFreeText": bool, # True
}
```
---
### 6. Calendar/Event/Schedule Structures
**Calendar** (`game_data.py:139-148`): An array of events keyed by day/period, some route-scoped. Fields: `id`, `day`, `period`, `label`, `location`, `tags`, `known`, `route` (optional), `repeatable` (optional).
**Schedule Actions** (`game_data.py:121-129`):
```python
{
"work": {"label": str, "currency": int, "fatigue": int, "stats": {...}},
"study": {"label": str, "currency": int, "fatigue": int, "stats": {...}},
"courage": {"label": str, "currency": int, "fatigue": int, "stats": {...}},
"scrap": {"label": str, "currency": int, "fatigue": int, "stats": {...}, "finds": [item_ids]},
"rest": {"label": str, "currency": int, "fatigue": int (negative), "stats": {...}},
"charm": {"label": str, "currency": int, "fatigue": int, "stats": {...}},
"focus": {"label": str, "currency": int, "fatigue": int, "stats": {...}},
}
```
**Endings** (`game_data.py:294-303`): Per-route map of kind → `{"label": str, "priority": int}`. Kinds: `bad`, `missed`, `friendship`, `normal`, `true`, `secret`. Resolved ending object: `{"route": str, "kind": str, "label": str, "resolvedAt": int, "prose": str}`.
**Route Pressures** (`game_data.py:150`): `["toward_romance", "toward_friendship", "needs_repair", "opens_secret", "toward_crisis", "toward_lockin"]`
**Periods** (`game_engine.py:26`): `["Morgen", "Nachmittag", "Abend", "Nacht"]` — 4 periods per day, 30 days max.