Skip to content
1 change: 1 addition & 0 deletions PR-PROMPT.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
> - Never use `--admin` to force the merge of a PR
> - **Always verify GitHub account before PR operations** - Contributor opens PRs, Maintainer - CLAUDE.local.md for account details
> - All changes need to be done via feature branch and merged via PR
> - If ALL PR's and ISSUES are closed stop with the message "*!ALL WORK DONE!*"
</EXTREMELY-IMPORTANT>

As a `Maintainer`/Team Lead monitor for open PRs and perform autonomous code reviews according to repo process - particularly around GH and GIT account usage (see CLAUDE.local.md). If fixes are required use git worktrees. PR should only be merged using automatically and after passing all CI checks.
Expand Down
10 changes: 6 additions & 4 deletions PROMPT.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@
> - Read CLAUDE.md and AGENTS.md before starting
> - Never use `--no-verify` to bypass git-hook verifications - always fix the root cause
> - Never use `--admin` to force the merge of a PR
> - **MANDATORY: Verify BOTH git config AND GitHub CLI account before ANY PR operations**
> - **MANDATORY: Verify BOTH git config AND GitHub CLI account before ANY PR operations using `CLAUDE.local.md`**
> - Run `git config user.email` AND `gh auth status` to verify correct account
> - Contributor (martincjarvis) opens PRs and commits code
> - Maintainer (mcj-coder) reviews and merges PRs
> - Contributor opens PRs and commits code
> - Maintainer reviews and merges PRs
> - **CRITICAL: Use `gh auth switch --user <username>` to change accounts** - NEVER use `gh auth login` (requires manual interaction)
> - See CLAUDE.local.md "CRITICAL: Verify Both Git and GitHub CLI Credentials" section
> - All changes need to be done via feature branch and merged via PR
> - Work as `Contributor` implementing code changes and raising PR's. Do not review PR's unless explicitly prompted by the user - just monitor active PR and Issues for comment feedback.
> - If PR is opened with `Maintainer` GH credentials, close PR and re-create with `Contributor` PR
> - **BLOCKING: Active PR Polling Loop - DO NOT STOP**
> - **MUST run at the start of EVERY iteration**: `gh pr list --author martincjarvis --state open`
> - **MUST run at the start of EVERY iteration**: `gh pr list --state open`
> - **IF open PRs exist**: Execute polling loop (DO NOT proceed to new tasks)
> - **Polling loop actions** (repeat until PR is merged/closed):
> 1. Check PR status: `gh pr view <PR#> --json state,reviewDecision,mergedAt,comments`
Expand All @@ -27,6 +28,7 @@
> 7. IF merged/closed: Proceed to next task
> - **DO NOT exit polling loop** until PR is merged or closed
> - **DO NOT assume** Maintainer will notify you - actively poll for updates
> - If ALL PR's and ISSUES are closed stop with the message "*!ALL WORK DONE!*"

</EXTREMELY-IMPORTANT>

Expand Down
235 changes: 235 additions & 0 deletions docs/plans/2026-01-19-price-calculator-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
# PriceCalculator Rule Design

> **Context:** Epic 5.4 - Issue #43
> **Purpose:** Calculate item prices based on quality and basic market conditions

## Overview

PriceCalculator is a stateless rule that calculates item prices using:

1. Base price (from item/recipe definition)
2. Quality multiplier (matching GDD value tiers)
3. Basic supply/demand modifier (settlement-level)

**Formula:**

```
Final Price = Base Price × Quality Multiplier × Supply/Demand Multiplier
```

## Design Decisions

| Decision | Choice | Rationale |
| ------------- | ---------------------------- | ---------------------------------------------------------- |
| Pattern | Static class, pure functions | Matches existing rules (XpCalculator, CraftingOutcomeRule) |
| Quality tiers | 6 tiers from GDD | Proven design from gdd-crafting.md |
| Supply/demand | Simple enum-based | Phase 1 requirement: "basic" |
| Return type | Currency value object | Type safety, matches existing patterns |
| Rounding | Math.Round() | Standard rounding for copper amounts |

## Quality Multipliers

Matching GDD crafting system quality tiers and value multipliers:

| Quality Tier | Quality Range | Price Multiplier | GDD Source |
| ------------ | ------------- | ---------------- | ------------------------- |
| Crude | 0-20 | 0.5x | gdd-crafting.md section 8 |
| Common | 21-40 | 1.0x | gdd-crafting.md section 8 |
| Quality | 41-60 | 1.5x | gdd-crafting.md section 8 |
| Fine | 61-80 | 2.0x | gdd-crafting.md section 8 |
| Superior | 81-99 | 3.0x | gdd-crafting.md section 8 |
| Masterwork | 100+ | 5.0x | gdd-crafting.md section 8 |

**Implementation:**

```csharp
private static decimal CalculateQualityMultiplier(int quality)
{
return quality switch
{
<= 20 => 0.5m,
<= 40 => 1.0m,
<= 60 => 1.5m,
<= 80 => 2.0m,
<= 99 => 3.0m,
_ => 5.0m // 100+
};
}
```

## Supply/Demand System (Basic)

**Phase 1 Implementation:** Simple enum-based modifier

