Claude Code Plugins

Community-maintained marketplace

Feedback

economic-simulation-patterns

@tachyon-beep/skillpacks
1
0

Master game economies - faucets, sinks, arbitrage prevention, pricing

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name economic-simulation-patterns
description Master game economies - faucets, sinks, arbitrage prevention, pricing

Economic Simulation Patterns

Description

Master supply/demand dynamics, market simulation, production chains, and economic balance for game economies. Apply faucet/sink analysis, exploit validation, and price stabilization to create stable, engaging economies that resist inflation, arbitrage loops, and market manipulation over months of gameplay.

When to Use This Skill

Use this skill when implementing or debugging:

  • Trading games with dynamic markets (space sims, MMO auction houses)
  • Resource-based economies (crafting, production chains)
  • Player-to-player marketplaces
  • NPC vendors and dynamic pricing
  • Currency systems and money supply management
  • Multi-month economic balance and stability

Do NOT use this skill for:

  • Simple fixed-price vendors (no supply/demand)
  • Single-player games without trading
  • Abstract resources without markets (XP, skill points)
  • Cosmetic-only economies (no gameplay impact)

Quick Start (Time-Constrained Implementation)

If you need a working economy quickly (< 4 hours), follow this priority order:

CRITICAL (Never Skip):

  1. Faucets < Sinks: Money entering system must be less than money leaving
  2. Validate chains: Production chains must be net-negative vs NPCs (no infinite money)
  3. Price bounds: Set min/max prices (prevent 0 or infinity)
  4. NPC bid-ask spread: NPCs buy at 50-70% of sell price

IMPORTANT (Strongly Recommended): 5. Transaction fees (1-5% per trade removes money from system) 6. Consumables (ammo, fuel, food - require constant spending) 7. Price dampening (1.01x adjustments, not 1.1x) 8. Daily limits (prevent 24/7 bot farming)

CAN DEFER (Optimize Later):

  • Complex market makers
  • Dynamic recipe costs
  • Regional price variations
  • Advanced anti-manipulation

Example - Minimal Stable Economy in 2 Hours:

class MinimalEconomy:
    def __init__(self):
        # Prices with bounds
        self.prices = {'Iron': 10}
        self.MIN_PRICE = 1
        self.MAX_PRICE = 100

    def sell_to_npc(self, player, item, qty):
        # NPC buys at 60% of market price (bid-ask spread)
        revenue = self.prices[item] * 0.6 * qty
        player.credits += revenue * 0.95  # 5% transaction fee (SINK)

    def buy_from_npc(self, player, item, qty):
        cost = self.prices[item] * qty
        if player.credits >= cost:
            player.credits -= cost
            return True
        return False

    def update_price(self, item, demand, supply):
        # Dampened adjustment (1.01x not 1.1x)
        if demand > supply:
            self.prices[item] *= 1.01
        else:
            self.prices[item] *= 0.99

        # Enforce bounds
        self.prices[item] = clamp(self.prices[item], self.MIN_PRICE, self.MAX_PRICE)

    def validate_recipe(self, recipe):
        # CRITICAL: Verify no infinite money
        input_cost = sum(self.prices[mat] * qty for mat, qty in recipe.inputs.items())
        output_value = self.prices[recipe.output] * 0.6  # NPC buy price

        assert output_value < input_cost, f"Recipe {recipe.output} creates infinite money!"

This gives you:

  • Money sinks (transaction fees)
  • No arbitrage (bid-ask spread + validation)
  • Stable prices (bounds + dampening)

Refine later based on playtesting.


Core Concepts

1. Faucets and Sinks (Money Supply Control)

Every game economy has faucets (money entering) and sinks (money leaving). The balance determines long-term stability.

Faucets (Money Creation):

# Examples of money entering the economy
class MoneyFaucets:
    def new_player_bonus(self, player):
        player.credits += 10000  # +10k per new player

    def quest_reward(self, player, quest):
        player.credits += 5000  # +5k per quest

    def npc_mission(self, player):
        player.credits += 1000  # +1k per mission

    def monster_drops(self, player, monster):
        player.credits += 50  # +50 per kill

    def calculate_daily_faucet(self, player_count):
        # Total money entering per day
        new_players = 100 * 10000          # 1,000,000
        quests = player_count * 5 * 5000   # 5 quests/day
        missions = player_count * 10 * 1000  # 10 missions/day
        monsters = player_count * 100 * 50   # 100 kills/day

        return new_players + quests + missions + monsters

Sinks (Money Destruction):

# Examples of money leaving the economy
class MoneySinks:
    def transaction_fee(self, trade_value):
        # 2% of every trade removed from game
        fee = trade_value * 0.02
        # Credits disappear (not given to anyone)
        return -fee

    def repair_cost(self, player, item):
        # Items degrade, require repairs
        cost = item.max_value * 0.1
        player.credits -= cost

    def consumable_purchase(self, player):
        # Fuel, ammo, food - constant spending
        player.credits -= 100  # Must buy from NPC (sinks)

    def luxury_items(self, player):
        # Cosmetics, housing - pure sinks
        player.credits -= 50000

    def market_listing_fee(self, listing):
        # Fee to list item on marketplace
        return listing.value * 0.05

    def calculate_daily_sink(self, player_count):
        # Total money leaving per day
        fees = player_count * 20 * 1000 * 0.02     # 20 trades/day
        repairs = player_count * 5 * 500            # 5 repairs/day
        consumables = player_count * 100            # Daily consumables
        luxuries = player_count * 0.1 * 50000       # 10% buy luxury/day

        return fees + repairs + consumables + luxuries

The Golden Rule:

def validate_economy_stability(faucets, sinks, player_count):
    '''CRITICAL: Sinks must exceed faucets for stability'''
    daily_faucet = calculate_daily_faucet(player_count)
    daily_sink = calculate_daily_sink(player_count)

    ratio = daily_sink / daily_faucet

    if ratio < 0.8:
        print("CRITICAL: Runaway inflation! Sinks too weak.")
        print(f"  Faucet: {daily_faucet}, Sink: {daily_sink}")
        print(f"  Ratio: {ratio:.2f} (need > 0.8)")
        return False

    elif ratio > 1.2:
        print("WARNING: Deflation! Sinks too strong.")
        print(f"  Players will run out of money.")
        return False

    else:
        print(f"HEALTHY: Sink/Faucet ratio = {ratio:.2f}")
        return True

Real-World Target:

  • 0.8-1.0: Slight inflation (prices gradually rise)
  • 1.0-1.1: Stable (target for most games)
  • 1.1-1.3: Slight deflation (late-game sink)

Why This Matters:

  • Ratio < 0.8: Money supply grows exponentially → hyperinflation
  • Ratio > 1.3: Money supply shrinks → players can't afford anything
  • Ratio 0.9-1.1: Stable economy for months/years

2. Arbitrage Prevention (No Free Money)

Arbitrage: Buying low and selling high for guaranteed profit with no risk.

The Problem:

# ❌ BROKEN: Infinite money exploit
class BrokenEconomy:
    def __init__(self):
        self.npc_sell_price = 10  # NPC sells Iron for 10
        self.npc_buy_price = 10   # NPC buys Iron for 10 (SAME!)

    def exploit(self, player):
        while True:
            # Buy from NPC
            player.credits -= 10
            player.inventory['Iron'] += 1

            # Sell to NPC
            player.inventory['Iron'] -= 1
            player.credits += 10

            # Net: 0 profit (but with manufacturing...)

            # Craft: 50 Iron → 1 Hull (takes time but guaranteed)
            if player.inventory['Iron'] >= 50:
                player.inventory['Iron'] -= 50
                player.inventory['Hull'] += 1

                # Sell Hull to NPC
                player.inventory['Hull'] -= 1
                player.credits += 1000  # Hull sells for 1000

                # Cost: 50 * 10 = 500 credits
                # Revenue: 1000 credits
                # Profit: 500 credits (100% guaranteed!)

The Fix: Bid-Ask Spread:

