Files
ashen-realms/docs/superpowers/plans/2026-08-22-slice-0.8.5-research-notes.md
Bastian Wagner 81e44b2c15 docs: record 0.8.5 acceptance and the 0.9 referral mechanism
Ticks Slice 0.8.5's acceptance criteria against the implemented
behavior, verified against the seed and service code rather than
assumed, and notes that the Bandit Blade's World Renown 3 gate is
deliberately unreachable until Slice 0.11 adds the milestones to
reach it.

Points Slice 0.9 at the concrete bypass mechanism that now exists
(BORIN_OFFER_IDS.hideBag's bypassConditions flag) instead of the
placeholder reference to "the quest/referral exception".

Also commits the slice's plan and research-notes documents, which
were untracked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 20:43:53 +02:00

5.7 KiB

Slice 0.8.5 — Rechercheergebnisse (Vorstufe zum Plan)

Spec: docs/playable-slices/0.8.5-Reputation-Gated-Merchant-Offers.md Branch/Worktree: slice/0.8.5-reputation-gated-merchant-offers in .claude/worktrees/slice-0.8.5-reputation-gated-offers Baseline: API 410 Tests / 49 Suites grün, Web 292 Tests / 27 Suites grün.

Was Slice 0.8 bereits geliefert hat

Der Großteil der Mechanik existiert schon — 0.8.5 ist überwiegend Content plus Präsentation, nicht Neubau.

  • apps/api/src/conditions/game-condition.types.tsGameConditionType mit REGION_REPUTATION, WORLD_RENOWN, FLAG_SET, HAS_ITEM als SUPPORTED_CONDITION_TYPES; ComparisonOperator + compare().
  • apps/api/src/conditions/game-condition.service.tsevaluate() (UND-Logik, fail-closed) und describe() (pro Bedingung {condition, met}). describe() hat noch keinen Aufrufer außer seinem eigenen Spec — es wurde explizit für 0.8.5 gebaut.
  • shop_offers.conditions (jsonb) existiert in Migration 1795000000000-CreateNpcSystem.ts; Seed setzt überall conditions: [].
  • ShopService.getShopView() liefert bereits unlocked + affordable; purchase() wirft SHOP_OFFER_LOCKED (403) serverseitig. Frontend rendert „Locked“/„Too costly“ und mappt SHOP_OFFER_LOCKED in merchant.store.ts.

Fazit: „Server rejects purchases when requirements are not met“ (§11) ist faktisch schon erfüllt und durch shop.service.spec.ts getestet. Fehlend sind Content, Anforderungs-Text/Fortschritt in der UI, das Bag-Verkaufsformat, der Referral-Bypass und das Unlock-Feedback.

Zentrale Architektur-Hürde: Taschen sind keine Items

shop_offers.item_definition_id ist NOT NULL mit FK auf item_definitions. Die beiden Vorzeige-Angebote der Spec (§4) sind aber LootBagDefinition-Zeilen (apps/api/src/loot-bags/entities/loot-bag-definition.entity.ts) — bewusst keine Items (0.7.5 §6: nie ausgerüstet, kein Loot, keine Kampfwerte). Der 0.8-Seed sagt das wörtlich voraus:

„a bag is a LootBagDefinition rather than an ItemDefinition, so selling one needs an offer shape this slice has no reason to build.“

Nötige Migration: item_definition_id nullable machen, loot_bag_definition_id (nullable, FK → loot_bag_definitions, ON DELETE RESTRICT) ergänzen, CHECK „genau eine der beiden gesetzt“, und der Unique-Index IDX_shop_offers_shop_item braucht ein Gegenstück für Taschen. ShopService muss beim Kauf einer Tasche eine CharacterLootBag-Zeile anlegen statt CharacterItem zu stapeln — idempotent, weil IDX_character_loot_bags_character_definition unique ist und 0.9 §11 „bag grant is idempotent“ verlangt.

Getroffene Entscheidungen (vom Nutzer bestätigt)

  1. Starter-Bags im Seed: Nur die Trophy Pouch aus dem Demo-Seed entfernen (vertical-slice.seed.ts:376-392). Die Hide Bag bleibt vorerst geseedet, bis 0.9 sie über die Quest-Referral vergibt.
  2. Hide-Bag-Gate: Ruf-Gate oder Referral-Flag (referred-by-south-gate-warden, 0.9 §5). Braucht ein klar benanntes Bypass-Feld am Angebot — ausdrücklich keine generische Regel-Engine (§3, §7, §11 „No generalized rules engine“). Vorschlag: shop_offers.bypass_conditions (jsonb) mit ODER-Semantik gegen conditions — kleinste Erweiterung, die den Ausnahmefall trägt.
  3. Drittes Angebot: Bandit Blade (existiert bereits als ItemDefinition, ITEM_IDS['bandit-blade'], weaponDamage 11 / +1 Attack) hinter WORLD_RENOWN >= 3.

Balancing-Anhaltspunkte

  • Exchange zahlt 2/3/5/12 Ruf pro Ware (npc-content.ts EXCHANGE_RULES).
  • Reputationsränge: 0 Stranger, 100 Tolerated, 250 Known, 500 Recognized, 800 Trusted, 1200 Esteemed (reputation-rank.ts). Die Gates sind numerisch, nicht rangbasiert — die Spec-Beispiele nennen „Reputation 25“.
  • Renown: Character.renown, aktuell nur ein Milestone (first-goods-returned, +1). Offen: Renown 3 ist mit dem heutigen Content nicht erreichbar — entweder niedrigerer Schwellwert oder das Angebot bleibt bis 0.11 sichtbar gesperrt (was §5 „visible rewards create goals“ sogar entspricht).
  • Spec-Beispiel §5 wörtlich: Trophy Pouch, 40 Silber, Requires Ashen Fields Reputation 25.

Betroffene Dateien

Datei Rolle
apps/api/src/database/migrations/1796*-SellableLootBags.ts neu: Bag-Angebote + Bypass-Spalte
apps/api/src/shops/entities/shop-offer.entity.ts nullable Item-FK, Bag-FK, bypassConditions
apps/api/src/shops/shop.service.ts Bag-Grant, ODER-Bypass, Anforderungs-Beschreibung via describe()
apps/api/src/shops/shop.errors.ts MERCHANT_REPUTATION_TOO_LOW (§6) statt/neben SHOP_OFFER_LOCKED
apps/api/src/database/seeds/npc-content.ts drei gesperrte Angebote
apps/api/src/database/seeds/vertical-slice.seed.ts:376 Trophy Pouch aus Demo-Seed nehmen
apps/web/src/app/core/api/game-api.models.ts:433 requirements am ShopOfferView
apps/web/src/app/features/npc/merchant-page.component.{html,scss} Anforderung + aktueller Wert je Zeile
apps/web/src/app/features/npc/merchant.store.ts Unlock-Feedback nach Trade (§9)

Noch offen vor dem Plan

  • Wortlaut/Träger des Unlock-Feedbacks (§9): Der Store lädt den Shop nach einem Trade ohnehin neu (merchant.store.ts:209-212), also lässt sich ein Vorher/Nachher-Vergleich der unlocked-Flags lokal ziehen — ohne neues Server-Event. Das ist die kleinste Lösung und braucht keinen Endpoint.
  • Ob MERCHANT_REPUTATION_TOO_LOW den bestehenden SHOP_OFFER_LOCKED ersetzt oder ergänzt. Ersetzen bricht den vorhandenen Frontend-Mapping-Eintrag und einen Test; ergänzen (spezifischer Code, wenn die verletzte Bedingung eine Reputationsbedingung ist) erfüllt §6 wörtlich ohne Regression.