- Restructure flat static prototype into pnpm workspace monorepo - apps/game: playable shell with R3F 3D scene, HUD, SpacetimeDB connection - apps/docs: design docs and prototypes - apps/site: landing page - packages/ui: shared Button and Panel primitives - services/spacetimedb: backend module (9 tables, 11 reducers) - Archive legacy static files to archive/legacy-static/ - Game loop: connect, undock, target, approach, dock, mine, sell - Add pnpm-workspace.yaml, tsconfig.base.json, spacetime.json
1051 lines
77 KiB
JavaScript
1051 lines
77 KiB
JavaScript
window.GDD = window.GDD || {};
|
||
|
||
function EconomyPage() {
|
||
const [activeSection, setActiveSection] = React.useState('overview');
|
||
|
||
const refiningTable = [
|
||
{ ore: 'Veldspar', mineral: 'Tritanium', yield: 415, batch: 333, time: '45s' },
|
||
{ ore: 'Scordite', mineral: 'Pyerite', yield: 171, batch: 333, time: '45s' },
|
||
{ ore: 'Pyroxeres', mineral: 'Nocxium', yield: 8, batch: 333, time: '60s' },
|
||
{ ore: 'Kernite', mineral: 'Isogen', yield: 107, batch: 200, time: '60s' },
|
||
{ ore: 'Omber', mineral: 'Isogen', yield: 86, batch: 500, time: '75s' },
|
||
{ ore: 'Jaspet', mineral: 'Zydrine', yield: 8, batch: 500, time: '75s' },
|
||
{ ore: 'Hemorphite', mineral: 'Nocxium', yield: 21, batch: 500, time: '90s' },
|
||
{ ore: 'Arkonor', mineral: 'Megacyte', yield: 18, batch: 200, time: '120s' },
|
||
];
|
||
|
||
const manufacturingRecipes = [
|
||
{ product: 'Mining Laser I', minerals: 'Tritanium ×200, Pyerite ×80', time: '5m', station: 'Any', skill: 'Industry I' },
|
||
{ product: '150mm Railgun', minerals: 'Tritanium ×400, Pyerite ×150, Nocxium ×20', time: '15m', station: 'Any', skill: 'Industry II' },
|
||
{ product: 'Shield Booster I', minerals: 'Tritanium ×300, Isogen ×50', time: '10m', station: 'Any', skill: 'Industry II' },
|
||
{ product: 'Frigate Hull', minerals: 'Tritanium ×2000, Pyerite ×800, Nocxium ×100', time: '30m', station: 'Factory', skill: 'Industry III' },
|
||
{ product: 'Cruiser Hull', minerals: 'Tritanium ×8000, Pyerite ×3000, Isogen ×500, Nocxium ×200', time: '2h', station: 'Factory', skill: 'Industry IV' },
|
||
{ product: '1MN Afterburner', minerals: 'Tritanium ×150, Pyerite ×50, Isogen ×20', time: '8m', station: 'Any', skill: 'Industry II' },
|
||
];
|
||
|
||
const faucetsAndSinks = [
|
||
{ type: 'faucet', name: 'NPC Buy Orders', description: 'Station NPCs buy basic ores at floor prices. Guarantees new players can always earn ISK.', rate: '~2,000 ISK/min for rookie miner' },
|
||
{ type: 'faucet', name: 'Insurance Payout', description: 'Ship destruction triggers insurance payout (30–120s delay). Tier-dependent: 40–95% of hull value. See Gameplay → Insurance tab.', rate: 'Varies by ship value and tier' },
|
||
{ type: 'faucet', name: 'Mission Rewards', description: 'NPC agents pay ISK for completed missions. Scaling rewards based on mission tier and standing. See Gameplay → Missions tab.', rate: '~5,000–300,000 ₢ per mission (level 1–4)' },
|
||
{ type: 'faucet', name: 'Bounty Prizes', description: 'NPC pirates drop bounty ISK when destroyed. Higher in low-sec space.', rate: '~1,000–20,000 ISK per kill' },
|
||
{ type: 'faucet', name: 'Loyalty Point Store', description: 'Mission LP can be exchanged for faction items at below-market prices, creating indirect ISK value. See Gameplay → Missions tab.', rate: '~500 LP per mission, redeemable for items worth 1–50₢/LP' },
|
||
{ type: 'sink', name: 'Market Tax', description: '2% tax on all market transactions. Higher in NPC stations, lower in player stations (post-MVP).', rate: '2% of transaction value' },
|
||
{ type: 'sink', name: 'Manufacturing Fees', description: 'Station takes a cut of mineral value as a manufacturing fee.', rate: '1–5% of output value' },
|
||
{ type: 'sink', name: 'Ship Purchase', description: 'Buying ships and modules from market or manufacturing. Largest single sink.', rate: 'Variable' },
|
||
{ type: 'sink', name: 'Insurance Premiums', description: 'Players pay 10–50% of hull value for insurance coverage. See Gameplay → Insurance tab.', rate: '10–50% of hull value per 30-day policy' },
|
||
{ type: 'sink', name: 'Blueprint Research', description: 'ME/TE research costs ISK and time. See Economy → Manufacturing tab.', rate: '50,000–5,000,000 ₢ per research level' },
|
||
{ type: 'sink', name: 'Crew Wages', description: 'AI crew members require wage payments. Higher rank = higher cost. (Post-MVP)', rate: '~500–5,000 ISK/cycle' },
|
||
];
|
||
|
||
return (
|
||
<div className="content-inner">
|
||
<h1 style={{ marginBottom: '8px' }}>Economy & Industry</h1>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.95rem', maxWidth: '680px' }}>
|
||
Player-led economy with NPC support as an underlying demand/supply buffer. Players mine, refine,
|
||
manufacture, and trade. NPCs provide a price floor and basic market liquidity so new players
|
||
always have a way to earn and spend.
|
||
</p>
|
||
|
||
<div className="stat-grid" style={{ marginTop: 'var(--sp-5)' }}>
|
||
<div className="stat-card">
|
||
<div className="stat-value" style={{ color: 'var(--accent)' }}>Player-led</div>
|
||
<div className="stat-label">Economy Model</div>
|
||
</div>
|
||
<div className="stat-card">
|
||
<div className="stat-value" style={{ color: 'var(--cyan)' }}>8 → 8</div>
|
||
<div className="stat-label">Ore Types → Minerals</div>
|
||
</div>
|
||
<div className="stat-card">
|
||
<div className="stat-value" style={{ color: 'var(--green)' }}>8+</div>
|
||
<div className="stat-label">Manufacturable Items</div>
|
||
</div>
|
||
<div className="stat-card">
|
||
<div className="stat-value" style={{ color: 'var(--purple)' }}>2%</div>
|
||
<div className="stat-label">Market Tax</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Tab navigation */}
|
||
<div style={{ display: 'flex', gap: 'var(--sp-2)', marginBottom: 'var(--sp-6)', flexWrap: 'wrap' }}>
|
||
{[
|
||
{ id: 'overview', label: 'Flow Overview' },
|
||
{ id: 'first30', label: '🚀 First 30 Minutes' },
|
||
{ id: 'diffusion', label: '📡 Info Diffusion' },
|
||
{ id: 'refining', label: 'Refining' },
|
||
{ id: 'manufacturing', label: 'Manufacturing' },
|
||
{ id: 'npc-pricing', label: '💹 NPC Pricing' },
|
||
{ id: 'faucets', label: 'Faucets & Sinks' },
|
||
].map(t => (
|
||
<button key={t.id} className={`btn btn-sm${activeSection === t.id ? ' btn-primary' : ''}`}
|
||
onClick={() => setActiveSection(t.id)}>{t.label}</button>
|
||
))}
|
||
</div>
|
||
|
||
{/* FIRST 30 MINUTES */}
|
||
{activeSection === 'first30' && (
|
||
<>
|
||
<div className="section-header">
|
||
<span className="section-num">ECON-30</span>
|
||
<h2 style={{ margin: 0 }}>First 30 Minutes of Economy</h2>
|
||
</div>
|
||
|
||
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
|
||
<strong>Exploration and commerce are the core pillars.</strong> This walkthrough shows what a new player sees, decides,
|
||
and experiences in their first 30 minutes — from undocking with an empty cargo hold to discovering their first price
|
||
difference and making a profitable trade. The goal: by minute 30, the player <em>understands</em> that information asymmetry
|
||
is the game, and they've profited from it at least once.
|
||
</div>
|
||
|
||
<div style={{ marginTop: 'var(--sp-6)' }}>
|
||
{[
|
||
{ time: '0:00', title: 'Spawn at Home Station', desc: 'Player connects to SpacetimeDB, spawns in a rookie frigate at their home station. Station Mode UI is active: Market Panel, Inventory, Fitting Screen, and Agent Panel are visible in the sidebar. Wallet shows 0 ISK.', color: 'var(--cyan)' },
|
||
{ time: '0:30', title: 'First NPC Agent Mission', desc: 'The Agent Panel glows with an available mission. Player talks to Agent — "Welcome, pilot. I need 500 units of Veldspar delivered to this station. I\'ll pay 1,500 ISK." Accept mission. Objective marker appears on System Map.', color: 'var(--green)' },
|
||
{ time: '1:00', title: 'Undock into Flight Mode', desc: '3D viewport fades in. Ship floats near the station. Asteroid belts visible as icons in the overview panel. Target the nearest belt and click "Warp To". Ship enters warp.', color: 'var(--accent)' },
|
||
{ time: '2:00', title: 'Mining Cycle', desc: 'Arrive at belt. Three asteroids in view. Target a Veldspar asteroid, click "Mine". Ship approaches automatically. Mining laser activates — progress bar fills. Cargo counter ticks up: 0/150, 10/150, 25/150... The mining laser hums. Zora chirps: "Cargo at 33%."', color: 'var(--purple)' },
|
||
{ time: '5:00', title: 'Cargo Full — Return to Station', desc: 'Cargo hits 150/150 units. HUD flashes "CARGO FULL". Player warps back to station. Docks. Station Mode transitions in. Inventory shows 150 Veldspar.', color: 'var(--cyan)' },
|
||
{ time: '6:00', title: 'First Sale — The NPC Price', desc: 'Open Market Panel. Veldspar shows: NPC Buy Price = 2.50\u2262/unit. Sell 150 units → wallet shows 375 ISK. Not enough for a module. Zora notes: "You could earn more if you refine first — your Industry skill gives 60% yield."', color: 'var(--green)' },
|
||
{ time: '8:00', title: 'The Discovery — Second Station Prices', desc: 'Player warps to a second station in the same system (or an adjacent system). Opens Market Panel. Veldspar NPC Buy Price here = 3.10\u2262/unit. That\'s 24% more. The price is different! This is the aha moment — geography matters.', color: 'var(--red)' },
|
||
{ time: '10:00', title: 'The Decision', desc: 'Mine more Veldspar at the nearby belt. Cargo fills to 150 again. Two choices visible: (A) Sell at Station 1 for 2.50\u2262 = 375 ISK, or (B) Fly to Station 2 and sell for 3.10\u2262 = 465 ISK. Option B earns 90 ISK more — but takes 2 minutes of travel time. Is it worth it?', color: 'var(--accent)' },
|
||
{ time: '12:00', title: 'Executing the Trade Route', desc: 'Player chooses Station 2. Warps there, docks, sells. Wallet: 840 ISK. They notice the Market Panel also shows Scordite at 5.20\u2262 here vs. 4.00\u2262 at Station 1. A second price gap. The loop beckons.', color: 'var(--cyan)' },
|
||
{ time: '15:00', title: 'Agent Mission Turn-In', desc: 'Return to home station. Turn in the 500 Veldspar mission (already over-delivered). Agent pays 1,500 ISK + 50 LP. Wallet: 2,340 ISK + 150 units excess ore. Standing +0.10 with agent.', color: 'var(--green)' },
|
||
{ time: '18:00', title: 'First Refining Decision', desc: 'Refining tab shows: 200 Veldspar at 60% yield = 249 Tritanium (worth ~797\u2262 vs. raw ore at 500\u2262). Refine. Minerals appear in hangar. The production chain becomes visible.', color: 'var(--purple)' },
|
||
{ time: '22:00', title: 'Zora Flags an Opportunity', desc: 'Zora\'s market module fires: "I\'ve tracked your trades. Veldspar prices at Station 2 have been above average for 3 cycles. This looks like sustained demand — possibly a manufacturing cluster buying raw materials. I\'d recommend filling your cargo before heading there next time."', color: 'var(--accent)' },
|
||
{ time: '25:00', title: 'Manufacturing Tab Peek', desc: 'Player opens Manufacturing tab. Mining Laser I requires: 200 Tritanium + 80 Pyerite (which they partially have). The blueprint shows a 5-minute job. They\'re 40 Pyerite short. They know Scordite refines to Pyerite. A new mining target appears.', color: 'var(--cyan)' },
|
||
{ time: '30:00', title: 'The Loop Is Set', desc: 'Player has 2,340 ISK, 249 Tritanium, a standing relationship with an NPC agent, and two known price differences between stations. They understand: mine → choose where to sell → reinvest → manufacture. The economy is no longer abstract — it\'s a map of opportunities they can see and act on.', color: 'var(--green)' },
|
||
].map((step, i) => (
|
||
<div key={i} className="phase-item">
|
||
<div className="phase-marker">
|
||
<div className="phase-dot" style={{ background: step.color, boxShadow: '0 0 8px ' + step.color + '40' }} />
|
||
{i < 13 && <div className="phase-line" />}
|
||
</div>
|
||
<div className="phase-content">
|
||
<div style={{ display: 'flex', alignItems: 'baseline', gap: 'var(--sp-3)', marginBottom: 'var(--sp-1)' }}>
|
||
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.7rem', color: step.color }}>MIN {step.time}</span>
|
||
<h4 style={{ margin: 0 }}>{step.title}</h4>
|
||
</div>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>{step.desc}</p>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<div className="callout callout-warn" style={{ marginTop: 'var(--sp-6)' }}>
|
||
<strong>Design intent:</strong> By minute 8, the player has discovered that prices differ between stations.
|
||
By minute 12, they\'ve acted on that discovery and profited. By minute 30, they have a mental model of the
|
||
economy as a landscape of opportunities, not a single "sell" button. This is the hook. If the price discovery
|
||
moment doesn\'t feel exciting, the entire game falls flat.
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
{/* DIFFUSION */}
|
||
{activeSection === 'diffusion' && (
|
||
<>
|
||
<div className="section-header">
|
||
<span className="section-num">ECON-📡</span>
|
||
<h2 style={{ margin: 0 }}>Information Diffusion Between Systems</h2>
|
||
</div>
|
||
|
||
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
|
||
<strong>This IS the game.</strong> In EVE Online, the richest players aren't the best pilots — they're the best
|
||
informed. Information about market prices, supply disruptions, and trade opportunities propagates at the speed
|
||
of player travel, not the speed of light. A player who knows that Veldspar spiked 40% in Amarr while it's still
|
||
cheap in Jita has a window of opportunity that closes as other traders make the same discovery. This is the
|
||
core loop of a spreadsheet simulator: <strong>information asymmetry → profitable action → market correction</strong>.
|
||
</div>
|
||
|
||
<div className="card card-accent" style={{ padding: 'var(--sp-5) var(--sp-6)', marginBottom: 'var(--sp-5)' }}>
|
||
<h4 style={{ color: 'var(--accent)', marginBottom: 'var(--sp-4)' }}>Information Propagation Model</h4>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', color: 'var(--fg-dim)', lineHeight: 2.2 }}>
|
||
<span style={{ color: 'var(--cyan)' }}>Event occurs</span> (belt depleted, station sold out, pirate attack) →{' '}
|
||
<span style={{ color: 'var(--accent)' }}>Local chat sees it immediately</span> →{' '}
|
||
<span style={{ color: 'var(--green)' }}>Adjacent systems learn in ~2 min</span> →{' '}
|
||
<span style={{ color: 'var(--purple)' }}>Hub markets update in ~5 min</span> →{' '}
|
||
<span style={{ color: 'var(--fg)' }}>Full region in ~15 min</span> →{' '}
|
||
<span style={{ color: 'var(--muted)' }}>Galaxy-wide in ~30 min</span> <span style={{ color: 'var(--accent)', fontSize: '0.7rem' }}>(single galaxy propagation)</span>
|
||
</div>
|
||
</div>
|
||
|
||
<h3 style={{ marginBottom: 'var(--sp-4)' }}>Information Channels</h3>
|
||
<table className="data-table">
|
||
<thead>
|
||
<tr><th>Channel</th><th>Propagation</th><th>Latency</th><th>Reliability</th><th>Example</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><span className="pill pill-cyan">Local Market</span></td>
|
||
<td>Instant, system-only</td>
|
||
<td>0s</td>
|
||
<td>100% — live order book</td>
|
||
<td>You see every buy/sell order in your current station</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span className="pill pill-green">Local Chat</span></td>
|
||
<td>Instant, system-only</td>
|
||
<td>0s</td>
|
||
<td>Unverified — players lie</td>
|
||
<td>"Scordite prices are crashing in Amarr" — could be true, could be market manipulation</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span className="pill pill-amber">Ship AI Reports</span></td>
|
||
<td>Aggregated, player-specific</td>
|
||
<td>Real-time</td>
|
||
<td>High — computed from data your ship has seen</td>
|
||
<td>Zora says: "Veldspar is 18% below its 7-day average here. I've logged 3 similar price dips that corrected within 2 hours."</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span className="pill pill-purple">Region Market Data</span></td>
|
||
<td>Delayed by jump distance</td>
|
||
<td>2–10 min</td>
|
||
<td>Stale — snapshot, not live</td>
|
||
<td>The price you see for a system 5 jumps away is from 5 minutes ago. It may have moved.</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span className="pill pill-red">News Feed</span></td>
|
||
<td>Galaxy-wide events</td>
|
||
<td>15–30 min</td>
|
||
<td>Factual but delayed</td>
|
||
<td>"Major fleet engagement in PF-346. Megacyte supply disruptions expected." Includes world events: faction wars, anomalies, migrations.</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span className="pill pill-cyan">Scout Reports</span></td>
|
||
<td>Manual, player-to-player</td>
|
||
<td>Variable</td>
|
||
<td>Trust-based</td>
|
||
<td>A corpmate tells you they saw a Tritanium shortage in Rens. First-hand intel.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<h3 style={{ marginTop: 'var(--sp-6)', marginBottom: 'var(--sp-4)' }}>Why This Matters</h3>
|
||
<div className="grid-2">
|
||
<div className="card" style={{ borderLeft: '3px solid var(--accent)' }}>
|
||
<h4 style={{ color: 'var(--accent)' }}>Information Asymmetry = Gameplay</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
The player who <em>knows</em> a price discrepancy exists <em>first</em> can fill a hauler and profit.
|
||
The player who arrives late finds the gap already closed. This is why speed of information matters,
|
||
and why players will pay for better intel, scout networks, and fast ships for information running.
|
||
</p>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--cyan)' }}>
|
||
<h4 style={{ color: 'var(--cyan)' }}>Delayed Data = Speculation</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
Because market data from other systems is delayed, traders must <em>predict</em> where prices are heading.
|
||
They see a snapshot from 5 minutes ago and must decide: is the trend still moving, or has it reversed?
|
||
This creates genuine risk and reward from pure information work — no combat required.
|
||
</p>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--green)' }}>
|
||
<h4 style={{ color: 'var(--green)' }}>Geography = Strategy</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
Systems farther from trade hubs have more stale data and wider spreads. Deep null-sec markets are
|
||
almost blind — the player who establishes a reliable supply chain out there controls the local economy.
|
||
Distance is not just travel time; it's an information barrier.
|
||
</p>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--purple)' }}>
|
||
<h4 style={{ color: 'var(--purple)' }}>Zora as Information Edge</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
The ship AI (Zora) is a force multiplier for information. She remembers every price the player has seen,
|
||
spots patterns, flags anomalies, and can continue monitoring markets while the player is offline.
|
||
A high-phase Zora is worth more than any single module — she's a living spreadsheet that
|
||
<em> thinks</em> about your portfolio.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<h3 style={{ marginTop: 'var(--sp-6)', marginBottom: 'var(--sp-4)' }}>Implementation: Market Data Pipeline</h3>
|
||
<div className="card" style={{ padding: 0, overflow: 'hidden' }}>
|
||
<div style={{ padding: 'var(--sp-3) var(--sp-4)', background: 'var(--surface-raised)', borderBottom: '1px solid var(--border)' }}>
|
||
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.7rem', color: 'var(--accent)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
|
||
SpacetimeDB — Market Diffusion Tables
|
||
</span>
|
||
</div>
|
||
<div className="code-block" style={{ margin: 0, borderRadius: 0 }}>
|
||
<code>
|
||
<span className="cm">// Each system has its own view of market reality</span><br/>
|
||
<span className="kw">table</span> <span className="type">system_market_view</span> {'{'}<br/>
|
||
system_id: <span className="type">u64</span>,<br/>
|
||
commodity_id: <span className="type">u64</span>,<br/>
|
||
best_bid: <span className="type">f64</span>,<br/>
|
||
best_ask: <span className="type">f64</span>,<br/>
|
||
last_trade_price: <span className="type">f64</span>,<br/>
|
||
volume_24h: <span className="type">u64</span>,<br/>
|
||
snapshot_time: <span className="type">timestamp</span>,<br/>
|
||
<span className="cm">// ^ How stale is this data? Key field.</span><br/>
|
||
{'}'}<br/>
|
||
<br/>
|
||
<span className="cm">// Diffusion scheduler — propagates data between connected systems</span><br/>
|
||
<span className="kw">reducer</span> <span className="fn">propagate_market_data</span>(ctx) {'{'}<br/>
|
||
<span className="cm">// Each tick, for each system pair connected by a gate:</span><br/>
|
||
<span className="cm">// 1. Compare local prices vs neighbor's snapshot</span><br/>
|
||
<span className="cm">// 2. If snapshot_age > propagation_delay, push updated prices</span><br/>
|
||
<span className="cm">// 3. Add random noise proportional to distance from source</span><br/>
|
||
<span className="cm">// 4. Diffusion speed ~1 system per 2 minutes per jump</span><br/>
|
||
{'}'}<br/>
|
||
</code>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
{/* OVERVIEW */}
|
||
{activeSection === 'overview' && (
|
||
<>
|
||
<div className="card card-accent" style={{ padding: 'var(--sp-6) var(--sp-8)' }}>
|
||
<h4 style={{ marginBottom: 'var(--sp-4)' }}>Economy Pipeline</h4>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.85rem', color: 'var(--fg-dim)', lineHeight: 2.2 }}>
|
||
<span style={{ color: 'var(--accent)' }}>Mine Ore</span> →{' '}
|
||
<span style={{ color: 'var(--fg-bright)' }}>Choose:</span><br/>
|
||
├─ <span style={{ color: 'var(--green)' }}>Sell Raw</span> → <span style={{ color: 'var(--cyan)' }}>Market</span> (fast, lower price)<br/>
|
||
└─ <span style={{ color: 'var(--purple)' }}>Refine</span> → <span style={{ color: 'var(--fg-bright)' }}>Minerals</span> →{' '}
|
||
<span style={{ color: 'var(--fg-bright)' }}>Choose:</span><br/>
|
||
├─ <span style={{ color: 'var(--green)' }}>Sell Minerals</span> → <span style={{ color: 'var(--cyan)' }}>Market</span> (higher value)<br/>
|
||
└─ <span style={{ color: 'var(--red)' }}>Manufacture</span> → <span style={{ color: 'var(--accent)' }}>Ships/Modules</span> →{' '}
|
||
<span style={{ color: 'var(--cyan)' }}>Use or Sell</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="section-header" style={{ marginTop: 'var(--sp-6)' }}>
|
||
<span className="section-num">ECON-PHI</span>
|
||
<h2 style={{ margin: 0 }}>Design Philosophy</h2>
|
||
</div>
|
||
|
||
<div className="grid-2">
|
||
<div className="card">
|
||
<h4 style={{ color: 'var(--accent)' }}>Player agency first</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>
|
||
Players set prices, build supply chains, and create market dynamics. NPC orders are a
|
||
safety net, not the primary economy. The goal is emergent trade routes, regional price
|
||
differences, and player-driven scarcity.
|
||
</p>
|
||
</div>
|
||
<div className="card">
|
||
<h4 style={{ color: 'var(--cyan)' }}>NPC as buffer</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>
|
||
NPC buy orders create a price floor so mining is always worth something. NPC sell orders
|
||
provide basic items at a premium so new players aren't stuck. As the player economy
|
||
matures, NPCs step back.
|
||
</p>
|
||
</div>
|
||
<div className="card">
|
||
<h4 style={{ color: 'var(--green)' }}>Geographic trade</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>
|
||
Different systems produce different ores. Null-sec has rarer minerals but higher risk.
|
||
This creates natural trade corridors and hauler opportunities. Region matters.
|
||
</p>
|
||
</div>
|
||
<div className="card">
|
||
<h4 style={{ color: 'var(--purple)' }}>Sinks balance faucets</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>
|
||
Every ISK entering the economy must eventually leave. Ship destruction is the primary
|
||
sink — it's fun, dramatic, and removes value. Taxes and fees are the steady drain.
|
||
Inflation is the enemy.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="section-header" style={{ marginTop: 'var(--sp-8)' }}>
|
||
<span className="section-num">ECON-MKT</span>
|
||
<h2 style={{ margin: 0 }}>Market Surface — The Primary Game Interface</h2>
|
||
</div>
|
||
|
||
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
|
||
<strong>The Market Panel is where players spend most of their time.</strong> It is the richest, most complex UI surface
|
||
in the game. The Market demo (<code>js/demos/market.js</code>) validates a feature set that goes beyond the original
|
||
Economy spec. This section brings the spec up to what the demo actually implements.
|
||
</div>
|
||
|
||
<div className="grid-2" style={{ marginBottom: 'var(--sp-6)' }}>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--cyan)' }}>
|
||
<h4 style={{ color: 'var(--cyan)' }}>Order Book & Depth</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
Every commodity has a live order book with <strong style={{ color: 'var(--fg)' }}>bid/ask spread</strong> visualization.
|
||
Buy orders (bids) stack on the left, sell orders (asks) on the right. Depth chart shows cumulative volume at each price level.
|
||
Players can see exactly where their order sits in the queue.
|
||
</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
|
||
Order types: Market (instant fill), Limit (price-specified), Stop (trigger-based)
|
||
</div>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--accent)' }}>
|
||
<h4 style={{ color: 'var(--accent)' }}>Price History & Charts</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
<strong style={{ color: 'var(--fg)' }}>Candlestick charts</strong> with configurable timeframes (1h, 6h, 24h, 7d, 30d).
|
||
Volume bars overlaid. Moving averages (7-period, 20-period). Players can annotate charts and share screenshots.
|
||
Historical data is per-station, per-commodity — no galaxy-wide aggregation (information is local).
|
||
</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
|
||
Data source: SpacetimeDB market_orders table + computed aggregations
|
||
</div>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--green)' }}>
|
||
<h4 style={{ color: 'var(--green)' }}>Contract Specifications</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
Each commodity is traded as a <strong style={{ color: 'var(--fg)' }}>contract</strong> with standardized lot sizes.
|
||
Contracts specify: commodity type, lot size, tick size (minimum price increment), and settlement rules.
|
||
This enables the order book to aggregate orders efficiently and provides a clean abstraction for the market engine.
|
||
</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
|
||
Example: Tritanium contract = 1,000 units/lot, tick = 0.01₢
|
||
</div>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--purple)' }}>
|
||
<h4 style={{ color: 'var(--purple)' }}>Margin Accounts & Positions</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
<strong style={{ color: 'var(--fg)' }}>Era 2 feature.</strong> Players can open <strong style={{ color: 'var(--fg)' }}>long and short positions</strong>
|
||
on commodity contracts using a margin account. Margin requires collateral (ISK + assets). Maintenance margin enforced —
|
||
if position moves against the player beyond the margin, the position is force-liquidated.
|
||
This adds speculative depth for advanced traders.
|
||
</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
|
||
Margin ratio: 20% initial, 10% maintenance · Force liquidation at 5%
|
||
</div>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--red)' }}>
|
||
<h4 style={{ color: 'var(--red)' }}>Commodity Ticker</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
Scrolling price ticker across all active contracts. Real-time price updates filtered by player's current station
|
||
or region. Category filters (Ore, Minerals, Modules, Ships). Sparkline mini-charts next to each ticker entry.
|
||
The ticker runs in both Flight Mode (bottom bar) and Station Mode (market panel sidebar).
|
||
</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
|
||
Update rate: 5s in-station, 30s regional (info diffusion applies)
|
||
</div>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--cyan)' }}>
|
||
<h4 style={{ color: 'var(--cyan)' }}>Station-Filtered View</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
Market data is <strong style={{ color: 'var(--fg)' }}>station-local by default</strong>. Players see the full order book
|
||
for their current station. To see prices at other stations, they must use the Region Market Data channel
|
||
(delayed, see Info Diffusion tab) or travel there physically. No global market view exists — information geography is real.
|
||
</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
|
||
Players at trade hubs see more orders, tighter spreads, and faster updates
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="callout callout-info" style={{ marginTop: 'var(--sp-5)' }}>
|
||
<strong>Priority callout:</strong> Economy feel + social/multiplayer feel are the two most
|
||
important things to get right. If the economy doesn't feel rewarding and dynamic, the game
|
||
falls flat regardless of how good combat or graphics are.
|
||
</div>
|
||
|
||
<div className="callout callout-warn" style={{ marginTop: 'var(--sp-4)' }}>
|
||
<strong>Currency naming:</strong> "ISK" (symbol \u2262) is a <em>temporary placeholder</em>. The final in-game currency name
|
||
will be chosen during development. All references to ISK throughout the GDD should be understood as subject to renaming.
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
{/* REFINING */}
|
||
{activeSection === 'refining' && (
|
||
<>
|
||
<div className="section-header">
|
||
<span className="section-num">ECON-REF</span>
|
||
<h2 style={{ margin: 0 }}>Ore → Mineral Refining</h2>
|
||
</div>
|
||
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.9rem', maxWidth: '680px', marginBottom: 'var(--sp-5)' }}>
|
||
Raw ore can be sold directly or refined into minerals at a station with reprocessing
|
||
facilities. Refined minerals are worth more per m³ but require batch sizes and processing
|
||
time. Refining efficiency improves with the player's Industry skill level.
|
||
</p>
|
||
|
||
<div style={{ overflowX: 'auto' }}>
|
||
<table className="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Ore</th>
|
||
<th>Yields Mineral</th>
|
||
<th>Units per Batch</th>
|
||
<th>Base Yield</th>
|
||
<th>Process Time</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{refiningTable.map((row, i) => (
|
||
<tr key={i}>
|
||
<td style={{ color: 'var(--accent)' }}>{row.ore}</td>
|
||
<td style={{ color: 'var(--cyan)' }}>{row.mineral}</td>
|
||
<td className="mono">{row.batch}</td>
|
||
<td className="mono">{row.yield}</td>
|
||
<td className="mono">{row.time}</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div className="grid-2" style={{ marginTop: 'var(--sp-5)' }}>
|
||
<div className="card">
|
||
<h4 style={{ color: 'var(--accent)' }}>Refining Efficiency</h4>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', color: 'var(--fg-dim)', lineHeight: 2 }}>
|
||
<strong style={{ color: 'var(--fg)' }}>Base efficiency:</strong> 50%<br/>
|
||
<strong style={{ color: 'var(--fg)' }}>Industry I:</strong> 60%<br/>
|
||
<strong style={{ color: 'var(--fg)' }}>Industry II:</strong> 70%<br/>
|
||
<strong style={{ color: 'var(--fg)' }}>Industry III:</strong> 80%<br/>
|
||
<strong style={{ color: 'var(--fg)' }}>Industry IV:</strong> 87.5%<br/>
|
||
<strong style={{ color: 'var(--fg)' }}>Industry V:</strong> 95%<br/>
|
||
<span style={{ color: 'var(--muted)' }}>Lost material is destroyed (not recovered).</span>
|
||
</div>
|
||
</div>
|
||
<div className="card">
|
||
<h4 style={{ color: 'var(--cyan)' }}>Decision: Sell Raw vs Refine?</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
At 50% efficiency, selling raw ore is usually better ISK/m³. At 80%+ efficiency,
|
||
refining is almost always more profitable. This creates a natural skill gate:
|
||
new players sell raw, veterans refine and manufacture.
|
||
</p>
|
||
<div className="callout callout-warn" style={{ fontSize: '0.8rem' }}>
|
||
<strong>Strategic choice:</strong> Do you invest XP into Industry early for better margins,
|
||
or into combat skills for safer mining in low-sec?
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
{/* MANUFACTURING */}
|
||
{activeSection === 'manufacturing' && (
|
||
<>
|
||
<div className="section-header">
|
||
<span className="section-num">ECON-MFG</span>
|
||
<h2 style={{ margin: 0 }}>Manufacturing</h2>
|
||
</div>
|
||
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.9rem', maxWidth: '680px', marginBottom: 'var(--sp-5)' }}>
|
||
Players can manufacture ships, modules, and ammunition from refined minerals. Manufacturing
|
||
requires a station with factory facilities, the correct minerals, and the appropriate Industry
|
||
skill level. Manufacturing time creates natural production queues and opportunity cost.
|
||
</p>
|
||
|
||
<div style={{ overflowX: 'auto' }}>
|
||
<table className="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Product</th>
|
||
<th>Mineral Cost</th>
|
||
<th>Time</th>
|
||
<th>Station</th>
|
||
<th>Skill Req.</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{manufacturingRecipes.map((row, i) => (
|
||
<tr key={i}>
|
||
<td style={{ color: 'var(--accent)' }}>{row.product}</td>
|
||
<td style={{ color: 'var(--fg-dim)', fontSize: '0.75rem' }}>{row.minerals}</td>
|
||
<td className="mono">{row.time}</td>
|
||
<td>{row.station}</td>
|
||
<td><span className="pill pill-cyan">{row.skill}</span></td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div className="section-header" style={{ marginTop: 'var(--sp-8)' }}>
|
||
<span className="section-num">ECON-MFG.1</span>
|
||
<h2 style={{ margin: 0 }}>Full Production Chain</h2>
|
||
</div>
|
||
|
||
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
|
||
<strong>Manufacturing is not one step — it's a chain.</strong>
|
||
Raw ore refines into minerals. Minerals can be directly manufactured into basic modules, but advanced items
|
||
require intermediate components first. This multi-stage chain creates specialization opportunities:
|
||
a player who focuses on component manufacturing sells to ship builders who have never mined a single asteroid.
|
||
</div>
|
||
|
||
<div className="card card-accent" style={{ padding: 'var(--sp-5) var(--sp-6)', marginBottom: 'var(--sp-5)' }}>
|
||
<h4 style={{ color: 'var(--accent)', marginBottom: 'var(--sp-4)' }}>Production Chain Tiers</h4>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', color: 'var(--fg-dim)', lineHeight: 2.2 }}>
|
||
<span style={{ color: 'var(--accent)' }}>Tier 0 — Ore</span> (mined from asteroids): Veldspar, Scordite, Pyroxeres, Kernite, Omber, Jaspet, Hemorphite, Arkonor<br/>
|
||
<span style={{ color: 'var(--cyan)' }}>Tier 1 — Mineral</span> (refined from ore): Tritanium, Pyerite, Nocxium, Isogen, Zydrine, Megacyte, Mexallon, Morphite<br/>
|
||
<span style={{ color: 'var(--green)' }}>Tier 2 — Component</span> (manufactured from minerals): Capacitor Unit, Shield Emitter, Armor Plate, Thruster Assembly, Electronics Cluster, Weapon Housing<br/>
|
||
<span style={{ color: 'var(--purple)' }}>Tier 3 — Module</span> (manufactured from components + minerals): Mining Laser, Railgun, Shield Booster, Afterburner, Warp Scrambler<br/>
|
||
<span style={{ color: 'var(--red)' }}>Tier 4 — Ship</span> (manufactured from components + modules + minerals): Frigate Hull, Destroyer Hull, Cruiser Hull, Industrial Hull, Battlecruiser Hull
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ overflowX: 'auto', marginBottom: 'var(--sp-6)' }}>
|
||
<table className="data-table">
|
||
<thead>
|
||
<tr><th>Stage</th><th>Input</th><th>Output</th><th>Example</th><th>Industry Skill</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
{[
|
||
{ stage: 'Refining', input: '333 Veldspar', output: '415 Tritanium', example: 'Ore → Mineral', skill: 'None (base 50%)' },
|
||
{ stage: 'Component Mfg', input: '200 Tritanium + 80 Pyerite', output: '1 Capacitor Unit', example: 'Mineral → Component', skill: 'Industry I' },
|
||
{ stage: 'Module Mfg', input: '2 Capacitor Units + 150 Tritanium', output: '1 Mining Laser I', example: 'Component → Module', skill: 'Industry II' },
|
||
{ stage: 'Ship Mfg', input: '5 Component packs + 2000 Tritanium + 800 Pyerite + 100 Nocxium', output: '1 Frigate Hull', example: 'Component+Mineral → Ship', skill: 'Industry III' },
|
||
{ stage: 'Advanced Ship', input: '15 Component packs + 8000 Tritanium + 3000 Pyerite + 500 Isogen + 200 Nocxium', output: '1 Cruiser Hull', example: 'Multi-tier chain', skill: 'Industry IV' },
|
||
].map((row, i) => (
|
||
<tr key={i}>
|
||
<td style={{ fontWeight: 600, color: ['var(--accent)', 'var(--green)', 'var(--purple)', 'var(--red)', 'var(--cyan)'][i] }}>{row.stage}</td>
|
||
<td style={{ color: 'var(--fg-dim)', fontSize: '0.75rem' }}>{row.input}</td>
|
||
<td style={{ color: 'var(--fg-dim)', fontSize: '0.75rem' }}>{row.output}</td>
|
||
<td style={{ color: 'var(--muted)', fontSize: '0.75rem' }}>{row.example}</td>
|
||
<td><span className="pill pill-cyan" style={{ fontSize: '0.65rem' }}>{row.skill}</span></td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div className="section-header" style={{ marginTop: 'var(--sp-8)' }}>
|
||
<span className="section-num">ECON-MFG.2</span>
|
||
<h2 style={{ margin: 0 }}>Blueprint Research (ME / TE)</h2>
|
||
</div>
|
||
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.9rem', maxWidth: '680px', marginBottom: 'var(--sp-5)' }}>
|
||
Every manufacturable item has a <strong>Blueprint Original (BPO)</strong>. BPOs define the base recipe, but they can be
|
||
<strong> researched</strong> to improve two attributes: <strong>Material Efficiency (ME)</strong> reduces mineral waste,
|
||
and <strong>Time Efficiency (TE)</strong> reduces manufacturing time. Research costs time and ISK but permanently improves the BPO.
|
||
</p>
|
||
|
||
<div className="grid-2" style={{ marginBottom: 'var(--sp-6)' }}>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--accent)' }}>
|
||
<h4 style={{ color: 'var(--accent)' }}>Material Efficiency (ME)</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
Each ME level reduces material waste by 1%. At ME 0, the recipe uses the base material list (includes 10% waste).
|
||
At ME 10, waste is reduced to 0%. Research time increases exponentially.
|
||
</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.75rem', color: 'var(--fg-dim)', lineHeight: 1.8 }}>
|
||
ME 0: 10% waste (base recipe)<br/>
|
||
ME 1: 9% waste — Research: 30 min + 50,000₢<br/>
|
||
ME 2: 8% waste — Research: 1h + 100,000₢<br/>
|
||
ME 5: 5% waste — Research: 8h + 500,000₢<br/>
|
||
ME 10: 0% waste (perfect) — Research: 48h + 5,000,000₢<br/>
|
||
</div>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)', marginTop: 'var(--sp-2)' }}>
|
||
Formula: actual_materials = base × (1.0 − ME × 0.01)<br/>
|
||
Skill gate: Blueprint Research I → ME 3 max · II → ME 5 · III → ME 8 · IV → ME 10
|
||
</div>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--cyan)' }}>
|
||
<h4 style={{ color: 'var(--cyan)' }}>Time Efficiency (TE)</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
Each TE level reduces manufacturing time by 2%. At TE 0, the recipe takes the base time.
|
||
At TE 20, manufacturing time is reduced by 40%. TE research is cheaper and faster than ME research.
|
||
</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.75rem', color: 'var(--fg-dim)', lineHeight: 1.8 }}>
|
||
TE 0: 0% time reduction (base)<br/>
|
||
TE 5: 10% faster — Research: 15 min + 25,000₢<br/>
|
||
TE 10: 20% faster — Research: 2h + 200,000₢<br/>
|
||
TE 15: 30% faster — Research: 12h + 1,000,000₢<br/>
|
||
TE 20: 40% faster (max) — Research: 24h + 2,500,000₢<br/>
|
||
</div>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)', marginTop: 'var(--sp-2)' }}>
|
||
Formula: actual_time = base_time × (1.0 − TE × 0.02)<br/>
|
||
Skill gate: Same skill levels as ME · TE and ME can be researched independently
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="section-header" style={{ marginTop: 'var(--sp-8)' }}>
|
||
<span className="section-num">ECON-MFG.3</span>
|
||
<h2 style={{ margin: 0 }}>Production Queues & Job Management</h2>
|
||
</div>
|
||
|
||
<div className="grid-2" style={{ marginBottom: 'var(--sp-6)' }}>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--green)' }}>
|
||
<h4 style={{ color: 'var(--green)' }}>Job Queue System</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
Each station has a limited number of <strong style={{ color: 'var(--fg)' }}>manufacturing slots</strong>.
|
||
Players queue jobs and they execute in order. A player can have multiple jobs running simultaneously,
|
||
limited by their Industry skill level.
|
||
</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.75rem', color: 'var(--fg-dim)', lineHeight: 1.8 }}>
|
||
Industry I: 1 concurrent job<br/>
|
||
Industry II: 2 concurrent jobs<br/>
|
||
Industry III: 3 concurrent jobs<br/>
|
||
Industry IV: 5 concurrent jobs<br/>
|
||
Industry V: 8 concurrent jobs (max)
|
||
</div>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--accent)' }}>
|
||
<h4 style={{ color: 'var(--accent)' }}>Station Facilities</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
Not all stations can manufacture everything. Station facility level determines what can be built:
|
||
</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.75rem', color: 'var(--fg-dim)', lineHeight: 1.8 }}>
|
||
<span style={{ color: 'var(--green)' }}>Basic Factory:</span> Modules, ammo, components (any station)<br/>
|
||
<span style={{ color: 'var(--cyan)' }}>Advanced Factory:</span> Frigates, destroyers, industrials<br/>
|
||
<span style={{ color: 'var(--accent)' }}>Capital Yard:</span> Cruisers, battlecruisers<br/>
|
||
<span style={{ color: 'var(--red)' }}>Shipyard:</span> All ships including capitals (null-sec outposts only, post-MVP)
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid-2" style={{ marginBottom: 'var(--sp-6)' }}>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--cyan)' }}>
|
||
<h4 style={{ color: 'var(--cyan)' }}>Blueprint Copies (BPC)</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
BPOs can be copied to create Blueprint Copies (BPCs) with a limited number of runs.
|
||
BPCs are tradable — a manufacturer can sell BPCs on the market without giving up the original BPO.
|
||
Copies inherit ME/TE levels from the original.
|
||
</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
|
||
Copy time: 50% of manufacturing time per run · Max runs: 10 per copy · BPCs stack in inventory
|
||
</div>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--purple)' }}>
|
||
<h4 style={{ color: 'var(--purple)' }}>Invention (Post-MVP)</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
<strong>Tech 2 items</strong> are created through invention — a research process that consumes a BPC, datacores,
|
||
and a decryptor to produce a T2 BPC with a probability of success. T2 items are significantly more powerful
|
||
than T1, making invention a high-value, high-risk specialization.
|
||
</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
|
||
Success rate: 20–60% (skill-dependent) · Requires: Science skills + datacores · T2 items: 1.5–3× T1 stats
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="section-header" style={{ marginTop: 'var(--sp-8)' }}>
|
||
<span className="section-num">ECON-MFG.4</span>
|
||
<h2 style={{ margin: 0 }}>Manufacturing Economics</h2>
|
||
</div>
|
||
|
||
<div className="grid-2" style={{ marginBottom: 'var(--sp-6)' }}>
|
||
<div className="card">
|
||
<h4 style={{ color: 'var(--green)' }}>Blueprints</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
Each manufacturable item has a Blueprint Original (BPO) that defines the recipe. BPOs can
|
||
be researched to reduce mineral waste (Material Efficiency) and production time (Time Efficiency).
|
||
</p>
|
||
<div style={{ fontSize: '0.8rem', color: 'var(--fg-dim)' }}>
|
||
<strong>ME levels:</strong> 0% → 10% waste reduction per level<br/>
|
||
<strong>TE levels:</strong> 0% → 20% time reduction per level
|
||
</div>
|
||
</div>
|
||
<div className="card">
|
||
<h4 style={{ color: 'var(--purple)' }}>Production Chains</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
Advanced items require intermediate components (not just raw minerals). This creates
|
||
multi-step production chains where different players can specialize in different stages.
|
||
</p>
|
||
<div style={{ fontSize: '0.8rem', color: 'var(--muted)', fontFamily: 'var(--font-mono)' }}>
|
||
Ore → Mineral → Component → Module → Fitted Ship
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="callout callout-warn">
|
||
<strong>Strategic depth:</strong> A player with a fully researched BPO (ME 10, TE 20) manufactures at 0% waste and 40% faster
|
||
than a player using an unresearched BPO. This is a permanent competitive advantage that rewards long-term investment.
|
||
A veteran industrialist with a researched Cruiser BPO can underprice a newcomer and still profit. The mineral → component → module → ship
|
||
chain means that <strong>industrialists who control the full vertical chain capture the most margin</strong>, while those who buy
|
||
components from the market pay a premium for convenience.
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
{/* NPC PRICING */}
|
||
{activeSection === 'npc-pricing' && (
|
||
<>
|
||
<div className="section-header">
|
||
<span className="section-num">ECON-NPC</span>
|
||
<h2 style={{ margin: 0 }}>NPC Price Adjustment Algorithm</h2>
|
||
</div>
|
||
|
||
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
|
||
<strong>Why this matters:</strong> NPC buy/sell orders are the economy's floor and ceiling. If NPC prices never move,
|
||
players will find exploits (buy from NPC in system A, sell to NPC in system B for guaranteed profit). If NPC prices
|
||
swing too wildly, new players can't predict income and the game feels unfair. The algorithm must be deterministic,
|
||
transparent to the player through Zora analysis, and self-correcting without manual intervention.
|
||
</div>
|
||
|
||
{/* Price seed model */}
|
||
<h3 style={{ marginBottom: 'var(--sp-4)' }}>Price Seed Model</h3>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.9rem', maxWidth: '680px', marginBottom: 'var(--sp-5)' }}>
|
||
Every commodity has a <strong>base price</strong> (the global reference), a <strong>regional modifier</strong>
|
||
(set at galaxy generation, reflects local abundance/scarcity), and a <strong>dynamic modifier</strong> that shifts
|
||
based on player activity. NPC prices are never random — they are a deterministic function of these three inputs.
|
||
</p>
|
||
|
||
<div className="card card-accent" style={{ padding: 'var(--sp-5) var(--sp-6)', marginBottom: 'var(--sp-5)' }}>
|
||
<h4 style={{ color: 'var(--accent)', marginBottom: 'var(--sp-4)' }}>NPC Price Formula</h4>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', color: 'var(--fg-dim)', lineHeight: 2.2 }}>
|
||
<span style={{ color: 'var(--cyan)' }}>npc_price</span>(commodity, station) =<br/>
|
||
<span style={{ color: 'var(--accent)' }}>base_price</span>(commodity)<br/>
|
||
× <span style={{ color: 'var(--green)' }}>regional_modifier</span>(commodity, region) <span style={{ color: 'var(--muted)' }}>// fixed at galaxy gen, range [0.7 – 1.5]</span><br/>
|
||
× <span style={{ color: 'var(--purple)' }}>demand_pressure</span>(commodity, station) <span style={{ color: 'var(--muted)' }}>// dynamic, range [0.8 – 1.4]</span><br/>
|
||
× <span style={{ color: 'var(--fg)' }}>station_type_factor</span>(station) <span style={{ color: 'var(--muted)' }}>// trade hub: 0.95 (cheaper), frontier: 1.15 (premium)</span><br/>
|
||
<br/>
|
||
<span style={{ color: 'var(--muted)' }}>// NPC BUY price (what NPCs pay you) is always below sell:</span><br/>
|
||
<span style={{ color: 'var(--cyan)' }}>npc_buy_price</span> = <span style={{ color: 'var(--cyan)' }}>npc_price</span> × <span style={{ color: 'var(--red)' }}>buy_spread</span> <span style={{ color: 'var(--muted)' }}>// buy_spread ∈ [0.65, 0.85]</span><br/>
|
||
<span style={{ color: 'var(--cyan)' }}>npc_sell_price</span> = <span style={{ color: 'var(--cyan)' }}>npc_price</span> × <span style={{ color: 'var(--green)' }}>sell_spread</span> <span style={{ color: 'var(--muted)' }}>// sell_spread ∈ [1.10, 1.35]</span><br/>
|
||
<span style={{ color: 'var(--muted)' }}>// Spread ensures NPCs always buy lower than they sell.</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Base price table */}
|
||
<h3 style={{ marginBottom: 'var(--sp-4)' }}>Base Prices (Global Reference)</h3>
|
||
<div style={{ overflowX: 'auto', marginBottom: 'var(--sp-6)' }}>
|
||
<table className="data-table">
|
||
<thead>
|
||
<tr><th>Commodity</th><th>Base Price (₢/unit)</th><th>Type</th><th>NPC Buy Spread</th><th>NPC Sell Spread</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td style={{ color: 'var(--accent)' }}>Veldspar</td><td className="mono">2.50</td><td>Ore</td><td className="mono">0.70</td><td className="mono">1.25</td></tr>
|
||
<tr><td style={{ color: 'var(--accent)' }}>Scordite</td><td className="mono">4.00</td><td>Ore</td><td className="mono">0.70</td><td className="mono">1.25</td></tr>
|
||
<tr><td style={{ color: 'var(--accent)' }}>Pyroxeres</td><td className="mono">12.00</td><td>Ore</td><td className="mono">0.68</td><td className="mono">1.28</td></tr>
|
||
<tr><td style={{ color: 'var(--accent)' }}>Kernite</td><td className="mono">25.00</td><td>Ore</td><td className="mono">0.65</td><td className="mono">1.30</td></tr>
|
||
<tr><td style={{ color: 'var(--cyan)' }}>Tritanium</td><td className="mono">3.20</td><td>Mineral</td><td className="mono">0.75</td><td className="mono">1.20</td></tr>
|
||
<tr><td style={{ color: 'var(--cyan)' }}>Pyerite</td><td className="mono">8.00</td><td>Mineral</td><td className="mono">0.75</td><td className="mono">1.20</td></tr>
|
||
<tr><td style={{ color: 'var(--cyan)' }}>Nocxium</td><td className="mono">120.00</td><td>Mineral</td><td className="mono">0.72</td><td className="mono">1.22</td></tr>
|
||
<tr><td style={{ color: 'var(--cyan)' }}>Megacyte</td><td className="mono">450.00</td><td>Mineral</td><td className="mono">0.68</td><td className="mono">1.30</td></tr>
|
||
<tr><td style={{ color: 'var(--green)' }}>Mining Laser I</td><td className="mono">800</td><td>Module</td><td className="mono">—</td><td className="mono">1.30</td></tr>
|
||
<tr><td style={{ color: 'var(--green)' }}>150mm Railgun</td><td className="mono">2,500</td><td>Module</td><td className="mono">—</td><td className="mono">1.30</td></tr>
|
||
<tr><td style={{ color: 'var(--purple)' }}>Frigate Hull</td><td className="mono">15,000</td><td>Ship</td><td className="mono">—</td><td className="mono">1.35</td></tr>
|
||
<tr><td style={{ color: 'var(--purple)' }}>Cruiser Hull</td><td className="mono">80,000</td><td>Ship</td><td className="mono">—</td><td className="mono">1.35</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
{/* Demand pressure algorithm */}
|
||
<div className="section-header">
|
||
<span className="section-num">ECON-NPC.1</span>
|
||
<h2 style={{ margin: 0 }}>Demand Pressure — The Dynamic Modifier</h2>
|
||
</div>
|
||
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.9rem', maxWidth: '680px', marginBottom: 'var(--sp-5)' }}>
|
||
The <strong>demand_pressure</strong> multiplier is the only dynamic component of NPC prices.
|
||
It tracks recent buy/sell volume at each station and adjusts prices to simulate supply and demand.
|
||
This is what prevents infinite arbitrage and makes regional trade meaningful.
|
||
</p>
|
||
|
||
<div className="card card-accent" style={{ padding: 'var(--sp-5) var(--sp-6)', marginBottom: 'var(--sp-5)' }}>
|
||
<h4 style={{ color: 'var(--purple)', marginBottom: 'var(--sp-4)' }}>Demand Pressure Algorithm</h4>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.78rem', color: 'var(--fg-dim)', lineHeight: 2.2 }}>
|
||
<span style={{ color: 'var(--muted)' }}>// Each station tracks a rolling demand state per commodity</span><br/>
|
||
<span style={{ color: 'var(--cyan)' }}>demand_pressure</span>(commodity, station) computation:<br/>
|
||
<br/>
|
||
<span style={{ color: 'var(--green)' }}>// 1. Accumulate net flow</span><br/>
|
||
net_flow = <span style={{ color: 'var(--green)' }}>volume_sold_to_npc</span> − <span style={{ color: 'var(--red)' }}>volume_bought_from_npc</span><br/>
|
||
<span style={{ color: 'var(--muted)' }}>// Positive = players dumping, NPC inventory filling → price should drop</span><br/>
|
||
<span style={{ color: 'var(--muted)' }}>// Negative = players buying out, NPC stock depleting → price should rise</span><br/>
|
||
<br/>
|
||
<span style={{ color: 'var(--green)' }}>// 2. Exponential moving average (EMA) — forgets old data</span><br/>
|
||
flow_ema = α × net_flow + (1 − α) × prev_flow_ema <span style={{ color: 'var(--muted)' }}>// α = 0.3 (adjusts over ~3 ticks)</span><br/>
|
||
<br/>
|
||
<span style={{ color: 'var(--green)' }}>// 3. Normalize to pressure multiplier</span><br/>
|
||
demand_pressure = clamp(1.0 − β × flow_ema, 0.8, 1.4) <span style={{ color: 'var(--muted)' }}>// β = 0.002 per unit</span><br/>
|
||
<br/>
|
||
<span style={{ color: 'var(--green)' }}>// 4. Decay toward 1.0 when idle (no trades)</span><br/>
|
||
demand_pressure = lerp(demand_pressure, 1.0, γ) <span style={{ color: 'var(--muted)' }}>// γ = 0.05 per tick (5% per 5-min tick)</span><br/>
|
||
<br/>
|
||
<span style={{ color: 'var(--muted)' }}>// Result: high player selling → NPC buy price drops toward floor.</span><br/>
|
||
<span style={{ color: 'var(--muted)' }}>// High player buying → NPC sell price rises toward ceiling.</span><br/>
|
||
<span style={{ color: 'var(--muted)' }}>// No activity → prices decay back to regional baseline.</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Worked example */}
|
||
<h3 style={{ marginBottom: 'var(--sp-4)' }}>Worked Example: Tritanium at Jita IV</h3>
|
||
<div className="card" style={{ padding: 0, overflow: 'hidden', marginBottom: 'var(--sp-5)' }}>
|
||
<div style={{ padding: 'var(--sp-3) var(--sp-4)', background: 'var(--surface-raised)', borderBottom: '1px solid var(--border)' }}>
|
||
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.7rem', color: 'var(--accent)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
|
||
Tick-by-Tick Price Trace
|
||
</span>
|
||
</div>
|
||
<div style={{ padding: 'var(--sp-4)', fontSize: '0.82rem', color: 'var(--fg-dim)' }}>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.78rem', lineHeight: 2 }}>
|
||
<strong>Inputs:</strong> base_price(Tritanium) = 3.20₢, regional_modifier(The Forge) = 1.05, station_type = Trade Hub (0.95)<br/>
|
||
<strong>Initial:</strong> demand_pressure = 1.0 → NPC price = 3.20 × 1.05 × 1.0 × 0.95 = <span style={{ color: 'var(--accent)' }}>3.19₢</span><br/>
|
||
<strong style={{ color: 'var(--green)' }}>Tick 1:</strong> 5 players sell 2000 units each. net_flow = +10,000. flow_ema = 3,000. demand_pressure = 1.0 − 0.002 × 3000 = <span style={{ color: 'var(--red)' }}>0.80</span> (clamped)<br/>
|
||
→ NPC buy price = 3.19 × 0.80 = <span style={{ color: 'var(--red)' }}>2.55₢</span> (floor hit — NPCs paying minimum)<br/>
|
||
<strong style={{ color: 'var(--cyan)' }}>Tick 2:</strong> Players stop selling. flow_ema decays: 0.3×0 + 0.7×3000 = 2,100. demand_pressure = 0.80 → 1.0−0.002×2100 = <span style={{ color: 'var(--amber)' }}>0.86</span><br/>
|
||
→ NPC buy price = 3.19 × 0.86 = <span style={{ color: 'var(--amber)' }}>2.74₢</span> (recovering)<br/>
|
||
<strong style={{ color: 'var(--purple)' }}>Tick 3:</strong> No trades. Decay: lerp(0.86, 1.0, 0.05) = <span style={{ color: 'var(--purple)' }}>0.867</span>. Plus EMA decay continues.<br/>
|
||
→ demand_pressure ≈ 0.89 → NPC buy price ≈ <span style={{ color: 'var(--purple)' }}>2.84₢</span><br/>
|
||
<strong style={{ color: 'var(--fg)' }}>Tick 6:</strong> Full recovery. demand_pressure → 1.0. NPC buy price = <span style={{ color: 'var(--fg)' }}>3.19₢</span> (baseline restored)
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Regional price seeds */}
|
||
<div className="section-header">
|
||
<span className="section-num">ECON-NPC.2</span>
|
||
<h2 style={{ margin: 0 }}>Regional Price Seeds</h2>
|
||
</div>
|
||
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.9rem', maxWidth: '680px', marginBottom: 'var(--sp-5)' }}>
|
||
Regional modifiers are set at galaxy generation and <strong>never change</strong>. They reflect the natural
|
||
abundance or scarcity of resources in a region. A region rich in Veldspar belts has a low modifier for
|
||
Veldspar (cheap locally, not worth importing) but might have a high modifier for Megacyte (rare locally,
|
||
profitable to import). This creates natural trade corridors.
|
||
</p>
|
||
|
||
<div style={{ overflowX: 'auto', marginBottom: 'var(--sp-6)' }}>
|
||
<table className="data-table">
|
||
<thead>
|
||
<tr><th>Region</th><th>Character</th><th>Veldspar Mod</th><th>Nocxium Mod</th><th>Megacyte Mod</th><th>Module Mod</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td style={{ color: 'var(--cyan)' }}>The Forge (Jita)</td><td>Trade Hub</td><td className="mono" style={{ color: 'var(--green)' }}>0.90</td><td className="mono" style={{ color: 'var(--green)' }}>0.85</td><td className="mono" style={{ color: 'var(--red)' }}>1.30</td><td className="mono" style={{ color: 'var(--green)' }}>0.95</td></tr>
|
||
<tr><td style={{ color: 'var(--accent)' }}>Domain (Amarr)</td><td>Manufacturing</td><td className="mono" style={{ color: 'var(--green)' }}>0.95</td><td className="mono" style={{ color: 'var(--green)' }}>0.90</td><td className="mono" style={{ color: 'var(--green)' }}>0.80</td><td className="mono" style={{ color: 'var(--green)' }}>0.90</td></tr>
|
||
<tr><td style={{ color: 'var(--green)' }}>Sinq Laison</td><td>High-sec Mixed</td><td className="mono">1.00</td><td className="mono">1.00</td><td className="mono" style={{ color: 'var(--red)' }}>1.20</td><td className="mono">1.05</td></tr>
|
||
<tr><td style={{ color: 'var(--amber)' }}>Metropolis</td><td>Border Zone</td><td className="mono" style={{ color: 'var(--red)' }}>1.15</td><td className="mono" style={{ color: 'var(--red)' }}>1.10</td><td className="mono" style={{ color: 'var(--red)' }}>1.35</td><td className="mono" style={{ color: 'var(--red)' }}>1.15</td></tr>
|
||
<tr><td style={{ color: 'var(--red)' }}>Pure Blind (Null)</td><td>Null-sec Frontier</td><td className="mono" style={{ color: 'var(--red)' }}>1.40</td><td className="mono" style={{ color: 'var(--green)' }}>0.70</td><td className="mono" style={{ color: 'var(--green)' }}>0.60</td><td className="mono" style={{ color: 'var(--red)' }}>1.35</td></tr>
|
||
<tr><td style={{ color: 'var(--purple)' }}>Fade (Low-sec)</td><td>Pirate Corridor</td><td className="mono" style={{ color: 'var(--red)' }}>1.30</td><td className="mono" style={{ color: 'var(--green)' }}>0.75</td><td className="mono" style={{ color: 'var(--green)' }}>0.75</td><td className="mono" style={{ color: 'var(--red)' }}>1.25</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div className="callout callout-warn" style={{ marginBottom: 'var(--sp-5)' }}>
|
||
<strong>Trade route implication:</strong> A player who mines Nocxium in Pure Blind (mod 0.70, cheap) and hauls
|
||
it to The Forge (mod 0.85) makes a modest profit from regional modifier alone — but the real margin comes from
|
||
<em> demand_pressure</em>. If Jita has been buying lots of Nocxium (manufacturing cruisers), the demand_pressure
|
||
there might be 1.15+, making the same haul much more profitable. The dynamic price is the opportunity;
|
||
the regional seed is the baseline.
|
||
</div>
|
||
|
||
{/* Anti-arbitrage safeguards */}
|
||
<div className="section-header">
|
||
<span className="section-num">ECON-NPC.3</span>
|
||
<h2 style={{ margin: 0 }}>Anti-Arbitrage Safeguards</h2>
|
||
</div>
|
||
|
||
<div className="grid-2" style={{ marginBottom: 'var(--sp-6)' }}>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--red)' }}>
|
||
<h4 style={{ color: 'var(--red)' }}>Buy-Sell Spread Guarantee</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
NPC buy price is always ≤ NPC sell price for the same commodity at the same station. The minimum
|
||
spread (buy_spread × sell_spread⁻¹) is 0.65/1.10 = 0.59 — meaning NPCs pay at most 59% of what
|
||
they charge. <strong>You cannot buy from an NPC and sell back to the same NPC at a profit.</strong>
|
||
</p>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--accent)' }}>
|
||
<h4 style={{ color: 'var(--accent)' }}>Price Floor & Ceiling</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
demand_pressure clamps to [0.8, 1.4]. Even at maximum demand, NPC prices only reach 1.4× baseline.
|
||
Even at maximum oversupply, they only drop to 0.8×. This prevents price spirals in either direction
|
||
and ensures new players can always estimate their income within a known range.
|
||
</p>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--green)' }}>
|
||
<h4 style={{ color: 'var(--green)' }}>Station Stock Limits</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
NPCs have finite order depth. If players flood a station with Veldspar, the NPC buy order eventually
|
||
fills and the price drops sharply (demand_pressure floor). This limits infinite farming of any single
|
||
station and encourages players to diversify or find remote stations with unfilled orders.
|
||
</p>
|
||
</div>
|
||
<div className="card" style={{ borderLeft: '3px solid var(--cyan)' }}>
|
||
<h4 style={{ color: 'var(--cyan)' }}>Cross-Station Price Independence</h4>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
|
||
Each station tracks its own demand_pressure independently. Selling Tritanium at Jita doesn't affect
|
||
Tritanium prices at Amarr — until the diffusion pipeline propagates the information (see Info Diffusion tab).
|
||
Players who <em>travel</em> between stations can exploit this lag; players who don't, can't.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Backend schema */}
|
||
<div className="section-header">
|
||
<span className="section-num">ECON-NPC.4</span>
|
||
<h2 style={{ margin: 0 }}>Backend Schema for NPC Pricing</h2>
|
||
</div>
|
||
|
||
<div className="card" style={{ padding: 0, overflow: 'hidden' }}>
|
||
<div style={{ padding: 'var(--sp-3) var(--sp-4)', background: 'var(--surface-raised)', borderBottom: '1px solid var(--border)' }}>
|
||
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.7rem', color: 'var(--accent)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
|
||
SpacetimeDB — NPC Pricing Tables
|
||
</span>
|
||
</div>
|
||
<div className="code-block" style={{ margin: 0, borderRadius: 0 }}>
|
||
<code>
|
||
<span className="cm">// Per-station per-commodity demand state</span><br/>
|
||
<span className="kw">table</span> <span className="type">station_commodity_demand</span> {'{'}<br/>
|
||
station_id: <span className="type">u64</span>,<br/>
|
||
commodity_id: <span className="type">u64</span>,<br/>
|
||
flow_ema: <span className="type">f64</span>, <span className="cm">// rolling net flow EMA</span><br/>
|
||
demand_pressure: <span className="type">f64</span>, <span className="cm">// current multiplier, clamped [0.8, 1.4]</span><br/>
|
||
volume_sold_to_npc: <span className="type">u64</span>, <span className="cm">// cumulative this tick</span><br/>
|
||
volume_bought_from_npc: <span className="type">u64</span>, <span className="cm">// cumulative this tick</span><br/>
|
||
npc_stock_remaining: <span className="type">u64</span>, <span className="cm">// stock limit for buy orders</span><br/>
|
||
last_tick: <span className="type">timestamp</span>,<br/>
|
||
{'}'}<br/>
|
||
<br/>
|
||
<span className="cm">// Per-commodity base prices and parameters</span><br/>
|
||
<span className="kw">table</span> <span className="type">commodity_price_params</span> {'{'}<br/>
|
||
commodity_id: <span className="type">u64</span>,<br/>
|
||
base_price: <span className="type">f64</span>,<br/>
|
||
buy_spread: <span className="type">f64</span>, <span className="cm">// [0.65, 0.85]</span><br/>
|
||
sell_spread: <span className="type">f64</span>, <span className="cm">// [1.10, 1.35]</span><br/>
|
||
ema_alpha: <span className="type">f64</span>, <span className="cm">// smoothing factor (default 0.3)</span><br/>
|
||
pressure_beta: <span className="type">f64</span>, <span className="cm">// sensitivity per unit (default 0.002)</span><br/>
|
||
decay_gamma: <span className="type">f64</span>, <span className="cm">// idle decay rate (default 0.05)</span><br/>
|
||
{'}'}<br/>
|
||
<br/>
|
||
<span className="cm">// Per-region per-commodity static modifier</span><br/>
|
||
<span className="kw">table</span> <span className="type">regional_price_seeds</span> {'{'}<br/>
|
||
region_id: <span className="type">u64</span>,<br/>
|
||
commodity_id: <span className="type">u64</span>,<br/>
|
||
modifier: <span className="type">f64</span>, <span className="cm">// fixed at galaxy gen, range [0.6, 1.5]</span><br/>
|
||
{'}'}<br/>
|
||
<br/>
|
||
<span className="cm">// Scheduled agent that ticks demand pressure</span><br/>
|
||
<span className="kw">reducer</span> <span className="fn">tick_npc_pricing</span>(ctx) {'{'}<br/>
|
||
<span className="cm">// Every 5 minutes, for each station_commodity_demand row:</span><br/>
|
||
<span className="cm">// 1. Compute net_flow = volume_sold_to_npc - volume_bought_from_npc</span><br/>
|
||
<span className="cm">// 2. Update flow_ema with EMA formula</span><br/>
|
||
<span className="cm">// 3. Recompute demand_pressure = clamp(1.0 - β × flow_ema, 0.8, 1.4)</span><br/>
|
||
<span className="cm">// 4. Apply idle decay: lerp(demand_pressure, 1.0, γ)</span><br/>
|
||
<span className="cm">// 5. Reset volume counters for next tick</span><br/>
|
||
{'}'}<br/>
|
||
</code>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="callout callout-info" style={{ marginTop: 'var(--sp-5)' }}>
|
||
<strong>Tuning knobs:</strong> The three parameters (α, β, γ) are per-commodity so that high-value items like Megacyte
|
||
can be less volatile (lower β) while common ores like Veldspar can react faster. The initial values listed
|
||
above are defaults — the actual values should be tuned during playtesting by observing how quickly arbitrage
|
||
opportunities close and whether new players can still estimate their mining income.
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
{/* FAUCETS & SINKS */}
|
||
{activeSection === 'faucets' && (
|
||
<>
|
||
<div className="section-header">
|
||
<span className="section-num">ECON-FS</span>
|
||
<h2 style={{ margin: 0 }}>Money Supply: Faucets & Sinks</h2>
|
||
</div>
|
||
|
||
<div className="callout callout-warn" style={{ marginBottom: 'var(--sp-5)' }}>
|
||
<strong>Critical balance:</strong> If faucets exceed sinks, the currency inflates and prices
|
||
spiral. If sinks exceed faucets, players feel poor and stop engaging. The economy needs
|
||
continuous monitoring — no fixed formula survives contact with real players.
|
||
</div>
|
||
|
||
<div className="grid-2">
|
||
{faucetsAndSinks.map((item, i) => (
|
||
<div key={i} className="card" style={{ borderLeft: `3px solid ${item.type === 'faucet' ? 'var(--green)' : 'var(--red)'}` }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--sp-3)', marginBottom: 'var(--sp-3)' }}>
|
||
<span className={`pill ${item.type === 'faucet' ? 'pill-green' : 'pill-red'}`}>
|
||
{item.type === 'faucet' ? '▲ FAUCET' : '▼ SINK'}
|
||
</span>
|
||
<h4 style={{ margin: 0 }}>{item.name}</h4>
|
||
</div>
|
||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-2) 0' }}>{item.description}</p>
|
||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.75rem', color: 'var(--muted)' }}>
|
||
{item.rate}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
window.GDD.EconomyPage = EconomyPage;
|