docs: record the Slice 0.9 quest system and its deviations

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Bastian Wagner
2026-08-22 23:36:42 +02:00
parent 3a84fd5371
commit 70ee207f1f
2 changed files with 172 additions and 12 deletions

View File

@@ -1354,13 +1354,22 @@ Erster NPC: **Borin, Quartermaster** an `south-gate` (Graufurt) mit
DIALOGUE + MERCHANT + RESOURCE_EXCHANGE gleichzeitig — der Kompositionsfall
aus §2/§26 in echt.
## Abweichung 1 — kein `NpcQuestAssignment`
## Abweichung 1 — kein `NpcQuestAssignment` — **aufgelöst in Slice 0.9**
§34 listet es im V1-Scope, aber es gibt noch kein Questsystem (Slice 0.9).
§34 listet es im V1-Scope, aber es gab in Slice 0.8 noch kein Questsystem.
Eine Tabelle mit Fremdschlüssel auf eine nicht existierende `quests`-Tabelle
ist nicht baubar, und ein `questKey`-String ohne Validierung wäre spekulative
Architektur (AGENTS §1.7). Nachzuholen mit Slice 0.9, zusammen mit den
Dialog-Actions `START_QUEST` / `COMPLETE_QUEST`.
Architektur (AGENTS §1.7).
Slice 0.9 hat das nachgeholt: `NpcQuestAssignment` liegt unter
`apps/api/src/quests/entities/npc-quest-assignment.entity.ts` und trägt die
Rollen `OFFER` / `TURN_IN` / `PROGRESS` aus §14. Der Unique-Index umfasst die
Rolle mit, damit derselbe NPC eine Quest sowohl vergeben als auch annehmen
kann — genau der Fall aus §14, den die erste Questkette benutzt: der South
Gate Warden vergibt und nimmt an, Borin steht als `PROGRESS` dazwischen.
Die Dialog-Actions `START_QUEST` / `COMPLETE_QUEST` bleiben offen, siehe
Abweichung 4.
## Abweichung 2 — kein `NpcDialogueProfile`
@@ -1372,14 +1381,52 @@ neu zu schreiben.
## Abweichung 3 — Conditions, die (noch) nichts beantworten kann
`GameConditionType` enthält die vollständige V1-Liste aus §19, aber nur
`REGION_REPUTATION`, `WORLD_RENOWN`, `FLAG_SET` und `HAS_ITEM` sind
auswertbar. `QUEST_ACTIVE`, `QUEST_COMPLETED`, `BOSS_DEFEATED` und
`LOCATION_DISCOVERED` haben noch kein System dahinter.
`GameConditionType` enthält die vollständige V1-Liste aus §19, aber nicht jeder
Typ hat ein System dahinter.
Sie werten **fail-closed** aus, also immer "nicht erfüllt". Für ein Gate ist
die sichere Richtung eines Fehlers zu, nicht offen — ein Quest-Gate darf
niemals aufgehen, nur weil es keine Quests gibt.
Seit Slice 0.9 auswertbar: `REGION_REPUTATION`, `WORLD_RENOWN`, `FLAG_SET`,
`HAS_ITEM`, `QUEST_ACTIVE`, `QUEST_COMPLETED`. Offen bleiben `BOSS_DEFEATED`
(Slice 0.11) und `LOCATION_DISCOVERED` (wird pro Charakter nicht getrackt).
Die beiden Quest-Typen werten boolesch aus wie `FLAG_SET`, nicht numerisch:
eine Quest läuft oder nicht, `actual` bleibt `null`. `value: false` ist die Art,
wie Content die Verneinung ausdrückt — die Angebotszeile des Wardens braucht
"noch nicht begonnen und noch nicht abgeschlossen", ohne dass die
Condition-Sprache ein NOT bekommt.
Die verbleibenden Typen werten **fail-closed** aus, also immer "nicht erfüllt".
Für ein Gate ist die sichere Richtung eines Fehlers zu, nicht offen.
## Abweichung 4 — Questschritte laufen nicht über Dialog-Actions
`DialogueActionType` enthält `START_QUEST` und `COMPLETE_QUEST`, aber beide
bleiben auch mit dem Questsystem aus Slice 0.9 wirkungslos.
Sie auszuführen bräuchte zwei Dinge, die es nicht gibt: einen Endpoint, über
den der Client eine Dialog-Response *auswählt*, und einen Action-Executor
dahinter. Playable Slice 0.9 §10 verlangt ausdrücklich einen
schrittbasierten Questautomaten und verbietet eine verzweigende
Narrative-Engine — und ein Response-Endpoint plus Executor wäre genau deren
Anfang, für eine Kette mit fünf Schritten.
Stattdessen:
```text
POST /api/npcs/:npcKey/quests/:questKey/accept
POST /api/npcs/:npcKey/quests/:questKey/advance
```
Keine der beiden Routen nimmt einen Body. Welcher Schritt gerade dran ist, was
er vergibt und was er verbraucht, entscheidet der Server (AGENTS §5). Der Satz,
den der NPC zu einem Schritt sagt, ist Content auf dem `QuestObjective`
(`npc_line`) und kommt mit der Antwort zurück.
Nachzuholen wäre das, sobald ein NPC wirklich verzweigende Antworten braucht:
dann ein Response-Auswahl-Endpoint, ein Executor über
`SUPPORTED_DIALOGUE_ACTIONS`, und die Questschritte können auf Dialog-Actions
umziehen, ohne dass die Questtabellen sich ändern.
---
## Offene Designfrage — World Renown im Tausch
@@ -1398,3 +1445,40 @@ anzufassen.
Falls Renown später doch als Punktwährung gedacht ist, muss das zuerst in
0.6.5 geändert werden — nicht hier.
---
# 41. Implementierungsstand (Slice 0.9)
```text
QuestDefinition / QuestObjective apps/api/src/quests/entities/
NpcQuestAssignment Rollen aus §14
CharacterQuest Player-State, ein Row pro Charakter/Quest
QuestProgressService leitet den aktiven Schritt ab (lesend)
QuestService accept / advance, Effekte, Belohnungen
QuestController / NpcQuestController apps/api/src/quests/quest.controller.ts
```
Erste Questkette: **Trouble Beyond the Gate**, vergeben und angenommen von
Halvik am Südtor, mit Borin als Zwischenschritt — der Mehr-NPC-Fall aus §14
in echt.
Zwei Module statt einem: `QuestProgressModule` enthält nur den lesenden Teil,
weil `NpcService` Questmarker braucht und `QuestService` umgekehrt `NpcService`
für die Erreichbarkeitsprüfung. Der Schnitt hält den Modulgraphen zyklenfrei,
ohne `forwardRef`.
Es gibt bewusst **keine** Tabelle für Objective-Fortschritt. Slice 0.9 §11
verlangt, dass Sammelschritte ihren Stand aus dem aktuellen Besitz ableiten;
genau das macht `QuestProgressService`, und dadurch fallen die Softlock-Fälle
aus §11 von selbst weg, statt einzeln behandelt zu werden.
Was ein Schritt *tut*, steht als Content auf dem `QuestObjective`
(`sets_flag_key` / `sets_flag_npc_key`, `grants_loot_bag_key`,
`consume_on_complete`, `advance_when_blocked`). `QuestService` wendet an, was
es vorfindet, und verzweigt nirgends auf einen Questkey (AGENTS §9).
Markerregeln für die Ortsansicht (§24, Slice 0.9 §12): höchstens ein
Questmarker pro NPC, Reihenfolge `QUEST_TURN_IN` > `QUEST_AVAILABLE` >
`QUEST_IN_PROGRESS`. Halvik hält für dieselbe Quest zwei Rollen; ohne
Vorrangregel trüge er zwei Symbole und keines würde etwas aussagen.

