From 28773e28649550762421fd9eec46804438dfe1ff Mon Sep 17 00:00:00 2001 From: Claude Code Date: Thu, 26 Mar 2026 00:06:47 -0700 Subject: [PATCH] =?UTF-8?q?ui(guide):=20=F0=9F=92=84=20Enhance=20Ecosystem?= =?UTF-8?q?Page=20component=20with=20new=20ecosystem=20features,=20improve?= =?UTF-8?q?d=20layout,=20and=20enhanced=20interactivity=20for=20better=20u?= =?UTF-8?q?ser=20experience?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- guide/age-of-four/src/pages/EcosystemPage.tsx | 255 ++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 guide/age-of-four/src/pages/EcosystemPage.tsx diff --git a/guide/age-of-four/src/pages/EcosystemPage.tsx b/guide/age-of-four/src/pages/EcosystemPage.tsx new file mode 100644 index 00000000..321334e9 --- /dev/null +++ b/guide/age-of-four/src/pages/EcosystemPage.tsx @@ -0,0 +1,255 @@ +import type { ReactElement } from 'react' +import styled from 'styled-components' +import { FadeIn, PageTitle, Section, SectionHeading, Prose } from '@magic-civ/guide-engine' +import { Heading, Text } from '@lilith/ui-typography' + +const QUALITY_TIERS = [ + { tier: 1, label: 'Prolific', color: '#666666', desc: 'Hardy survivors — common species only' }, + { tier: 2, label: 'Common', color: '#cccccc', desc: 'Healthy baseline — standard species diversity' }, + { tier: 3, label: 'Rare', color: '#4080e0', desc: 'Thriving ecosystem — rare species appear' }, + { tier: 4, label: 'Legendary', color: '#9040cc', desc: 'Natural wonder — landmark named, unique fauna' }, + { tier: 5, label: 'Epic', color: '#e07020', desc: 'Pristine wilderness — apex predators, ancient groves' }, +] as const + +const FLORA_COMPONENTS = [ + { name: 'Canopy Cover', field: 'canopy_cover', color: '#1a9928', + desc: 'Forest canopy density. Grows toward biome climax when climate matches. Shades undergrowth above 0.7.' }, + { name: 'Undergrowth', field: 'undergrowth', color: '#8cc634', + desc: 'Ground vegetation. Supports herbivores. Capped by canopy shade. Decays faster during drought.' }, + { name: 'Fungi Network', field: 'fungi_network', color: '#9040a0', + desc: 'Mycorrhizal network. Requires undergrowth > 0.3 and moisture > 0.15. Accelerates regrowth.' }, +] as const + +const ECOLOGY_WEIGHTS = [ + { name: 'Flora Health', weight: 0.30, desc: 'Canopy + undergrowth + fungi vs biome climax' }, + { name: 'Fauna Diversity', weight: 0.25, desc: 'Species count vs biome capacity' }, + { name: 'Biome Stability', weight: 0.25, desc: 'BiomeClassifier agrees with current biome_id' }, + { name: 'Population Balance', weight: 0.20, desc: 'Predator-prey ratio near 1:4 equilibrium' }, +] + +export default function EcosystemPage(): ReactElement { + return ( + + + Ecosystem + + The living world. Flora grows, fauna hunts, biomes shift, and tile quality + emerges from ecological health — driving everything from resource yields to + natural wonder formation. + + + +
+ Quality Tiers (Q1-Q5) + + Every land tile has an ecology quality from Q1 (Prolific) to Q5 (Epic). + Quality determines which species can spawn, food yield multipliers, and whether + a tile becomes a natural wonder. Q4-Q5 tiles are named by the FlavorGenerator + and function as emergent natural wonders. + + + {QUALITY_TIERS.map(({ tier, label, color, desc }) => ( + + Q{tier} + + {label} + {desc} + + + ))} + +
+ +
+ Flora Dynamics + + Three vegetation layers are simulated per tile each turn: canopy, undergrowth, + and fungi network. Each grows toward biome-specific climax values, modulated + by climate match and tile quality. Cross-effects create feedback loops: + canopy shades undergrowth, undergrowth feeds fungi, fungi accelerates regrowth. + + + {FLORA_COMPONENTS.map(({ name, color, desc }) => ( + + +
+ {name} + {desc} +
+
+ ))} +
+
+ +
+ Quality Scoring + + Tile quality is a weighted composite of four ecology dimensions, recomputed + each turn. The score maps to Q1-Q5 tiers, capped by the biome's quality range. + + + + DimensionWeightDescription + + + {ECOLOGY_WEIGHTS.map(({ name, weight, desc }) => ( + + {name} + {(weight * 100).toFixed(0)}% + {desc} + + ))} + + +
+ +
+ Succession and Desertification + + When canopy remains above 80% for 50 consecutive turns, the BiomeClassifier + reclassifies the tile — forest succession. Conversely, sustained drought + (moisture below 0.2 for 30 turns) triggers desertification with 2x flora decay. + After clearing (wildfire, logging), tiles progress through regrowth stages: + barren, scrub, young forest, forest — with fungi accelerating the process up to 2x. + +
+ +
+ Food Web + + Species traits determine predator-prey relationships. Carnivores eat strictly + smaller fauna (pack social grants +1 size tier). Herbivores reduce undergrowth + proportional to body size. Population dynamics follow Lotka-Volterra with + 3 substeps per turn and 5% stochastic perturbation, preventing lock-step + oscillations. Populations that drop below 2 individuals migrate or go extinct. + +
+ +
+ Visualization Lenses + + The climate simulation page now includes ecology overlay lenses. Toggle them + in the Layers panel to visualize canopy cover (green gradient), undergrowth + (yellow-green), fungi network (purple), tile quality (Q1-Q5 colors), fish + populations (blue on water), and wildlife habitat suitability. + +
+
+ ) +} + +const TierGrid = styled.div` + display: flex; + flex-direction: column; + gap: 0.75rem; + margin-top: 1rem; +` + +const TierCard = styled.div` + display: flex; + align-items: center; + gap: 1rem; + padding: 0.75rem 1rem; + background: ${({ theme }) => theme.colors.surface}; + border: 1px solid ${({ theme }) => theme.colors.border.default}; + border-radius: 6px; +` + +const TierBadge = styled.span` + display: flex; + align-items: center; + justify-content: center; + width: 2.5rem; + height: 2.5rem; + border-radius: 6px; + font-family: monospace; + font-weight: 700; + font-size: 0.875rem; + color: #fff; + flex-shrink: 0; +` + +const TierInfo = styled.div` + display: flex; + flex-direction: column; + gap: 0.125rem; +` + +const TierLabel = styled.span` + font-weight: 600; + font-size: 0.9375rem; + color: ${({ theme }) => theme.colors.text.primary}; +` + +const TierDesc = styled.span` + font-size: 0.8125rem; + color: ${({ theme }) => theme.colors.text.muted}; +` + +const ComponentGrid = styled.div` + display: flex; + flex-direction: column; + gap: 0.75rem; + margin-top: 1rem; +` + +const ComponentCard = styled.div` + display: flex; + align-items: flex-start; + gap: 0.75rem; + padding: 0.75rem 1rem; + background: ${({ theme }) => theme.colors.surface}; + border: 1px solid ${({ theme }) => theme.colors.border.default}; + border-radius: 6px; +` + +const ComponentDot = styled.span` + width: 0.75rem; + height: 0.75rem; + border-radius: 50%; + margin-top: 0.25rem; + flex-shrink: 0; +` + +const ComponentName = styled.div` + font-weight: 600; + font-size: 0.875rem; + color: ${({ theme }) => theme.colors.text.primary}; +` + +const ComponentDesc = styled.div` + font-size: 0.8125rem; + color: ${({ theme }) => theme.colors.text.muted}; + margin-top: 0.125rem; +` + +const WeightTable = styled.table` + width: 100%; + border-collapse: collapse; + margin-top: 1rem; + font-size: 0.8125rem; + + th { + text-align: left; + padding: 0.5rem 0.75rem; + border-bottom: 2px solid ${({ theme }) => theme.colors.border.default}; + color: ${({ theme }) => theme.colors.text.muted}; + font-weight: 600; + text-transform: uppercase; + font-size: 0.6875rem; + letter-spacing: 0.06em; + } + + td { + padding: 0.5rem 0.75rem; + border-bottom: 1px solid ${({ theme }) => theme.colors.border.default}; + color: ${({ theme }) => theme.colors.text.primary}; + } + + td:nth-child(2) { + font-family: monospace; + font-weight: 600; + text-align: center; + width: 5rem; + } +`