# ✅ SAFE: NPCs buy low, sell high
class SafeEconomy:
    def __init__(self):
        self.market_price = 10  # "Fair" price

        # NPCs sell at premium
        self.npc_sell_price = self.market_price * 1.0  # 10 (sell to players)

        # NPCs buy at discount
        self.npc_buy_price = self.market_price * 0.6   # 6 (buy from players)

    def validate_no_arbitrage(self):
        '''Verify all production chains are net-negative'''
        recipes = {
            'Hull': {'Iron': 50, 'Silicon': 20},
            'Electronics': {'Silicon': 30, 'Platinum': 10},
        }

        for output, inputs in recipes.items():
            # Calculate cost (buying materials from NPC)
            input_cost = sum(
                self.npc_sell_price[mat] * qty
                for mat, qty in inputs.items()
            )

            # Calculate revenue (selling product to NPC)
            output_revenue = self.npc_buy_price[output]

            profit = output_revenue - input_cost

            if profit > 0:
                print(f"ERROR: {output} creates infinite money!")
                print(f"  Cost: {input_cost}, Revenue: {output_revenue}")
                print(f"  Profit: {profit} (should be negative!)")
                raise ValueError(f"Recipe {output} is exploitable!")

            else:
                print(f"SAFE: {output} requires player trading for profit")

Why This Works:

  • Players MUST trade with each other to profit
  • NPC loops are always net-negative
  • Crafting is only profitable if you sell to players (creates real economy)

Bid-Ask Spread Guidelines:

  • Conservative: NPC buys at 50% (safe, forces player trading)
  • Moderate: NPC buys at 60-70% (reasonable, still safe)
  • Aggressive: NPC buys at 80% (risky, verify carefully!)
  • Never: NPC buys at 100% (guaranteed exploits)

3. Supply and Demand Dynamics

Basic Model:

class SupplyDemandMarket:
    def __init__(self):
        self.price = 10
        self.supply = 0     # Items available for sale
        self.demand = 0     # Items players want to buy

    def update_price_simple(self):
        '''Simple supply/demand adjustment'''
        if self.demand > self.supply:
            # Shortage: Price rises
            self.price *= 1.05
        elif self.supply > self.demand:
            # Surplus: Price falls
            self.price *= 0.95

Problem: This is unstable! Prices oscillate wildly.

Better: Dampened Adjustment:

    def update_price_dampened(self):
        '''Dampened adjustment for stability'''
        # Calculate imbalance
        if self.supply > 0:
            ratio = self.demand / self.supply
        else:
            ratio = 10.0  # High demand, no supply

        # Dampened adjustment (smaller steps)
        if ratio > 1.1:  # Demand > Supply by 10%+
            self.price *= 1.01  # Increase 1% (not 5%!)
        elif ratio < 0.9:  # Supply > Demand by 10%+
            self.price *= 0.99  # Decrease 1%

        # Reset counters for next period
        self.supply = 0
        self.demand = 0

Best: Exponential Moving Average:

    def update_price_ema(self, recent_trades):
        '''Smooth price adjustment using EMA'''
        # Calculate average trade price from recent trades
        if len(recent_trades) == 0:
            return

        avg_trade_price = sum(t.price for t in recent_trades) / len(recent_trades)

        # Exponential moving average (smooth towards trade price)
        SMOOTHING = 0.1  # 10% weight to new data
        self.price = self.price * (1 - SMOOTHING) + avg_trade_price * SMOOTHING

        # Still enforce bounds
        self.price = clamp(self.price, self.MIN_PRICE, self.MAX_PRICE)

Advanced: Market Clearing Price:

class MarketClearingPrice:
    def __init__(self):
        self.buy_orders = []   # [(price, quantity), ...]
        self.sell_orders = []  # [(price, quantity), ...]

    def find_clearing_price(self):
        '''Find price where supply = demand'''
        # Sort orders
        self.buy_orders.sort(reverse=True)   # Highest price first
        self.sell_orders.sort()              # Lowest price first

        # Walk through order book
        for sell_price, sell_qty in self.sell_orders:
            # Count how many buyers at this price or higher
            demand_at_price = sum(
                qty for price, qty in self.buy_orders
                if price >= sell_price
            )

            # Count how many sellers at this price or lower
            supply_at_price = sum(
                qty for price, qty in self.sell_orders
                if price <= sell_price
            )

            if demand_at_price >= supply_at_price:
                # This is the clearing price!
                return sell_price

        # No clearing price found
        return None

Decision Framework:

  • Simple games: Use dampened adjustment (easy, stable enough)
  • Trading-focused games: Use market clearing price (realistic, complex)
  • Hybrid: EMA for NPC prices, order book for player marketplace

4. Price Bounds and Stabilization

Never Let Prices Go to Zero or Infinity:

class BoundedPricing:
    def __init__(self, base_price):
        self.price = base_price
        self.MIN_PRICE = base_price * 0.1   # Never below 10% of base
        self.MAX_PRICE = base_price * 10.0  # Never above 10x base

    def update_price(self, adjustment):
        self.price *= adjustment

        # Enforce bounds (CRITICAL!)
        self.price = clamp(self.price, self.MIN_PRICE, self.MAX_PRICE)

        # Alternative: Asymptotic bounds (smooth approach to limits)
        # self.price = self.MIN_PRICE + (self.MAX_PRICE - self.MIN_PRICE) * (
        #     1 / (1 + math.exp(-(self.price - base_price) / base_price))
        # )

Why Bounds Matter:

  • No bounds: Price can explode to 1,000,000+ or crash to 0.0001
  • With bounds: Price stays playable (players can always afford basics)

NPC Market Makers (Advanced Stabilization):

class NPCMarketMaker:
    def __init__(self, target_price, liquidity):
        self.target_price = target_price
        self.liquidity = liquidity  # How much NPC will trade

    def provide_liquidity(self, market):
        '''NPC always offers to buy/sell at near-target price'''
        # NPC sells at slight premium
        npc_sell_price = self.target_price * 1.05
        npc_sell_quantity = self.liquidity

        # NPC buys at slight discount
        npc_buy_price = self.target_price * 0.95
        npc_buy_quantity = self.liquidity

        # Add NPC orders to market
        market.add_sell_order(npc_sell_price, npc_sell_quantity, is_npc=True)
        market.add_buy_order(npc_buy_price, npc_buy_quantity, is_npc=True)

    def adjust_target(self, current_price):
        '''NPC slowly adjusts target towards market price'''
        SMOOTHING = 0.01  # Very slow adjustment
        self.target_price = self.target_price * (1 - SMOOTHING) + current_price * SMOOTHING

Effect:

  • Players can always buy/sell (NPC provides liquidity)
  • Prices stabilize near target (NPC absorbs shocks)
  • Market still responds to player activity (target adjusts slowly)

5. Production Chain Balance

Every recipe must be balanced for profitability and gameplay:

class ProductionChainBalancer:
    def __init__(self, market):
        self.market = market

    def analyze_chain(self, recipe):
        '''Analyze profitability and balance of a recipe'''
        # Calculate input cost (buying from players at market price)
        input_cost_market = sum(
            self.market.get_price(mat) * qty
            for mat, qty in recipe.inputs.items()
        )

        # Calculate input cost (buying from NPCs)
        input_cost_npc = sum(
            self.market.npc_sell_price(mat) * qty
            for mat, qty in recipe.inputs.items()
        )

        # Calculate output value (selling to players)
        output_value_market = self.market.get_price(recipe.output)

        # Calculate output value (selling to NPCs)
        output_value_npc = self.market.npc_buy_price(recipe.output)

        # Calculate time cost
        time_hours = recipe.crafting_time / 3600  # Convert to hours

        # Profit analysis
        profit_vs_npc = output_value_npc - input_cost_npc
        profit_vs_players = output_value_market - input_cost_market

        profit_per_hour_npc = profit_vs_npc / time_hours
        profit_per_hour_players = profit_vs_players / time_hours

        print(f"\nRecipe: {recipe.output}")
        print(f"  Input cost (NPC): {input_cost_npc:.0f}")
        print(f"  Output value (NPC): {output_value_npc:.0f}")
        print(f"  Profit vs NPC: {profit_vs_npc:.0f} (should be NEGATIVE)")
        print(f"  Profit vs Players: {profit_vs_players:.0f}")
        print(f"  Profit/hour (players): {profit_per_hour_players:.0f}")

        # Validation
        if profit_vs_npc > 0:
            print(f"  ⚠️  ERROR: Creates infinite money vs NPCs!")
            return False

        if profit_vs_players < 0:
            print(f"  ⚠️  WARNING: Unprofitable even with player trading!")

        if profit_per_hour_players < 100:
            print(f"  ⚠️  WARNING: Poor profit/hour (players won't craft)")

        return True

    def balance_all_chains(self, recipes):
        '''Ensure all recipes have similar profit/hour'''
        profits = []

        for recipe in recipes:
            # Calculate profit per hour vs player market
            input_cost = sum(
                self.market.get_price(mat) * qty
                for mat, qty in recipe.inputs.items()
            )
            output_value = self.market.get_price(recipe.output)
            time_hours = recipe.crafting_time / 3600

            profit_per_hour = (output_value - input_cost) / time_hours
            profits.append((recipe.output, profit_per_hour))

        # Sort by profit
        profits.sort(key=lambda x: x[1])

        print("\nProduction Chain Balance:")
        for name, profit in profits:
            print(f"  {name}: {profit:.0f} credits/hour")

        # Check for dominant strategies
        max_profit = profits[-1][1]
        min_profit = profits[0][1]

        if max_profit > min_profit * 3:
            print(f"  ⚠️  WARNING: Imbalanced chains!")
            print(f"  Best: {profits[-1][0]} ({max_profit:.0f}/hr)")
            print(f"  Worst: {profits[0][0]} ({min_profit:.0f}/hr)")
            print(f"  Ratio: {max_profit / min_profit:.1f}x (should be < 3x)")

Balance Guidelines:

  • All chains should have similar profit/hour (within 2-3x)
  • No chain should be strictly better (dominant strategy)
  • Complex chains can pay more (reward skill/knowledge)
  • Late-game chains should be more profitable (progression)

Decision Frameworks

Framework 1: Economy Complexity Level

Choose complexity based on game focus:

Complexity Use When Examples Implementation Time
Simple Economy is secondary to core gameplay Action RPG, FPS 1-2 days
Moderate Trading is important but not central MMO, Strategy 1-2 weeks
Complex Economy IS the game Eve Online, Trading sim 1-3 months

Simple Economy:

# Fixed prices, transaction fees, consumables
class SimpleEconomy:
    PRICES = {'Sword': 100, 'Potion': 10}  # Fixed

    def buy_from_npc(self, player, item):
        player.credits -= self.PRICES[item]

    def sell_to_npc(self, player, item):
        player.credits += self.PRICES[item] * 0.5  # 50% back

Features:

  • Fixed prices (no supply/demand)
  • NPC vendors only
  • Simple sinks (repair, consumables)
  • No player trading

Use for: Games where economy is flavor, not focus

Moderate Economy:

# Dynamic prices, player marketplace, production
class ModerateEconomy:
    def __init__(self):
        self.prices = {}  # Dynamic
        self.player_market = OrderBook()

    def update_prices(self, trades):
        # EMA based on recent trades
        for item in trades:
            self.prices[item] = ema(self.prices[item], trades[item].avg_price)

    def npc_buy_price(self, item):
        return self.prices[item] * 0.6  # 60% of market

    def npc_sell_price(self, item):
        return self.prices[item] * 1.0  # 100% of market

Features:

  • Dynamic prices (supply/demand)
  • Player marketplace (peer trading)
  • Production chains (crafting)
  • Faucet/sink balance

Use for: MMOs, persistent world games

Complex Economy:

# Full market simulation, regional prices, contracts
class ComplexEconomy:
    def __init__(self):
        self.regions = {}  # Different prices per region
        self.order_books = {}  # Full order matching
        self.contracts = []  # Player contracts
        self.corporations = []  # Player organizations

    def match_orders(self, item, region):
        '''Full order book matching'''
        book = self.order_books[(item, region)]
        clearing_price = book.find_clearing_price()
        book.execute_trades(clearing_price)

    def transport_goods(self, item, from_region, to_region):
        '''Regional arbitrage (hauling gameplay)'''
        # Different prices in different regions
        # Players profit by hauling goods
        pass

Features:

  • Regional economies (different prices per zone)
  • Full order book matching
  • Player contracts and corporations
  • Hauling/transport gameplay
  • Complex production chains

Use for: Eve Online-style games, trading simulators

Framework 2: Player-Driven vs NPC-Driven Markets

NPC-Driven (Simpler, More Stable):

class NPCDrivenMarket:
    '''NPCs set prices, players buy/sell from NPCs'''
    def __init__(self):
        self.npc_prices = {}  # NPCs always available

    def buy_from_npc(self, player, item, qty):
        cost = self.npc_prices[item] * qty
        if player.credits >= cost:
            player.credits -= cost
            player.inventory[item] += qty
            return True
        return False

    def sell_to_npc(self, player, item, qty):
        revenue = self.npc_prices[item] * 0.6 * qty  # 60% back
        player.credits += revenue
        player.inventory[item] -= qty

Pros:

  • Always available (no market failures)
  • Stable prices (predictable)
  • Simple to implement
  • Works for small populations

Cons:

  • Less dynamic
  • No emergent gameplay
  • Artificial feeling

Use for: Single-player, co-op, small multiplayer

Player-Driven (Complex, Dynamic):

class PlayerDrivenMarket:
    '''Players set prices, NPCs only provide liquidity'''
    def __init__(self):
        self.order_book = OrderBook()
        self.npc_maker = NPCMarketMaker()  # Backup liquidity

    def create_sell_order(self, seller, item, qty, price):
        '''Player lists item for sale'''
        order = {'seller': seller, 'item': item, 'qty': qty, 'price': price}
        self.order_book.add_sell_order(order)

    def create_buy_order(self, buyer, item, qty, max_price):
        '''Player offers to buy at price'''
        order = {'buyer': buyer, 'item': item, 'qty': qty, 'price': max_price}
        self.order_book.add_buy_order(order)

    def match_orders(self):
        '''Match buy and sell orders'''
        self.order_book.match()

        # If no liquidity, NPC provides backup
        if self.order_book.is_empty():
            self.npc_maker.provide_liquidity(self.order_book)

Pros:

  • Emergent gameplay (market manipulation, speculation)
  • Dynamic prices (responds to player behavior)
  • Engaging economy (players set prices)
  • Scales to large populations

Cons:

  • Can fail (no buyers/sellers)
  • Requires balancing
  • Complex implementation
  • Needs large player base

Use for: MMOs, persistent worlds, large multiplayer

Hybrid (Best of Both):

class HybridMarket:
    '''Players trade with each other, NPCs provide fallback'''
    def __init__(self):
        self.player_market = OrderBook()
        self.npc_vendor = NPCVendor()

    def buy_item(self, player, item, qty):
        '''Try player market first, fall back to NPC'''
        # Check player market
        orders = self.player_market.get_sell_orders(item)
        if len(orders) > 0:
            # Buy from cheapest player
            cheapest = min(orders, key=lambda o: o.price)
            if player.credits >= cheapest.price * qty:
                self.execute_player_trade(player, cheapest, qty)
                return True

        # Fall back to NPC (more expensive)
        return self.npc_vendor.buy_from_npc(player, item, qty)

Use for: Most games (combines stability + dynamics)

Framework 3: When to Use Regional Economies

Single Global Market (Simpler):

  • All players see same prices
  • No hauling gameplay
  • Works for small games

Regional Markets (Complex):

  • Different prices per zone
  • Hauling creates arbitrage opportunities
  • Requires large world and population

Decision Table:

Factor Global Market Regional Markets
World size Small (<10 zones) Large (>20 zones)
Player count <1,000 >5,000
Travel time <1 minute >5 minutes
Hauling gameplay No Yes (core mechanic)
Implementation time 1 week 1 month

Regional Economy Example:

class RegionalEconomy:
    def __init__(self):
        self.regions = {
            'Mining Zone': {'Iron': 5, 'Ships': 4000},   # Iron cheap, ships expensive
            'Industrial Zone': {'Iron': 15, 'Ships': 3000},  # Iron expensive, ships cheaper
            'Trade Hub': {'Iron': 10, 'Ships': 3500},   # Average prices
        }

    def get_price(self, region, item):
        return self.regions[region][item]

    def arbitrage_opportunity(self, item):
        '''Find best buy/sell regions for hauling'''
        prices = [(region, self.get_price(region, item)) for region in self.regions]
        prices.sort(key=lambda x: x[1])

        buy_region, buy_price = prices[0]   # Cheapest
        sell_region, sell_price = prices[-1]  # Most expensive

        profit_per_unit = sell_price - buy_price
        print(f"Haul {item} from {buy_region} to {sell_region}")
        print(f"  Profit: {profit_per_unit} per unit")

        return buy_region, sell_region, profit_per_unit

Implementation Patterns

Pattern 1: Faucet/Sink Validation System

Complete validation for economic stability:

class EconomyValidator:
    def __init__(self, economy, player_count):
        self.economy = economy
        self.player_count = player_count

    def validate_stability(self, simulation_days=30):
        '''Simulate economy for N days to check stability'''
        print(f"Simulating economy for {simulation_days} days...")

        total_money = self.calculate_initial_money()
        daily_faucet = self.calculate_daily_faucet()
        daily_sink = self.calculate_daily_sink()

        print(f"\nInitial money supply: {total_money:,}")
        print(f"Daily faucet: {daily_faucet:,}")
        print(f"Daily sink: {daily_sink:,}")
        print(f"Sink/Faucet ratio: {daily_sink/daily_faucet:.2f}")

        # Simulate
        money_over_time = [total_money]
        for day in range(simulation_days):
            total_money += daily_faucet
            total_money -= daily_sink
            money_over_time.append(total_money)

        # Analyze
        final_money = money_over_time[-1]
        money_growth_rate = (final_money / money_over_time[0]) ** (1/simulation_days) - 1

        print(f"\nAfter {simulation_days} days:")
        print(f"  Total money: {final_money:,}")
        print(f"  Growth rate: {money_growth_rate*100:.2f}% per day")

        # Check for inflation
        if money_growth_rate > 0.01:  # >1% growth per day
            print(f"  ⚠️  WARNING: Runaway inflation!")
            print(f"  Money supply growing too fast.")
            print(f"  Increase sinks or reduce faucets.")
            return False

        elif money_growth_rate < -0.01:  # <-1% shrink per day
            print(f"  ⚠️  WARNING: Deflation!")
            print(f"  Money supply shrinking.")
            print(f"  Reduce sinks or increase faucets.")
            return False

        else:
            print(f"  ✓ STABLE: Money supply growth is healthy.")
            return True

    def calculate_initial_money(self):
        '''Money already in economy'''
        return self.player_count * 50000  # Average per player

    def calculate_daily_faucet(self):
        '''Money entering per day'''
        new_players = 100 * 10000               # New player bonuses
        quests = self.player_count * 5 * 5000  # Quests per player
        missions = self.player_count * 10 * 1000  # Missions
        monster_drops = self.player_count * 100 * 50  # Combat

        return new_players + quests + missions + monster_drops

    def calculate_daily_sink(self):
        '''Money leaving per day'''
        transaction_fees = self.player_count * 20 * 1000 * 0.02  # 20 trades/day, 2% fee
        repairs = self.player_count * 5 * 500  # 5 repairs/day
        consumables = self.player_count * 100  # Daily ammo/fuel
        listing_fees = self.player_count * 5 * 1000 * 0.05  # Market listings

        return transaction_fees + repairs + consumables + listing_fees

    def validate_no_arbitrage(self):
        '''Ensure no infinite money loops'''
        print("\nValidating production chains...")

        for recipe in self.economy.recipes:
            input_cost_npc = sum(
                self.economy.npc_sell_price(mat) * qty
                for mat, qty in recipe.inputs.items()
            )

            output_value_npc = self.economy.npc_buy_price(recipe.output)

            profit = output_value_npc - input_cost_npc

            if profit > 0:
                print(f"  ❌ ERROR: {recipe.output} creates infinite money!")
                print(f"     Input cost: {input_cost_npc}")
                print(f"     Output value: {output_value_npc}")
                print(f"     Profit: {profit}")
                return False
            else:
                print(f"  ✓ {recipe.output}: Safe (requires player trading)")

        return True

    def validate_price_bounds(self):
        '''Ensure prices have min/max'''
        print("\nValidating price bounds...")

        for item in self.economy.items:
            if not hasattr(item, 'min_price') or not hasattr(item, 'max_price'):
                print(f"  ❌ ERROR: {item.name} missing price bounds!")
                return False

            if item.min_price <= 0:
                print(f"  ❌ ERROR: {item.name} min_price is {item.min_price} (must be > 0)")
                return False

            if item.max_price < item.min_price * 5:
                print(f"  ⚠️  WARNING: {item.name} max_price too close to min")
                print(f"     Range: {item.min_price} - {item.max_price}")

            print(f"  ✓ {item.name}: {item.min_price} - {item.max_price}")

        return True

    def run_full_validation(self):
        '''Run all validation checks'''
        print("="*60)
        print("ECONOMIC STABILITY VALIDATION")
        print("="*60)

        checks = [
            ("Stability", self.validate_stability),
            ("Arbitrage", self.validate_no_arbitrage),
            ("Price Bounds", self.validate_price_bounds),
        ]

        results = []
        for name, check in checks:
            print(f"\n{name} Check:")
            passed = check()
            results.append((name, passed))

        print("\n" + "="*60)
        print("VALIDATION SUMMARY")
        print("="*60)

        all_passed = all(passed for _, passed in results)

        for name, passed in results:
            status = "✓ PASS" if passed else "❌ FAIL"
            print(f"{status}: {name}")

        if all_passed:
            print("\n✓ Economy is stable and ready for production!")
        else:
            print("\n❌ Economy has critical issues. Fix before launch!")

        return all_passed

Pattern 2: Dynamic Price Adjustment with Stabilization

Robust price adjustment system:

class StabilizedPricing:
    def __init__(self, item_name, base_price):
        self.item_name = item_name
        self.base_price = base_price
        self.current_price = base_price

        # Bounds (10% to 10x base)
        self.min_price = base_price * 0.1
        self.max_price = base_price * 10.0

        # Smoothing parameters
        self.ema_alpha = 0.1  # 10% weight to new data
        self.adjustment_rate = 0.01  # 1% change per update

        # History for analysis
        self.price_history = []
        self.trade_history = []

    def update_from_supply_demand(self, supply, demand):
        '''Update price based on supply/demand imbalance'''
        if supply == 0 and demand == 0:
            return  # No activity

        if supply == 0:
            # Pure demand, no supply
            self.current_price *= (1 + self.adjustment_rate)

        elif demand == 0:
            # Pure supply, no demand
            self.current_price *= (1 - self.adjustment_rate)

        else:
            # Calculate ratio
            ratio = demand / supply

            if ratio > 1.1:  # 10% more demand than supply
                # Increase price (dampened)
                self.current_price *= (1 + self.adjustment_rate)

            elif ratio < 0.9:  # 10% more supply than demand
                # Decrease price (dampened)
                self.current_price *= (1 - self.adjustment_rate)

        # Enforce bounds
        self.current_price = clamp(self.current_price, self.min_price, self.max_price)

        # Record
        self.price_history.append(self.current_price)

    def update_from_trades(self, trades):
        '''Update price based on actual trade prices (more accurate)'''
        if len(trades) == 0:
            return

        # Calculate average trade price
        avg_trade_price = sum(t.price * t.quantity for t in trades) / sum(t.quantity for t in trades)

        # Exponential moving average
        self.current_price = (
            self.current_price * (1 - self.ema_alpha) +
            avg_trade_price * self.ema_alpha
        )

        # Enforce bounds
        self.current_price = clamp(self.current_price, self.min_price, self.max_price)

        # Record
        self.price_history.append(self.current_price)
        self.trade_history.extend(trades)

    def get_volatility(self):
        '''Calculate recent price volatility'''
        if len(self.price_history) < 10:
            return 0.0

        recent = self.price_history[-10:]
        mean = sum(recent) / len(recent)
        variance = sum((p - mean)**2 for p in recent) / len(recent)
        std_dev = variance ** 0.5

        return std_dev / mean  # Coefficient of variation

    def should_intervene(self):
        '''Check if NPC intervention is needed'''
        volatility = self.get_volatility()

        # High volatility: Market unstable
        if volatility > 0.2:  # 20% volatility
            return True

        # Price at extremes
        if self.current_price <= self.min_price * 1.1:
            return True  # Near floor
        if self.current_price >= self.max_price * 0.9:
            return True  # Near ceiling

        return False

Pattern 3: NPC Market Maker for Stabilization

NPCs provide liquidity when markets fail:

class NPCMarketMaker:
    def __init__(self, item, target_price, liquidity_amount):
        self.item = item
        self.target_price = target_price
        self.liquidity_amount = liquidity_amount  # Max qty NPC will trade

        # Spread (NPC buys low, sells high)
        self.bid_spread = 0.95  # NPC buys at 95% of target
        self.ask_spread = 1.05  # NPC sells at 105% of target

    def get_npc_buy_order(self):
        '''NPC standing offer to buy'''
        price = self.target_price * self.bid_spread
        quantity = self.liquidity_amount

        return {
            'item': self.item,
            'price': price,
            'quantity': quantity,
            'is_npc': True
        }

    def get_npc_sell_order(self):
        '''NPC standing offer to sell'''
        price = self.target_price * self.ask_spread
        quantity = self.liquidity_amount

        return {
            'item': self.item,
            'price': price,
            'quantity': quantity,
            'is_npc': True
        }

    def adjust_target_price(self, current_market_price):
        '''Slowly adjust target towards market price'''
        # Very slow adjustment (1% per update)
        ADJUSTMENT_RATE = 0.01

        self.target_price = (
            self.target_price * (1 - ADJUSTMENT_RATE) +
            current_market_price * ADJUSTMENT_RATE
        )

    def intervene_if_needed(self, market):
        '''Provide liquidity if market is thin'''
        order_book = market.get_order_book(self.item)

        # Check if market has liquidity
        total_buy_orders = sum(o.quantity for o in order_book.buy_orders if not o.is_npc)
        total_sell_orders = sum(o.quantity for o in order_book.sell_orders if not o.is_npc)

        # If market is thin, add NPC orders
        if total_buy_orders < self.liquidity_amount * 0.5:
            market.add_buy_order(self.get_npc_buy_order())

        if total_sell_orders < self.liquidity_amount * 0.5:
            market.add_sell_order(self.get_npc_sell_order())

Effect:

  • Market always has liquidity (players can always trade)
  • Prices stabilize near target (NPC absorbs volatility)
  • NPC adapts to market (target adjusts slowly)

Pattern 4: Production Chain Simulator

Test economic balance before launch:

class ProductionChainSimulator:
    def __init__(self, economy):
        self.economy = economy

    def simulate_player_behavior(self, num_players=1000, days=30):
        '''Simulate player economy for N days'''
        print(f"Simulating {num_players} players for {days} days...\n")

        # Track metrics
        total_money = num_players * 10000  # Starting credits
        resource_production = {item: 0 for item in self.economy.resources}
        goods_production = {item: 0 for item in self.economy.goods}
        trade_volume = 0

        for day in range(days):
            # Players mine resources
            for resource in self.economy.resources:
                daily_mining = num_players * 100  # 100 units/player/day
                resource_production[resource] += daily_mining

            # Players craft (choose most profitable chain)
            best_recipe = self.find_best_recipe()
            if best_recipe:
                crafters = num_players * 0.3  # 30% of players craft
                daily_crafting = crafters * 2  # 2 items/day
                goods_production[best_recipe.output] += daily_crafting

                # Consume resources
                for mat, qty in best_recipe.inputs.items():
                    resource_production[mat] -= daily_crafting * qty

            # Players trade
            daily_trades = num_players * 10  # 10 trades/player/day
            avg_trade_value = 1000
            trade_volume += daily_trades * avg_trade_value

            # Money faucets
            daily_faucet = self.economy.calculate_daily_faucet(num_players)
            total_money += daily_faucet

            # Money sinks
            daily_sink = self.economy.calculate_daily_sink(num_players)
            total_money -= daily_sink

            # Log every 5 days
            if day % 5 == 0:
                print(f"Day {day}:")
                print(f"  Total money: {total_money:,}")
                print(f"  Trade volume: {trade_volume:,}")
                print(f"  Most produced: {max(goods_production, key=goods_production.get)}")

        # Final report
        print(f"\n{'='*60}")
        print(f"SIMULATION COMPLETE ({days} days)")
        print(f"{'='*60}")

        print(f"\nMoney Supply:")
        print(f"  Initial: {num_players * 10000:,}")
        print(f"  Final: {total_money:,}")
        print(f"  Growth: {(total_money / (num_players * 10000) - 1) * 100:.1f}%")

        print(f"\nResource Production:")
        for resource, qty in resource_production.items():
            print(f"  {resource}: {qty:,}")

        print(f"\nGoods Production:")
        for good, qty in goods_production.items():
            print(f"  {good}: {qty:,}")

        # Check for imbalances
        self.check_for_imbalances(resource_production, goods_production)

    def find_best_recipe(self):
        '''Find most profitable recipe (dominant strategy)'''
        best_recipe = None
        best_profit_per_hour = 0

        for recipe in self.economy.recipes:
            input_cost = sum(
                self.economy.get_price(mat) * qty
                for mat, qty in recipe.inputs.items()
            )
            output_value = self.economy.get_price(recipe.output)
            time_hours = recipe.time / 3600

            profit_per_hour = (output_value - input_cost) / time_hours

            if profit_per_hour > best_profit_per_hour:
                best_profit_per_hour = profit_per_hour
                best_recipe = recipe

        return best_recipe

    def check_for_imbalances(self, resource_production, goods_production):
        '''Detect economic imbalances'''
        print(f"\nImbalance Detection:")

        # Check for unused resources
        for resource, qty in resource_production.items():
            if qty > 100000:  # Excess production
                print(f"  ⚠️  {resource}: Overproduced ({qty:,} units)")
                print(f"     Consider increasing demand or decreasing production.")

            if qty < 0:  # Deficit
                print(f"  ⚠️  {resource}: Deficit ({abs(qty):,} units)")
                print(f"     Production can't keep up with demand.")

        # Check for dominant strategies
        if len(goods_production) > 0:
            max_produced = max(goods_production.values())
            min_produced = min(goods_production.values())

            if max_produced > min_produced * 5:  # 5x imbalance
                print(f"  ⚠️  Production imbalance detected!")
                print(f"     Most produced: {max(goods_production, key=goods_production.get)}")
                print(f"     Least produced: {min(goods_production, key=goods_production.get)}")
                print(f"     Ratio: {max_produced/min_produced:.1f}x")

Pattern 5: Anti-Exploit Validation

Comprehensive exploit detection:

class ExploitDetector:
    def __init__(self, economy):
        self.economy = economy

    def find_arbitrage_loops(self):
        '''Detect cycles in production/trading that generate profit'''
        print("Searching for arbitrage loops...\n")

        loops_found = []

        # Check direct buy-sell loops
        for item in self.economy.items:
            npc_sell = self.economy.npc_sell_price(item)
            npc_buy = self.economy.npc_buy_price(item)

            if npc_buy >= npc_sell:
                print(f"❌ CRITICAL: {item} - Direct arbitrage!")
                print(f"   NPC sells for: {npc_sell}")
                print(f"   NPC buys for: {npc_buy}")
                print(f"   Profit: {npc_buy - npc_sell} per unit")
                loops_found.append(('direct', item))

        # Check production loops
        for recipe in self.economy.recipes:
            input_cost = sum(
                self.economy.npc_sell_price(mat) * qty
                for mat, qty in recipe.inputs.items()
            )
            output_value = self.economy.npc_buy_price(recipe.output)

            if output_value > input_cost:
                profit = output_value - input_cost
                print(f"❌ CRITICAL: {recipe.output} - Production arbitrage!")
                print(f"   Input cost: {input_cost}")
                print(f"   Output value: {output_value}")
                print(f"   Profit: {profit}")
                loops_found.append(('production', recipe.output))

        # Check multi-step loops (A→B→C→A)
        # This requires graph traversal to find cycles
        loops_found.extend(self.find_conversion_cycles())

        if len(loops_found) == 0:
            print("✓ No arbitrage loops found!")
        else:
            print(f"\n❌ Found {len(loops_found)} exploitable loops!")

        return loops_found

    def find_conversion_cycles(self):
        '''Find multi-step conversion cycles'''
        # Build conversion graph
        graph = {}
        for recipe in self.economy.recipes:
            # Each recipe is an edge: inputs → output
            graph[recipe.output] = recipe.inputs

        # DFS to find cycles
        cycles = []
        # ... graph traversal logic ...
        return cycles

    def test_market_manipulation(self):
        '''Simulate coordinated buying to check for cornering'''
        print("\nTesting market manipulation resistance...\n")

        for item in self.economy.items:
            # Simulate 100 players buying entire supply
            available_supply = self.economy.get_total_supply(item)
            market_price = self.economy.get_price(item)

            # Cost to buy entire supply
            total_cost = available_supply * market_price

            # Price after buying
            new_price = self.economy.simulate_price_after_buying(item, available_supply)

            price_increase = (new_price / market_price - 1) * 100

            if price_increase > 500:  # 5x price increase
                print(f"⚠️  {item}: Vulnerable to cornering!")
                print(f"   Supply: {available_supply}")
                print(f"   Cost to corner: {total_cost:,}")
                print(f"   Price increase: {price_increase:.0f}%")
                print(f"   Recommendation: Increase supply or add price ceiling")

        print("\n✓ Market manipulation test complete")

    def test_duping_detection(self):
        '''Verify transactions are atomic and validated'''
        print("\nTesting duping prevention...\n")

        # Check if trades are atomic
        if not hasattr(self.economy, 'execute_trade_atomic'):
            print("⚠️  WARNING: No atomic trade execution found!")
            print("   Implement database transactions to prevent duping")

        # Check if inventory is validated
        if not hasattr(self.economy, 'validate_inventory'):
            print("⚠️  WARNING: No inventory validation found!")
            print("   Add checks for negative quantities and overflow")

        print("✓ Duping prevention checks complete")

    def run_all_exploit_tests(self):
        '''Run comprehensive exploit detection'''
        print("="*60)
        print("EXPLOIT DETECTION SUITE")
        print("="*60 + "\n")

        tests = [
            ("Arbitrage Loops", self.find_arbitrage_loops),
            ("Market Manipulation", self.test_market_manipulation),
            ("Duping Prevention", self.test_duping_detection),
        ]

        exploits_found = []

        for name, test in tests:
            print(f"\n{name}:")
            print("-" * 40)
            result = test()
            if result:
                exploits_found.extend(result)

        print("\n" + "="*60)
        print("EXPLOIT DETECTION SUMMARY")
        print("="*60)

        if len(exploits_found) == 0:
            print("✓ No exploits detected!")
        else:
            print(f"❌ Found {len(exploits_found)} potential exploits!")
            print("Fix these before launch!")

        return exploits_found

Common Pitfalls

Pitfall 1: No Money Sinks (Runaway Inflation)

The Mistake:

# ❌ Money enters but never leaves
class BrokenEconomy:
    def new_player_joins(self, player):
        player.credits += 10000  # Faucet

    def complete_quest(self, player):
        player.credits += 5000  # Faucet

    # NO SINKS! Money accumulates forever

Why It Fails:

  • Every player brings 10,000 credits
  • Quests add 5,000 credits each
  • After 1 month: Millions of excess credits
  • Hyperinflation: Prices skyrocket
  • New players can't afford anything

Real-World Example: Early World of Warcraft had insufficient gold sinks. Players accumulated millions of gold, causing runaway inflation. Blizzard added repair costs, mounts, and consumables to drain gold.

The Fix:

# ✅ Balance faucets with sinks
class StableEconomy:
    def calculate_faucets(self, player_count):
        daily_faucet = player_count * 10000  # New players + quests
        return daily_faucet

    def calculate_sinks(self, player_count):
        # Transaction fees
        daily_trades = player_count * 10
        trade_fee = daily_trades * 1000 * 0.02

        # Repairs
        daily_repairs = player_count * 5 * 500

        # Consumables (ammo, fuel)
        daily_consumables = player_count * 100

        # Listing fees
        daily_listings = player_count * 5 * 1000 * 0.05

        daily_sink = trade_fee + daily_repairs + daily_consumables + daily_listings
        return daily_sink

    def validate_balance(self, player_count):
        faucet = self.calculate_faucets(player_count)
        sink = self.calculate_sinks(player_count)
        ratio = sink / faucet

        assert 0.8 <= ratio <= 1.2, f"Sink/Faucet ratio is {ratio} (need 0.8-1.2)"

Critical Sinks to Include:

  • Transaction fees (1-5%)
  • Repair costs (items degrade)
  • Consumables (ammo, fuel, food)
  • Listing fees (marketplace)
  • Fast travel costs
  • Housing/storage fees
  • NPC luxury items (cosmetics)

Pitfall 2: NPC Arbitrage (Infinite Money Loops)

The Mistake:

# ❌ NPCs buy and sell at same price
class ExploitableEconomy:
    def npc_sell_price(self, item):
        return self.market_price[item]

    def npc_buy_price(self, item):
        return self.market_price[item]  # SAME!

    # Players discover:
    # Mine 50 Iron (free) → Craft Hull → Sell to NPC
    # Input: 50 Iron @ 10 = 500 credits
    # Output: 1 Hull @ 1000 = 1000 credits
    # Profit: 500 credits (INFINITE LOOP!)

Why It Fails:

  • Players find optimal craft chains
  • Everyone grinds the same exploit
  • Economy becomes meaningless (everyone prints money)
  • Hyperinflation accelerates

Real-World Example: Path of Exile had a vendor recipe that generated profit. Players automated it, crashing the economy. GGG nerfed the recipe.

The Fix:

# ✅ NPCs buy at discount (bid-ask spread)
class SafeEconomy:
    def npc_sell_price(self, item):
        return self.market_price[item] * 1.0  # 100% (sell to players)

    def npc_buy_price(self, item):
        return self.market_price[item] * 0.6  # 60% (buy from players)

    def validate_no_arbitrage(self):
        for recipe in self.recipes:
            input_cost = sum(
                self.npc_sell_price(mat) * qty
                for mat, qty in recipe.inputs.items()
            )
            output_value = self.npc_buy_price(recipe.output)

            if output_value >= input_cost:
                raise ValueError(f"{recipe.output} creates infinite money!")

Bid-Ask Spread Guidelines:

  • Safe: NPCs buy at 50-60% (forces player trading)
  • Moderate: NPCs buy at 60-70%
  • Risky: NPCs buy at 80%+ (validate carefully!)

Pitfall 3: Unbounded Prices (Wild Swings)

The Mistake:

# ❌ Prices multiply with no bounds
class UnstablePrices:
    def update_price(self, item, demand, supply):
        if demand > supply:
            self.price[item] *= 1.1  # +10% every update

        # After 50 updates: 10 * (1.1^50) = 1,173 credits!
        # After 100 updates: 137,806 credits (unusable)

Why It Fails:

  • Prices can explode to infinity or crash to zero
  • Market becomes unusable
  • Players can't plan or budget
  • Coordinated manipulation causes chaos

Real-World Example: Eve Online had periods of extreme price volatility before implementing market stabilization tools.

The Fix:

# ✅ Bounded prices with dampening
class StablePrices:
    def __init__(self, base_price):
        self.price = base_price
        self.min_price = base_price * 0.1   # Floor: 10% of base
        self.max_price = base_price * 10.0  # Ceiling: 10x base

    def update_price(self, demand, supply):
        if demand > supply:
            self.price *= 1.01  # Dampened: +1% not +10%
        else:
            self.price *= 0.99  # Dampened: -1%

        # Enforce bounds
        self.price = clamp(self.price, self.min_price, self.max_price)

Key fixes:

  • Price floors (minimum > 0)
  • Price ceilings (maximum < infinity)
  • Dampening (1.01x adjustments, not 1.1x)
  • Smoothing (EMA, not instant)

Pitfall 4: Unbalanced Production Chains

The Mistake:

# ❌ Some chains way more profitable than others
class ImbalancedChains:
    # Hull: 800 cost → 1000 value = 200 profit (25%)
    # Electronics: 950 cost → 800 value = -150 profit (LOSS!)
    # Fuel Cell: 280 cost → 500 value = 220 profit (79%!)

    # Result: Everyone crafts Fuel Cells, nobody crafts Electronics

Why It Fails:

  • Dominant strategies emerge (one chain is best)
  • Other chains become useless
  • Market distorts (excess supply of one item)
  • Ships can't be built (missing Electronics)

Real-World Example: Final Fantasy XIV regularly rebalances crafting recipes to ensure all disciplines are equally profitable.

The Fix:

# ✅ Analyze and balance all chains
class BalancedChains:
    def analyze_profit_margins(self):
        for recipe in self.recipes:
            input_cost = sum(self.price[mat] * qty for mat, qty in recipe.inputs.items())
            output_value = self.price[recipe.output]
            time_hours = recipe.time / 3600

            profit_per_hour = (output_value - input_cost) / time_hours

            print(f"{recipe.output}: {profit_per_hour:.0f} credits/hour")

        # Ensure all chains have similar profit/hour (within 2-3x)

    def balance_recipe(self, recipe, target_profit_per_hour):
        current_profit = self.calculate_profit_per_hour(recipe)

        if current_profit < target_profit_per_hour:
            # Increase output value or decrease input cost
            multiplier = target_profit_per_hour / current_profit
            recipe.output_quantity *= multiplier

Balance targets:

  • All chains should be within 2-3x profit/hour
  • No chain should be strictly better (dominant strategy)
  • Complex chains can pay more (reward knowledge)

Pitfall 5: No Velocity Control (Resource Flood)

The Mistake:

# ❌ Infinite resource production, no consumption
class ResourceFlood:
    def mine(self, player, resource):
        # 100 units/hour per player
        # With 1000 players: 2.4M units/day
        # After 30 days: 72M units (infinite!)
        player.inventory[resource] += 100

Why It Fails:

  • Resources accumulate infinitely
  • Supply >> demand always
  • Prices crash to near-zero
  • Mining becomes pointless (market flooded)

Real-World Example: Runescape had resource inflation for years. They added item sinks (high-level equipment degrades and must be repaired with resources).

The Fix:

# ✅ Add resource sinks
class ControlledVelocity:
    def consume_fuel(self, player):
        # Travel requires fuel
        player.inventory['Fuel'] -= 10

    def repair_ship(self, player, ship):
        # Repairs consume resources
        player.inventory['Iron'] -= 20

    def decay_food(self, player):
        # Food spoils over time
        for item in player.inventory:
            if item.type == 'food':
                item.durability -= 1
                if item.durability <= 0:
                    player.inventory.remove(item)

    def storage_limits(self, player):
        # Can't hoard infinite resources
        max_storage = 10000
        for resource in player.inventory:
            if player.inventory[resource] > max_storage:
                player.inventory[resource] = max_storage

Resource sinks to add:

  • Consumables (fuel, ammo, food)
  • Repairs (items degrade)
  • Decay (items expire)
  • Storage limits (can't hoard infinitely)
  • Crafting failures (chance to lose materials)

Pitfall 6: New Player Hyperinflation Trap

The Mistake:

# ❌ New players join into inflated economy
class NewPlayerTrap:
    def __init__(self):
        # Month 1: Ships cost 3,500
        # Month 6: Ships cost 500,000 (inflation)
        # New player has: 10,000 credits
        # Can't afford basic items!
        pass

Why It Fails:

  • Veterans have millions of credits
  • Prices inflate to match veteran wealth
  • New players can't afford anything
  • New player retention plummets

Real-World Example: Eve Online addresses this with "new player areas" where prices are capped and subsidized.

The Fix:

# ✅ Scale starting credits with inflation
class NewPlayerProtection:
    def calculate_starting_credits(self):
        # Calculate current price index
        current_price_level = self.calculate_average_price()
        base_price_level = 100  # Launch prices

        inflation_ratio = current_price_level / base_price_level

        # Scale starting credits
        base_starting_credits = 10000
        adjusted_credits = base_starting_credits * inflation_ratio

        return adjusted_credits

    def new_player_marketplace(self, player):
        # Separate market for new players
        if player.account_age_days < 7:
            # Access to subsidized prices
            return self.new_player_market
        else:
            return self.main_market

Protections to add:

  • Scaling starting credits (track inflation)
  • New player zones (capped prices)
  • Subsidized vendors (sell basics cheaply)
  • Progressive taxation (veterans lose money to system)

Pitfall 7: Market Death Spirals

The Mistake:

# ❌ Price spikes cause demand collapse
class DeathSpiral:
    # Platinum spikes to 500 (10x normal)
    # → Nobody buys Platinum
    # → Supply accumulates
    # → Price crashes to 5 (10x too low)
    # → Nobody mines Platinum
    # → Supply dries up
    # → Price spikes again
    # OSCILLATION FOREVER

Why It Fails:

  • Unstable equilibrium (no damping)
  • Market never settles
  • Players can't rely on prices
  • Crafting becomes impossible

The Fix:

# ✅ NPC market makers stabilize prices
class StabilizedMarket:
    def __init__(self):
        self.npc_maker = NPCMarketMaker(
            target_price=50,
            liquidity=10000
        )

    def update(self):
        if self.is_market_unstable():
            # NPC provides liquidity to stabilize
            self.npc_maker.provide_liquidity(self.order_book)

Stabilization techniques:

  • NPC market makers (provide liquidity)
  • Price floors (NPCs always buy at minimum)
  • Supply decay (excess inventory expires)
  • Inventory limits (can't stockpile infinitely)

Real-World Examples

Example 1: Eve Online (Complex Player-Driven Economy)

Eve Online has one of the most complex game economies, with:

  • 300,000+ active players
  • $100+ million USD equivalent traded annually
  • Full order book matching
  • Regional markets (different prices per system)
  • Player corporations (guilds with shared resources)

Key Patterns:

# Conceptual Eve economy system
class EveOnlineEconomy:
    def __init__(self):
        # Regional markets (different prices per system)
        self.regions = {}  # {region_id: Market}

        # Full order book matching
        self.order_books = {}  # {(item, region): OrderBook}

        # NPC sinks
        self.npc_seeding = True  # NPCs sell blueprints (faucets)
        self.transaction_tax = 0.025  # 2.5% sales tax (sink)
        self.broker_fee = 0.03  # 3% listing fee (sink)

    def regional_arbitrage(self, item):
        '''Hauling creates gameplay (buy cheap, sell expensive)'''
        # Example: Minerals cheap in mining systems
        jita_price = self.order_books[(item, 'Jita')].get_best_price()
        null_sec_price = self.order_books[(item, 'Null-Sec')].get_best_price()

        profit_per_unit = null_sec_price - jita_price

        # Hauling is risky (pirates, travel time)
        # But profitable if successful
        return profit_per_unit

    def production_chains(self):
        '''Deep production chains (10+ steps)'''
        # Minerals → Components → Subsystems → Ships
        # Each step adds value
        # Complex chains require specialization

        # Example: Building a Titan (supercarrier)
        # - Requires 100+ different materials
        # - Takes 6+ weeks of production time
        # - Costs 70+ billion ISK (700 PLEX ≈ $11,000 USD)

    def destruction_as_sink(self):
        '''Ships are destroyed in combat (major sink)'''
        # When ship explodes, 50% of materials are lost
        # Creates constant demand for new ships
        # Player-driven conflict = economy engine

What Eve Gets Right:

  1. Strong sinks: 50% of destroyed ship value removed from game
  2. Regional economies: Hauling creates gameplay and arbitrage
  3. Deep production chains: Specialization and interdependence
  4. Player-driven conflict: PvP creates demand (ships blow up)
  5. Full transparency: All market data is public (third-party tools)

Lessons:

  • Destruction is the ultimate sink (items permanently removed)
  • Regional markets create hauling gameplay
  • Complex chains encourage specialization
  • Transparency builds trust

Example 2: Path of Exile (Currency Item Economy)

Path of Exile has no gold. Instead, currency items are:

  • Functional (used for crafting)
  • Tradeable (player-to-player)
  • Self-regulating (supply/demand natural)

Key Patterns:

# Conceptual PoE currency system
class PathOfExileEconomy:
    def __init__(self):
        # Currency items are consumable (sinks built-in)
        self.currencies = {
            'Chaos Orb': {'function': 'reroll_item', 'drop_rate': 0.001},
            'Exalted Orb': {'function': 'add_mod', 'drop_rate': 0.0001},
            'Mirror': {'function': 'duplicate_item', 'drop_rate': 0.000001},
        }

        # No NPC trading (player-driven only)
        self.npc_vendors = None  # NPCs only sell basic items

    def currency_as_sink(self, player, item):
        '''Using currency consumes it (automatic sink)'''
        if player.inventory['Chaos Orb'] > 0:
            player.inventory['Chaos Orb'] -= 1  # Consumed!
            item.reroll_mods()  # Item gets random mods

        # This creates natural demand:
        # - Players use currency to craft
        # - Currency is removed from economy
        # - Prices remain stable

    def player_trading(self):
        '''No auction house - player negotiation'''
        # Trade ratios emerge naturally:
        # - 1 Exalted Orb ≈ 150 Chaos Orbs
        # - 1 Mirror ≈ 300 Exalted Orbs ≈ 45,000 Chaos Orbs

        # No NPC prices mean:
        # - Supply/demand sets prices naturally
        # - No arbitrage loops (no NPCs to exploit)
        # - Barter economy (currency for currency)

What PoE Gets Right:

  1. Currency is consumable: Using it removes it from economy (built-in sink)
  2. No NPC trading: Eliminates arbitrage exploits
  3. Functional currency: Items have inherent value (not abstract credits)
  4. Player-driven prices: Natural supply/demand equilibrium
  5. Scarcity tiers: Common to ultra-rare currencies (progression)

Lessons:

  • Make currency consumable (sinks built-in)
  • Eliminate NPCs from core trading (no exploits)
  • Functional currency has inherent value
  • Let players set prices (emergent economy)

Example 3: World of Warcraft Auction House

WoW has a hybrid economy:

  • Player auction house (peer-to-peer)
  • NPC vendors (fixed prices)
  • Gold faucets (quests, dailies)
  • Gold sinks (repairs, mounts, consumables)

Key Patterns:

# Conceptual WoW auction house
class WoWAuctionHouse:
    def __init__(self):
        self.listings = []  # Player listings
        self.deposit_fee = 0.05  # 5% to list (sink)
        self.auction_cut = 0.05  # 5% when sold (sink)

    def create_listing(self, seller, item, price, duration):
        '''Player lists item for sale'''
        # Deposit fee (lost even if item doesn't sell)
        deposit = price * self.deposit_fee
        seller.gold -= deposit  # SINK

        listing = {
            'seller': seller,
            'item': item,
            'buyout_price': price,
            'duration': duration
        }
        self.listings.append(listing)

    def buy_listing(self, buyer, listing):
        '''Player buys item'''
        price = listing['buyout_price']

        # Buyer pays full price
        buyer.gold -= price

        # Auction house takes cut (SINK)
        ah_cut = price * self.auction_cut

        # Seller receives (price - cut)
        listing['seller'].gold += (price - ah_cut)

        # Item transferred
        buyer.inventory.add(listing['item'])

    def gold_sinks(self, player):
        '''Various gold sinks'''
        # Repairs (items degrade)
        repair_cost = 100
        player.gold -= repair_cost

        # Mounts (one-time purchase)
        mount_cost = 5000
        player.gold -= mount_cost

        # Consumables (potions, food)
        consumable_cost = 50
        player.gold -= consumable_cost

        # Fast travel
        flight_cost = 10
        player.gold -= flight_cost

What WoW Gets Right:

  1. Auction house fees: 5-10% removed from every trade (major sink)
  2. Repair costs: Items degrade, require gold to fix
  3. One-time purchases: Mounts, pets, transmog (large sinks)
  4. Consumables: Constant demand (potions, food, enchants)
  5. NPC luxury items: Cosmetics, toys (pure sinks)

Lessons:

  • Transaction fees are effective sinks (every trade removes gold)
  • Durability/repairs create constant spending
  • One-time purchases (mounts) remove large amounts
  • Consumables provide perpetual sinks

Example 4: Diablo 3 (Failed Economy → Fixed)

Diablo 3 at launch:

  • Real Money Auction House (RMAH)
  • Item drops balanced around trading
  • Players could buy best gear (pay-to-win)

What Went Wrong:

# Diablo 3 original economy (FAILED)
class Diablo3OriginalEconomy:
    def __init__(self):
        # Real money auction house
        self.rmah = AuctionHouse(currency='USD')

        # Item drops nerfed (force players to trade)
        self.drop_rate_multiplier = 0.1  # 10x lower drops

    def perverse_incentives(self):
        '''Players stop playing, start trading'''
        # Best gear comes from RMAH, not gameplay
        # Players farm gold → buy gear
        # OR: Farm items → sell for $$$

        # Result: Game becomes job, not fun
        # Players quit

# Diablo 3 fixed economy (SUCCESSFUL)
class Diablo3FixedEconomy:
    def __init__(self):
        # RMAH removed entirely
        self.rmah = None

        # No trading (account-bound loot)
        self.trading = False

        # Drop rates massively increased
        self.drop_rate_multiplier = 10.0  # 10x higher

    def loot_as_reward(self):
        '''Playing is rewarding (not trading)'''
        # Best gear comes from playing
        # No economy, no inflation, no exploits
        # Pure game balance

Lessons from Diablo 3:

  1. Real-money trading is toxic: Creates pay-to-win, farming bots
  2. Removing economy can work: Account-bound loot eliminates exploits
  3. Don't force trading: Let players opt-in to economy
  4. Gameplay should reward players: Not trading/grinding

Example 5: Albion Online (Full Loot PvP Economy)

Albion Online economy:

  • Full loot PvP (killed players drop everything)
  • Player-crafted gear (no NPC vendors)
  • Localized resources (encourages regional markets)
# Conceptual Albion Online economy
class AlbionOnlineEconomy:
    def __init__(self):
        # All gear is player-crafted
        self.npc_vendors = None

        # Full loot PvP (major sink)
        self.full_loot = True

    def death_as_sink(self, killed_player):
        '''Player death removes items from economy'''
        # Killed player drops ALL gear
        # 50% is destroyed, 50% is loot
        for item in killed_player.equipment:
            if random() < 0.5:
                # Destroyed (SINK)
                item.delete()
            else:
                # Dropped (loot for killer)
                killed_player.position.spawn_loot(item)

        # This creates constant demand for new gear
        # Players must re-equip after death
        # Crafters always have customers

    def localized_resources(self):
        '''Different zones have different resources'''
        # Tier 8 resources only in dangerous zones
        # Forces risk vs reward decisions
        # Creates regional markets (hauling gameplay)

What Albion Gets Right:

  1. Full loot PvP: Massive item sink (gear destroyed on death)
  2. Player-crafted economy: No NPC vendors (pure player-driven)
  3. Localized resources: Regional markets and hauling
  4. Risk vs reward: Dangerous zones have best resources

Lessons:

  • Destruction (death) is powerful sink
  • Player-crafting creates interdependence
  • Regional resources create hauling gameplay

Cross-References

Use This Skill WITH:

  • game-balance/economy-balancing: Overall game economy (XP, rewards, progression)
  • multiplayer-netcode: Synchronizing economic state across clients
  • database-design: Storing transactions, inventories, market data
  • anti-cheat: Preventing duping, botting, and exploits

Use This Skill AFTER:

  • game-design-fundamentals: Understanding core loops and player motivations
  • progression-systems: Balancing rewards with economic constraints
  • systems-thinking: Understanding feedback loops and equilibrium

Related Skills:

  • crafting-systems: Production chains and recipes
  • trading-ui-patterns: Interface for player marketplaces
  • auction-house-algorithms: Order matching and price discovery

Testing Checklist

Pre-Launch Validation

  • Faucet/Sink Balance: Sink/Faucet ratio is 0.8-1.2
  • No Arbitrage: All production chains are net-negative vs NPCs
  • Price Bounds: All items have min/max prices set
  • Bid-Ask Spread: NPCs buy at 50-70% of sell price
  • Production Balance: All chains within 3x profit/hour of each other
  • Transaction Fees: 1-5% fee on all trades (major sink)
  • Consumables: Items that require constant spending exist
  • Repair Costs: Items degrade and require gold/resources to fix
  • New Player Protection: Starting credits scale with inflation OR subsidized market
  • Rate Limiting: Daily caps on mining/trading to prevent bots

Stability Testing

  • 30-Day Simulation: Simulate 1,000 players for 30 days, verify stable
  • Exploit Search: Run exploit detector to find arbitrage loops
  • Market Manipulation: Test coordinated buying (cornering)
  • Bot Resistance: Verify daily limits prevent 24/7 farming
  • Death Spiral: Check if price spikes cause permanent instability

Post-Launch Monitoring

  • Track Money Supply: Log total credits in economy daily
  • Track Inflation: Monitor price index over time
  • Detect Exploits: Alert if player earns credits too fast
  • Monitor Imbalances: Flag if one production chain dominates
  • New Player Metrics: Track if new players can afford basics

Emergency Fixes (If Economy Breaks)

  • Rollback Database: Restore to before exploit was discovered
  • Patch Exploit: Fix infinite money loop immediately
  • Emergency Sinks: Add temporary high-cost NPC items
  • Ban Exploiters: Remove profits from players who exploited
  • Communication: Announce fixes to player base transparently

Summary

Economic simulation in games requires balancing faucets (money entering) and sinks (money leaving), validating production chains for arbitrage exploits, and implementing price stabilization to prevent wild swings. The core principles are:

  1. Faucets < Sinks: Money entering must be less than money leaving (0.8-1.0 ratio)
  2. Validate chains: Production chains must be net-negative vs NPCs (no infinite money)
  3. Price bounds: Set min/max prices (prevent 0 or infinity)
  4. Bid-ask spread: NPCs buy at 50-70% of sell price (forces player trading)
  5. Balance chains: All production chains should have similar profit/hour (within 3x)
  6. Resource sinks: Consumables, repairs, decay (control velocity)
  7. New player protection: Scale starting credits with inflation OR subsidized markets
  8. Test before launch: Run 30-day simulation, exploit detection, balance analysis

Master these patterns and avoid the common pitfalls (no sinks, NPC arbitrage, unbounded prices, unbalanced chains), and your economy will be stable, engaging, and exploit-resistant for months or years.