View File

@@ -314,9 +314,85 @@ Hide Capacity: 5
- [ ] Quest grants no XP.
- [ ] All dialogue/UI text is English.
### Verification status
Every criterion above is covered by automated tests: the quest chain in
`quest-state.spec.ts` and `quest.service.spec.ts`, the content in
`vertical-slice.seed.spec.ts`, the markers in `npc.service.spec.ts`, and the
three UI requirements of §12 in the web specs for the journal, the NPC screen
and the location hotspots.
The boxes stay unticked because two things have not been run: the migration and
seed against a real PostgreSQL database, and a hand-played pass through the
chain in the running app. Neither was possible in the environment this slice was
built in — no database was reachable. Do both before ticking, following the
walkthrough in the implementation plan
(`docs/superpowers/plans/2026-08-22-first-quest-and-bag-tutorial.md`, Task 13).
---
## 15. Out of Scope
## 15. Implementation Decisions
Recorded here rather than only in the implementation plan, so the next slice
reads them from the specification.
### D1 — The Basic Hide Bag is granted, not sold
§6 has Borin say "Take this" while also describing the Slice 0.8.5 offer, which
costs 35 Silver. A player standing at that step has close to none, so the
purchase path alone would stall the chain.
Borin's quest step therefore hands the bag over directly and idempotently. The
referral flag is still written to the character's Borin state, so the 0.8.5
`bypassConditions` gate opens the offer as well — a visible consequence, and a
way back to a bag that is somehow lost. Nothing about reputation changes.
### D2 — No World Renown milestone
§9 lists one as optional. The demo character sits at Renown 1 and the first
trade-in takes them to 2; a quest milestone would reach 3 and unlock Borin's
Bandit Blade, which Slice 0.8.5 deliberately placed out of reach until Slice
0.11. The quest pays regional reputation instead: **+10 Border Watch**.
### D3 — No Silver reward
§9 warns against a Silver reward that undermines the merchant trade loop, and
names the bag and system knowledge as the point of the chain. `rewardSilver` is
a content column set to 0, so retuning it later is a seed change.
### D4 — Quest steps use their own endpoints
NPC spec §40 asked Slice 0.9 to also wire the `START_QUEST` / `COMPLETE_QUEST`
dialogue actions. There is no dialogue-response endpoint at all, and building
one plus an action executor is the branching narrative engine §10 rules out.
Steps run through `POST /api/npcs/:npcKey/quests/:questKey/{accept,advance}`.
Neither takes a body — which step is current, what it grants and what it
consumes are the server's to decide. The line an NPC speaks for a step is
content on the objective. Recorded as Abweichung 4 in the NPC specification.
### D5 — The demo character starts with no loot bag
The vertical-slice seed handed over a free Basic Hide Bag as a stopgap between
Slices 0.8.5 and 0.9, with a note to remove it once a quest could grant one.
That is done: the bagless HIDE capacity of 1 is the whole premise of §4. The
migration also clears that one row for the demo character on databases seeded
before this change.
### Addition — a hint for the second pelt hunt
Not in the specification, but the same courtesy §4 asks for. The Hide Bag holds
five of *any* hide, so Tough Hides can crowd out the fifth Ashen Pelt. That step
must not give up the way the first one does — it is meant to be finished (§7) —
so it carries a hint instead:
```text
Your hide bag is full of other goods. Trade some to Borin to make room for pelts.
```
---
## 16. Out of Scope
Do not implement: