Arcane Tycoon — Heroes & Magic theme park tycoon game

Complete browser game inspired by OpenRCT2 with fantasy twist:
- Custom roller coaster designer with physics-based ratings + on-ride POV
- 10 animated rides, 7 shops, 16 scenery items, path network & guest AI
- Heroes guild vs monster invasions (5 classes, XP/gear/bosses)
- Magic spell system (8 spells), research tree, economy/marketing/loans
- Day-night cycle, weather, park rating, awards, 4 scenarios
- Save/load slots + autosave, procedural WebAudio SFX/music
- Isometric canvas renderer, minimap, diagnostics overlay
- Test suites: smoke(13), linkcheck, inputcheck, framecheck, rendercheck, flow
This commit is contained in:
2026-08-23 06:59:21 +00:00
commit ac00687480
30 changed files with 6772 additions and 0 deletions
+308
View File
@@ -0,0 +1,308 @@
// ============ config.js — all game data & tuning constants ============
export const TILE_W = 64, TILE_H = 32; // iso tile size at zoom 1
export const Z_STEP = 14; // pixels per height unit at zoom 1
export const TERRAIN = {
0: { name: 'Grass', base: '#4d8a3d', alt: '#57a047', walk: false },
1: { name: 'Sand', base: '#cbb26a', alt: '#d5bd77', walk: false },
2: { name: 'Rock', base: '#7a7f8a', alt: '#868b96', walk: false },
3: { name: 'Water', base: '#2e6db4', alt: '#3a7cc9', walk: false },
};
// Directions: E, S, W, N (screen: +x = lower-right, +y = lower-left)
export const DIRS = [[1, 0], [0, 1], [-1, 0], [0, -1]];
export const DIR_NAMES = ['E', 'S', 'W', 'N'];
export const PATH_TYPES = {
pavement: { name: 'Pavement', cost: 10, color: '#b8b2a5', edge: '#8d887c' },
cobble: { name: 'Cobblestone', cost: 14, color: '#6f6a80', edge: '#514d60' },
};
// ---------------- RIDES ----------------
// stats: excite/intensity/nausea base; rideTime sec; capacity guests per cycle
export const RIDE_TYPES = {
carousel: {
id: 'carousel', name: 'Unicorn Carousel', icon: '🎠', w: 2, h: 2, cost: 2200,
runCost: 18, rideTime: 22, capacity: 12, excite: 2.4, intensity: 1.6, nausea: 1.1,
tier: 0, desc: 'A gentle classic. Glittering unicorns spin beneath golden canopy.',
color: '#e59ae0', color2: '#f5c542',
},
ferris: {
id: 'ferris', name: 'Sky Wheel', icon: '🎡', w: 3, h: 3, cost: 4200,
runCost: 30, rideTime: 40, capacity: 20, excite: 3.2, intensity: 2.0, nausea: 1.4,
tier: 0, desc: 'Tower above the park in a glass gondola with views for miles.',
color: '#7fb2ff', color2: '#ffd166',
},
teacups: {
id: 'teacups', name: "Wizard's Teacups", icon: '🫖', w: 2, h: 2, cost: 1800,
runCost: 15, rideTime: 25, capacity: 15, excite: 2.8, intensity: 3.4, nausea: 4.2,
tier: 0, desc: 'Spinning cups of a very caffeinated sorcerer. Nauseating fun!',
color: '#ff9d76', color2: '#ffe08a',
},
drop_tower: {
id: 'drop_tower', name: 'Gravity Spire', icon: '🗼', w: 2, h: 2, cost: 5200,
runCost: 38, rideTime: 18, capacity: 16, excite: 5.6, intensity: 7.2, nausea: 3.4,
tier: 1, desc: 'Rise 40 meters… then let gravity have you. A breath-stealer.',
color: '#b0b6c8', color2: '#ff5c5c',
},
swings: {
id: 'swings', name: 'Fairy Swings', icon: '🎐', w: 3, h: 2, cost: 3400,
runCost: 24, rideTime: 28, capacity: 16, excite: 3.6, intensity: 3.0, nausea: 2.4,
tier: 1, desc: 'Fly on enchanted chairs steered by tiny fairies.',
color: '#a5e6ff', color2: '#f7a8ff',
},
haunted: {
id: 'haunted', name: 'Haunted Crypt', icon: '👻', w: 3, h: 3, cost: 4800,
runCost: 34, rideTime: 45, capacity: 18, excite: 5.2, intensity: 4.6, nausea: 2.0,
tier: 1, desc: 'Dark ride through crypts guarded by very committed ghosts.',
color: '#9a86c8', color2: '#5de0c8',
},
logflume: {
id: 'logflume', name: 'River Sprite Flume', icon: '🛶', w: 4, h: 6, cost: 7200,
runCost: 46, rideTime: 70, capacity: 20, excite: 5.8, intensity: 4.8, nausea: 3.0,
tier: 2, desc: 'Meandering river channel ending in a mighty splash.',
color: '#63c5ea', color2: '#8d5a2b',
},
dragon_coaster: {
id: 'dragon_coaster', name: 'Dragonling Coaster', icon: '🐉', w: 5, h: 5, cost: 9000,
runCost: 55, rideTime: 60, capacity: 24, excite: 6.8, intensity: 6.4, nausea: 3.6,
tier: 2, desc: 'A ready-made junior coaster ridden on friendly young dragons.',
color: '#66d977', color2: '#f43f5e',
},
portal: {
id: 'portal', name: 'Portal Blasters', icon: '🌀', w: 3, h: 3, cost: 8500,
runCost: 50, rideTime: 50, capacity: 16, excite: 6.2, intensity: 5.4, nausea: 2.2,
tier: 2, desc: 'Shoot glowing orbs across dimensions from your hover-chair.',
color: '#a86bff', color2: '#58c1ff',
},
broom_tower: {
id: 'broom_tower', name: 'Broomstick Tower', icon: '🧹', w: 2, h: 2, cost: 6800,
runCost: 42, rideTime: 35, capacity: 14, excite: 6.0, intensity: 5.0, nausea: 3.2,
tier: 3, desc: 'Straddle a racing broom as it spirals up and around the spire.',
color: '#c98d4e', color2: '#ffd166',
},
};
// ---------------- COASTER PIECES ----------------
export const PIECES = {
straight: { id: 'straight', name: 'Straight', icon: '━', cost: 120, dz: 0, turn: 0 },
curveL: { id: 'curveL', name: 'Curve Left', icon: '↰', cost: 140, dz: 0, turn: -1 },
curveR: { id: 'curveR', name: 'Curve Right',icon: '↱', cost: 140, dz: 0, turn: 1 },
up: { id: 'up', name: 'Slope Up', icon: '↗', cost: 170, dz: 1, turn: 0 },
down: { id: 'down', name: 'Slope Down', icon: '↘', cost: 150, dz: -1, turn: 0 },
steepUp: { id: 'steepUp', name: 'Steep Up', icon: '⇗', cost: 210, dz: 2, turn: 0 },
steepDown: { id: 'steepDown', name: 'Steep Drop', icon: '⇘', cost: 190, dz: -2, turn: 0 },
loop: { id: 'loop', name: 'Loop', icon: '◯', cost: 400, dz: 0, turn: 0 },
station: { id: 'station', name: 'Station', icon: '▤', cost: 300, dz: 0, turn: 0 },
};
export const MAX_Z = 14;
export const MIN_COASTER_PIECES = 6;
// ---------------- SHOPS ----------------
export const SHOP_TYPES = {
drinks: { id: 'drinks', name: 'Potion Fizz', icon: '🥤', cost: 500, price: 6, need: 'thirst', needFix: 75, stock: 250, tier: 0, desc: 'Sparkling tonics in wild colors.' },
food: { id: 'food', name: 'Dragon Grill', icon: '🍗', cost: 650, price: 9, need: 'hunger', needFix: 80, stock: 250, tier: 0, desc: 'Flame-grilled drumsticks (mildly fireproof).' },
icecream: { id: 'icecream', name: 'Frost Imp Cream', icon: '🍦', cost: 550, price: 7, need: 'thirst', needFix: 55, stock: 200, tier: 0, desc: 'Ice cream that whispers frost puns.' },
souvenir: { id: 'souvenir', name: 'Curiosity Shop', icon: '🎁', cost: 700, price: 14, need: 'shop', needFix: 100, stock: 999999, tier: 0, desc: 'Wands, hats and questionable relics.', happyBoost: 8 },
toilet: { id: 'toilet', name: 'Restrooms', icon: '🚻', cost: 400, price: 2, need: 'toilet', needFix: 100, stock: 999999, tier: 0, desc: 'Essential plumbing. Guests will thank you.' },
balloon: { id: 'balloon', name: 'Balloon Stall', icon: '🎈', cost: 350, price: 5, need: 'shop', needFix: 100, stock: 999999, tier: 0, desc: 'Floating joy on a string.', happyBoost: 6 },
firstaid: { id: 'firstaid', name: 'Healers Hut', icon: '⛑️', cost: 600, price: 0, need: 'health', needFix: 100, stock: 999999, tier: 1, desc: 'Patch up guests who overdid the loops.' },
};
// ---------------- SCENERY ----------------
export const SCENERY_TYPES = {
tree_oak: { id: 'tree_oak', name: 'Oak Tree', icon: '🌳', cost: 45, size: 1, beauty: 3 },
tree_pine: { id: 'tree_pine', name: 'Pine Tree', icon: '🌲', cost: 45, size: 1, beauty: 3 },
tree_cherry: { id: 'tree_cherry', name: 'Cherry Blossom',icon: '🌸', cost: 90, size: 1, beauty: 6, tier: 1 },
flowerbed: { id: 'flowerbed', name: 'Flower Bed', icon: '🌷', cost: 35, size: 1, beauty: 4 },
hedge: { id: 'hedge', name: 'Hedge', icon: '🌿', cost: 30, size: 1, beauty: 2 },
bench: { id: 'bench', name: 'Bench', icon: '🪑', cost: 40, size: 1, beauty: 1, rest: true },
bin: { id: 'bin', name: 'Litter Bin', icon: '🗑️', cost: 30, size: 1, beauty: 0, antiLitter: 6 },
lamp: { id: 'lamp', name: 'Street Lamp', icon: '🏮', cost: 55, size: 1, beauty: 2, light: 4 },
fountain: { id: 'fountain', name: 'Fountain', icon: '⛲', cost: 380, size: 1, beauty: 10, tier: 0 },
statue_knight:{id:'statue_knight',name: 'Knight Statue', icon: '🗿', cost: 260, size: 1, beauty: 8, tier: 1 },
statue_dragon:{id:'statue_dragon',name: 'Dragon Statue', icon: '🐲', cost: 520, size: 2, beauty: 14, tier: 2 },
crystal_lamp:{ id: 'crystal_lamp',name: 'Crystal Lamp', icon: '💎', cost: 160, size: 1, beauty: 5, light: 7, manaCap: 5, tier: 1 },
ley_pool: { id: 'ley_pool', name: 'Ley Pool', icon: '🔮', cost: 800, size: 2, beauty: 8, manaCap: 25, manaRegen: .5, light: 6, tier: 0, magic: true, desc: 'A pool of raw magic. Raises max mana & regen.' },
rune_stone: { id: 'rune_stone', name: 'Rune Stone', icon: '🪨', cost: 300, size: 1, beauty: 6, manaCap: 10, manaRegen: .2, tier: 1, magic: true, desc: 'Ancient stone humming with power.' },
mushroom_glow:{id:'mushroom_glow',name: 'Glowcap Cluster',icon:'🍄', cost: 120, size: 1, beauty: 5, light: 5, manaCap: 3, tier: 1, magic: true },
banner: { id: 'banner', name: 'Park Banner', icon: '🚩', cost: 50, size: 1, beauty: 3 },
};
// ---------------- STAFF ----------------
export const STAFF_TYPES = {
handyman: { id: 'handyman', name: 'Handyman', icon: '🧹', wage: 14, desc: 'Sweeps litter & vomit, waters flowers.' },
mechanic: { id: 'mechanic', name: 'Mechanic', icon: '🔧', wage: 22, desc: 'Inspects & repairs rides.' },
guard: { id: 'guard', name: 'Guard', icon: '💂', wage: 18, desc: 'Deters vandalism near shops & rides.' },
entertainer: { id: 'entertainer', name: 'Court Jester', icon: '🤡', wage: 16, desc: 'Entertains queuing guests (+happiness).' },
};
// ---------------- HEROES ----------------
export const HERO_CLASSES = {
knight: { id: 'knight', name: 'Knight', icon: '🛡️', hp: 130, dmg: 14, range: 1.2, speed: 1.9, atkCd: 1.1, cost: 800, tier: 0, desc: 'Stalwart frontline defender.' },
ranger: { id: 'ranger', name: 'Ranger', icon: '🏹', hp: 75, dmg: 11, range: 4.0, speed: 2.4, atkCd: .9, cost: 750, tier: 0, desc: 'Strikes from afar with enchanted arrows.' },
mage: { id: 'mage', name: 'Battle Mage', icon: '🧙', hp: 60, dmg: 19, range: 3.4, speed: 1.8, atkCd: 1.6, cost: 950, tier: 0, aoe: 1.6, desc: 'Hurls arcane bolts that splash damage.' },
cleric: { id: 'cleric', name: 'Cleric', icon: '⚕️', hp: 70, dmg: 4, range: 3.2, speed: 2.0, atkCd: 1.4, cost: 900, tier: 1, heal: 10, desc: 'Heals nearby heroes every second.' },
paladin: { id: 'paladin', name: 'Paladin', icon: '⚔️', hp: 175, dmg: 17, range: 1.3, speed: 1.8, atkCd: 1.2, cost: 1500, tier: 2, desc: 'Holy warrior with immense staying power.' },
};
export const MAX_HEROES = 6;
// ---------------- MONSTERS ----------------
export const MONSTER_TYPES = {
slime: { id: 'slime', name: 'Slime', icon: '🟢', hp: 34, dmg: 6, speed: .9, gold: 40, xp: 10, threat: 1 },
goblin: { id: 'goblin', name: 'Goblin', icon: '👺', hp: 52, dmg: 9, speed: 1.6, gold: 60, xp: 16, threat: 2 },
wolf: { id: 'wolf', name: 'Dire Wolf', icon: '🐺', hp: 44, dmg: 11, speed: 2.4, gold: 70, xp: 18, threat: 2 },
brute: { id: 'brute', name: 'Troll Brute', icon: '👹', hp: 240, dmg: 24, speed: .95, gold: 260, xp: 60, threat: 5 },
boss: { id: 'boss', name: 'Void Wraith', icon: '☠️', hp: 700, dmg: 34, speed: 1.2, gold: 900, xp: 200, threat: 10 },
};
// ---------------- SPELLS ----------------
export const SPELLS = {
sunburst: { id: 'sunburst', name: 'Sunburst', icon: '☀️', mana: 15, cd: 60, dur: 0, tier: 0,
desc: 'Instantly clear the skies to sunny weather.' },
joy_aura: { id: 'joy_aura', name: 'Joy Aura', icon: '😊', mana: 25, cd: 45, dur: 20, tier: 0,
desc: 'All guests gain steady happiness while active.' },
healing_light: { id: 'healing_light', name: 'Healing Light', icon: '💚', mana: 30, cd: 50, dur: 0, tier: 0,
desc: 'Fully heals all heroes instantly.' },
fortune_rain: { id: 'fortune_rain', name: 'Fortune Rain', icon: '💸', mana: 40, cd: 100, dur: 30, tier: 1,
desc: 'Guests spend 60% more for the duration.' },
swift_build: { id: 'swift_build', name: "Artificer's Haste", icon: '⚡', mana: 35, cd: 130, dur: 25, tier: 1,
desc: 'Construction is instant and half price while active.' },
monster_bane: { id: 'monster_bane', name: 'Monster Bane', icon: '💥', mana: 45, cd: 90, dur: 0, tier: 2,
desc: 'Deals 70 damage to every monster in the park.' },
warding_sigil: { id: 'warding_sigil', name: 'Warding Sigil', icon: '🛡️', mana: 50, cd: 160, dur: 60, tier: 2,
desc: 'Blocks invasions; monsters flee while active.' },
transmute: { id: 'transmute', name: 'Transmutation', icon: '🪙', mana: 55, cd: 150, dur: 0, tier: 3,
desc: 'Conjure $900 from thin air.' },
};
// ---------------- RESEARCH ----------------
export const RESEARCH_TRACKS = {
rides: { id: 'rides', name: 'Ride Engineering', icon: '🎢' },
trade: { id: 'trade', name: 'Commerce', icon: '🏪' },
magic: { id: 'magic', name: 'Arcane Studies', icon: '✨' },
heroes: { id: 'heroes', name: 'Heroes Guild', icon: '⚔️' },
};
// unlock entries: {key, track, rp, label}
export const UNLOCKS = [
// rides
{ key: 'drop_tower', track: 'rides', rp: 110, label: 'Gravity Spire', kind: 'ride' },
{ key: 'swings', track: 'rides', rp: 80, label: 'Fairy Swings', kind: 'ride' },
{ key: 'haunted', track: 'rides', rp: 140, label: 'Haunted Crypt', kind: 'ride' },
{ key: 'logflume', track: 'rides', rp: 220, label: 'River Sprite Flume', kind: 'ride' },
{ key: 'dragon_coaster', track: 'rides', rp: 260, label: 'Dragonling Coaster', kind: 'ride' },
{ key: 'portal', track: 'rides', rp: 300, label: 'Portal Blasters', kind: 'ride' },
{ key: 'broom_tower',track: 'rides', rp: 380, label: 'Broomstick Tower', kind: 'ride' },
// commerce
{ key: 'icecream', track: 'trade', rp: 60, label: 'Frost Imp Cream', kind: 'shop' },
{ key: 'balloon', track: 'trade', rp: 40, label: 'Balloon Stall', kind: 'shop' },
{ key: 'firstaid', track: 'trade', rp: 90, label: 'Healers Hut', kind: 'shop' },
{ key: 'cobble', track: 'trade', rp: 50, label: 'Cobblestone paths', kind: 'path' },
{ key: 'statue_dragon', track: 'trade', rp: 130, label: 'Dragon Statue', kind: 'scenery' },
// magic
{ key: 'ley_pool', track: 'magic', rp: 100, label: 'Ley Pool', kind: 'scenery' },
{ key: 'rune_stone', track: 'magic', rp: 70, label: 'Rune Stone', kind: 'scenery' },
{ key: 'mushroom_glow', track: 'magic', rp: 50, label: 'Glowcap Cluster', kind: 'scenery' },
{ key: 'crystal_lamp', track: 'magic', rp: 60, label: 'Crystal Lamp', kind: 'scenery' },
{ key: 'tree_cherry', track: 'magic', rp: 40, label: 'Cherry Blossom', kind: 'scenery' },
{ key: 'fortune_rain', track: 'magic', rp: 120, label: 'Spell: Fortune Rain', kind: 'spell' },
{ key: 'swift_build', track: 'magic', rp: 130, label: 'Spell: Artificer\u2019s Haste', kind: 'spell' },
{ key: 'monster_bane', track: 'magic', rp: 200, label: 'Spell: Monster Bane', kind: 'spell' },
{ key: 'warding_sigil',track: 'magic', rp: 240, label: 'Spell: Warding Sigil', kind: 'spell' },
{ key: 'transmute', track: 'magic', rp: 320, label: 'Spell: Transmutation', kind: 'spell' },
// heroes
{ key: 'cleric', track: 'heroes', rp: 110, label: 'Cleric class', kind: 'hero' },
{ key: 'paladin', track: 'heroes', rp: 260, label: 'Paladin class', kind: 'hero' },
{ key: 'guild2', track: 'heroes', rp: 150, label: 'Guild Hall II (+2 roster)', kind: 'heroCap' },
{ key: 'gear2', track: 'heroes', rp: 190, label: 'Fine Gear (+25% hero power)', kind: 'heroGear' },
];
// ---------------- GUESTS ----------------
export const GUEST_NAMES = ['Ada','Bram','Cora','Dilan','Elke','Finn','Gwen','Hugo','Iris','Jasper','Kira','Liam','Mira','Noah','Odette','Pim','Quinn','Rosa','Sven','Tilda','Ulf','Vera','Wren','Xavi','Yara','Zane','Bree','Cato','Dora','Eppo','Faye','Gus','Hanne','Ivo','Jet','Koos','Loes','Mads','Nienke','Otto','Puck','Rens','Saar','Ties','Usko','Vos','Wilma','Ylva','Zeno'];
export const GUEST_COLORS = ['#e05b5b','#5b8ee0','#57d97a','#e0b23d','#b45be0','#5bd9c9','#ff9d76','#8fa3ff'];
export const THOUGHTS = {
great_ride: ['{r} was amazing!', 'Best. Ride. Ever!', '{r} made my day!'],
good_ride: ['{r} was fun!', 'Enjoyed {r} a lot.'],
meh_ride: ["{r} was okay I guess.", '{r} could be better…'],
scary: ['{r} was terrifying!', 'Never again on {r}!'],
hungry: ["I'm starving", 'Need food soon'],
thirsty: ["I'm so thirsty", 'Could really use a drink'],
toilet: ['I need a restroom!', 'Where are the toilets?!'],
broke: ["I've spent all my money", 'Everything costs gold…'],
happy_park: ['What a lovely park!', 'This place is magical!'],
litter: ['All this litter…', 'Someone should clean up!'],
vandal: ['Vandals everywhere!', 'This park feels unsafe'],
monster: ['MONSTER!! Run!', 'Help! Heroes! Help!'],
long_queue: ['Such a long queue…', 'Waiting forever for {r}'],
broken: ['{r} has broken down again!'],
expensive: ['{r} is pricey…', 'Entrance fee is steep!'],
no_exit: ["Can't find the way out!", 'This maze of paths…'],
};
// ---------------- WEATHER ----------------
export const WEATHER = {
sunny: { id: 'sunny', name: 'Sunny', icon: '☀️', spawnMul: 1.15, happyDrain: -.02, tint: null },
cloudy: { id: 'cloudy', name: 'Cloudy', icon: '⛅', spawnMul: 1.0, happyDrain: 0, tint: 'rgba(120,130,160,.08)' },
rain: { id: 'rain', name: 'Rain', icon: '🌧️', spawnMul: .6, happyDrain: .05, tint: 'rgba(60,80,140,.18)' },
storm: { id: 'storm', name: 'Storm', icon: '⛈️', spawnMul: .35, happyDrain: .12, tint: 'rgba(30,40,90,.28)' },
};
// ---------------- SCENARIOS ----------------
export const SCENARIOS = [
{
id: 'meadows', name: 'Enchanted Meadows', diff: 'EASY', icon: '🌻',
blurb: 'Rolling green fields beside a sleepy lake. Perfect ground for a first magical kingdom.',
cash: 30000, loanLimit: 20000, mapSeed: 1337, mapSize: 52,
gen: { lake: 1, trees: 90, rocks: 12, sand: true },
goals: [
{ id: 'guests', text: 'Have 220 guests in the park', type: 'guests', value: 220 },
{ id: 'rating', text: 'Reach park rating 450', type: 'rating', value: 450 },
{ id: 'cash', text: 'Grow park value to $45,000', type: 'cash', value: 45000 },
],
loseCash: -8000, invasionStartMonth: 4, invasionEvery: 4, invasionScale: 1,
},
{
id: 'dragonspine', name: 'Dragonspine Pass', diff: 'MEDIUM', icon: '🏔️',
blurb: 'A rocky mountain pass where dragons nest. Build fast — the horde comes early and often.',
cash: 26000, loanLimit: 25000, mapSeed: 4242, mapSize: 56,
gen: { lake: 1, trees: 60, rocks: 60, sand: false, rocky: true },
goals: [
{ id: 'guests', text: 'Have 320 guests in the park', type: 'guests', value: 320 },
{ id: 'rating', text: 'Reach park rating 550', type: 'rating', value: 550 },
{ id: 'invasions', text: 'Repel 3 monster invasions', type: 'invasions', value: 3 },
{ id: 'coaster', text: 'Open a custom coaster with excitement ≥ 5.0', type: 'coasterExcite', value: 5 },
],
loseCash: -10000, invasionStartMonth: 2, invasionEvery: 3, invasionScale: 1.4,
},
{
id: 'voidrift', name: 'Void Rift Crisis', diff: 'HARD', icon: '🌌',
blurb: 'The sky is torn. Void wraiths pour through rifts while guests still demand roller coasters. Heroes wanted.',
cash: 24000, loanLimit: 30000, mapSeed: 9021, mapSize: 56,
gen: { lake: 1, trees: 40, rocks: 80, sand: false, rocky: true, void: true },
goals: [
{ id: 'guests', text: 'Have 420 guests in the park', type: 'guests', value: 420 },
{ id: 'rating', text: 'Reach park rating 620', type: 'rating', value: 620 },
{ id: 'boss', text: 'Defeat a Void Wraith', type: 'bossKill', value: 1 },
{ id: 'coaster', text: 'Open a custom coaster with excitement ≥ 6.0', type: 'coasterExcite', value: 6 },
],
loseCash: -12000, invasionStartMonth: 1, invasionEvery: 2, invasionScale: 1.9, bossAt: 3,
},
{
id: 'sandbox', name: 'Sandbox Kingdom', diff: 'SANDBOX', icon: '🧪',
blurb: 'Unlimited money, everything unlocked. Build the impossible.',
cash: 1000000, loanLimit: 0, mapSeed: 777, mapSize: 64, sandbox: true,
gen: { lake: 2, trees: 110, rocks: 20, sand: true },
goals: [], loseCash: -999999999, invasionStartMonth: 6, invasionEvery: 5, invasionScale: 1.2,
},
];
export const AWARDS_POOL = [
{ id: 'prettiest', name: 'Prettiest Park', test: s => s.avgBeauty > 4 },
{ id: 'safest', name: 'Safest Kingdom', test: s => s.heroStats.kills > 20 && s.vandalism < 3 },
{ id: 'tidiest', name: 'Tidiest Park', test: s => s.litterCount < 8 },
{ id: 'thrills', name: 'Best Thrills', test: s => s.bestExcite >= 7 },
{ id: 'foodie', name: 'Finest Dining', test: s => s.shops.length >= 5 && s.avgHappy > 65 },
{ id: 'magical', name: 'Most Magical Park', test: s => s.magicCount >= 6 },
];