Thirteen TDD tasks from the approved design: schema, the guard and
enrage combat abilities, the discovery service and its two enforcement
points, the seed content, the derived artwork, and the two frontend
surfaces. Each task carries its own failing test, the code to make it
pass and a commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012NjEPjZ8R8e9c23z3vR8bt
Records the seven decisions taken while brainstorming the slice: a
per-character discovery table gating the Ash Pit route, a stub Ash Pit
so the route has a real target, guard and enrage as content-driven
combat abilities, a POI rather than a quest as the investigation, two
new trade goods above the Burned Road tier, a dedicated
WorldDiscoveryService, and the deliberate crossing of the two raider
artwork files so each enemy matches its role.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012NjEPjZ8R8e9c23z3vR8bt
Commit 9391645 lowered the three common guaranteed trade goods from a certain
drop to 60% and left the rare Charred Raider Insignia at 100%, but the seed
test kept asserting 1.0000 for all four and has been failing since.
Asserts the real per-item value instead of one shared constant, so the next
retune fails on the number that actually changed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
master turned the gate watch into painted scenery with its own portrait and
result image; Slice 0.9 turns that same hotspot into a real NPC. Kept the
NPC doorway and dropped the result text and image with it -- a hotspot
carries one or the other, never both.
The warden now uses master's `graufurt-gate-watch.png` instead of the raw
copy this branch added: same figure, already optimized. Borin keeps master's
optimized portrait for the same reason.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds a hint to the second pelt hunt: the Hide Bag holds five of any hide,
so Tough Hides can crowd out the fifth pelt with no way forward shown.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The demo character no longer starts with a free Basic Hide Bag: the
bagless HIDE capacity of 1 is what the quest chain exists to teach.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AGENTS.md §33 and this branch's own constraint are English-only, and the notes
were the one German document left. Content and structure are unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The five offer tests this branch added all read the SHOP_OFFERS constant; none
ran the seed, so the one guarantee the stable-id design exists to provide --
re-seeding does not duplicate content -- was covered by nothing. This runs
`seedVisibleVerticalSlice` twice against an in-memory shop-offer repository and
asserts the five stable ids survive.
Confirmed catchable: reverting the conflict target to
['shopId', 'itemDefinitionId'] leaves 4 rows under the fake, because both bag
offers carry `itemDefinitionId: null` and collapse into one. On Postgres it
would fail outright.
The prices (12/30/40/35/60) and thresholds (reputation 25 and 40, renown 3) are
pinned in the same test: AGENTS.md §39 forbids silent rebalancing, and the
structural tests never looked at a number.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Case 7 ("price is still required even when the reputation condition is met")
and case 3 ("sufficient regional reputation allows purchase") both built
fixtures with `conditions: []`. With no requirement present, none can be met,
so neither test touched the gate it was named after. Both now carry a
satisfied REGION_REPUTATION condition.
Case 4 ("insufficient World Renown blocks purchase") had no test at all. It
matters because a renown block must surface as SHOP_OFFER_LOCKED rather than
MERCHANT_REPUTATION_TOO_LOW -- renown is not the merchant's regard, and
telling the player to go and earn reputation would point at the wrong bar.
Verified by widening the reputation-blame check to include WORLD_RENOWN, which
fails the new test alone.
Also pins a bag offer's description to empty, so the duplicate capacity line
cannot come back through the API side.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`purchase` listed a shop's offers with no ordering while `getShopView` orders
by `sortOrder`, so the two paths answered "which offer does this key mean" by
different rules, one of them at the database's discretion. Harmless today
because item and bag keys are disjoint, but not a difference worth keeping.
The faction lookup behind requirement labels also read every faction while the
condition engine only matches enabled ones, so a gate on a disabled faction
would have shown that faction's name next to a requirement the engine treats
as unmeetable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`resolveTarget` gave a bag target the same string for `description` and
`effectSummary`, and the shop row renders both, so the slice's two flagship
offers showed "Capacity: 5 Raider Trophies" on consecutive lines. Spec §5's
worked example shows it once.
A bag definition carries no flavour text of its own, so the description is now
empty and the row omits the span entirely rather than emitting an empty one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Lint was intentionally deferred through tasks 1-7 to keep each task's
diff scoped. Running it now only reformats line-wrapping in the four
shop/seed files this slice already modified; the ~86 pre-existing
problems in npcs, travel, the e2e spec and elsewhere are untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The server always sends both fields; an optional frontend type only
dodged a transient gap in merchant.store.spec.ts's shopView() fixture
and risked a requirement line silently failing to render if a future
fixture omitted the fields. Add the two fields to that single fixture
instead (an open offer, so null/[] are the honest values) and drop the
now-unnecessary `?? []` tolerance in the template.
The migration spec only asserted SQL substrings against a mocked
QueryRunner and had no getMetadataArgsStorage() check that ShopOffer's
column options and partial unique indexes actually match the new
schema. Since ts-jest does not type-check in this package
(isolatedModules: true), this is the only automated guard against
entity/migration drift -- matches the house convention in
npc-system.migration.spec.ts and loot-bags.migration.spec.ts.