| Level | Modifier | Use Case |
| -------- | -------- | -------------------------------------- |
| Abundant | 0.5x | Local overproduction, common materials |
| Normal | 1.0x | Balanced market (default) |
| Scarce | 1.5x | Limited supply, increased demand |
| VeryRare | 2.0x | Crisis, trade route disruption |

**Phase 2 Expansion (Deferred):**

- Per-item inventory tracking
- Dynamic calculation based on supply/demand curves
- Trade route impacts
- Event-driven scarcity
- Regional specialization effects

## API Design

### Primary Method

```csharp
public static Currency CalculatePrice(
Currency basePrice,
int quality,
decimal supplyDemandModifier = 1.0m)
```

**Parameters:**

- `basePrice`: Base currency value for the item (from recipe/definition)
- `quality`: Item quality (0-100+, matches CraftingOutcomeResult.FinalQuality)
- `supplyDemandModifier`: Settlement market modifier (0.5-2.0, default 1.0)

**Returns:** Final price as `Currency` value object

**Validation:**

- `basePrice`: Must be non-negative (Currency enforces this)
- `quality`: Must be non-negative (negative quality = ArgumentOutOfRangeException)
- `supplyDemandModifier`: Must be positive (zero/negative = ArgumentOutOfRangeException)

### Helper Methods

```csharp
private static decimal CalculateQualityMultiplier(int quality)
```

Maps quality value to price multiplier using tier thresholds.

## Architecture

**Location:** `src/FantasyRpgWorld.Core/Rules/PriceCalculator.cs`

**Pattern:** Static class with pure functions (stateless)

**Dependencies:**

- `FantasyRpgWorld.Core.Domain.ValueObjects.Currency`

**Used By (Future):**

- `TradeAction` (Issue #44) - NPC trading
- `EconomySystem` (Issue #45) - Trade processing
- Commission pricing
- Shop inventory generation

## Test Coverage

**File:** `tests/FantasyRpgWorld.Core.Tests/Rules/PriceCalculatorTests.cs`

**Test Cases:**

1. **Quality Multipliers** (6 tests)
- Crude quality (quality=10) → 0.5x
- Common quality (quality=30) → 1.0x
- Quality tier (quality=50) → 1.5x
- Fine quality (quality=70) → 2.0x
- Superior quality (quality=90) → 3.0x
- Masterwork quality (quality=100) → 5.0x

2. **Supply/Demand Modifiers** (4 tests)
- Abundant (0.5x)
- Normal (1.0x)
- Scarce (1.5x)
- Very Rare (2.0x)

3. **Combined Modifiers** (2 tests)
- High quality + scarce supply
- Low quality + abundant supply

4. **Edge Cases** (5 tests)
- Quality = 0 (minimum)
- Quality > 100 (masterwork tier)
- Zero base price
- Negative quality (throws exception)
- Negative/zero supply modifier (throws exception)

5. **Rounding Behavior** (2 tests)
- Fractional copper rounds correctly
- Large values don't overflow

**Total Expected Tests:** ~19 tests

## Example Calculations

**Example 1: Common Iron Sword**

- Base Price: 50 copper
- Quality: 35 (Common)
- Supply/Demand: 1.0 (Normal)
- Final Price: 50 × 1.0 × 1.0 = **50 copper**

**Example 2: Fine Steel Sword (Scarce Market)**

- Base Price: 50 copper
- Quality: 75 (Fine)
- Supply/Demand: 1.5 (Scarce)
- Final Price: 50 × 2.0 × 1.5 = **150 copper**

**Example 3: Masterwork Mithril Blade (Abundant Market)**

- Base Price: 500 copper (5 silver)
- Quality: 105 (Masterwork)
- Supply/Demand: 0.5 (Abundant)
- Final Price: 500 × 5.0 × 0.5 = **1250 copper (12 silver, 50 copper)**

## Verification

Issue #43 requirements:

✅ **Base price × quality modifier** - Implemented with 6-tier quality multiplier system
✅ **Settlement supply/demand (basic)** - Implemented with 4-level enum-based modifier
✅ **Prices vary by quality** - Prices range from 0.5x to 5.0x based on quality tier

## Future Enhancements (Phase 2)

1. **Dynamic Supply/Demand**
- Track settlement inventory levels per item type
- Calculate modifier from supply/demand curves
- Factor in production vs consumption rates

2. **Market Complexity**
- Regional price variations
- Trade route status impacts
- Event-driven price spikes
- Merchant skill modifiers

3. **Advanced Pricing**
- Item rarity modifiers
- Material quality impact (beyond outcome quality)
- Enchantment value calculations
- Historical price tracking

4. **NPC Personality Effects**
- Generous merchants → lower prices
- Greedy merchants → higher prices
- Relationship bonuses/penalties
- Haggling skill impacts

## Implementation Notes

- Use `decimal` for intermediate calculations to avoid precision loss
- Round final result to integer copper (Currency.FromCopper expects int)
- Supply/demand modifier defaults to 1.0 for convenience
- Quality validation matches existing quality system (0-100+ range)
- Pattern consistency with XpCalculator (static class, pure functions)

## References

- GDD: `docs/design/gdd-crafting.md` (quality tiers, value multipliers)
- GDD: `docs/design/gdd-economy.md` (dynamic pricing factors)
- Existing: `ExpenseDefinition.CalculateCost` (quality-based calculation pattern)
- Existing: `CraftingOutcomeResult.FinalQuality` (quality value source)
Loading