# Assistant conversation E55BM7

- Canonical URL: <https://m.sheaf.less.rest/E55BM7>
- Resource IRI: <https://m.sheaf.less.rest/E55BM7>
- Type: assistant conversation
- Mode: chat

## Transcript

### 1. User

can you look around the moppe source code? mostly to check that our new git repo integration is working on your end

### 2. Assistant tool calls

#### Tool call: `list_documents`

```json
{}
```

### 3. Tool result: list_documents

FOLDERS
- Accounting Algebra & Property Theory
- Living Structure, Wholeness & Beauty
- Living Structure, Wholeness &amp; Beauty
- ontology
- Procedural Generation & Co-Creation
- Roads, Trails & Movement
- Scenario-Based & Behavioral Programming
- Technology, Scale & Conviviality
- Terrain, Hydrology & Erosion
- Water Simulation & Rendering

Accounting Algebra & Property Theory (4)
- #GANFP9 A Fundamental Duality in the Mathematical and Natural Sciences: From Logic to Biology - 2024 | David Ellerman | 53 pp.
  Micro abstract: Traces an overlooked subset–partition duality—from logic and category theory through entropy and quantum indefiniteness to selectionist and generative mechanisms in biology.
- #NBH3BE Algebraic Models for Accounting Systems - 2010 | Derek J. S. Robinson, José García Pérez, Robert A. Nehmer, Salvador Cruz Rambaud | World Scientific | 255 pp.
  Micro abstract: Develops accounting theory algebraically: balance vectors as modules, transactions as a group, and whole accounting systems as automata with quotients, homomorphisms, and decision algorithms for audit and control.
- #7ESDBJ Economics, Accounting, and Property Theory - 1982 | David P. Ellerman | Lexington Books | 110 pp.
  Micro abstract: Ellerman's vector-accounting monograph: double entry generalized to property vectors ("accounting without valuation"), grounding a property-theoretic account of appropriation, the firm, and goodwill.
- #C8FHDZ On implication and negation in partition logic - 2025 |  , David Ellerman | Open Journal of Mathematical Sciences | 9 pp. | doi:10.30538/oms2025.0250
  Micro abstract: Develops implication as a refinement-sensitive operation on set partitions, showing how relative negation yields local Boolean cores within the non-distributive algebra of partitions.

Living Structure, Wholeness & Beauty (9)
- #MH5J8D Beautimeter: Harnessing GPT for Assessing Architectural and Urban Beauty Based on the 15 Properties of Living Structure - 2025 | Bin Jiang | AI | 12 pp. | doi:10.3390/ai6040074
  Micro abstract: Presents Beautimeter, a GPT-based tool that scores buildings and urban scenes against Christopher Alexander’s 15 properties of living structure to assess their coherence and beauty.
- #XW22YY Generative Codes: The Path to Building Welcoming, Beautiful, Sustainable Neighborhoods - 2005 | Brian Hanson, Christopher Alexander, Maggie Moore Alexander, Michael Mehaffy, Randall Schmidt | Center for Environmental Structure | 21 pp.
  Micro abstract: Argues that living neighborhoods arise from generative codes: ordered, participatory steps that let buildings and public spaces unfold from local people, land, and context.
- #SKRF4C Geography as a Science of the Earth’s Surface Founded on the Third View of Space - 2022 | Bin Jiang | Annals of GIS | 14 pp. | doi:10.1080/19475683.2021.1966502
  Micro abstract: Recasts geography around an organismic view of space, using scaling and spatial dependence to understand—and deliberately create—places with greater living structure.
- #PXG56P Harmony-Seeking Computations: A Science of Non-Classical Dynamics Based on the Progressive Evolution of the Larger Whole - 2009 | Christopher Alexander | Unpublished manuscript | 66 pp.
  Micro abstract: Proposes harmony-seeking computation as a creative process that repeatedly strengthens latent centers in a configuration while preserving and deepening the larger whole.
- #MJKTBB Living Images: A Recursive Approach to Computing the Structural Beauty of Images or the Livingness of Space - 2023 | Bin Jiang, Chris de Rijke | Annals of the American Association of Geographers | 19 pp. | doi:10.1080/24694452.2023.2178376
  Micro abstract: Measures an image’s structural beauty by recursively extracting its nested substructures, revealing a compact hierarchy that also captures visual saliency.
- #3XSLTA Structural Beauty: A Structure-Based Computational Approach to Quantifying the Beauty of an Image - 2021 | Bin Jiang, Chris de Rijke | Journal of Imaging | 15 pp. | doi:10.3390/jimaging7050078
  Micro abstract: Proposes a quantitative measure of structural beauty based on how many substructures an image contains and how strongly they form a hierarchy across scales.
- #ZU8GZV Structure-Preserving Transformations - 2002 | Christopher Alexander | The Nature of Order, Book Two: The Process of Creating Life | 4 pp. | doi:10.2307/j.ctv27ftw6c.5
  Micro abstract: Explains structure-preserving transformations: incremental changes that extend the centers and relationships already present in a place rather than weakening its wholeness.
- #AULNWD The Nature of Poetic Order - 1998 | Richard P. Gabriel | Warren Wilson Alumni Conference, Mount Holyoke | 99 pp.
  Micro abstract: Gabriel's slide essay relating poetry's formal order to Christopher Alexander's ideas of generative structure, exploring how constraint and pattern produce living order in creative work.
- #BYG3BQ Wholeness as a Hierarchical Graph to Capture the Nature of Space - 2015 | Bin Jiang | International Journal of Geographical Information Science | 14 pp. | doi:10.1080/13658816.2015.1038542
  Micro abstract: Models spatial wholeness as a hierarchical graph of mutually reinforcing centers, using PageRank and scaling depth to quantify the life of parts and wholes.

ontology (2)
- #CGE2NC Against Fantology - 2005 | Barry Smith | Experience and Analysis | 22 pp.
  Micro abstract: Critiques the idea that first-order logic reveals reality’s ontology, tracing its atomism, timelessness, Booleanism, and reductionism before proposing a six-category ontology and an enhanced Davidsonian formal language.
- #JZG4PM Against Fantology Again - 2016 | Ingvar Johansson | The Theory and Practice of Ontology | 12 pp.
  Micro abstract: Extends the critique of fantology through default ontologization, arguing that Quine’s canonical notation is incoherent about classes and excludes intentional phenomena and distinct modes of existence.

Procedural Generation & Co-Creation (14)
- #ABD2B8 Between Tech and Art: The Vegetation of Horizon Zero Dawn - 2018 | Gilbert Sanders, Guerrilla Games | Game Developers Conference (GDC) 2018 | 87 pp.
  Micro abstract: A production breakdown of Horizon Zero Dawn’s vegetation pipeline, covering global wind simulation, layered foliage motion, coverage-preserving alpha mipmaps, shading, asset LODs, placement, and cascaded shadows.
- #4TH488 Explainable AI for Designers: A Human-Centered Perspective on Mixed-Initiative Co-Creation - 2018 | Antonios Liapis, G. Michael Youngblood, Jichen Zhu, Rafael Bidarra, Sebastian Risi | 2018 IEEE Conference on Computational Intelligence and Games (CIG) | 8 pp. | doi:10.1109/CIG.2018.8490433
  Micro abstract: Defines explainable AI for game designers, mapping co-creative systems by their explainability, initiative, and domain overlap so explanations serve concrete design tasks.
- #9NQ94D Extracting Physics from Blended Platformer Game Levels - 2020 | Adam Summerville, Anurag Sarkar, Joseph C. Osborn, Sam Snodgrass | Joint Proceedings of the AIIDE 2020 Workshops (CEUR Workshop Proceedings, Vol. 2862) | 7 pp.
  Micro abstract: Infers playable jump physics from generated platformer levels, including hybrid physics models for levels that blend the geometry and style of multiple games.
- #66Q3W3 Ghost of Tsushima: Procedural Grass - 2021 | Eric Wohllaib, Sucker Punch Productions | Game Developers Conference (GDC) 2021 | 55 pp.
  Micro abstract: Explains Ghost of Tsushima’s compute-driven grass pipeline, from tiled placement and culling to indirect drawing, cubic Bézier blade geometry, variable LOD, wind animation, and material shading.
- #QHMFH2 Improved Alpha Testing Using Hashed Sampling - 2019 | Chris Wyman, Morgan McGuire | IEEE Transactions on Visualization and Computer Graphics | 12 pp. | doi:10.1109/TVCG.2017.2739149
  Micro abstract: Develops hashed alpha testing, a stable quasi-random thresholding method that preserves distant alpha-mapped foliage and hair while controlling flicker, anisotropy, and interactions with TAA and alpha-to-coverage.
- #7GR3AQ Procedural Content Generation through Quality Diversity - 2019 | Ahmed Khalifa, Antonios Liapis, Daniele Gravina, Georgios N. Yannakakis, Julian Togelius | 2019 IEEE Conference on Games (CoG) | 8 pp. | doi:10.1109/CIG.2019.8848053
  Micro abstract: Argues for quality-diversity algorithms in procedural generation, producing broad collections of varied, playable content while exposing the design space for exploration and co-creation.
- #CQBDX4 Procedural Content Generation via Machine Learning (PCGML) - 2018 | Aaron Isaksen, Adam Summerville, Amy K. Hoover, Andy Nealen, Christoffer Holmgård, Julian Togelius, Matthew Guzdial, Sam Snodgrass | IEEE Transactions on Games | 15 pp. | doi:10.1109/TG.2018.2846639
  Micro abstract: Defines and surveys PCGML: generating functional game content directly from models trained on existing examples, with uses spanning creation, completion, repair, critique, and compression.
- #EARFEK Procedural Generation of Villages on Arbitrary Terrains - 2012 | Adrien Bernhardt, Adrien Peytavie, Arnaud Emilien, Eric Galin, Marie-Paule Cani | The Visual Computer | 10 pp. | doi:10.1007/s00371-012-0699-7
  Micro abstract: Presents a three-stage procedural model that grows terrain-responsive village roads and settlements, partitions land into plausible parcels, and generates slope-adapted buildings with open shape grammars.
- #EDURTK Real-Time GPU Tree Generation - 2025 | Bastian Kuth, Carsten Faber, Dominik Baumeister, Max Oberberger, Pirmin Pfeifer, Quirin Meyer, Seyedmasih Tabaei | High-Performance Graphics – Symposium Papers | 10 pp. | doi:10.2312/hpg.20251168
  Micro abstract: Introduces a GPU work-graph pipeline that generates, animates, edits, and continuously LODs detailed seasonal trees every frame, replacing gigabytes of baked geometry with kilobytes of parameters.
- #GBXEP3 Realistic Modeling and Rendering of Plant Ecosystems - 1998 | Bernd Lintermann, Matt Pharr, Oliver Deussen, Pat Hanrahan, Przemyslaw Prusinkiewicz, Radomír Měch | Proceedings of SIGGRAPH ’98 | 12 pp. | doi:10.1145/280814.280898
  Micro abstract: Presents a foundational pipeline for authoring plant ecosystems through terrain design, ecological simulation, procedural plant models, approximate instancing, and efficient rendering of billion-primitive scenes.
- #BDBBL6 Real‐time Realistic Rendering and Lighting of Forests - 2012 | Eric Bruneton, Fabrice Neyret | Computer Graphics Forum | 11 pp. | doi:10.1111/j.1467-8659.2012.03016.x
  Micro abstract: Combines detailed z-field trees with terrain shader-maps to render immense forests in real time, preserving sun, sky, canopy, and ground-lighting effects through seamless, scale-consistent transitions.
- #PQ68ZH Responsive Real-Time Grass Rendering for General 3D Scenes - 2017 | Klemens Jahrmann, Michael Wimmer | Proceedings of the 2017 Symposium on Interactive 3D Graphics and Games (I3D ’17) | 10 pp. | doi:10.1145/3023368.3023380
  Micro abstract: Renders every grass blade as responsive tessellated geometry on arbitrary 3D surfaces, with per-blade wind, gravity, and collision physics plus aggressive culling that retains dense fields in real time.
- #WZ8DHP Runtime Evaluation of Procedural Content Generation in an Endless Runner Game Using Autonomous Agents - 2026 | Rishabh Kar | arXiv | 25 pp. | doi:10.48550/arXiv.2605.01783
  Micro abstract: Integrates procedural generation and validation in an endless runner, using aerial and ground agents to detect blocked or unnavigable content before the player reaches it.
- #NRBMD5 Towards Friendly Mixed Initiative Procedural Content Generation: Three Pillars of Industry - 2020 | Frederic Fol Leymarie, Gorm Lai, William Latham | Proceedings of the International Conference on the Foundations of Digital Games (FDG '20) | 4 pp. | doi:10.1145/3402942.3402946
  Micro abstract: Distills three requirements for industry-friendly co-creative PCG tools: preserve designer control, keep feedback loops short, and fit into existing production pipelines.

Roads, Trails & Movement (8)
- #G3TBNG A Sequential Two-Step Algorithm for Fast Generation of Vehicle Racing Trajectories - 2016 | J. Christian Gerdes, John Subosits, Nitin R. Kapania | Journal of Dynamic Systems, Measurement, and Control | 12 pp. | doi:10.1115/1.4033311
  Micro abstract: Generates near-optimal racing trajectories quickly by alternating between a minimum-time speed profile and a convex path update that reduces curvature.
- #B6P8L4 Active walker model for the formation of human and animal trail systems - 1997 | Dirk Helbing, Frank Schweitzer, Joachim Keltsch, Péter Molnár | Physical Review E | 34 pp. | doi:10.1103/physreve.56.2527
  Micro abstract: Models trail systems as self-organization: walkers reinforce attractive routes while unused traces fade, producing dendritic ant trails and low-detour pedestrian networks.
- #V4TQYB Interactive procedural street modeling - 2008 | Eugene Zhang, Gregory Esch, Guoning Chen, Pascal Müller, Peter Wonka | ACM Transactions on Graphics | 10 pp. | doi:10.1145/1360612.1360702
  Micro abstract: Lets designers generate and edit large street networks through tensor fields, combining procedural speed with brush-like global and local control over street patterns.
- #UYLTYJ Modelling the Evolution of Human Trail Systems - 1997 | Dirk Helbing, Joachim Keltsch, Péter Molnár | Nature | 11 pp. | doi:10.1038/40353
  Micro abstract: Shows how pedestrian trails emerge through feedback between destination-seeking walkers, existing paths, and vegetation recovery, yielding a compromise between directness and shared infrastructure.
- #GY93FG Mountain Trail Formation and the Active Walker Model - 2009 | J. P. Hague, S. J. Gilks | International Journal of Modern Physics C | 22 pp. | doi:10.1142/S0129183109014059
  Micro abstract: Extends the active-walker model to steep terrain, explaining zigzag mountain trails through slope avoidance, directional persistence, and mutual reinforcement by ascending and descending walkers.
- #LXV9AT Principles of Trail Layout and Design - 2019 | California State Parks | California State Parks Trails Handbook | 64 pp.
  Micro abstract: A field-oriented guide to durable trail design, emphasizing curvilinear alignment, natural drainage, sustainable grades, control points, and close reading of landform and soils.
- #XDEFZS Procedural Generation of Roads - 2010 | A. Peytavie, E. Galin, E. Guérin, N. Maréchal | Computer Graphics Forum | 10 pp. | doi:10.1111/j.1467-8659.2009.01612.x
  Micro abstract: Automatically routes and constructs roads with an anisotropic shortest-path method that weighs slope and obstacles while treating surface segments, bridges, and tunnels consistently.
- #ARP5U7 The Topography of Minoan Peak Sanctuaries - 1983 | A. A. D. Peatfield | The Annual of the British School at Athens | 8 pp. | doi:10.1017/s0068245400019729
  Micro abstract: Argues that Minoan peak sanctuaries were chosen for visibility and proximity to local settlements, forming a beacon-like sacred network whose contraction tracked settlement abandonment rather than cultic collapse.

Scenario-Based & Behavioral Programming (8)
- #P2W4J5 Adaptive Behavioral Programming - 2011 | David Harel, Nir Eitan | 8 pp. | doi:10.1109/ictai.2011.109
  Micro abstract: Adds reinforcements to live sequence charts and BPJ so scenario-based programs can learn from their environment, specifying goals to pursue and scenarios to avoid, with modular learning decompositions.
- #XQ5NKX Challenges in Modeling and Unmodeling Emergence, Rule Composition, and Networked Interactions in Complex Reactive Systems - 2023 | Assaf Marron, David Harel, Guy Frankel, Irun Cohen, Smadar Szekely | 8 pp. | doi:10.5220/0011728900003402
  Micro abstract: Position paper on modeling emergence, rule composition, and networked interactions in complex reactive systems, introducing "unmodeling"—explicitly excluding entities and behaviors from model execution.
- #D4VB7S Distributing Scenario-Based Models: A Replicate-and-Project Approach - 2017 | Assaf Marron, Daniel Gritzner, David Harel, Guy Katz, Joel Greenyer, Shlomi Steinberg | MODELSWARD 2017 | 16 pp. | doi:10.5220/0006271301820195
  Micro abstract: Distributes scenario-based models by replicating the full specification on every component and projecting it per component, mimicking centralized behavior while sharply reducing synchronization.
- #CSJARA Enhancing Scenario-Based Modeling Using Large Language Models - 2026 | Assaf Marron, David Harel, Guy Katz, Smadar Szekely | Communications in Computer and Information Science | Springer Nature Switzerland | pp. 43-68 | 26 pp. | doi:10.1007/978-3-031-96841-9_3
  Micro abstract: Extended methodology for combining LLM chatbots with scenario-based modeling: iterative generation of stand-alone scenarios checked by analysis and human review, framed as a step toward Wise Computing.
- #3JCRAD On Augmenting Scenario-Based Modeling with Generative AI - 2024 | Assaf Marron, David Harel, Guy Katz, Smadar Szekely | MODELSWARD 2024 | 12 pp. | doi:10.5220/0012427100003645
  Micro abstract: Outlines a structured method for using generative-AI chatbots in modeling: iteratively generate scenario-based model fragments, then analyze and inspect them to converge on an accurate system model.
- #QV3BWZ On tracing reactive systems - 2011 | David Harel, Shahar Maoz | Software &amp; Systems Modeling | 22 pp. | doi:10.1007/s10270-010-0151-2
  Micro abstract: Introduces model-based trace visualization and exploration for reactive systems, using scenario-based (LSC) abstractions and the Tracer prototype, demonstrated on a PacMan game.
- #TDS4H2 Relaxing Synchronization Constraints in Behavioral Programs - 2013 | Amir Kantor, David Harel, Guy Katz | LPAR 2013 (Logic for Programming, Artificial Intelligence, and Reasoning) | 17 pp. | doi:10.1007/978-3-642-45221-5_25
  Micro abstract: Proposes eager execution for behavioral programs: fast b-threads run ahead when synchronization outcomes are predictable, improving performance, modularity, and distributability, shown in a C++ BP framework.
- #M5788P Towards Behavioral Programming in Distributed Architectures - 2015 | Amir Kantor, Assaf Marron, David Harel, Gera Weiss, Guy Katz, Guy Wiener | Science of Computer Programming | 58 pp. | doi:10.1016/j.scico.2014.03.003
  Micro abstract: Extends behavioral programming to distributed architectures: b-threads as Erlang processes, eager execution to relax synchronization, and modular distributed execution, demonstrated on simulations and a quadrotor.

Technology, Scale & Conviviality (2)
- #WYH36B The City as Convivial Centre - 1974 | Leopold Kohr | Tract, no. 12 (Gryphon Press) | 18 pp.
  Micro abstract: Kohr's essay arguing that cities exist for convivial life rather than economic function, and that human-scale size is what lets a city serve as a centre of leisure, culture, and encounter.
- #67REFX The Question Concerning Technology - 1977 | Martin Heidegger | The Question Concerning Technology and Other Essays (Harper & Row) | 23 pp.
  Micro abstract: Heidegger's essay on the essence of technology as Enframing (Gestell), a mode of revealing that reduces the world to standing-reserve, and on art as a possible saving power.

Terrain, Hydrology & Erosion (8)
- #NV2YRW FastFlow: GPU Acceleration of Flow and Depression Routing for Landscape Simulation - 2024 | Aryamaan Jain, Bernhard Kerbl, Brandon Finley, Guillaume Cordonnier, James Gain | Computer Graphics Forum | 13 pp. | doi:10.1111/cgf.15243
  Micro abstract: A GPU framework for routing surface flow through terrain and its depressions fast enough to make erosion, river, lake, and ecosystem simulations interactive.
- #2284QZ From features to fingerprints: A general diagnostic framework for anthropogenic geomorphology - 2019 | Damian Evans, Erle C Ellis, Giulia Sofia, Paolo Tarolli, Wenfang Cao | Progress in Physical Geography: Earth and Environment | 34 pp. | doi:10.1177/0309133318825284
  Micro abstract: Integrates geomorphology, archaeology, and high-resolution remote sensing into a framework for reading anthropogenic landforms as landscape-scale sociocultural fingerprints.
- #96ZMGK Large Scale Terrain Generation from Tectonic Uplift and Fluvial Erosion - 2016 | Adrien Peytavie, Bedrich Benes, Guillaume Cordonnier, Jean Braun, Marie-Paule Cani, Éric Galin, Éric Guérin | Computer Graphics Forum | 11 pp. | doi:10.1111/cgf.12820
  Micro abstract: Generates large, controllable mountain terrains by coupling user-painted tectonic uplift with fluvial erosion, then turning the resulting stream graph into detailed landforms.
- #K82AS7 Legacy sediment: Definitions and processes of episodically produced anthropogenic sediment - 2013 | L. Allan James | Anthropocene | 11 pp. | doi:10.1016/j.ancene.2013.04.001
  Micro abstract: Broadens legacy sediment to episodically produced anthropogenic alluvium and colluvium, and explains its deposition, storage, and remobilization through sediment delivery–transport capacity dynamics.
- #DWXKYQ Physically-based analytical erosion for fast terrain generation - 2024 | Boris Gailleton, Guillaume Cordonnier, Petros Tzathas, Philippe Steer | Computer Graphics Forum | 14 pp. | doi:10.1111/cgf.15033
  Micro abstract: Turns the stream power law into an interactive terrain tool, replacing thousands of erosion time steps with analytical solutions and a direct control for landscape age.
- #MTDKDE Priority-Flood: An Optimal Depression-Filling and Watershed-Labeling Algorithm for Digital Elevation Models - 2014 | Clarence Lehman, David Mulla, Richard Barnes | Computers & Geosciences | 17 pp. | doi:10.1016/j.cageo.2013.04.024
  Micro abstract: Introduces Priority-Flood, a simple, optimal algorithm that removes drainage-blocking depressions from elevation models and can also derive watersheds and flow directions.
- #AK7NGE Procedural Riverscapes - 2019 | A. Peytavie, B. Benes, E. Galin, E. Guérin, J. Gain, T. Dupont, Y. Cortial | Computer Graphics Forum | 12 pp. | doi:10.1111/cgf.13814
  Micro abstract: Builds editable, animated riverscapes from bare terrain by carving hydrologically plausible channels and blending real-time procedural water primitives instead of simulating fluids.
- #DMTA8Y Terrain Generation Using Procedural Models Based on Hydrology - 2013 | Adrien Peytavie, Bedřich Beneš, Jean-David Génevaux, Éric Galin, Éric Guérin | ACM Transactions on Graphics | 10 pp. | doi:10.1145/2461912.2461996
  Micro abstract: Generates controllable, multiscale terrain from a sketched drainage network, representing rivers and landforms as an editable hierarchy of continuous procedural primitives.

Water Simulation & Rendering (12)
- #RBS5K6 A Layered Particle-Based Fluid Model for Real-Time Rendering of Water - 2010 | Daniel Scherzer, Florian Bagar, Michael Wimmer | Computer Graphics Forum | 7 pp. | doi:10.1111/j.1467-8659.2010.01734.x
  Micro abstract: Renders particle-based water and volumetric foam in real time using perspective-aware surface smoothing, physically guided foam formation, and layered depth compositing.
- #C4AY2M A Survey of Ocean Simulation and Rendering Techniques in Computer Graphics - 2011 | B. Crespin, D. Ghazanfarpour, E. Darles, J.-C. Gonzato | Computer Graphics Forum | 17 pp. | doi:10.1111/j.1467-8659.2010.01828.x
  Micro abstract: Surveys ocean graphics from spectral deep-water models to near-shore fluid simulation, then covers the foam, spray, and light transport needed for convincing rendering.
- #WZMZGY Advected river textures - 2009 | Dirk Arnold, Stephen Brooks, Tim Burrell | Computer Animation and Virtual Worlds | 11 pp. | doi:10.1002/cav.288
  Micro abstract: Combines a 2D Navier–Stokes solver, hydrostatic pressure columns, and advected procedural textures to render detailed, terrain-responsive rivers at real-time frame rates.
- #92XRH7 Lagrangian Texture Advection: Preserving both Spectrum and Velocity Field - 2011 |  Qizhi Yu, E. Bruneton, F. Neyret, N. Holzschuch | IEEE Transactions on Visualization and Computer Graphics | 13 pp. | doi:10.1109/tvcg.2010.263
  Micro abstract: Advects fluid textures with deformable particle grids, preserving both the input texture’s visual spectrum and exact motion along the velocity field without cumulative stretching.
- #8SERGP Real-time Breaking Waves for Shallow Water Simulations - 2007 | Markus Gross, Matthias Müller-Fischer, Nils Thürey, Simon Schirm | 15th Pacific Conference on Computer Graphics and Applications (Pacific Graphics 2007) | 8 pp. | doi:10.1109/PG.2007.33
  Micro abstract: Adds real-time overturning waves to shallow-water heightfields by detecting steep fronts and spawning connected particle sheets that collapse into splashes and foam.
- #CWC7H9 Real-time Rendering of Enhanced Shallow Water Fluid Simulations - 2013 | Antonio Susín, Jesús Ojeda | Computers & Graphics | 9 pp.
  Micro abstract: Builds a real-time rendering pipeline for shallow-water simulations, adding fine surface detail, advected foam, photon-based caustics, and screen-space reflection and refraction.
- #MVUJ8Z Real-time Rendering of River Networks - 2010 | Quintijn Hendrickx, Rafael Bidarra, Ruben M. Smelik | Proceedings of the ACM SIGGRAPH Symposium on Interactive 3D Graphics and Games | 1 pp.
  Micro abstract: Renders branching river networks efficiently with quadratic Bézier curves, GPU distance fields, and streaming normal maps instead of dense geometry or particle simulation.
- #5MGCZ5 Real-time River Representation by Dynamic Control of Data on Waves - 2008 | Makoto Kosugi, Nobuhiko Mukai, Yasuhiro Kato | 4 pp. | doi:10.3169/itej.62.2063
  Micro abstract: Dynamically switches river-wave models by viewing distance, preserving nearby reflection and wave detail while retaining wind-driven motion across the full landscape.
- #XDESU9 Scalable real‐time animation of rivers - 2009 | Eric Bruneton, Fabrice Neyret, Nicolas Holzschuch, Qizhi Yu | Computer Graphics Forum | 11 pp. | doi:10.1111/j.1467-8659.2009.01363.x
  Micro abstract: Animates rivers across vast terrains by computing steady flow locally and advecting fine surface detail only where visible, at a screen-space sampling density.
- #869NHK Very Fast Real-Time Ocean Wave Foam Rendering Using Halftoning - 2011 | Ian Parberry, Jennifer R. Alford, Mary Yingst | Proceedings of the 6th International North American Conference on Intelligent Games and Simulation (GAMEON-NA) | 7 pp.
  Micro abstract: Uses a spectrally controlled halftone mask to make texture-based ocean foam dissipate as clumped bubbles that pop, rather than merely fading transparently.
- #A2QB8L Water Flow in Portal 2 - 2010 | Alex Vlachos | ACM SIGGRAPH 2010 Course: Advances in Real-Time Rendering in 3D Graphics and Games | 54 pp.
  Micro abstract: Explains Portal 2’s production-friendly water shader: artist-painted flow maps advect layered normal maps, while offsets and noise suppress repetition and pulsing on limited hardware.
- #PBM2TC Water surface wavelets - 2018 | Chris Wojtan, Matthias Müller-Fischer, Miles Macklin, Nuttapong Chentanez, Stefan Jeschke, Tomáš Skřivan | ACM Transactions on Graphics | 13 pp. | doi:10.1145/3197517.3201336
  Micro abstract: Represents water waves as spatially varying directional amplitudes, enabling vast interactive simulations with fine detail, moving obstacles, solid coupling, and direct artistic control.

Unfiled (1)
- #3CV92Z moppe

### 4. Assistant tool calls

#### Tool call: `get_document`

```json
{
  "id": "3CV92Z"
}
```

### 5. Tool result: get_document

GIT REPOSITORY #3CV92Z
Title: moppe

Outline:
  - /3CV92Z/source-directories/YXRlbGllcg atelier
  - /3CV92Z/source-directories/Y21ha2U cmake
  - /3CV92Z/source-directories/ZG9jcw docs
  - /3CV92Z/source-directories/aWRlYXM ideas
  - /3CV92Z/source-directories/bW9wcGU moppe
  - /3CV92Z/source-directories/cGxhbg plan
  - /3CV92Z/source-directories/cGxhbm5pbmc planning
  - /3CV92Z/source-directories/cmVzZWFyY2g research
  - /3CV92Z/source-directories/dGVzdHM tests
  - /3CV92Z/source-directories/dG9vbHM tools
  - /3CV92Z/source-files/LmNsYW5nLWZvcm1hdA .clang-format
  - /3CV92Z/source-files/LmNsYW5nZA .clangd
  - /3CV92Z/source-files/LmdpdGF0dHJpYnV0ZXM .gitattributes
  - /3CV92Z/source-files/LmdpdGlnbm9yZQ .gitignore
  - /3CV92Z/source-files/QUdFTlRTLm1k AGENTS.md
  - /3CV92Z/source-files/Q0xBVURFLm1k CLAUDE.md
  - /3CV92Z/source-files/Q01ha2VMaXN0cy50eHQ CMakeLists.txt
  - /3CV92Z/source-files/Q01ha2VQcmVzZXRzLmpzb24 CMakePresets.json
  - /3CV92Z/source-files/TWFrZWZpbGU Makefile
  - /3CV92Z/source-files/UkVBRE1FLm1k README.md

### 6. Assistant tool calls

#### Tool call: `read`

```json
{
  "blocks": [
    "/3CV92Z/source-directories/bW9wcGU",
    "/3CV92Z/source-directories/dGVzdHM",
    "/3CV92Z/source-directories/ZG9jcw",
    "/3CV92Z/source-directories/YXRlbGllcg"
  ],
  "expand": true
}
```

#### Tool call: `read`

```json
{
  "blocks": [
    "/3CV92Z/source-files/UkVBRE1FLm1k#content",
    "/3CV92Z/source-files/QUdFTlRTLm1k#content",
    "/3CV92Z/source-files/Q01ha2VMaXN0cy50eHQ#content"
  ],
  "expand": false
}
```

### 7. Tool result: read

GIT REPOSITORY #3CV92Z
moppe

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGU moppe

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvZ2FtZQ game

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9ibG9iX3NoYWRvdy5jYw#content moppe/game/blob_shadow.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9ibG9iX3NoYWRvdy5oaA#content moppe/game/blob_shadow.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9jaGFzZV9jYW1lcmEuY2M#content moppe/game/chase_camera.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9jaGFzZV9jYW1lcmEuaGg#content moppe/game/chase_camera.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9jaW5lbWF0aWNfZmxpZ2h0LmNj#content moppe/game/cinematic_flight.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9jaW5lbWF0aWNfZmxpZ2h0Lmho#content moppe/game/cinematic_flight.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9kb29yLmho#content moppe/game/door.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9kdXN0LmNj#content moppe/game/dust.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9kdXN0Lmho#content moppe/game/dust.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9mb3Jlc3QuY2M#content moppe/game/forest.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9mb3Jlc3QuaGg#content moppe/game/forest.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9mcmFtZV92aWV3LmNj#content moppe/game/frame_view.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9mcmFtZV92aWV3Lmho#content moppe/game/frame_view.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9nYW1lLmNj#content moppe/game/game.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9nYW1lX3Nlc3Npb24uY2M#content moppe/game/game_session.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9nYW1lX3Nlc3Npb24uaGg#content moppe/game/game_session.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9nYW1lX3N0YXRlLmho#content moppe/game/game_state.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9nZW5lcmF0ZWRfd29ybGQuY2M#content moppe/game/generated_world.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9nZW5lcmF0ZWRfd29ybGQuaGg#content moppe/game/generated_world.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9nbGlkZXJfcmVuZGVyLmNj#content moppe/game/glider_render.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9nbGlkZXJfcmVuZGVyLmho#content moppe/game/glider_render.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9ncmFwaGljc19iZW5jaG1hcmsuaGg#content moppe/game/graphics_benchmark.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9ncmFwaGljc19zZXR0aW5ncy5jYw#content moppe/game/graphics_settings.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9ncmFwaGljc19zZXR0aW5ncy5oaA#content moppe/game/graphics_settings.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9odWQuY2M#content moppe/game/hud.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9odWQuaGg#content moppe/game/hud.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9pbnB1dF9mcmFtZS5oaA#content moppe/game/input_frame.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9pbnB1dF9mcmFtZV9hZGFwdGVyLmho#content moppe/game/input_frame_adapter.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9pbnNwZWN0b3JfdWkuY2M#content moppe/game/inspector_ui.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9pbnNwZWN0b3JfdWkuaGg#content moppe/game/inspector_ui.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9tb2RlbC5jYw#content moppe/game/model.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9tb2RlbC5oaA#content moppe/game/model.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9yaXZlcl9zdXJmYWNlLmNj#content moppe/game/river_surface.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9yaXZlcl9zdXJmYWNlLmho#content moppe/game/river_surface.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9zcHJpdGVzLmNj#content moppe/game/sprites.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9zcHJpdGVzLmho#content moppe/game/sprites.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9zdGFycy5jYw#content moppe/game/stars.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9zdGFycy5oaA#content moppe/game/stars.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9zdXJmYWNlX3ByZXNlbnRhdGlvbi5jYw#content moppe/game/surface_presentation.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS9zdXJmYWNlX3ByZXNlbnRhdGlvbi5oaA#content moppe/game/surface_presentation.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS90ZXJyYWluLmNj#content moppe/game/terrain.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS90ZXJyYWluLmho#content moppe/game/terrain.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS90ZXJyYWluX2xhYi5jYw#content moppe/game/terrain_lab.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS90ZXJyYWluX2xhYi5oaA#content moppe/game/terrain_lab.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS90ZXJyYWluX2xhYl9tb2RlbC5jYw#content moppe/game/terrain_lab_model.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS90ZXJyYWluX2xhYl9tb2RlbC5oaA#content moppe/game/terrain_lab_model.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS90ZXN0YmVkLmNj#content moppe/game/testbed.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS90cmVlX3N0YW5kLmNj#content moppe/game/tree_stand.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS90cmVlX3N0YW5kLmho#content moppe/game/tree_stand.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS92ZWhpY2xlX3JlbmRlci5jYw#content moppe/game/vehicle_render.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS92ZWhpY2xlX3JlbmRlci5oaA#content moppe/game/vehicle_render.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS93YWxrZXIuY2M#content moppe/game/walker.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS93YWxrZXIuaGg#content moppe/game/walker.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS93YWxrZXJfcmVuZGVyLmNj#content moppe/game/walker_render.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS93YWxrZXJfcmVuZGVyLmho#content moppe/game/walker_render.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS93YXRlcl9jYXB0dXJlLmNj#content moppe/game/water_capture.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS93YXRlcl9jYXB0dXJlLmho#content moppe/game/water_capture.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS93YXRlcl9wcmVzZW50YXRpb24uY2M#content moppe/game/water_presentation.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS93YXRlcl9wcmVzZW50YXRpb24uaGg#content moppe/game/water_presentation.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS93b3JsZC5oaA#content moppe/game/world.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS93b3JsZF9sb2FkaW5nLmNj#content moppe/game/world_loading.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2FtZS93b3JsZF9sb2FkaW5nLmho#content moppe/game/world_loading.hh

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvZ2Z4 gfx

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2Z4L21hdDQuaGg#content moppe/gfx/mat4.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2Z4L21hdGguaGg#content moppe/gfx/math.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2Z4L3RnYS5jYw#content moppe/gfx/tga.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvZ2Z4L3RnYS5oaA#content moppe/gfx/tga.hh

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvbWFw map

SOURCE FILE /3CV92Z/source-files/bW9wcGUvbWFwL2dlbmVyYXRlLmNj#content moppe/map/generate.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvbWFwL2dlbmVyYXRlLmho#content moppe/map/generate.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvbWFwL3N1cmZhY2UuY2M#content moppe/map/surface.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvbWFwL3N1cmZhY2UuaGg#content moppe/map/surface.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvbWFwL3N1cmZhY2VfYXRsYXMuaGg#content moppe/map/surface_atlas.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvbWFwL3N1cmZhY2VfZG9tYWluLmNj#content moppe/map/surface_domain.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvbWFwL3N1cmZhY2VfZG9tYWluLmho#content moppe/map/surface_domain.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvbWFwL3N1cmZhY2VfZWNvbG9neS5jYw#content moppe/map/surface_ecology.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvbWFwL3N1cmZhY2VfbWF0ZXJpYWxzLmNj#content moppe/map/surface_materials.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvbWFwL3N1cmZhY2Vfc2VjdGlvbnMuaGg#content moppe/map/surface_sections.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvbWFwL3RlcnJhaW5fZXZhbHVhdG9yLmNj#content moppe/map/terrain_evaluator.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvbWFwL3RlcnJhaW5fZXZhbHVhdG9yLmho#content moppe/map/terrain_evaluator.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvbWFwL3dhdGVyX3N1cmZhY2UuY2M#content moppe/map/water_surface.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvbWFwL3dhdGVyX3N1cmZhY2UuaGg#content moppe/map/water_surface.hh

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvbW92 mov

SOURCE FILE /3CV92Z/source-files/bW9wcGUvbW92L2dsaWRlci5jYw#content moppe/mov/glider.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvbW92L2dsaWRlci5oaA#content moppe/mov/glider.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvbW92L3ZlaGljbGUuY2M#content moppe/mov/vehicle.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvbW92L3ZlaGljbGUuaGg#content moppe/mov/vehicle.hh

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvcGxhdGZvcm0 platform

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvcGxhdGZvcm0vYXBwbGU apple

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcGxhdGZvcm0vYXBwbGUvYXBwbGVfY29tbW9uLm1t#content moppe/platform/apple/apple_common.mm

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcGxhdGZvcm0vYXBwbGUvZ2FtZV9jb250cm9sbGVyLmho#content moppe/platform/apple/game_controller.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcGxhdGZvcm0vYXBwbGUvZ2FtZV9jb250cm9sbGVyLm1t#content moppe/platform/apple/game_controller.mm

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvcGxhdGZvcm0vaW9z ios

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvcGxhdGZvcm0vaW9zL0Fzc2V0cy54Y2Fzc2V0cw Assets.xcassets

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvcGxhdGZvcm0vaW9zL0Fzc2V0cy54Y2Fzc2V0cy9BcHBJY29uLmFwcGljb25zZXQ AppIcon.appiconset

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcGxhdGZvcm0vaW9zL0Fzc2V0cy54Y2Fzc2V0cy9BcHBJY29uLmFwcGljb25zZXQvQ29udGVudHMuanNvbg#content moppe/platform/ios/Assets.xcassets/AppIcon.appiconset/Contents.json

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcGxhdGZvcm0vaW9zL0Fzc2V0cy54Y2Fzc2V0cy9Db250ZW50cy5qc29u#content moppe/platform/ios/Assets.xcassets/Contents.json

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcGxhdGZvcm0vaW9zL0V4cG9ydE9wdGlvbnMucGxpc3Q#content moppe/platform/ios/ExportOptions.plist

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcGxhdGZvcm0vaW9zL0luZm8ucGxpc3QuaW4#content moppe/platform/ios/Info.plist.in

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcGxhdGZvcm0vaW9zL21haW5faW9zLm1t#content moppe/platform/ios/main_ios.mm

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvcGxhdGZvcm0vbWFj mac

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcGxhdGZvcm0vbWFjL0luZm8ucGxpc3QuaW4#content moppe/platform/mac/Info.plist.in

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcGxhdGZvcm0vbWFjL21haW5fbWFjLm1t#content moppe/platform/mac/main_mac.mm

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvcGxhdGZvcm0vdHZvcw tvos

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcGxhdGZvcm0vdHZvcy9JbmZvLnBsaXN0Lmlu#content moppe/platform/tvos/Info.plist.in

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcGxhdGZvcm0vdHZvcy9tYWluX3R2b3MubW0#content moppe/platform/tvos/main_tvos.mm

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvcGxhdGZvcm0vd2Vi web

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcGxhdGZvcm0vd2ViL21haW5fd2ViLmNj#content moppe/platform/web/main_web.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcGxhdGZvcm0vd2ViL3NoZWxsLmh0bWw#content moppe/platform/web/shell.html

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcGxhdGZvcm0vcGxhdGZvcm0uaGg#content moppe/platform/platform.hh

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvcmVuZGVy render

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvcmVuZGVyL21ldGFs metal

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcmVuZGVyL21ldGFsL21ldGFsX3JlbmRlcmVyLmho#content moppe/render/metal/metal_renderer.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcmVuZGVyL21ldGFsL21ldGFsX3JlbmRlcmVyLm1t#content moppe/render/metal/metal_renderer.mm

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcmVuZGVyL21ldGFsL3NoYWRlcl90eXBlcy5o#content moppe/render/metal/shader_types.h

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvcmVuZGVyL3dlYmdwdQ webgpu

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcmVuZGVyL3dlYmdwdS93ZWJncHVfcmVuZGVyZXIuY2M#content moppe/render/webgpu/webgpu_renderer.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcmVuZGVyL3dlYmdwdS93ZWJncHVfcmVuZGVyZXIuaGg#content moppe/render/webgpu/webgpu_renderer.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcmVuZGVyL2RyYXcuY2M#content moppe/render/draw.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcmVuZGVyL2RyYXcuaGg#content moppe/render/draw.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcmVuZGVyL3JlbmRlcmVyLmho#content moppe/render/renderer.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcmVuZGVyL3RleHQuY2M#content moppe/render/text.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcmVuZGVyL3RleHQuaGg#content moppe/render/text.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcmVuZGVyL3R5cGVzLmho#content moppe/render/types.hh

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvc2hhZGVycw shaders

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvc2hhZGVycy9tZXRhbA metal

SOURCE FILE /3CV92Z/source-files/bW9wcGUvc2hhZGVycy9tZXRhbC9jb21tb24uaA#content moppe/shaders/metal/common.h

SOURCE FILE /3CV92Z/source-files/bW9wcGUvc2hhZGVycy9tZXRhbC9kdXN0Lm1ldGFs#content moppe/shaders/metal/dust.metal

SOURCE FILE /3CV92Z/source-files/bW9wcGUvc2hhZGVycy9tZXRhbC9maWVsZC5tZXRhbA#content moppe/shaders/metal/field.metal

SOURCE FILE /3CV92Z/source-files/bW9wcGUvc2hhZGVycy9tZXRhbC9vY2Vhbi5tZXRhbA#content moppe/shaders/metal/ocean.metal

SOURCE FILE /3CV92Z/source-files/bW9wcGUvc2hhZGVycy9tZXRhbC9vcm9nZW55Lm1ldGFs#content moppe/shaders/metal/orogeny.metal

SOURCE FILE /3CV92Z/source-files/bW9wcGUvc2hhZGVycy9tZXRhbC9wb3N0Lm1ldGFs#content moppe/shaders/metal/post.metal

SOURCE FILE /3CV92Z/source-files/bW9wcGUvc2hhZGVycy9tZXRhbC9yaXZlci5tZXRhbA#content moppe/shaders/metal/river.metal

SOURCE FILE /3CV92Z/source-files/bW9wcGUvc2hhZGVycy9tZXRhbC9za3kubWV0YWw#content moppe/shaders/metal/sky.metal

SOURCE FILE /3CV92Z/source-files/bW9wcGUvc2hhZGVycy9tZXRhbC90ZXJyYWluLm1ldGFs#content moppe/shaders/metal/terrain.metal

SOURCE FILE /3CV92Z/source-files/bW9wcGUvc2hhZGVycy9tZXRhbC91YmVyLm1ldGFs#content moppe/shaders/metal/uber.metal

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvc3BhdGlhbA spatial

SOURCE FILE /3CV92Z/source-files/bW9wcGUvc3BhdGlhbC9idW5kbGUuaGg#content moppe/spatial/bundle.hh

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvdGVycmFpbg terrain

SOURCE DIRECTORY /3CV92Z/source-directories/bW9wcGUvdGVycmFpbi9tZXRhbA metal

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9tZXRhbC9maWVsZF9zaGFkZXJfdHlwZXMuaA#content moppe/terrain/metal/field_shader_types.h

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9tZXRhbC9tZXRhbF9ldmFsdWF0b3IuaGg#content moppe/terrain/metal/metal_evaluator.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9tZXRhbC9tZXRhbF9ldmFsdWF0b3IubW0#content moppe/terrain/metal/metal_evaluator.mm

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9tZXRhbC9tZXRhbF9zdHJlYW1fcG93ZXJfZXZvbHV0aW9uLmho#content moppe/terrain/metal/metal_stream_power_evolution.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9tZXRhbC9tZXRhbF9zdHJlYW1fcG93ZXJfZXZvbHV0aW9uLm1t#content moppe/terrain/metal/metal_stream_power_evolution.mm

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9tZXRhbC9vcm9nZW55X3NoYWRlcl90eXBlcy5o#content moppe/terrain/metal/orogeny_shader_types.h

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9jcHVfZXZhbHVhdG9yLmNj#content moppe/terrain/cpu_evaluator.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9jcHVfZXZhbHVhdG9yLmho#content moppe/terrain/cpu_evaluator.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9kaXNjcmV0aXphdGlvbi5oaA#content moppe/terrain/discretization.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9kcmFpbmFnZS5jYw#content moppe/terrain/drainage.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9kcmFpbmFnZS5oaA#content moppe/terrain/drainage.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9lZGl0b3IuY2M#content moppe/terrain/editor.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9lZGl0b3IuaGg#content moppe/terrain/editor.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9ldmFsdWF0b3IuaGg#content moppe/terrain/evaluator.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9maWVsZC5jYw#content moppe/terrain/field.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9maWVsZC5oaA#content moppe/terrain/field.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9mbG9vZC5jYw#content moppe/terrain/flood.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9mbG9vZC5oaA#content moppe/terrain/flood.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9mcmFjdGlvbmFsX2RyYWluYWdlLmNj#content moppe/terrain/fractional_drainage.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9mcmFjdGlvbmFsX2RyYWluYWdlLmho#content moppe/terrain/fractional_drainage.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9nZW9sb2dpY2FsLmNj#content moppe/terrain/geological.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9nZW9sb2dpY2FsLmho#content moppe/terrain/geological.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9tZXJnZV90cmVlLmNj#content moppe/terrain/merge_tree.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9tZXJnZV90cmVlLmho#content moppe/terrain/merge_tree.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9tb2lzdHVyZS5jYw#content moppe/terrain/moisture.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9tb2lzdHVyZS5oaA#content moppe/terrain/moisture.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9ub2lzZS5jYw#content moppe/terrain/noise.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9ub2lzZS5oaA#content moppe/terrain/noise.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9vcm9nZW55X2JlbmNobWFyay5jYw#content moppe/terrain/orogeny_benchmark.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9wcm9ncmFtLmNj#content moppe/terrain/program.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9wcm9ncmFtLmho#content moppe/terrain/program.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9yZWFkaW5ncy5jYw#content moppe/terrain/readings.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9yZWFkaW5ncy5oaA#content moppe/terrain/readings.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9yaXZlci5jYw#content moppe/terrain/river.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9yaXZlci5oaA#content moppe/terrain/river.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9zdHJlYW1fcG93ZXJfZXZvbHV0aW9uLmNj#content moppe/terrain/stream_power_evolution.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi9zdHJlYW1fcG93ZXJfZXZvbHV0aW9uLmho#content moppe/terrain/stream_power_evolution.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi90ZXJyYWluX3ZpZXcuaGg#content moppe/terrain/terrain_view.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi90b3BvbG9neS5oaA#content moppe/terrain/topology.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi90cmFpbC5jYw#content moppe/terrain/trail.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi90cmFpbC5oaA#content moppe/terrain/trail.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi90cmFuc2Zvcm0uaGg#content moppe/terrain/transform.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi90eXBlcy5oaA#content moppe/terrain/types.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi93YXRlcl9kZXB0aF9leHBlcmltZW50LmNj#content moppe/terrain/water_depth_experiment.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi93YXRlcmNvdXJzZS5jYw#content moppe/terrain/watercourse.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi93YXRlcmNvdXJzZS5oaA#content moppe/terrain/watercourse.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi93YXRlcmxpbmUuY2M#content moppe/terrain/waterline.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi93YXRlcmxpbmUuaGg#content moppe/terrain/waterline.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi93b3JsZF9yZWNpcGUuY2M#content moppe/terrain/world_recipe.cc

SOURCE FILE /3CV92Z/source-files/bW9wcGUvdGVycmFpbi93b3JsZF9yZWNpcGUuaGg#content moppe/terrain/world_recipe.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvY29sb3IuaGg#content moppe/color.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcGFydGl0aW9uLmho#content moppe/partition.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcHJvZmlsZS5oaA#content moppe/profile.hh

SOURCE FILE /3CV92Z/source-files/bW9wcGUvcXVhbnRpdGllcy5oaA#content moppe/quantities.hh

GIT REPOSITORY #3CV92Z
moppe

SOURCE DIRECTORY /3CV92Z/source-directories/dGVzdHM tests

SOURCE DIRECTORY /3CV92Z/source-directories/dGVzdHMvYXRlbGllcg atelier

SOURCE FILE /3CV92Z/source-files/dGVzdHMvYXRlbGllci9idW5kbGVfdGVzdC5jYw#content tests/atelier/bundle_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvYXRlbGllci9lbWJlZGRpbmdfdGVzdC5jYw#content tests/atelier/embedding_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvYXRlbGllci9oZXhfc2hlZXRfdGVzdC5jYw#content tests/atelier/hex_sheet_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvYXRlbGllci90cmVlX3Rlc3QuY2M#content tests/atelier/tree_test.cc

SOURCE DIRECTORY /3CV92Z/source-directories/dGVzdHMvZ2FtZQ game

SOURCE FILE /3CV92Z/source-files/dGVzdHMvZ2FtZS9jaW5lbWF0aWNfZmxpZ2h0X3Rlc3QuY2M#content tests/game/cinematic_flight_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvZ2FtZS9mb3Jlc3RfdGVzdC5jYw#content tests/game/forest_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvZ2FtZS9mcmFtZV92aWV3X3Rlc3QuY2M#content tests/game/frame_view_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvZ2FtZS9nYW1lX3N0YXRlX3Rlc3QuY2M#content tests/game/game_state_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvZ2FtZS9nZW5lcmF0ZWRfd29ybGRfdGVzdC5jYw#content tests/game/generated_world_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvZ2FtZS9ncmFwaGljc19iZW5jaG1hcmtfdGVzdC5jYw#content tests/game/graphics_benchmark_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvZ2FtZS9ncmFwaGljc19zZXR0aW5nc190ZXN0LmNj#content tests/game/graphics_settings_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvZ2FtZS9pbnB1dF9mcmFtZV90ZXN0LmNj#content tests/game/input_frame_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvZ2FtZS9pbnNwZWN0b3JfdWlfdGVzdC5jYw#content tests/game/inspector_ui_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvZ2FtZS9yaXZlcl9zdXJmYWNlX3Rlc3QuY2M#content tests/game/river_surface_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvZ2FtZS90ZXJyYWluX2xhYl9tb2RlbF90ZXN0LmNj#content tests/game/terrain_lab_model_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvZ2FtZS90cmVlX3N0YW5kX3Rlc3QuY2M#content tests/game/tree_stand_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvZ2FtZS93YXRlcl9jYXB0dXJlX3Rlc3QuY2M#content tests/game/water_capture_test.cc

SOURCE DIRECTORY /3CV92Z/source-directories/dGVzdHMvbWFw map

SOURCE FILE /3CV92Z/source-files/dGVzdHMvbWFwL3N1cmZhY2VfdGVzdC5jYw#content tests/map/surface_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvbWFwL3RlcnJhaW5fZXZhbHVhdG9yX3Rlc3QuY2M#content tests/map/terrain_evaluator_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvbWFwL3dhdGVyX3N1cmZhY2VfdGVzdC5jYw#content tests/map/water_surface_test.cc

SOURCE DIRECTORY /3CV92Z/source-directories/dGVzdHMvc3BhdGlhbA spatial

SOURCE FILE /3CV92Z/source-files/dGVzdHMvc3BhdGlhbC9idW5kbGVfdGVzdC5jYw#content tests/spatial/bundle_test.cc

SOURCE DIRECTORY /3CV92Z/source-directories/dGVzdHMvdGVycmFpbg terrain

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVycmFpbi9jcHVfZXZhbHVhdG9yX3Rlc3QuY2M#content tests/terrain/cpu_evaluator_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVycmFpbi9kaXNjcmV0aXphdGlvbl90ZXN0LmNj#content tests/terrain/discretization_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVycmFpbi9kcmFpbmFnZV90ZXN0LmNj#content tests/terrain/drainage_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVycmFpbi9maWVsZF90ZXN0LmNj#content tests/terrain/field_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVycmFpbi9mbG9vZF90ZXN0LmNj#content tests/terrain/flood_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVycmFpbi9mcmFjdGlvbmFsX2RyYWluYWdlX3Rlc3QuY2M#content tests/terrain/fractional_drainage_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVycmFpbi9nZW9sb2dpY2FsX3Rlc3QuY2M#content tests/terrain/geological_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVycmFpbi9tZXJnZV90cmVlX3Rlc3QuY2M#content tests/terrain/merge_tree_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVycmFpbi9tZXRhbF9ldmFsdWF0b3JfdGVzdC5jYw#content tests/terrain/metal_evaluator_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVycmFpbi9tZXRhbF9zdHJlYW1fcG93ZXJfZXZvbHV0aW9uX3Rlc3QuY2M#content tests/terrain/metal_stream_power_evolution_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVycmFpbi9tb2lzdHVyZV90ZXN0LmNj#content tests/terrain/moisture_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVycmFpbi9wcm9ncmFtX3Rlc3QuY2M#content tests/terrain/program_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVycmFpbi9yZWFkaW5nc190ZXN0LmNj#content tests/terrain/readings_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVycmFpbi9zdHJlYW1fcG93ZXJfZXZvbHV0aW9uX3Rlc3QuY2M#content tests/terrain/stream_power_evolution_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVycmFpbi90cmFpbF90ZXN0LmNj#content tests/terrain/trail_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVycmFpbi93YXRlcmNvdXJzZV90ZXN0LmNj#content tests/terrain/watercourse_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVycmFpbi93YXRlcmxpbmVfdGVzdC5jYw#content tests/terrain/waterline_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVycmFpbi93b3JsZF9yZWNpcGVfdGVzdC5jYw#content tests/terrain/world_recipe_test.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvcmVjb3JkaW5nX3JlbmRlcmVyLmho#content tests/recording_renderer.hh

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVzdC5oaA#content tests/test.hh

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdGVzdF9tYWluLmNj#content tests/test_main.cc

SOURCE FILE /3CV92Z/source-files/dGVzdHMvdW5pdHNfdGVzdC5jYw#content tests/units_test.cc

GIT REPOSITORY #3CV92Z
moppe

SOURCE DIRECTORY /3CV92Z/source-directories/ZG9jcw docs

SOURCE FILE /3CV92Z/source-files/ZG9jcy9SRUFETUUubWQ#content docs/README.md

SOURCE FILE /3CV92Z/source-files/ZG9jcy9hdGVsaWVyLWVhcnRoLm1k#content docs/atelier-earth.md

SOURCE FILE /3CV92Z/source-files/ZG9jcy9hdGVsaWVyLXRyZWUubWQ#content docs/atelier-tree.md

SOURCE FILE /3CV92Z/source-files/ZG9jcy9lbmdpbmUtYXRsYXMubWQ#content docs/engine-atlas.md

SOURCE FILE /3CV92Z/source-files/ZG9jcy9nYW1lLXN0YXRlLm1k#content docs/game-state.md

SOURCE FILE /3CV92Z/source-files/ZG9jcy9nZW5lcmF0ZWQtd29ybGQubWQ#content docs/generated-world.md

SOURCE FILE /3CV92Z/source-files/ZG9jcy9vcm9nZW55LXBlcmZvcm1hbmNlLm1k#content docs/orogeny-performance.md

SOURCE FILE /3CV92Z/source-files/ZG9jcy9wcm9qZWN0Lm9yZw#content docs/project.org

SOURCE FILE /3CV92Z/source-files/ZG9jcy9yZWZhY3RvcmluZy1zZWFtcy5tZA#content docs/refactoring-seams.md

SOURCE FILE /3CV92Z/source-files/ZG9jcy9yZW5kZXJlci1kZXNpZ24ubWQ#content docs/renderer-design.md

SOURCE FILE /3CV92Z/source-files/ZG9jcy9zdXJmYWNlLWF0bGFzLm1k#content docs/surface-atlas.md

SOURCE FILE /3CV92Z/source-files/ZG9jcy90ZXJyYWluLWV4cHJlc3Npb25zLm1k#content docs/terrain-expressions.md

SOURCE FILE /3CV92Z/source-files/ZG9jcy90cmFpbHMubWQ#content docs/trails.md

SOURCE FILE /3CV92Z/source-files/ZG9jcy91bml0cy5tZA#content docs/units.md

SOURCE FILE /3CV92Z/source-files/ZG9jcy93YXRlci1iZW5jaG1hcmtzLm1k#content docs/water-benchmarks.md

SOURCE FILE /3CV92Z/source-files/ZG9jcy93ZWIubWQ#content docs/web.md

SOURCE FILE /3CV92Z/source-files/ZG9jcy93b3JraW5nLXByYWN0aWNlcy5tZA#content docs/working-practices.md

GIT REPOSITORY #3CV92Z
moppe

SOURCE DIRECTORY /3CV92Z/source-directories/YXRlbGllcg atelier

SOURCE FILE /3CV92Z/source-files/YXRlbGllci9hdGVsaWVyLmNj#content atelier/atelier.cc

SOURCE FILE /3CV92Z/source-files/YXRlbGllci9hdGVsaWVyLmho#content atelier/atelier.hh

SOURCE FILE /3CV92Z/source-files/YXRlbGllci9hdGVsaWVyX21hYy5tbQ#content atelier/atelier_mac.mm

SOURCE FILE /3CV92Z/source-files/YXRlbGllci9hdGVsaWVyX3JlbmRlcmVyLmNj#content atelier/atelier_renderer.cc

SOURCE FILE /3CV92Z/source-files/YXRlbGllci9hdGVsaWVyX3JlbmRlcmVyLmho#content atelier/atelier_renderer.hh

SOURCE FILE /3CV92Z/source-files/YXRlbGllci9hdGVsaWVyX3NoYWRlcnMuaGg#content atelier/atelier_shaders.hh

SOURCE FILE /3CV92Z/source-files/YXRlbGllci9jYW1lcmEuaGg#content atelier/camera.hh

SOURCE FILE /3CV92Z/source-files/YXRlbGllci9jYXJwZXQuaGg#content atelier/carpet.hh

SOURCE FILE /3CV92Z/source-files/YXRlbGllci9lbWJlZGRpbmcuY2M#content atelier/embedding.cc

SOURCE FILE /3CV92Z/source-files/YXRlbGllci9lbWJlZGRpbmcuaGg#content atelier/embedding.hh

SOURCE FILE /3CV92Z/source-files/YXRlbGllci9oZXhfc2hlZXQuY2M#content atelier/hex_sheet.cc

SOURCE FILE /3CV92Z/source-files/YXRlbGllci9oZXhfc2hlZXQuaGg#content atelier/hex_sheet.hh

SOURCE FILE /3CV92Z/source-files/YXRlbGllci9tYXRyaXguaGg#content atelier/matrix.hh

SOURCE FILE /3CV92Z/source-files/YXRlbGllci9tZXRhbC5oaA#content atelier/metal.hh

SOURCE FILE /3CV92Z/source-files/YXRlbGllci9waXBlbGluZS5oaA#content atelier/pipeline.hh

SOURCE FILE /3CV92Z/source-files/YXRlbGllci9wcmlzbS5oaA#content atelier/prism.hh

SOURCE FILE /3CV92Z/source-files/YXRlbGllci9zcGFjZS5oaA#content atelier/space.hh

SOURCE FILE /3CV92Z/source-files/YXRlbGllci9zdXJmYWNlLmho#content atelier/surface.hh

SOURCE FILE /3CV92Z/source-files/YXRlbGllci90YWJ1bGF0ZS5oaA#content atelier/tabulate.hh

SOURCE FILE /3CV92Z/source-files/YXRlbGllci90cmVlLmNj#content atelier/tree.cc

SOURCE FILE /3CV92Z/source-files/YXRlbGllci90cmVlLmho#content atelier/tree.hh

SOURCE FILE /3CV92Z/source-files/YXRlbGllci90cmVlX2VtYmVkZGluZy5jYw#content atelier/tree_embedding.cc

SOURCE FILE /3CV92Z/source-files/YXRlbGllci90cmVlX2VtYmVkZGluZy5oaA#content atelier/tree_embedding.hh

SOURCE FILE /3CV92Z/source-files/YXRlbGllci93aXJlLmho#content atelier/wire.hh

### 8. Tool result: read

SOURCE FILE
Path: README.md
Resource: /3CV92Z/source-files/UkVBRE1FLm1k#content
Size: 3943 bytes, 110 lines

# Moppe

**A motorcycle game and a laboratory for generated worlds.**

![A forested generated landscape beside a lake](screenshots/forest-and-lake.png)

Moppe grows landscapes from geological recipes, lets water find its routes,
settles forests into the resulting habitats, and puts a motorcycle into the
world. The aim is to make places with a readable history: mountains, rivers,
lakes, and trails that arise from the land instead of being scattered on top
of it.

Moppe is an active experimental project, not a packaged release. The current
game and its Terrain Lab run on a custom Metal renderer across macOS, iOS, and
tvOS.

## Generated landscapes

<p align="center">
  <img src="screenshots/mountain-panorama.png" width="49%"
       alt="Mountain ranges fading into atmospheric haze">
  <img src="screenshots/ridge-and-lake.png" width="49%"
       alt="A dark mountain ridge above a distant lake">
</p>
<p align="center">
  <img src="screenshots/valley-light.png" width="49%"
       alt="Sunlight spilling through a high mountain valley">
  <img src="screenshots/alpine-ridges.png" width="49%"
       alt="Closely framed alpine ridges in warm evening light">
</p>
<p align="center">
  <img src="screenshots/ridge-panorama.png" width="98%"
       alt="A broad panorama of layered generated ridges">
</p>

Every world begins with a seed and a `TerrainProgram`: a geological source
followed by an explicit sequence of transformations. The same terrain model
feeds the game, Terrain Lab, tests, and command-line tools.

The current engine includes:

- procedural orogeny, erosion, drainage, lakes, rivers, and designed trails;
- deterministic seeded worlds with periodic topology;
- terrain-aware vegetation and generated-world spawn selection;
- motorcycle physics, a deployable hang glider, atmosphere, water, shadows,
  and post-processing;
- keyboard, touch, and game-controller input;
- Terrain Lab, an interactive instrument panel for editing and inspecting the
  world-generation pipeline; and
- a portable game-shaped rendering API with Metal backends for Apple devices.

## Build and run

The normal desktop workflow requires macOS, Xcode's developer tools, CMake
3.24 or newer, and Ninja. Configure and build with:

```sh
cmake -B build -G Ninja
cmake --build build
```

Run the game:

```sh
./build/moppe.app/Contents/MacOS/moppe
```

Useful ways to start it include:

```sh
# Revisit a particular generated world.
./build/moppe.app/Contents/MacOS/moppe --windowed --seed 123

# Open the world-generation workbench.
./build/moppe.app/Contents/MacOS/moppe --terrain-lab

# Select a supported graphics preset.
./build/moppe.app/Contents/MacOS/moppe --graphics-quality balanced
```

Run the test suite with:

```sh
ctest --test-dir build --output-on-failure
```

The first configuration fetches pinned source dependencies. CMake also emits
`compile_commands.json` automatically for editor tooling.

## Find your way around

- [Project status](docs/project.org) describes what exists, what is active,
  and what remains experimental.
- [Engine atlas](docs/engine-atlas.md) is the current map of the engine's
  domains, ownership boundaries, and target graph.
- [Generated worlds](docs/generated-world.md) explains construction and
  activation of a completed world.
- [Terrain expressions](docs/terrain-expressions.md) describes the runtime
  field language, recipes, and evaluator backends.
- [Planning](planning/README.md) contains accepted RFCs and dependency-shaped
  work items.
- [Ideas](ideas/README.md) holds the longer-range design writing behind the
  world.
- [Development guidelines](AGENTS.md) collects the complete build, capture,
  profiling, iOS, and working-practice commands.

The shortest description of the direction is: generate a landscape,
understand how it formed, render it convincingly, and ride through it. The
larger question is what becomes possible once the ride has taken you somewhere
worth noticing.

SOURCE FILE
Path: AGENTS.md
Resource: /3CV92Z/source-files/QUdFTlRTLm1k#content
Size: 7211 bytes, 127 lines

# Moppe Development Guidelines

## Build Commands
- Configure: `cmake -B build -G Ninja`
- Build everything: `cmake --build build`
- Unit tests: `ctest --test-dir build --output-on-failure`
- WebAssembly/WebGPU: `make web-serve`, then open
  `http://localhost:8080` (renderer testbed: `/moppe-web-testbed.html`)
- Run the game: `./build/moppe.app/Contents/MacOS/moppe`
  (or `open build/moppe.app`)
  - Game controller: left stick drives and steers; right trigger boosts; `A`
    deploys the glider or restarts; `B` mounts/dismounts; `X` cycles the camera;
    and `Y` boosts, flares, or skips the cinematic. The D-pad navigates Terrain
    Lab. Xbox, PlayStation, and compatible MFi controllers use this layout.
  - Hang glider: boost the bike into the air and press `E` once the deploy
    prompt appears. `A`/`D` bank, `W`/`S` select airspeed, and `Space` flares;
    the motocross stays tethered beneath the wing. Press `E` again to drop it
    and reduce wing loading; otherwise touching down folds the wing and
    continues on the bike. On iOS, the mount/dismount corner deploys the wing
    while airborne and drops the attached bike while gliding.
  - Modes: `--terrain-lab`, `--fullscreen`, `--windowed`,
    `--graphics-quality low|balanced|high`
    - Override Boolean graphics features with comma-separated
      `--graphics-enable <names>` and `--graphics-disable <names>` lists.
  - Fast deterministic UI capture: `make terrain-lab-shot` (writes
    `terrain-lab.png`), or `tools/capture-terrain-lab /tmp/lab.png`.
  - Deterministic opening-cinematic video:
    `tools/capture-cinematic /tmp/cinematic.mp4 12`. Set `MOPPE_SEED`,
    `MOPPE_TERRAIN_PROFILE`, or `MOPPE_CINEMATIC_CAPTURE_FPS` to override the
    defaults.
  - Feature-targeted water capture: `tools/capture-water /tmp/mouth.png mouth`.
    Feature names are `stream`, `river`, `confluence`, `mouth`, `waterfall`,
    and `lake`;
    set `MOPPE_SEED` and `MOPPE_TERRAIN_PROFILE` for reproducible comparisons.
  - Automated screenshots and graphics benchmarks keep their macOS windows
    inactive, so repeated captures do not steal focus from the current app.
  - Partitioned hot-feature GPU benchmark (32 configurations):
    `./build/moppe.app/Contents/MacOS/moppe --graphics-benchmark /tmp/gpu.csv
    --windowed --seed 123 --terrain-quality fast`. Development overrides are
    `--benchmark-prelude`, `--benchmark-settle`, and `--benchmark-frames`.
    Analyze a completed CSV with
    `tools/graphics-benchmark-analyze INPUT.csv [OUTPUT_DIR]`.
  - Dev env vars: `MOPPE_ASSETS=<repo>` (asset override), `MOPPE_DEMO=1`
    (autopilot for screenshots), `MOPPE_SUNHEIGHT=<0..1>`, `MOPPE_NOSHADOW=1`,
    `MOPPE_RENDERSCALE=<0.25..1>`
- Renderer smoke test: `./build/moppe-testbed`
- iOS (simulator): `cmake -B build-ios -G Xcode -DCMAKE_SYSTEM_NAME=iOS
  -DCMAKE_OSX_SYSROOT=iphonesimulator` then build the `moppe-ios` target
  with `CODE_SIGNING_ALLOWED=NO`
- tvOS (simulator): `cmake -B build-tvos -G Xcode -DCMAKE_SYSTEM_NAME=tvOS
  -DCMAKE_OSX_SYSROOT=appletvsimulator` then build the `moppe-tvos` target
  with `CODE_SIGNING_ALLOWED=NO`
- Apple TV (build, install, launch): `make tv`
  - Pair the Apple TV in Xcode's Device Hub first. Overrides:
    `MOPPE_TVOS_DEVICE`, `MOPPE_TVOS_TEAM`, `MOPPE_TVOS_CONFIGURATION`, and
    `MOPPE_TVOS_BUILD_DIR`.
- iPhone (build, install, launch): `make phone`
  - The paired phone must be unlocked and reachable; its Personal Hotspot
    works when other Wi-Fi networks isolate clients.
  - Overrides: `MOPPE_IOS_DEVICE`, `MOPPE_IOS_TEAM`,
    `MOPPE_IOS_CONFIGURATION`, and `MOPPE_IOS_BUILD_DIR`.

## Architecture (see docs/renderer-design.md)
- `moppe/render/` — portable renderer API (DrawList immediate mode,
  MeshBuilder-baked meshes, game-shaped Renderer interface); no GL/Metal
  types in headers. `moppe/render/metal/` and `moppe/render/webgpu/` own the
  native Metal and browser WGSL/WebGPU backends.
- `moppe/shaders/metal/` — MSL shaders, built into moppe.metallib per SDK.
- `moppe/platform/` — Game interface, input, assets, speech; `mac/`, `ios/`,
  `web/`, and shared `apple/` layers. The browser host uses Canvas2D glyph
  rasterization and a `requestAnimationFrame` loop.
- `moppe/game/` — the game systems, one file each (terrain, city, wildlife,
  dust, HUD, vehicle rendering; glue in game.cc).
  Mutable replay state is gathered incrementally in `game/game_state.hh`; see
  `docs/game-state.md` for the checkpoint boundary and remaining systems.
- `moppe/mov/` is simulation only; `moppe/map/` is terrain generation.
  Both are GL-free and portable.
- `moppe/terrain/` is the portable runtime field-expression DAG, recipe and
  pipeline values, evaluator backends, and artifact writers; see
  `docs/terrain-expressions.md`.
- `moppe/spatial/` contains finite typed quantity bundles and generic local or
  interpolated sampling operations. `moppe/map/surface*` materializes typed
  ground sections over one shared surface domain; `water_surface.*` carries a
  distinct water bundle in the same elevation frame. Game-side presentation
  bridges are the only place those quantities become renderer texture lanes.
- Terrain renders by vertex-pulling from an R32F height texture +
  RG16Snorm normals; physics keeps the authoritative CPU heightmap.
- Reversed-Z scene pass (MSAA→resolve), post chain (underwater grade,
  motion-blur feedback), then HUD in point coordinates.
- World generation runs on a background thread behind a loading screen.

## Code Style Guidelines
- **Namespaces**: Use nested namespaces (`moppe::render`, `moppe::game`)
- **Function names**: Use snake_case (`render_directly()`)
- **Member variables**: Prefix with `m_` (`m_width`)
- **Indentation**: 2 spaces
- **Braces**: Opening brace on same line for functions
- **Line Length**: Keep under 80 characters
- **Includes**: Group in order: 1) Project headers 2) STL 3) External libraries

## Error Handling
- Use exceptions for error conditions
- Catch in main function or event handlers
- Use `std::cerr` for error messages
- Graceful exit on errors with code -1

## Version Control
- After completing a task or request, generally commit and push proactively as
  a checkpoint unless the user asks not to. This is a single-developer repo;
  commits are cheap save points and do not need to represent a final design.
- When asked to "commit and push," commit all non-ignored changes in the
  worktree, including unrelated work, and push the current branch with plain
  `git push`.
- Keep generated files and build products out of commits; add appropriate
  ignore rules when necessary.
- Write clear commit messages. Split changes into multiple commits when there
  is a useful, natural separation and the changes are not entangled, but do
  not over-optimize for a pristine commit history.
- Finish with a clean worktree synchronized with its upstream branch.
- Do not create a pull request or use a publishing workflow unless explicitly
  requested.

## C++ Features
- C++23 standard; newer C++26 features may be enabled per target when the
  active Apple and CI toolchains support them without compatibility shims
- RAII for resource management
- Enable compiler warnings (-Wall)

SOURCE FILE
Path: CMakeLists.txt
Resource: /3CV92Z/source-files/Q01ha2VMaXN0cy50eHQ#content
Size: 30714 bytes, 812 lines

cmake_minimum_required(VERSION 3.24)

set(MOPPE_IOS FALSE)
set(MOPPE_TVOS FALSE)
set(MOPPE_MOBILE_APPLE FALSE)
if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
  set(MOPPE_IOS TRUE)
  set(MOPPE_MOBILE_APPLE TRUE)
elseif(CMAKE_SYSTEM_NAME STREQUAL "tvOS")
  set(MOPPE_TVOS TRUE)
  set(MOPPE_MOBILE_APPLE TRUE)
endif()

# Ninja and Makefiles otherwise default to an unoptimized build. Keep symbols
# for useful crash reports while making the normal desktop build representative
# of real gameplay performance.
if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING
    "Build type (Debug, Release, RelWithDebInfo, or MinSizeRel)" FORCE)
  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
    Debug Release RelWithDebInfo MinSizeRel)
endif()

# Without an explicit target, Xcode uses the installed SDK version as the
# minimum OS, making device builds uninstallable on slightly older iPhones.
if(MOPPE_MOBILE_APPLE AND NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET)
  if(MOPPE_TVOS)
    set(CMAKE_OSX_DEPLOYMENT_TARGET "16.0" CACHE STRING
      "Minimum supported tvOS version")
  else()
    set(CMAKE_OSX_DEPLOYMENT_TARGET "15.0" CACHE STRING
      "Minimum supported iOS version")
  endif()
endif()

project(moppe LANGUAGES CXX)

# Keep editor tooling in sync with the normal Ninja and Makefile builds.
# Multi-config generators such as Xcode do not support this output.
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

include(CTest)
include(FetchContent)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
add_compile_options(-Wall)

# A Moppe web build keeps world generation off the browser UI thread. Every
# object in a shared-memory WebAssembly executable must be compiled with the
# atomics/bulk-memory feature set, not only the platform target which creates
# the worker.
if(EMSCRIPTEN)
  add_compile_options(-pthread)
  add_link_options(-pthread)
endif()

set(MOPPE_VERSION "1.0.0" CACHE STRING "Moppe release version")
set(MOPPE_BUILD_NUMBER "1" CACHE STRING "Moppe release build number")
option(MOPPE_BUILD_DEVELOPER_TOOLS
  "Build demos, experiments, testbeds, and command-line tools" OFF)
option(MOPPE_UNITY_BUILD
  "Compile each target as a single unity translation unit" ON)
option(MOPPE_ENABLE_TRACY "Enable Tracy runtime profiling" OFF)

# Moppe's implementation files are deliberately small, but compiling every one
# separately makes heavily-templated headers (notably mp-units) dominate build
# time and memory.  A very large batch size gives each target one translation
# unit per source language.  CMake still splits C++ from Objective-C++, which is
# required by AppleClang, but a normal game build no longer launches dozens of
# redundant C++ compiler processes.
if(MOPPE_UNITY_BUILD)
  set(CMAKE_UNITY_BUILD ON)
  set(CMAKE_UNITY_BUILD_BATCH_SIZE 1000)
  # A plain `cmake --build build` should remain usable while a large unity
  # source is instantiating templates.  Ninja otherwise starts one compiler
  # for every ready target and can consume the whole machine.
  set_property(GLOBAL PROPERTY JOB_POOLS moppe_compile_pool=2)
  set(CMAKE_JOB_POOL_COMPILE moppe_compile_pool)
endif()

# Keep the units vocabulary available to every target without forcing a
# large-scale conversion of existing float-based APIs.  Consumers opt in with
# target_link_libraries(<target> PRIVATE mp-units::mp-units).
set(MP_UNITS_BUILD_INSTALL OFF CACHE BOOL
  "Do not install the vendored mp-units target" FORCE)
set(MP_UNITS_API_CONTRACTS NONE CACHE STRING
  "Avoid an additional contracts dependency" FORCE)
FetchContent_Declare(mp-units
  # Pinned to master (post-v2.5.0, pre-v2.6.0) for cartesian_vector/tensor
  # and the reworked representation-concepts machinery.
  URL https://github.com/mpusz/mp-units/archive/bb86582ec2561419bc461a5ce7df01328ba08ea1.tar.gz
  URL_HASH
    SHA256=eb78dbc19c65d5954d31b78149936ca46f2eafb5e5f9c2b1e58f583fcf6705ea
  SOURCE_SUBDIR src
  DOWNLOAD_EXTRACT_TIMESTAMP TRUE)
FetchContent_MakeAvailable(mp-units)

# The Atelier's combinatorial and intrinsic botany is a small portable
# library. Both the workshop renderer and the game provide their own
# extrinsic embeddings of the same organism.
add_library(atelier_botany STATIC
  atelier/tree.cc
  atelier/tree.hh
  atelier/space.hh
)
target_include_directories(atelier_botany PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(atelier_botany PUBLIC mp-units::mp-units)

if(MOPPE_ENABLE_TRACY)
  set(TRACY_ENABLE ON CACHE BOOL "Enable Tracy profiling" FORCE)
  set(TRACY_ON_DEMAND ON CACHE BOOL "Record only with a connected viewer"
    FORCE)
  set(TRACY_ONLY_LOCALHOST ON CACHE BOOL "Accept only local Tracy clients"
    FORCE)
  set(TRACY_NO_BROADCAST ON CACHE BOOL "Disable Tracy network discovery"
    FORCE)
  FetchContent_Declare(tracy
    URL https://github.com/wolfpld/tracy/archive/refs/tags/v0.13.1.tar.gz
    URL_HASH
      SHA256=d4efc50ebcb0bfcfdbba148995aeb75044c0d80f5d91223aebfaa8fa9e563d2b
    DOWNLOAD_EXTRACT_TIMESTAMP TRUE)
  FetchContent_MakeAvailable(tracy)
endif()

if(APPLE)
  enable_language(OBJCXX)
  set(CMAKE_OBJCXX_STANDARD 23)
  set(CMAKE_OBJCXX_STANDARD_REQUIRED ON)
  set(CMAKE_OBJCXX_EXTENSIONS OFF)
endif()

# ---------------------------------------------------------------
# Atelier: a deliberately tiny, standalone Metal graphics workshop.

if(APPLE AND NOT MOPPE_MOBILE_APPLE)
  FetchContent_Declare(metal-cpp
    GIT_REPOSITORY https://github.com/apple/metal-cpp.git
    GIT_TAG 27c4382b7151d55a51692cdcb27aaa98752240de)
  FetchContent_MakeAvailable(metal-cpp)

  add_executable(atelier MACOSX_BUNDLE EXCLUDE_FROM_ALL
    atelier/atelier.cc
    atelier/atelier.hh
    atelier/atelier_mac.mm
    atelier/atelier_renderer.cc
    atelier/atelier_renderer.hh
    atelier/atelier_shaders.hh
    atelier/camera.hh
    atelier/carpet.hh
    atelier/embedding.cc
    atelier/embedding.hh
    atelier/hex_sheet.cc
    atelier/hex_sheet.hh
    atelier/matrix.hh
    atelier/metal.hh
    atelier/pipeline.hh
    atelier/prism.hh
    atelier/space.hh
    atelier/surface.hh
    atelier/tabulate.hh
    atelier/tree.hh
    atelier/tree_embedding.cc
    atelier/tree_embedding.hh
    atelier/wire.hh
  )
  set_target_properties(atelier PROPERTIES UNITY_BUILD OFF)
  target_include_directories(atelier PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}
    ${metal-cpp_SOURCE_DIR})
  target_compile_options(atelier PRIVATE
    -mmacosx-version-min=26.0
    $<$<COMPILE_LANGUAGE:OBJCXX>:-fobjc-arc>)
  target_link_options(atelier PRIVATE -mmacosx-version-min=26.0)
  target_link_libraries(atelier PRIVATE
    atelier_botany
    mp-units::mp-units
    "-framework Cocoa"
    "-framework ImageIO"
    "-framework Metal"
    "-framework QuartzCore"
    "-framework UniformTypeIdentifiers")
endif()

# ---------------------------------------------------------------
# Metal shader library: one metallib per Apple SDK. AIR is
# platform-targeted, so each app must load a library built for its OS.

if(APPLE)
if(MOPPE_TVOS)
  if(CMAKE_OSX_SYSROOT MATCHES "[Ss]imulator")
    set(MOPPE_METAL_SDK appletvsimulator)
  else()
    set(MOPPE_METAL_SDK appletvos)
  endif()
elseif(MOPPE_IOS)
  if(CMAKE_OSX_SYSROOT MATCHES "[Ss]imulator")
    set(MOPPE_METAL_SDK iphonesimulator)
  else()
    set(MOPPE_METAL_SDK iphoneos)
  endif()
else()
  set(MOPPE_METAL_SDK macosx)
endif()

set(MOPPE_METAL_SOURCES
  moppe/shaders/metal/uber.metal
  moppe/shaders/metal/post.metal
  moppe/shaders/metal/terrain.metal
  moppe/shaders/metal/field.metal
  moppe/shaders/metal/orogeny.metal
  moppe/shaders/metal/sky.metal
  moppe/shaders/metal/ocean.metal
  moppe/shaders/metal/river.metal
  moppe/shaders/metal/dust.metal
)

set(MOPPE_METAL_DEPS
  moppe/shaders/metal/common.h
  moppe/render/metal/shader_types.h
  moppe/terrain/metal/field_shader_types.h
  moppe/terrain/metal/orogeny_shader_types.h
)

execute_process(
  COMMAND xcrun -sdk ${MOPPE_METAL_SDK} -find metal
  RESULT_VARIABLE MOPPE_METAL_RESULT
  OUTPUT_QUIET ERROR_QUIET)

if(MOPPE_METAL_RESULT EQUAL 0)
  set(MOPPE_AIR_FILES)
  foreach(src ${MOPPE_METAL_SOURCES})
    get_filename_component(name ${src} NAME_WE)
    set(air ${CMAKE_CURRENT_BINARY_DIR}/${name}.air)
    add_custom_command(
      OUTPUT ${air}
      COMMAND xcrun -sdk ${MOPPE_METAL_SDK} metal
              -std=metal3.0
              -c ${CMAKE_CURRENT_SOURCE_DIR}/${src}
              -o ${air}
      DEPENDS ${src} ${MOPPE_METAL_DEPS}
      COMMENT "Compiling ${src} (${MOPPE_METAL_SDK})"
      VERBATIM)
    list(APPEND MOPPE_AIR_FILES ${air})
  endforeach()

  set(MOPPE_SHADER_ASSET ${CMAKE_CURRENT_BINARY_DIR}/moppe.metallib)
  set(MOPPE_SHADER_NAME moppe.metallib)
  add_custom_command(
    OUTPUT ${MOPPE_SHADER_ASSET}
    COMMAND xcrun -sdk ${MOPPE_METAL_SDK} metallib
            ${MOPPE_AIR_FILES} -o ${MOPPE_SHADER_ASSET}
    DEPENDS ${MOPPE_AIR_FILES}
    COMMENT "Linking moppe.metallib (${MOPPE_METAL_SDK})"
    VERBATIM)
else()
  if(MOPPE_MOBILE_APPLE)
    message(FATAL_ERROR
      "The iOS/tvOS build requires Xcode's Metal toolchain")
  endif()

  # Command Line Tools includes the Metal runtime but not the offline shader
  # compiler.  Build one self-contained source file for runtime compilation.
  set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
    ${MOPPE_METAL_SOURCES} ${MOPPE_METAL_DEPS})
  file(READ moppe/render/metal/shader_types.h MOPPE_METAL_SOURCE)
  file(READ moppe/terrain/metal/field_shader_types.h
    MOPPE_METAL_FIELD_TYPES)
  string(APPEND MOPPE_METAL_SOURCE "\n${MOPPE_METAL_FIELD_TYPES}\n")
  file(READ moppe/terrain/metal/orogeny_shader_types.h
    MOPPE_METAL_OROGENY_TYPES)
  string(APPEND MOPPE_METAL_SOURCE "\n${MOPPE_METAL_OROGENY_TYPES}\n")
  file(READ moppe/shaders/metal/common.h MOPPE_METAL_COMMON)
  string(REPLACE
    "#include \"../../render/metal/shader_types.h\"" ""
    MOPPE_METAL_COMMON "${MOPPE_METAL_COMMON}")
  string(APPEND MOPPE_METAL_SOURCE "\n${MOPPE_METAL_COMMON}\n")
  foreach(src ${MOPPE_METAL_SOURCES})
    file(READ ${src} MOPPE_METAL_PART)
    string(REPLACE "#include \"common.h\"" ""
      MOPPE_METAL_PART "${MOPPE_METAL_PART}")
    string(REPLACE
      "#include \"../../terrain/metal/field_shader_types.h\"" ""
      MOPPE_METAL_PART "${MOPPE_METAL_PART}")
    string(REPLACE
      "#include \"../../terrain/metal/orogeny_shader_types.h\"" ""
      MOPPE_METAL_PART "${MOPPE_METAL_PART}")
    string(APPEND MOPPE_METAL_SOURCE "\n${MOPPE_METAL_PART}\n")
  endforeach()
  set(MOPPE_SHADER_ASSET ${CMAKE_CURRENT_BINARY_DIR}/moppe.metal)
  set(MOPPE_SHADER_NAME moppe.metal)
  file(WRITE ${MOPPE_SHADER_ASSET} "${MOPPE_METAL_SOURCE}")
  message(STATUS
    "Metal compiler not found; shaders will compile at runtime on macOS")
endif()

add_custom_target(moppe_shaders DEPENDS ${MOPPE_SHADER_ASSET})
endif()

# ---------------------------------------------------------------
# Portable core: rendering abstraction + game-independent code.

# `moppe/spatial` is header-only, but it is a real dependency boundary:
# Bundle and the quantity vocabulary expose mp-units values to their users.
add_library(moppe_spatial INTERFACE)
target_include_directories(moppe_spatial INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(moppe_spatial INTERFACE mp-units::mp-units)

add_library(moppe_render STATIC
  moppe/render/draw.cc
  moppe/render/text.cc
)
target_include_directories(moppe_render PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(moppe_render PUBLIC mp-units::mp-units)

if(EMSCRIPTEN)
  add_library(moppe_webgpu STATIC
    moppe/render/webgpu/webgpu_renderer.cc
    moppe/render/webgpu/webgpu_renderer.hh
  )
  target_include_directories(moppe_webgpu PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
  target_compile_options(moppe_webgpu PUBLIC --use-port=emdawnwebgpu)
  target_link_options(moppe_webgpu PUBLIC --use-port=emdawnwebgpu)
  target_link_libraries(moppe_webgpu PUBLIC moppe_render)
endif()

# Runtime scalar-field expressions plus evaluator backends.  This stays
# independent of game and platform code so tools and tests use the same DAG.
add_library(moppe_terrain STATIC
  moppe/terrain/field.cc
  moppe/terrain/noise.cc
  moppe/terrain/cpu_evaluator.cc
  moppe/terrain/readings.cc
  moppe/terrain/drainage.cc
  moppe/terrain/fractional_drainage.cc
  moppe/terrain/river.cc
  moppe/terrain/flood.cc
  moppe/terrain/merge_tree.cc
  moppe/terrain/waterline.cc
  moppe/terrain/stream_power_evolution.cc
  moppe/terrain/trail.cc
  moppe/terrain/moisture.cc
  moppe/terrain/watercourse.cc
  moppe/terrain/geological.cc
  moppe/terrain/editor.cc
  moppe/terrain/program.cc
  moppe/terrain/world_recipe.cc
)
target_include_directories(moppe_terrain PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
# The field DAG stays float-typed, but the recipe layer's phantom
# quantity kinds (Field<QS>) come from mp-units.
target_link_libraries(moppe_terrain PUBLIC moppe_spatial)
if(MOPPE_ENABLE_TRACY)
  target_link_libraries(moppe_terrain PUBLIC Tracy::TracyClient)
endif()

if(APPLE AND NOT MOPPE_IOS)
  add_library(moppe_terrain_metal STATIC
    moppe/terrain/metal/metal_evaluator.mm
    moppe/terrain/metal/metal_stream_power_evolution.mm
  )
  target_include_directories(moppe_terrain_metal
    PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
  target_compile_options(moppe_terrain_metal PRIVATE -fobjc-arc)
  target_link_libraries(moppe_terrain_metal PUBLIC moppe_terrain
    "-framework Metal" "-framework Foundation")
  add_dependencies(moppe_terrain_metal moppe_shaders)

endif()

# Concrete completed-world storage, its finite surface sections, and the
# renderer-free Terrain Lab model all sit above the reusable terrain programs.
add_library(moppe_world STATIC
  moppe/map/generate.cc
  moppe/map/surface_domain.cc
  moppe/map/surface.cc
  moppe/map/surface_ecology.cc
  moppe/map/surface_materials.cc
  moppe/map/water_surface.cc
  moppe/map/terrain_evaluator.cc
  moppe/game/generated_world.cc
  moppe/game/terrain_lab_model.cc
  moppe/game/water_capture.cc
)
target_include_directories(moppe_world PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(moppe_world PUBLIC moppe_terrain)
if(MOPPE_ENABLE_TRACY)
  target_link_libraries(moppe_world PUBLIC Tracy::TracyClient)
endif()

# Mutable gameplay state is deliberately separate from completed-world
# construction. Stars and Dust remain here because GameSession owns them;
# their retained meshes make the current simulation-to-render edge real.
add_library(moppe_simulation STATIC
  moppe/mov/glider.cc
  moppe/mov/vehicle.cc
  moppe/game/chase_camera.cc
  moppe/game/game_session.cc
  moppe/game/stars.cc
  moppe/game/dust.cc
  moppe/game/walker.cc
)
target_include_directories(moppe_simulation
  PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(moppe_simulation PUBLIC moppe_world moppe_render)
if(MOPPE_ENABLE_TRACY)
  target_link_libraries(moppe_simulation PUBLIC Tracy::TracyClient)
endif()

# Presentation reads completed worlds and sessions through FrameView. This is
# distinct from the renderer API and its platform-specific backends.
add_library(moppe_scene STATIC
  moppe/game/graphics_settings.cc
  moppe/game/frame_view.cc
  moppe/gfx/tga.cc
  moppe/game/surface_presentation.cc
  moppe/game/water_presentation.cc
  moppe/game/river_surface.cc
  moppe/game/cinematic_flight.cc
  moppe/game/walker_render.cc
  moppe/game/blob_shadow.cc
  moppe/game/sprites.cc
  moppe/game/model.cc
  moppe/game/glider_render.cc
  moppe/game/vehicle_render.cc
  moppe/game/hud.cc
  moppe/game/inspector_ui.cc
  moppe/game/forest.cc
  moppe/game/tree_stand.cc
)
target_include_directories(moppe_scene PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(moppe_scene PUBLIC
  moppe_simulation
  moppe_world
  moppe_render
  atelier_botany)
if(MOPPE_ENABLE_TRACY)
  target_link_libraries(moppe_scene PUBLIC Tracy::TracyClient)
endif()

# The shared application layer is intentionally narrow: loading and the two
# presentation systems make concrete platform service calls, while game.cc
# stays on each terminal executable because it defines main.
add_library(moppe_app STATIC
  moppe/game/terrain.cc
  moppe/game/terrain_lab.cc
  moppe/game/world_loading.cc
)
target_include_directories(moppe_app PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(moppe_app PUBLIC moppe_scene)

# Developer tools for inspecting and iterating on terrain generation.
if(MOPPE_BUILD_DEVELOPER_TOOLS AND NOT MOPPE_MOBILE_APPLE)
  add_executable(terrain-orogeny-benchmark EXCLUDE_FROM_ALL
    moppe/terrain/orogeny_benchmark.cc
  )
  target_link_libraries(terrain-orogeny-benchmark PRIVATE moppe_world)
  if(TARGET moppe_terrain_metal)
    target_link_libraries(terrain-orogeny-benchmark
      PRIVATE moppe_terrain_metal)
    target_compile_definitions(terrain-orogeny-benchmark PRIVATE
      MOPPE_OROGENY_METAL_BACKEND=1
      MOPPE_SHADER_ASSET_PATH="${MOPPE_SHADER_ASSET}")
  endif()

  add_executable(terrain-water-depth-experiment EXCLUDE_FROM_ALL
    moppe/terrain/water_depth_experiment.cc
  )
  target_link_libraries(terrain-water-depth-experiment PRIVATE moppe_world)
endif()

# ---------------------------------------------------------------
# Apple: Metal backend + platform layers.

if(APPLE)
  add_library(moppe_metal STATIC
    moppe/render/metal/metal_renderer.mm
  )
  target_include_directories(moppe_metal PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
  target_compile_options(moppe_metal PRIVATE -fobjc-arc)
  target_link_libraries(moppe_metal PUBLIC moppe_render
    "-framework Metal" "-framework MetalKit" "-framework QuartzCore"
    "-framework ImageIO")
  if(MOPPE_ENABLE_TRACY)
    target_link_libraries(moppe_metal PUBLIC Tracy::TracyClient)
  endif()
  add_dependencies(moppe_metal moppe_shaders)

  add_library(moppe_apple STATIC
    moppe/platform/apple/apple_common.mm
    moppe/platform/apple/game_controller.mm
  )
  target_include_directories(moppe_apple PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
  target_compile_options(moppe_apple PRIVATE -fobjc-arc)
  target_link_libraries(moppe_apple PUBLIC
    mp-units::mp-units
    "-framework Foundation" "-framework CoreText"
    "-framework CoreGraphics" "-framework AVFoundation"
    "-framework GameController")

  # InspectorUi and the portable text path use Apple-common asset and glyph
  # services, but the host event loop stays above the scene target.
  target_link_libraries(moppe_scene PUBLIC moppe_apple)
endif()

# Keep the entire portable test suite behind one executable and one unity
# translation unit.  The macOS Metal evaluator test joins the same binary when
# that backend is available; it does not need its own copy of test_main.cc.
if(BUILD_TESTING AND NOT MOPPE_MOBILE_APPLE)
  add_executable(moppe-tests
    tests/test_main.cc
    tests/units_test.cc
    tests/spatial/bundle_test.cc
    tests/terrain/discretization_test.cc
    tests/terrain/field_test.cc
    tests/terrain/cpu_evaluator_test.cc
    tests/terrain/geological_test.cc
    tests/terrain/program_test.cc
    tests/terrain/world_recipe_test.cc
    tests/terrain/readings_test.cc
    tests/terrain/drainage_test.cc
    tests/terrain/fractional_drainage_test.cc
    tests/terrain/flood_test.cc
    tests/terrain/merge_tree_test.cc
    tests/terrain/waterline_test.cc
    tests/terrain/stream_power_evolution_test.cc
    tests/terrain/trail_test.cc
    tests/terrain/moisture_test.cc
    tests/terrain/watercourse_test.cc
    tests/game/game_state_test.cc
    tests/game/frame_view_test.cc
    tests/game/graphics_benchmark_test.cc
    tests/game/graphics_settings_test.cc
    tests/game/input_frame_test.cc
    tests/game/inspector_ui_test.cc
    tests/game/generated_world_test.cc
    tests/game/terrain_lab_model_test.cc
    tests/game/river_surface_test.cc
    tests/game/water_capture_test.cc
    tests/game/forest_test.cc
    tests/game/tree_stand_test.cc
    tests/game/cinematic_flight_test.cc
    tests/map/surface_test.cc
    tests/map/water_surface_test.cc
    tests/map/terrain_evaluator_test.cc
  )
  target_link_libraries(moppe-tests PRIVATE moppe_scene)
  if(TARGET moppe_terrain_metal)
    target_sources(moppe-tests PRIVATE
      tests/terrain/metal_evaluator_test.cc
      tests/terrain/metal_stream_power_evolution_test.cc)
    target_link_libraries(moppe-tests PRIVATE moppe_terrain_metal)
    target_compile_definitions(moppe-tests PRIVATE
      MOPPE_SHADER_ASSET_PATH="${MOPPE_SHADER_ASSET}")
  endif()
  if(APPLE)
    target_sources(moppe-tests PRIVATE
      atelier/embedding.cc
      atelier/hex_sheet.cc
      atelier/tree_embedding.cc
      tests/atelier/bundle_test.cc
      tests/atelier/embedding_test.cc
      tests/atelier/hex_sheet_test.cc
      tests/atelier/tree_test.cc)
    set_source_files_properties(
      atelier/embedding.cc
      atelier/hex_sheet.cc
      atelier/tree_embedding.cc
      PROPERTIES
      SKIP_UNITY_BUILD_INCLUSION ON)
    # The testable scene reaches the shared Apple asset and glyph services,
    # but no longer inherits the desktop event-loop target from a broad
    # unity-built game archive.
    target_link_libraries(moppe-tests PRIVATE moppe_apple)
    target_link_libraries(moppe-tests PRIVATE atelier_botany)
  endif()
  add_test(NAME moppe-tests COMMAND moppe-tests)
endif()

if(APPLE AND MOPPE_IOS)
  set(MOPPE_IOS_ASSETS
    ${CMAKE_CURRENT_SOURCE_DIR}/moppe/platform/ios/Assets.xcassets)
  set_source_files_properties(${MOPPE_IOS_ASSETS} PROPERTIES
    MACOSX_PACKAGE_LOCATION Resources)

  add_library(moppe_platform_ios STATIC
    moppe/platform/ios/main_ios.mm
  )
  target_include_directories(moppe_platform_ios
    PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
  target_compile_options(moppe_platform_ios PRIVATE -fobjc-arc)
  target_link_libraries(moppe_platform_ios PUBLIC
    moppe_metal moppe_apple "-framework UIKit")
  target_link_libraries(moppe_app PUBLIC moppe_platform_ios)

  add_executable(moppe-ios MACOSX_BUNDLE
    moppe/game/game.cc
    ${MOPPE_IOS_ASSETS}
  )
  target_link_libraries(moppe-ios PRIVATE moppe_app moppe_platform_ios)
  set_target_properties(moppe-ios PROPERTIES
    OUTPUT_NAME "Moppe"
    XCODE_GENERATE_SCHEME TRUE
    MACOSX_BUNDLE_INFO_PLIST
      ${CMAKE_CURRENT_SOURCE_DIR}/moppe/platform/ios/Info.plist.in
    MACOSX_BUNDLE_BUNDLE_NAME "Moppe"
    MACOSX_BUNDLE_GUI_IDENTIFIER "se.brockman.moppe"
    MACOSX_BUNDLE_SHORT_VERSION_STRING "${MOPPE_VERSION}"
    MACOSX_BUNDLE_BUNDLE_VERSION "${MOPPE_BUILD_NUMBER}"
    XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
    XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${MOPPE_BUILD_NUMBER}"
    XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf-with-dsym"
    XCODE_ATTRIBUTE_GCC_GENERATE_DEBUGGING_SYMBOLS "YES"
    XCODE_ATTRIBUTE_INSTALL_PATH "$(LOCAL_APPS_DIR)"
    XCODE_ATTRIBUTE_MARKETING_VERSION "${MOPPE_VERSION}"
    XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "se.brockman.moppe"
    XCODE_ATTRIBUTE_SKIP_INSTALL "NO"
    XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2")
  add_custom_command(TARGET moppe-ios POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
      ${MOPPE_SHADER_ASSET}
      $<TARGET_BUNDLE_CONTENT_DIR:moppe-ios>/${MOPPE_SHADER_NAME}
    COMMAND ${CMAKE_COMMAND} -E copy_directory
      ${CMAKE_CURRENT_SOURCE_DIR}/textures
      $<TARGET_BUNDLE_CONTENT_DIR:moppe-ios>/textures
    COMMAND ${CMAKE_COMMAND} -E copy_directory
      ${CMAKE_CURRENT_SOURCE_DIR}/data
      $<TARGET_BUNDLE_CONTENT_DIR:moppe-ios>/data
    COMMENT "Bundling assets into Moppe.app (iOS)")
endif()

if(APPLE AND MOPPE_TVOS)
  add_library(moppe_platform_tvos STATIC
    moppe/platform/tvos/main_tvos.mm
  )
  target_include_directories(moppe_platform_tvos
    PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
  target_compile_options(moppe_platform_tvos PRIVATE -fobjc-arc)
  target_link_libraries(moppe_platform_tvos PUBLIC
    moppe_metal moppe_terrain_metal moppe_apple "-framework UIKit")
  target_link_libraries(moppe_app PUBLIC moppe_platform_tvos)

  add_executable(moppe-tvos MACOSX_BUNDLE
    moppe/game/game.cc
  )
  target_compile_definitions(moppe-tvos PRIVATE
    MOPPE_DEFAULT_APPLE_TV_GRAPHICS)
  target_link_libraries(moppe-tvos PRIVATE moppe_app moppe_platform_tvos)
  set_target_properties(moppe-tvos PROPERTIES
    OUTPUT_NAME "Moppe"
    XCODE_GENERATE_SCHEME TRUE
    MACOSX_BUNDLE_INFO_PLIST
      ${CMAKE_CURRENT_SOURCE_DIR}/moppe/platform/tvos/Info.plist.in
    MACOSX_BUNDLE_BUNDLE_NAME "Moppe"
    MACOSX_BUNDLE_GUI_IDENTIFIER "se.brockman.moppe.tvos"
    MACOSX_BUNDLE_SHORT_VERSION_STRING "${MOPPE_VERSION}"
    MACOSX_BUNDLE_BUNDLE_VERSION "${MOPPE_BUILD_NUMBER}"
    XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${MOPPE_BUILD_NUMBER}"
    XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf-with-dsym"
    XCODE_ATTRIBUTE_GCC_GENERATE_DEBUGGING_SYMBOLS "YES"
    XCODE_ATTRIBUTE_INSTALL_PATH "$(LOCAL_APPS_DIR)"
    XCODE_ATTRIBUTE_MARKETING_VERSION "${MOPPE_VERSION}"
    XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "se.brockman.moppe.tvos"
    XCODE_ATTRIBUTE_SKIP_INSTALL "NO"
    XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "3")
  add_custom_command(TARGET moppe-tvos POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
      ${MOPPE_SHADER_ASSET}
      $<TARGET_BUNDLE_CONTENT_DIR:moppe-tvos>/${MOPPE_SHADER_NAME}
    COMMAND ${CMAKE_COMMAND} -E copy_directory
      ${CMAKE_CURRENT_SOURCE_DIR}/textures
      $<TARGET_BUNDLE_CONTENT_DIR:moppe-tvos>/textures
    COMMAND ${CMAKE_COMMAND} -E copy_directory
      ${CMAKE_CURRENT_SOURCE_DIR}/data
      $<TARGET_BUNDLE_CONTENT_DIR:moppe-tvos>/data
    COMMENT "Bundling assets into Moppe.app (tvOS)")
endif()

if(APPLE AND NOT MOPPE_MOBILE_APPLE)
  add_library(moppe_platform_mac STATIC
    moppe/platform/mac/main_mac.mm
  )
  target_include_directories(moppe_platform_mac
    PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
  target_compile_options(moppe_platform_mac PRIVATE -fobjc-arc)
  target_compile_definitions(moppe_platform_mac PRIVATE
    MOPPE_SHADER_NAME="${MOPPE_SHADER_NAME}")
  target_link_libraries(moppe_platform_mac PUBLIC
    moppe_metal moppe_apple moppe_terrain_metal "-framework AppKit")
  target_link_libraries(moppe_app PUBLIC moppe_platform_mac)

  if(MOPPE_BUILD_DEVELOPER_TOOLS)
    # Renderer testbed: spinning solids + HUD shapes, no game.
    add_executable(moppe-testbed EXCLUDE_FROM_ALL moppe/game/testbed.cc)
    target_link_libraries(moppe-testbed PRIVATE moppe_platform_mac)
  endif()

  # The game, as a proper .app bundle with the assets inside.
  # (Still runnable from a shell: build/moppe.app/Contents/MacOS/moppe,
  # and MOPPE_ASSETS=<repo> overrides asset resolution for dev.)
  add_executable(moppe MACOSX_BUNDLE moppe/game/game.cc)
  target_link_libraries(moppe PRIVATE moppe_app moppe_platform_mac)
  set_target_properties(moppe PROPERTIES
    XCODE_GENERATE_SCHEME TRUE
    XCODE_SCHEME_WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "se.brockman.moppe"
    MACOSX_BUNDLE_INFO_PLIST
      ${CMAKE_CURRENT_SOURCE_DIR}/moppe/platform/mac/Info.plist.in
    MACOSX_BUNDLE_BUNDLE_NAME "Moppe"
    MACOSX_BUNDLE_GUI_IDENTIFIER "se.brockman.moppe"
    MACOSX_BUNDLE_SHORT_VERSION_STRING "1.0"
    MACOSX_BUNDLE_BUNDLE_VERSION "1")
  add_custom_command(TARGET moppe POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
      ${MOPPE_SHADER_ASSET}
      $<TARGET_BUNDLE_CONTENT_DIR:moppe>/Resources/${MOPPE_SHADER_NAME}
    COMMAND ${CMAKE_COMMAND} -E copy_directory
      ${CMAKE_CURRENT_SOURCE_DIR}/textures
      $<TARGET_BUNDLE_CONTENT_DIR:moppe>/Resources/textures
    COMMAND ${CMAKE_COMMAND} -E copy_directory
      ${CMAKE_CURRENT_SOURCE_DIR}/data
      $<TARGET_BUNDLE_CONTENT_DIR:moppe>/Resources/data
    COMMENT "Bundling assets into moppe.app")
  # POST_BUILD bundling only runs when the executable relinks, which lets
  # shader-only edits go stale inside the bundle. Refresh them every build.
  add_custom_target(moppe_bundle_shaders ALL
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
      ${MOPPE_SHADER_ASSET}
      $<TARGET_BUNDLE_CONTENT_DIR:moppe>/Resources/${MOPPE_SHADER_NAME}
    DEPENDS ${MOPPE_SHADER_ASSET} moppe
    COMMENT "Refreshing bundled shaders")
endif()

if(EMSCRIPTEN)
  add_library(moppe_platform_web STATIC
    moppe/platform/web/main_web.cc
  )
  target_include_directories(moppe_platform_web
    PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
  target_compile_options(moppe_platform_web PUBLIC
    -pthread
    --use-port=emdawnwebgpu)
  target_link_options(moppe_platform_web PUBLIC
    -pthread
    --use-port=emdawnwebgpu
    -sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency
    -sALLOW_MEMORY_GROWTH=1
    -sINITIAL_MEMORY=268435456
    -sDISABLE_EXCEPTION_CATCHING=0)
  target_link_libraries(moppe_platform_web PUBLIC
    moppe_webgpu
    moppe_terrain)
  target_link_libraries(moppe_app PUBLIC moppe_platform_web)

  function(moppe_configure_web_executable target)
    set_target_properties(${target} PROPERTIES
      SUFFIX ".html"
      LINK_DEPENDS
        "${CMAKE_CURRENT_SOURCE_DIR}/moppe/platform/web/shell.html")
    target_link_options(${target} PRIVATE
      "--shell-file=${CMAKE_CURRENT_SOURCE_DIR}/moppe/platform/web/shell.html"
      "--preload-file=${CMAKE_CURRENT_SOURCE_DIR}/textures@/textures"
      -sASSERTIONS=1)
  endfunction()

  add_executable(moppe-web-testbed moppe/game/testbed.cc)
  target_link_libraries(moppe-web-testbed PRIVATE moppe_platform_web)
  moppe_configure_web_executable(moppe-web-testbed)

  add_executable(moppe-web moppe/game/game.cc)
  target_link_libraries(moppe-web PRIVATE moppe_app moppe_platform_web)
  moppe_configure_web_executable(moppe-web)
endif()

# CMake's Xcode generator normally presents the project target-by-target,
# which is useful for building but awkward for browsing. Add one read-only
# source tree and tuck implementation targets into a collapsed folder.
if(CMAKE_GENERATOR STREQUAL "Xcode")
  set_property(GLOBAL PROPERTY USE_FOLDERS ON)
  file(GLOB_RECURSE MOPPE_XCODE_SOURCES CONFIGURE_DEPENDS
    ${CMAKE_CURRENT_SOURCE_DIR}/moppe/*.cc
    ${CMAKE_CURRENT_SOURCE_DIR}/moppe/*.h
    ${CMAKE_CURRENT_SOURCE_DIR}/moppe/*.hh
    ${CMAKE_CURRENT_SOURCE_DIR}/moppe/*.metal
    ${CMAKE_CURRENT_SOURCE_DIR}/moppe/*.mm
    ${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cc
    ${CMAKE_CURRENT_SOURCE_DIR}/tests/*.h
    ${CMAKE_CURRENT_SOURCE_DIR}/tests/*.hh)
  add_custom_target(Source SOURCES ${MOPPE_XCODE_SOURCES})
  source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}
    FILES ${MOPPE_XCODE_SOURCES})

  foreach(target
      moppe_apple
      moppe_app
      moppe_bundle_shaders
      moppe_metal
      moppe_platform_mac
      moppe_render
      moppe_scene
      moppe_shaders
      moppe_simulation
      moppe_spatial
      moppe_terrain
      moppe_terrain_metal
      moppe_world)
    if(TARGET ${target})
      set_target_properties(${target} PROPERTIES FOLDER "Build Internals")
    endif()
  endforeach()
endif()

### 9. Assistant tool calls

#### Tool call: `read`

```json
{
  "blocks": [
    "/3CV92Z/source-files/bW9wcGUvdGVycmFpbi93b3JsZF9yZWNpcGUuY2M#content",
    "/3CV92Z/source-files/bW9wcGUvZ2FtZS9nYW1lX3Nlc3Npb24uY2M#content",
    "/3CV92Z/source-files/bW9wcGUvcmVuZGVyL3dlYmdwdS93ZWJncHVfcmVuZGVyLmNj#content"
  ],
  "expand": false
}
```

#### Tool call: `read`

```json
{
  "blocks": [
    "/3CV92Z/source-files/ZG9jcy9lbmdpbmUtYXRsYXMubWQ#content",
    "/3CV92Z/source-files/ZG9jcy9wcm9qZWN0Lm9yZw#content"
  ],
  "expand": false
}
```

### 10. Tool result: read

{"error":"\"resource https://m.sheaf.less.rest/3CV92Z/source-files/bW9wcGUvcmVuZGVyL3dlYmdwdS93ZWJncHVfcmVuZGVyLmNj#content not found\""}

### 11. Tool result: read

SOURCE FILE
Path: docs/engine-atlas.md
Resource: /3CV92Z/source-files/ZG9jcy9lbmdpbmUtYXRsYXMubWQ#content
Size: 12652 bytes, 224 lines

# Moppe engine atlas

This is the reader's map of the current engine after RFC-0001. It names the
values that make up a world, the mutable state that rides it, the immutable
reading that presents a frame, and the CMake targets that carry those
boundaries. Read it before the detailed subsystem documents; the
`current-engine-refactoring` track is retained as the history of how this
shape was reached, not as the architecture reference.

## One world, one frame

The main flow is data and borrows, not a generic scene graph or an ownership
diagram for CMake:

```mermaid
flowchart LR
  recipe["WorldRecipe + TerrainProgram"] --> build["GeneratedWorld::Builder"]
  build --> world["GeneratedWorld"]
  world --> session["GameSession"]
  world --> view["FrameView"]
  session --> view
  view --> scene["world, actor, water, effect, HUD presentation"]
  scene --> renderer["render::Renderer"]
  renderer --> metal["Metal backend"]
  renderer --> webgpu["WebGPU backend"]
  app["application: loading, input, mode selection"] --> build
  app --> session
  app --> view
```

`GeneratedWorld` is the stable owner of one completed landscape.
`GameSession` is the mutable life played on that landscape. `FrameView` is a
new immutable reading composed for each visible frame; it does not own a
renderer, a platform object, or mutable actor state. The application selects
input and mode, advances the session, composes the view, and invokes the
concrete presenters in game-shaped order.

## Domains and ownership

| Domain | Owns | Does not own | Main locations |
| --- | --- | --- | --- |
| Quantity and finite-section vocabulary | `spatial::Bundle`, typed domains, and mp-units-facing section types | A terrain map, a world, or a rendering policy | `moppe/spatial/`, `moppe/quantities.hh` |
| Terrain programs | Field DAGs, terrain sources, transforms, recipes, and portable evaluators | A completed map, scene, platform, or renderer | `moppe/terrain/` |
| Completed world | Heightmap, surface, materialized analyses, water surface, and trails | Mutable player state, GPU resources, or an event loop | `moppe/map/`, `moppe/game/generated_world.*` |
| Simulation | Mutable rider, vehicle, glider, walker, camera, stars, dust, and checkpoint state | Loading, `GeneratedWorld` ownership, and platform effects | `moppe/mov/`, `moppe/game/game_session.*` |
| Frame and scene presentation | Immutable frame readings and focused terrain, water, actor, effect, and HUD presenters | Simulation mutation or an OS event loop | `moppe/game/frame_view.*`, game presentation files |
| Application and platform | Loading/activation, input adaptation, mode selection, host services, and terminal `main` | Portable terrain and simulation laws | `moppe/game/world_loading.*`, `moppe/game/game.cc`, `moppe/game/terrain.*`, `moppe/game/terrain_lab.*`, `moppe/platform/` |
| Renderer and backend | Game-shaped draw/resource API, Metal resources, passes, command submission, and capture/timing lifecycle | Terrain policy, session state, or a generic render graph | `moppe/render/`, `moppe/render/metal/`, `moppe/shaders/metal/` |

`moppe/game/` is intentionally not one architectural layer. Its source files
belong to world construction, simulation, scene presentation, or application
composition according to what they own. The CMake targets below make that
division executable.

## World and intrinsic readings

`terrain::WorldRecipe` binds a terrain program to physical world parameters
and water datum. `GeneratedWorld::Builder` is the short-lived mutable
capability that evaluates the map, rebuilds `map::Surface`, analyzes hydrology,
and materializes derived readings. Once active, ordinary gameplay receives
const views of the completed world.

`map::SurfaceDomain` is the one finite lattice for the ground. It owns the
topology, site correspondence, spacing, and reconstruction stencil. Its
`SurfaceAtlas` groups typed 0-cochains by the named materialization boundary:

| Group | Typed sections | Valid when |
| --- | --- | --- |
| Geometry | `surface_elevation`, `surface_normal`, `snow_support` | `Surface::refresh()` |
| Hydrology | `channel_flux`, `surface_moisture`, `waterline_distance` | World hydrology/materialization |
| Geology | `erosion_exposure`, `deposition_cover` | Geological materialization |
| Ecology | `tree_habitat`, `forest_cover` | Ecological materialization |
| Use | `trail_influence`, `home_base_influence` | Trail and home-base materialization |

Geometry is always present after refresh. Later groups are individually
optional: absence means the corresponding world-building barrier has not run,
while a present all-zero section is a real reading. `map::WaterSurface` uses
the same domain but a distinct water bundle: `surface_elevation`,
`wave_amplitude`, and `water_velocity`. It is not a ground-atlas group merely
because both surfaces have matching texture dimensions.

`GeneratedWorld::Hydrology` is similarly a complete analytical value rather
than a collection of app-level optionals. It contains standing water, lake
census, drainage, fractional channels, waterways, and the river network.
`WaterSurface` and `TrailNetwork` remain optional completed-world artifacts:
a Terrain Lab preview may intentionally omit hydrology, and a terrain program
may omit trail formation. The detailed vocabulary, validity rules, and
quantity-to-texture mappings live in [Surface atlas](surface-atlas.md).

## State, lifetime, and handoff

| Value or phase | Owner and rule | Deliberately outside it |
| --- | --- | --- |
| `WorldRecipe` and `WorldParams` | Immutable construction description for a world | Live player progress and renderer history |
| `GeneratedWorld` | Non-copyable, non-movable owner of completed terrain and analyses | Platform, session, and GPU ownership |
| Loading candidate | Worker builds a fresh world; the loading preview sees copied height snapshots only | Mutation of the active world/session |
| Activation | Main thread transfers the completed owner once, retires the old session before its old world, then creates a fresh session | A half-built visible world |
| `GameSession` | Mutable run against one completed world's terrain and surface borrows | A replacement world or loading lifecycle |
| `GameState` | Copyable snapshot of mutable session systems, portable only between sessions on the same world | Terrain, water, renderer history, window state, and asynchronous loading |
| `FrameView` | Immutable per-frame snapshot of selected camera, lighting, graphics, poses, HUD, overlays, and visibility | Renderer/platform types and later simulation mutation |
| Terrain Lab transaction | Named mutable terrain borrow that restores the game's map on exit | An implicit permanent edit to the active world |

The ordinary playable step is
`advance_game_session(context, session, input, seconds_t)`. Its context lends
only the world-side readings simulation needs. Platform-side speech and other
application effects return as a small result for the application to realize.
The full checkpoint and replay boundary is described in
[Game state and replay](game-state.md); completed-world construction and
handoff are described in [Generated worlds](generated-world.md).

## Presentation and renderer boundaries

Presentation turns completed-world and frame readings into the renderer's
game-shaped API without pushing numeric packing or platform work down into the
terrain/simulation layers.

| Input | Presentation owner | Renderer-facing result |
| --- | --- | --- |
| Typed ground atlas and trails | `game::SurfacePresentation` | Terrain material/path texture lanes |
| Typed water bundle plus water datum/extent | `game::WaterPresentation` | Numeric ocean setup and water texture lanes |
| Completed-world river network | `game::RiverSurface` | Curved river ribbon mesh/data |
| `FrameView`, world, and session readings | Focused world, actor, water, effect, and HUD routines | Retained resources and `DrawList` commands in fixed frame order |
| Renderer calls | `render::Renderer` | Backend-independent resource/pass requests |
| Metal renderer state | `MetalTerrainResources`, `MetalWaterResources`, `MetalFrameTargets`, `MetalFrameEncoding` | Retained world resources, target resources, and one drawable submission |

Terrain, Water, and Scene operations share one lazy scene encoder; their
separate names do not imply a generic render graph or separate depth histories.
The Metal facade owns drawable acquisition, command-buffer lifetime, capture,
timing, and benchmark completion. See [Renderer and platform architecture]
(renderer-design.md) for resource and pass detail.

## Target graph

The source ownership above is reflected by these CMake targets. Arrows point
from a consumer to the target it consumes. Dashed Apple edges exist only in
Apple configurations; exactly one selected-platform edge is present per build.

```mermaid
flowchart LR
  spatial["moppe_spatial"] --> units["mp-units"]
  terrain["moppe_terrain"] --> spatial
  world["moppe_world"] --> terrain
  simulation["moppe_simulation"] --> world
  simulation --> render["moppe_render"]
  scene["moppe_scene"] --> simulation
  scene --> world
  scene --> render
  scene --> botany["atelier_botany"]
  scene -.-> apple["moppe_apple"]
  app["moppe_app"] --> scene
  app -.-> mac["moppe_platform_mac"]
  app -.-> ios["moppe_platform_ios"]
  app -.-> web["moppe_platform_web"]
  metal["moppe_metal"] --> render
  webgpu["moppe_webgpu"] --> render
  terrain_metal["moppe_terrain_metal"] --> terrain
  mac --> metal
  mac --> terrain_metal
  mac --> apple
  ios --> metal
  ios --> apple
  web --> webgpu
  desktop["moppe"] --> app
  desktop --> mac
  phone["moppe-ios"] --> app
  phone --> ios
  browser["moppe-web"] --> app
  browser --> web
  tests["moppe-tests"] --> scene
  tools["terrain tools"] --> world
```

`moppe_spatial` is deliberately header-only: its types expose mp-units
vocabulary but need no translation unit. `moppe_terrain` keeps reusable field
and hydrology algorithms free of world, scene, and platform code.
`moppe_world` adds concrete map storage, materialization, `GeneratedWorld`,
the renderer-free Terrain Lab model, and deterministic water-capture selection.

`moppe_simulation` has a real dependency on `moppe_render`: session-owned
Stars retain meshes and Stars/Dust expose their presentation operations. This
is an explicit current constraint, not a claim that physics needs Metal.
`moppe_scene` composes the completed-world and session readings, with
Apple-common asset/glyph support where available, but has no OS event loop or
renderer backend. `moppe_app` holds the host-service callers (`Terrain`,
`TerrainLab`, and `WorldLoading`); terminal programs retain `game.cc` because
it defines `main` and chooses the macOS, iOS, or browser host.

The ordinary desktop game consumes the app/scene path; portable tests begin at
the testable scene target and do not link a desktop event loop. Terrain
command-line tools consume only `moppe_world`, so they do not pull in the
desktop platform merely because unrelated presentation code was compiled into
a broad archive.

## Current scope and deliberate gaps

This atlas describes the completed RFC-0001 slice. It does not claim that the
Atelier proposals have replaced Moppe, that every run is bitwise reproducible,
or that every renderer backend has visual feature parity.

- The duplicated periodic heightmap seam and implicit elevation/chart origins
  remain current-engine facts; a seam-free topology and registered frame
  projections remain Atelier-earth work.
- `GameState` makes fixed-world session replay practical, but world generation,
  renderer history, window state, and loading are not checkpoint state.
- Metal is the full-fidelity native backend. WebGPU is the supported playable
  browser backend, with a deliberately lower-cost default presentation;
  Android remains unimplemented.
- Persistent places, routes, and player traces belong to later world work, not
  to the completed-world/session boundary described here.

## Detailed maps

- [Surface atlas](surface-atlas.md) — domains, all typed sections, validity,
  and presentation lanes.
- [Terrain expressions](terrain-expressions.md) — field/program/recipe and
  evaluator design.
- [Generated worlds](generated-world.md) — construction capability and
  activation lifetime.
- [Game state and replay](game-state.md) — session checkpoint and benchmark
  boundary.
- [Refactoring seams](refactoring-seams.md) — preserved characterization and
  runtime smoke contracts.
- [Renderer and platform architecture](renderer-design.md) — Metal resources,
  frame encoding, and host implementation detail.

SOURCE FILE
Path: docs/project.org
Resource: /3CV92Z/source-files/ZG9jcy9wcm9qZWN0Lm9yZw#content
Size: 39327 bytes, 737 lines

#+title: Moppe project
#+startup: overview

* Purpose

Moppe is currently a motorcycle game and a laboratory for generated worlds.
It has several inherited or experimental modes, but the main line of work is
the random world: generate a landscape, understand how it formed, render it
convincingly, and ride through it.

The immediate goal is not yet to build a large game around that world.  It is
to make the world coherent enough that riding it produces places worth
noticing: waterways that make sense, terrain with a readable history, and
routes and obstacles that arise from the land rather than being scattered on
top of it.

The more speculative ambition is an inhabited procedural world.  The
[[file:../ideas/second-author.md][second-author notes]] explore how geology might
eventually support paths, roads, centers, and settlements.  The
[[file:../ideas/tending-the-world.md][tending-the-world essay]] develops the
related possibility that riding, interpreting, and changing the landscape
could become one continuous form of play instead of a game/editor split.  The
[[file:../ideas/structure-of-space.md][structure-of-space notes]] explore an
irregular cellular tissue between smooth terrain fields and later paths,
construction, and memory.  These are promising design directions, not current
subsystems or a settled roadmap.

* Current product

** Random world

This is the most active and interesting game mode. A =TerrainProgram= combines
a geological source, an explicit seed, and an ordered sequence of transforms.
The normal play profile evolves periodic terrain with the orogeny model,
forms a designed trail circuit, computes standing water and continuous river
alignments, chooses a safe spawn, and puts the motorcycle into the result.

The world is topologically periodic.  Terrain lookup and the newer analysis
passes respect that topology; it is not merely a rendering trick at the map
edge.

** Terrain Lab

Terrain Lab is the instrument panel for the random-world pipeline.  It edits
the terrain program, regenerates previews, and displays derived readings such
as slope, drainage area, basins, streams, standing water, and lake classes.
It is already central to development, but it is still an engineering tool
rather than a polished player-facing mode.

** Other modes

- Pico mode loads a fixed real-world heightmap.
- The renderer testbed exercises the portable rendering layer.

These modes are real and should remain healthy, but they do not currently add
up to a single designed game structure.  New world-generation work should
not be forced to justify all of them or prematurely unify them.

* What is implemented

** Terrain language and evaluation

- [X] Runtime scalar-field expression DAG and geological recipes.
- [X] First-class =TerrainProgram= with source, seed, and transforms.
- [X] CPU evaluation shared by tools and game generation.
- [X] Metal compilation for pointwise terrain fields.
- [X] Fast, play, and research generation profiles.
- [X] Periodic generation and seam-aware tests.
- [ ] Stable program serialization.
- [ ] Cross-toolchain golden terrain corpus.

Deterministic replay is tested within the current evaluator and toolchain.
Cross-machine bit identity is an aim, not yet a repository-wide guarantee.

** Water and erosion

- [X] Periodic D8 dry-surface drainage with slope, contributing area,
  basins, and sinks.
- [X] Orogeny-local D-infinity dry routing with conservative two-receiver
  accumulation over a typed unique-sample terrain bundle.
- [X] Finite-time =n=1= analytical stream-power transform with physical age,
  uplift, erodibility, drainage exponent, and routing controls.
- [X] Default shallow-continent orogeny source with a typed uplift field,
  implicit per-step incision, routing refresh, and interleaved diffusion.
- [X] Priority-flood standing-water surface and deterministic lake census.
- [X] Puddle, pond, lake, and sea classifications.
- [X] Area/depth/volume permanence policy for rendered inland water.
- [X] Standing lakes rendered in the random game world.
- [X] Depression-aware drainage through lakes with topological accumulation.
- [X] Explicit global-ocean identity and route-proven inland body spills.
- [X] Body inlet and outflow catchment ledgers for rendering and erosion.
- [X] Directed dry river reaches split at confluences and water bodies.
- [X] Dense cubic river alignments with confluence-continuous flow distance.
- [X] Multi-row river ribbons animated along the continuous alignment.
- [X] Standing-water sheets that accept river current through each mouth.
- [X] Clustered high-flow knickpoint candidates with physical drop and slope.
- [X] Finite-time stream-power orogeny in the world generation program.
- [ ] Erosion coupled to lake infill and spillway incision.

The dry drainage reference still describes strict descent on bare terrain.
Terrain Lab now interprets the =FloodField= as a wet drainage surface: strict
downhill D8 routes on slopes and one deterministic route through each level
water body.  The largest connected below-sea component is the explicit ocean;
enclosed low basins fill to their own spills.  Inland bodies expose exact
spills, inlets, and accumulated outflow. River rendering consumes the wet
graph without mutating the terrain.

** Riding and presentation

- [X] Metal renderer on macOS and iOS.
- [X] Terrain LOD, vegetation, atmosphere, water, shadows, post-processing,
  and vehicle effects.
- [X] Motorcycle physics and generated-world spawn selection.
- [X] Desktop, simulator, and physical-phone workflows.
- [ ] A clear game loop built specifically around discovering a generated
  world.
- [ ] Persistent places, routes, or player traces in generated worlds.

* Active work

** DONE Keep periodic rivers on the ground and inside the frame budget

Reach alignments are locally continuous on the torus, but the renderer used
to join a reach to the downstream reach's canonical coordinate.  At a world
seam that produced a map-wide translucent triangle: the apparent aerial
waterways in wide views, and a large source of river/ocean overdraw.  Junctions
now select the downstream endpoint's nearest periodic image, with a regression
test that bounds every generated triangle edge.

Rivers are also an independent hot graphics feature and benchmark block.  The
standing-water pass writes depth before ribbons, and fully fogged ribbon
fragments exit before procedural flow, reflection, and shadow work.  On the
seed-123 aerial benchmark the all-features median improved from 25.35 ms to
16.50 ms after the geometry repair and fog rejection; normal gameplay
profiling returns to a steady 60 Hz, with occasional view-dependent spikes
still worth addressing through spatial river-mesh partitioning.

Acceptance evidence:

- [X] A synthetic seam-crossing junction contains no map-wide triangle.
- [X] Wide cinematic and feature-targeted river/mouth captures contain no
  aerial sheets and retain clean mouth layering.
- [X] The 32-case GPU partition measures ocean and river ribbons separately.
- [X] Full build and test suite pass with the Metal river shader compiled.

** DONE Make continuous rivers the rendering authority

The game and Terrain Lab build retained multi-row ribbons from dense
=RiverAlignment= values. They follow damped cubic trajectories, widen with
physical catchment laws, carry a flow coordinate continuously through
confluences, derive depth against the orogeny terrain, and dissolve beneath
standing water at mouths. The shader orients two-phase advected detail along
the curved surface. The standing-water painter now carries only real water
bodies and mouth currents; raster channel carving and droplet erosion are
retired.

Acceptance evidence:

- [X] Seed 123 completes wet routing and renders in both Lab and gameplay.
- [X] Width increases with catchment and animation follows a globally
  continuous downstream distance.
- [X] Seven-row cross-sections feather at banks and remain above sampled
  terrain without visible z-fight.
- [X] Mouths blend into their water body and continue current into its sheet.
- [X] Rendering consumes derived readings and does not edit terrain.

This is not finished river morphology. Confluences still use overlapping soft
coverage, standing-water shores expose their grid, and a steep reach is shaded
as a rapid rather than turned into a vertical fall or spray system.

** TODO Make channels read as watercourses rather than routed ribbons

- [X] Resample reach centerlines with bounded cubic tangents while preserving
  exact source/downstream endpoints, periodic continuity, and terrain contact.
- [X] Replace raster channel beds with bank-limited cross-sections derived
  directly from the continuous alignment and orogeny terrain.
- [X] Replace plane-wave streaks with flow-advected value noise, restrained
  churn/cascade foam, a bank contact line, and Fresnel sky reflection.
- [X] Fade lake/ocean mouths into standing water, extending the current only
  when the next wet receiver is an adjacent D8 cell rather than a body-level
  routing shortcut.
- [ ] Build a non-overlapping junction mesh. A blended fan was rejected after
  feature-targeted captures showed that it darkened the already overlapping
  reach strips.
- [X] Add deterministic clean captures for a river, confluence, mouth,
  waterfall candidate, or lake selected from the hydrology itself.
- [X] Cluster steep visible-channel steps into deterministic fall candidates,
  expose them in FALLS and TRACE, and intensify cascade foam from that reading.
- [ ] Distinguish free-falling lips from steep continuous cascades using a
  longer longitudinal profile or explicit channel morphology.
- [ ] Render a curved falling sheet and soft foot spray only after a candidate
  has an actual lip/face; a vertical quad over the continuous heightfield was
  tested and rejected because it intersected terrain and broke the river.
- [ ] Give the rider water contact, crossing, and depth feedback after the visual
  surface is stable.

** TODO Improve lake identity and inspection

- [X] Distinguish the global ocean from enclosed below-sea basins.
- [X] Add mean depth and an exact per-body spill contract.
- [X] Show body class, mean depth, inlet count, and catchment in TRACE.
- [ ] Add lake-size distribution export.
- [ ] Make =WaterPermanence= editable program data rather than a default C++
  policy at the rendering boundary.

** DONE Establish the first global stream-power comparison

The first global pass implements the finite-time characteristic solution from
Tzathas et al. for =n=1=.  It evaluates initial elevation and uplift along the
depression-aware drainage trees, fixes ocean cells, and optionally recomputes
routing with relaxed fixed-point passes.  It is a first analytical slice, not
the paper's complete multigrid solver.

Terrain Lab exposes =+AGE= independently from =+DROP= and =+TALUS=.  The
=terrain-stream-power-experiment= tool replays source, analytical, droplets,
and their combinations from one checkpoint.  On seed 123 at 257 square, the
200 ky / four-pass / 30K-drop combined-plus-talus result reduced dry sinks
from 515 in the source and 416 after droplets to 311, and reduced ponds from
46 after droplets to 13.  It retained 98 persistent-channel cells versus 132
after droplets alone.  The analytical-only result creates many one-cell
discontinuities, confirming the paper's warning that hillslope treatment is
part of a usable method rather than optional polish.

* Near horizon

** NEXT Couple erosion to standing water

Once wet routing is trustworthy, droplets can deposit on entering still
water, transfer discharge to a classified outlet, and incise spillways under
the basin's full flow.  This should allow ponds to fill, outlets to retreat,
and drainage networks to mature instead of treating every sink as a terminal
error.

** NEXT Complete the analytical erosion method

Keep the new finite-time transform as the drainage-scale stage and droplets as
the detailed finishing stage.  Next, add the paper's coarse-to-fine routing
acceleration and slope/hillslope correction, then repeat the comparison across
several fixed seeds and at rider resolution.  FastFlow-style GPU routing
remains a possible backend for the same graph contract rather than a different
erosion meaning.

** NEXT Let hydrology drive water rendering

Build rendering inputs from the same structured water account used by
erosion: surface level, depth, body identity, flow direction, discharge,
surface slope, shore distance, and turbulence.  Use body scale to distinguish
calm ponds and lakes from ocean swell; extract high-discharge routes as river
ribbons with flow-aligned detail; derive rapids, falls, foam, and spray from
discharge and changes in the longitudinal bed profile.  Rivers should first
be an inspectable rendering layer, before any channel-carving transform.

** NEXT Make the random world a more legible ride

Hydrology should pay off in play.  Lakes can become boundaries, spill points
can become gates, valleys can guide motion, and regenerated worlds can offer
recognizable destinations.  The first experiments should use overlays or
temporary markers and remain reversible; they do not require committing to a
settlement simulation.

** NEXT Define a repeatable ride-and-judge loop

The project has fast deterministic captures and analytical readings.  It also
needs a small human evaluation routine: generate a named seed/profile, inspect
its readings, ride it, and record what was memorable or broken.  This is the
bridge between terrain metrics and the actual game.

* Later / ideas under consideration

** LATER Centers and routes

The strongest part of the second-author proposal is its first small step:
derive candidate places from terrain readings, show why they were selected,
and test whether they feel meaningful from the motorcycle.  A later route
network could connect accepted places under different movement costs before
any terrain carving occurs.

This direction should begin as observation and overlays.  =CenterSet= and
=RouteNetwork= are names from a design essay, not existing types or promised
architecture.

** LATER Roads, settlements, and world memory

Road carving, settlement formation, traffic ledgers, and player-made desire
paths are compelling possible consequences of good terrain and routing.
They are deliberately not decomposed into implementation tasks yet.  The
project does not know enough about its places or movement networks to specify
them honestly.

* Working rules

- Keep generated-world behavior reproducible and seeds reportable.
- Make derived knowledge inspectable before making it authoritative.
- Keep readings non-mutating; make terrain edits explicit transforms.
- Prefer small experiments with a visible or numerical acceptance test.
- Treat negative experimental results as progress when they narrow the model.
- Keep important policies as named values rather than hidden thresholds.
- Judge changes both in Terrain Lab and from the motorcycle.

Detailed engineering guidance remains in
[[file:working-practices.md][working-practices.md]].

* Log

Entries below are a chronological experiment record. They intentionally
describe implementations that may since have been replaced; the Current
product and What is implemented sections above are authoritative.

** 2026-07-16 Rivers become continuous geometry

- Retired particle droplets, raster channel carving, their Terrain Lab
  controls, dedicated experiments, program variants, and active tests.
- Made dense cubic =RiverAlignment= values the shared geometric reading for
  every reach, with physical width/depth and globally continuous flow distance.
- Rebuilt running water as a seven-row soft-edged ribbon, with bank-limited
  levels, curve-oriented two-phase advection, and standing-water mouth fades.
- Restricted lattice water sheets to standing bodies and the river currents
  that enter them. Orogeny remains the sole terrain-forming world model.

** 2026-07-13 Uplift and erosion can grow the mountain range together

- Added an opt-in shallow-continent source whose typed uplift field reuses the
  geological recipe without treating tectonic velocity as initial elevation.
- Implemented deterministic backward-Euler =n=1= stream-power evolution with
  priority-flood routing refreshed each step, fixed ocean base level, and
  interleaved hillslope diffusion.
- Added Orogeny Lab controls, age comparison, convergence and process-volume
  reports, a pipeline option, and Fast/Play/Research duration profiles.
- At 1025 square, seed 123's 20-step Research run took about 6.2 seconds on an
  M2 Pro and produced coherent coast-scale dendritic relief; the ordinary
  world source remains unchanged while the new look is compared explicitly.

** 2026-07-11 Swash at the waterline, grass in hillside shade

Rider feedback pass on the new mesh-shader surfaces.

- The lattice waterline no longer stands still: a value-noise-phased
  swash term breathes the effective water column across the shallow
  shelf, the foam surge follows the run-up, and the edge alpha
  feathers over the last centimeters instead of ending at the discard
  cliff.
- Grass blades now inherit their hillside's light: a per-blade ground
  shade from the terrain normal against the sun keeps swards on
  sun-averted slopes dark instead of glowing against shaded terrain.
  A distance floor on blade width stops sub-pixel shimmer.

Agreed next milestone: retire the river ribbons onto the lattice
water machinery — splat reach surface levels and flow vectors into
rasters so the wet-tile mesh pipeline renders rivers too, with
world-space flow-map advection replacing the ribbon uv streaks.

** 2026-07-11 Mesh shaders arrive: grass patches, lattice water, moisture

The first Metal mesh pipelines (gated on =MTLGPUFamilyMetal3=, with
fallbacks), plus the hydrology feeding vegetation directly.

- Grass: blade construction factored into shared per-blade functions;
  an object stage culls 4x2-cell patches (distance, terrain band,
  frustum) and a mesh stage emits surviving blades as indexed
  triangles, 32 per meshlet at 8 vertices / 6 primitives.  The
  instanced vertex path remains as fallback and shares all styling.
- Moisture: =terrain/moisture.cc= derives ground moisture from the
  flood census and drainage (multi-source BFS to standing water plus a
  drainage term, torus-aware, unit-tested).  Grass samples it for
  height, dryness color, and density: lush banks, bleached ridges, and
  a few FPS back because dry ground grows fewer blades.
- Lattice water: near standing water renders through a mesh pipeline
  on the terrain's own sample grid — an object stage walks a 700 m
  window of 15x15-cell tiles, probes wetness from the same RG water
  raster, and culls dry or unseen tiles; the mesh stage emits 16x16
  vertex lattices sharing the ocean fragment shader.  The coarse 300
  grid keeps the horizon; both passes discard on the same radius so
  they partition exactly.  Near shorelines now resolve at terrain
  resolution.
- The lake checkerboard from the paused shoreline investigation is
  solved, and it was never grid aliasing: the fragment ripple normals
  were crossed plane-wave sines interfering into a plaid — the third
  appearance of that failure (ocean foam, grass patches, now ripples).
  Drifting value noise replaced them; swell amplitude also averages a
  five-tap footprint so shallow shelves stop flickering per cell.

Seed-123 captures: lakes are continuous organic water with shorelines
that hug the banks; the mouth bay reads clean turquoise.  Rider capture
93 FPS with grass, lattice water, and moisture all on.  Remaining known
seams: the fine/coarse water boundary can show a subtle tone shift at
700 m, and shore foam still blobs at mouth aprons.

** 2026-07-11 GPU grass loses its jitter, terraces, and stripes

Three defects in the new procedural grass shared one theme: derived
data where exact data was available.

1. Blade seeds recovered the window's integer origin by dividing floats
   (=uint(origin_world / spacing)= with an unrepresentable spacing), so
   the truncation flickered by one cell as the camera moved and re-rolled
   every seed in the field — the full-field jitter.  The uniforms now
   carry the origin as exact integer cell indices and both seeds and
   world positions derive from them.
2. Roots sampled terrain height and normal nearest-neighbor, so every
   blade in a terrain cell snapped to one height (terraced rows on
   slopes) and one normal (per-cell shading patchwork).  Both samples
   are now bilinear.
3. The patchiness field was plane-wave sines, which band into stripes —
   the same plaid failure the ocean foam had.  It is now two octaves of
   =moppe_value_noise=.

Also in this pass: whole blades reject early (radius, terrain band,
frustum with sway margin) before any styling math and clip out with a
degenerate position, valid per-blade since every vertex agrees; the
outer density LOD starts earlier; blades lean slightly downhill with
the ground normal; and the grass fragment shadow dropped from five PCF
taps to one — per-blade color and occlusion variation hides the
penumbra completely.  Rider capture 88 FPS at the twin-lake spawn.

** 2026-07-11 Water becomes transparent over ridable channels

Design rule from riding the world: anything worth rendering as water gets
a real channel, and water transparency follows its actual column.

- Channel carving no longer scales depth down for sub-cell streams; every
  visible reach earns its full 0.4-2.5 m bed, so the motorcycle physically
  drops into any stream it crosses (the carve writes the authoritative
  heightmap).  The shared width law widened to =clamp(0.012 sqrt(A),
  1.5, 24)= so trunk rivers read as rivers inside their AGE-scale valleys.
- Ribbons now pack the true water column into the green vertex channel
  (span constant shared with the shader).  Transparency, deep-water color,
  and the bank contact line all key on real depth: a hand-deep stream is
  glass over its bed, a meter reads as a body, and shallow streams carry
  no bank foam at all.
- Dropping opacity exposed strip self-overlap as dark wedges at bends.  A
  curvature-clamped width was tried and rejected (D8 splines pinch
  everywhere); the real fix is a stencil plane on the scene depth target
  (=Depth32Float_Stencil8=): the river pass blends first-fragment-wins
  (NotEqual 1 / Replace), so bends and confluence overlaps never
  double-blend.  Confluences still show a seam where strips meet — the
  junction-mesh item remains open — but no darkening.
- The standing-water grid is now RG32F: level plus a per-body wave
  amplitude from the lake census classification (sea 1.0, lake 0.10, pond
  0.04, puddle 0).  Only the ocean carries the full Gerstner swells, which
  removes the "waving sheet lifting off the shore" artifact on lakes and
  calms mountain tarns.

Seed-123 captures: rivers are translucent glass in incised channels with
clean waterlines; the cascade keeps its foam; the hanging tarn sits calm;
rider-spawn lakes lie flat and mirror-like against their shores.  The
rider capture reads 60 FPS (vsync-flat graph).  Not done here: rider
water-contact feedback when crossing channels, and the visible confluence
seam.

** 2026-07-11 Rivers fill their carved channels

The rendering pass that carving was for.  Ribbon cross-sections are now
level and ride at 0.75 of the shared =channel_depth_m= law above the carved
bed, so the water plane meets the stamped banks and the depth test cuts a
clean waterline; sides are no longer draped onto terrain, and each section's
normal is the water plane tilted by the downstream drop.  The river shader
was rewritten around two value-noise layers advected downstream at different
speeds: normal ripple from their gradients, churn foam gated by the rapid
signal, cascade foam on waterfall strength, a thin bank contact line, and
the same Fresnel-to-haze sky reflection the ocean uses (without it the
channel read as a black trench).  The ocean's foam breakup switched from a
plane-wave product (plaid moire, polka-dot surge rings) to drifting value
noise, computed only inside the shore band.  =moppe_value_noise= lives in
the shared shader header.

Two erosion corrections fell out of inspecting the results:

1. The carved bed now respects a backwater floor.  Beds are clamped above
   the surface level of the water body each path eventually enters
   (propagated upstream through the reach DAG), with only the final mouth
   step dipping under the surface.  Without this, channels crossing flat
   lowland dug below their lake's level kilometres early and priority-flood
   turned whole reaches into standing-water arms.
2. The naive AGE hookup tripled the standing-water census (1231 water
   bodies without it, 5539 with; the paper's warning about one-cell
   discontinuities at work).  A thermal pass interposed between the
   analytical stage and droplets, four fixed-point passes on every profile,
   and droplet budgets raised to 100k/300k/500k bring seed 123 at 1025
   square to 1734 bodies.  World generation logs a one-line standing-water
   census so pond explosions from future erosion changes show up at load.

One build-system trap cost this session an hour of false theories: bundled
shaders were only copied into moppe.app by a POST_BUILD step on the
executable, so shader-only edits silently tested the previous shader.  A
=moppe_bundle_shaders= target now refreshes the bundle on every build.
When a capture contradicts a shader edit, suspect staleness first.

Verification: seed-123 river, mouth, waterfall, and lake captures.  Rivers
sit visibly below grade with real banks; the waterfall candidate renders as
a bright coherent cascade from a hanging tarn; the mouth widens and fades
into its lake.  The rider capture reads 59 FPS at its new twin-lake spawn
against 98 on the previous terrain — suspected vsync/scene difference
rather than shader cost (the foam noise is shore-band-only), but it needs a
proper perf pass.  Lake shorelines keep their coarse-grid sawtooth and
checkerboard; that remains the paused fine-boundary-band project.

** 2026-07-11 The world program ages valleys and carves river channels

The random world now runs the finite-time analytical stream-power transform
and a new channel-carving stage.  The world program is source, power,
=AnalyticalErosion= (200 ky, two fixed-point passes on the fast profile and
four on play/research), droplets, thermal, and finally =ChannelCarving=.
Aging runs before droplets as the drainage-scale stage; carving runs last so
smoothing cannot refill the beds.

Carving is deterministic stamping, not simulation.  It recomputes the flood
field, wet drainage, and river network on the eroded terrain — the same
account the renderer consumes — then cuts a channel cross-section along each
reach centerline.  Bed depth follows catchment area (clamped 0.4-2.5 m,
scaled down for channels narrower than a cell), widths share the renderer's
area-to-width law, banks blend over six meters, and beds are propagated
monotone downstream through reach junctions (Kahn order over the reach DAG),
so every carved channel drains to its mouth by construction.  Reaches also
carve one receiver step into standing water so mouths pass under the lake
surface instead of ending in a wall.

Verification: unit tests cover trunk lowering, monotone beds, determinism,
never-raising, and torus stamping; the seed-123 water captures show incised
grooves under the ribbons and matured branched valleys, and the ordinary
rider capture holds 98 FPS.  Two fixed-point aging passes at 1025 square
cost about 1.5 s, so fast-profile captures remain quick.  Terrain caches
key on the executable hash and invalidated automatically.

Known limits, deliberately left for the rendering pass: ribbons still draw
their surface 0.06-0.10 m above the carved bed, so channels read as grooves
under translucent strips rather than filled watercourses; flow-aligned
surface detail, depth-based absorption, and foam keyed to the carved bed
are the natural next steps.  Lake shorelines are untouched by carving and
still need the planned fine boundary band.  Terrain Lab has no =+CARVE=
stage control yet; the pipeline demo accepts =carve= and
=carve=area_cells[,depth_scale,min_depth,max_depth,sea,blend]=.

** 2026-07-11 Shoreline rendering investigation paused

*** Current situation

The hydrology is supplying the right standing-water bodies and levels, but the
shared ocean/lake renderer is not yet presenting their boundaries convincingly.
Close =lake=, =mouth=, and =river= inspection shots show bright, sawtooth shore
edges and occasional triangular skirts where the water surface meets terrain.
The problem is especially visible around small islands, narrow inlets, and the
foreground edge of a lake. It is a rendering artifact, not evidence that the
lake census or body routing changed.

The current renderer draws one regular 300 by 300 water grid across an
11-kilometre square, so its vertices are about 36.7 metres apart. The hydrology
and terrain rasters are sampled at roughly 5-metre spacing. The vertex shader
therefore displaces a coarse triangle using much finer water-level data, while
the fragment shader reconstructs depth and discards dry fragments. Shallow
foam makes the resulting coarse/masked boundary particularly bright. This
scale mismatch is now the leading explanation for the visible skirts and
facets.

*** How it is being tested

Use the hydrology-targeted capture tool rather than the ordinary spawn camera:

#+begin_src sh
MOPPE_SEED=123 tools/capture-water /tmp/water-lake.png lake
MOPPE_SEED=123 tools/capture-water /tmp/water-mouth.png mouth
MOPPE_SEED=123 tools/capture-water /tmp/water-river.png river
MOPPE_SEED=123 tools/capture-water /tmp/water-fall.png waterfall
MOPPE_SEED=123 tools/capture-water /tmp/water-join.png confluence
MOPPE_SEED=123 tools/capture-game /tmp/water-ride.png
#+end_src

The feature shots hide vegetation, actors, and HUD, select a deterministic
hydrology cell, and print its cell, score, eye, and target. Compare images in
pairs from the same feature/seed; do not treat a normal rider shot that merely
has water in the distance as shoreline evidence. Run the ordinary rider shot
as the separate performance/regression check. The accepted 300-cell renderer
records about 115 FPS in that seed-123 capture.

*** Experiments rejected in this pass

1. The shader was changed to interpolate wet occupancy separately from surface
   elevation and to normalize elevation over wet corners only. This avoided
   mathematically mixing a real lake level with the dry-cell zero sentinel, but
   paired lake, mouth, and river captures were materially unchanged. The coarse
   geometry, rather than that interpolation alone, is the limiting factor. The
   shader change was reverted.
2. The global water grid was increased from 300 to 600 cells. Shore silhouettes
   became somewhat finer, but the same bright facets remained and the ordinary
   rider capture fell from 115 to 81 FPS. Four times the global triangle count
   is too expensive for an incomplete improvement, so this change was reverted.

*** A better next attempt

Keep the coarse grid for deep lake and ocean interiors, but stop asking it to
represent the shore. Build a separate fine boundary band from the permanent
water mask, probably with marching squares or an equivalent clipped contour at
terrain-cell resolution. Give that band stable body surface levels and a signed
shore-distance/coverage coordinate for wave suppression, transparency, and
foam. The coarse interior must be clipped back far enough that it does not
overlap and darken the boundary band. Test the extracted contour and periodic
seam deterministically on the CPU before adding backend geometry, then repeat
the exact fixed-camera comparisons above. A signed-distance texture without
finer boundary geometry may still be useful for material control, but the
failed interpolation experiment is evidence that it will not fix the shape by
itself.

Other visible water work remains separate: confluences still need a
non-overlapping strip union, and waterfall candidates still expose gaps or
steep terrain-following ribbons rather than a selected falling sheet. The
inspection cameras are doing their job by making these failures repeatable.

** 2026-07-11 Water features get deterministic inspection cameras

- Added =tools/capture-water OUTPUT FEATURE= and the corresponding
  =--water-screenshot FEATURE OUTPUT= mode for =river=, =confluence=, =mouth=,
  =waterfall=, and =lake=.
- Each shot deterministically scores the structured hydrology, reports its
  selected cell and score, frames it with a fixed inspection camera, and hides
  actors, vegetation, and HUD while retaining the final drawable composite.
- Added a D8-adjacency guard before extending inlet current through standing
  water. The first mouth capture exposed that a wet receiver may jump directly
  to a body outlet; treating it as local geometry drew a sheet across the lake.
- Added endpoint opacity to the river material so mouths dissolve into the
  standing surface and upstream reach ownership is reduced at confluences.
- Tested and rejected a blended confluence fan because overlap made a dark
  polygon. Exact endpoints remain, but a proper non-overlapping union is open.
- Seed 123 now has clean river, confluence, mouth, waterfall-candidate, and lake
  views. They expose remaining sawtooth shores, strip overlap, and the fact
  that a steep candidate is not yet a free fall; the ordinary rider capture
  returned to 115 FPS after removing the fan.

** 2026-07-11 River ribbons stop tracing cell-center corners

- Added two-sample cubic Hermite resampling per receiver edge, with horizontal
  tangents clamped to one edge length to prevent loops and overshoot.
- Resampled dry height and normals from the terrain; interpolated lake-entry
  height, width, discharge, rapid, and cascade signals along the same curve.
- Preserved exact reach endpoints so confluences and lake/ocean boundaries do
  not move away from the authoritative =RiverNetwork=.
- Rejected three samples per edge after a 59 FPS capture; two samples retained
  the visible rounding at 114 FPS versus the 115 FPS unsmoothed baseline.

** 2026-07-11 Steep river steps become inspectable cascade candidates

- Added deterministic =Waterfall= candidates derived from physical drop,
  slope, discharge threshold, and adjacency clustering on ordered reaches.
- Added FALLS markers, counts, and TRACE details in Terrain Lab.
- Fed the candidate signal into the existing river material as stronger foam
  without breaking terrain-following ribbon continuity.
- Seed 123 at 1025 square found 183 candidates across 346 source reaches and
  234 across 481 reaches after 30K droplets; one-time analysis took roughly
  262--306 ms and the ordinary rider capture remained near 115 FPS.
- Tested and rejected a free-falling quad prototype: without an actual carved
  lip or curved sheet it intersected the continuous heightfield and rendered
  as disconnected white blocks.  True waterfall geometry remains open work.

** 2026-07-11 Finite-time stream power becomes a pipeline stage

- Implemented the paper's =n=1= characteristic solution over routed river
  trees with physical age, uplift, erodibility, and drainage-area exponent.
- Used ocean cells as fixed boundaries and depression-aware routes for inland
  bodies; enforced the detachment-limited bound that terrain cannot rise
  faster than uplift.
- Added deterministic, seam, zero-age, uplift, and no-spurious-rise tests.
- Added =+AGE= controls and change ledgers to Terrain Lab and a six-mode
  fixed-seed comparison tool.
- Recorded that the global pass supplies strong dendritic structure but needs
  talus/droplet finishing to suppress fixed-graph discontinuities.

** 2026-07-11 Routed reaches become animated river surfaces

- Added a retained river-ribbon mesh and a flow-aligned Metal material shared
  by Terrain Lab and the random game.
- Made ribbon width a physical catchment reading and separated the visible
  channel cutoff from the denser analysis network.
- Fixed a large-world routing cycle by selecting a lake's final flood-tree
  departure after any dry-saddle re-entry; added a synthetic regression test.
- Verified fixed seed 123 from the motorcycle and added direct ribbon geometry
  tests for scale, widening, and finite vertices.

** 2026-07-11 Wet drainage crosses standing water

- Added a wet drainage interpretation over the priority-flood surface.
- Kept steepest D8 descent on dry slopes and used the acyclic spill forest to
  cross equal-height lake interiors.
- Switched Terrain Lab flow, stream, basin, and outlet readings to the wet
  graph while leaving erosion and geometry unchanged.

** 2026-07-11 Water bodies gain routing identity

- Chose the largest connected below-sea component as the torus's explicit
  global ocean and gave it one outlet tree.
- Replaced approximate boundary spill candidates with route-proven outlet and
  spill pairs, and forced each inland body to discharge through that pair.
- Added =WaterNetwork= inlet and outflow catchment ledgers and exposed body
  depth, inlet count, and catchment area through Terrain Lab tracing.

** 2026-07-11 Water routes become visible river reaches

- Extracted deterministic directed reaches above a physical drainage-area
  threshold, split at sources, confluences, lake inlets, and proven spills.
- Excluded standing-water and global-ocean cells so the visible stream layer
  never exposes the arbitrary bookkeeping routes across flat water.
- Switched Terrain Lab STREAMS to the reach value and added reach identity,
  discharge, slope, and destination to TRACE inspection.

** 2026-07-11 Documentation reorganization

- Separated speculative design essays into =ideas/=.
- Replaced the orientation and hydrology snapshots with this living ledger.
- Cross-checked the ledger against the terrain, drainage, flood, and game
  generation code and the July 10-11 commit sequence.

** 2026-07-11 Water becomes visible in play

- Added standing-water analysis, deterministic lake census, Terrain Lab
  overlays, and random-world lake rendering.
- Closed the hydraulic sediment ledger and recorded the droplet-lifetime
  experiment.
- Added and measured path-monotone carving; it reduces but does not eliminate
  erosion-created pits.

** 2026-07-10 Random worlds become programmable

- Introduced terrain expressions, recipes, pipelines, and =TerrainProgram=.
- Made the generated world periodic and removed special map boundaries.
- Added Terrain Lab editing, preview, capture, readings, and faster evaluation.

** 2026-07-09 Revival baseline

- Replaced the OpenGL-era platform and renderer with portable renderer APIs
  and a Metal backend.
- Brought the game to macOS and iOS while retaining city, Pico, and random
  terrain modes.

* Maintaining this file

Update this ledger when a change alters project state or priorities, not for
every commit.  Move completed active items into =What is implemented= and add
a short dated log entry.  Keep =Active work= small.  If an item has no clear
next experiment or acceptance evidence, it probably belongs under =Later= or
in an essay in =ideas/=.

### 12. Assistant tool calls

#### Tool call: `read`

```json
{
  "blocks": [
    "/3CV92Z/source-files/bW9wcGUvdGVycmFpbi93b3JsZF9yZWNpcGUuY2M#content",
    "/3CV92Z/source-files/bW9wcGUvZ2FtZS9nYW1lX3Nlc3Npb24uY2M#content"
  ],
  "expand": false
}
```

### 13. Tool result: read

SOURCE FILE
Path: moppe/terrain/world_recipe.cc
Resource: /3CV92Z/source-files/bW9wcGUvdGVycmFpbi93b3JsZF9yZWNpcGUuY2M#content
Size: 2573 bytes, 62 lines

#include <moppe/terrain/world_recipe.hh>

#include <stdexcept>
#include <utility>
#include <variant>

namespace moppe::terrain {
  namespace {
    float normalized_water_datum_for (spatial_extent_t extent,
                                      meters_t water_datum) {
      return meters_value (water_datum) / extent_value (extent)[1];
    }

    void set_program_water_datum (TerrainProgram& program, float datum) {
      program.source.sea_level = datum;
      for (TerrainTransform& transform : program.transforms)
        if (auto* orogeny = std::get_if<OrogenyEvolution> (&transform))
          orogeny->evolution.sea_level = datum;
        else if (auto* trails = std::get_if<TrailFormation> (&transform))
          trails->sea_level = datum;
    }
  }

  WorldRecipe::WorldRecipe (spatial_extent_t extent,
                            int resolution,
                            Topology topology,
                            Seed seed,
                            meters_t water_datum,
                            TerrainGenerationProfile generation_profile,
                            TerrainProgram terrain_program)
      : m_extent (extent), m_resolution (resolution), m_topology (topology),
        m_seed (seed), m_water_datum (water_datum),
        m_generation_profile (generation_profile),
        m_terrain_program (std::move (terrain_program)) {
    if (m_terrain_program.seed != m_seed)
      throw std::invalid_argument (
        "world recipe program seed must match the world seed");
  }

  float WorldRecipe::normalized_water_datum () const noexcept {
    return normalized_water_datum_for (m_extent, m_water_datum);
  }

  WorldRecipe WorldRecipe::with_terrain_program (TerrainProgram program) const {
    return { m_extent,      m_resolution,         m_topology,         m_seed,
             m_water_datum, m_generation_profile, std::move (program) };
  }

  WorldRecipe make_world_recipe (spatial_extent_t extent,
                                 int resolution,
                                 Topology topology,
                                 Seed seed,
                                 meters_t water_datum,
                                 TerrainGenerationProfile generation_profile) {
    TerrainProgram program =
      make_world_program (seed.value, generation_profile);
    set_program_water_datum (program,
                             normalized_water_datum_for (extent, water_datum));
    return { extent,      resolution,         topology,           seed,
             water_datum, generation_profile, std::move (program) };
  }
}

SOURCE FILE
Path: moppe/game/game_session.cc
Resource: /3CV92Z/source-files/bW9wcGUvZ2FtZS9nYW1lX3Nlc3Npb24uY2M#content
Size: 22644 bytes, 577 lines

#include <moppe/game/game_session.hh>

#include <algorithm>
#include <cmath>
#include <random>

namespace moppe::game {
  namespace {
    void sync_attached_bike (GameSession& session) {
      const mov::Glider& glider = session.glider ();
      const Vec3 up =
        Quaternion::rotate (Vec3 (0, 1, 0), glider.heading (), -glider.bank ());
      session.bike ().carry (glider.physical_position () - up * 2.4f * u::m,
                             glider.physical_velocity (),
                             glider.heading (),
                             up);
    }

    void set_turn (GameSession& session, float value) {
      GameLogicState& logic = session.logic ();
      logic.m_turn_input = value;
      if (logic.m_mode == M_FOOT)
        session.walker ().set_turn (value);
      else if (logic.m_mode == M_GLIDER)
        session.glider ().set_turn (value);
      else
        session.active_vehicle ().set_yaw ((90 * value) * u::deg);
    }

    void set_go (GameSession& session, float value) {
      GameLogicState& logic = session.logic ();
      logic.m_go_input = value;
      if (logic.m_mode == M_FOOT)
        session.walker ().set_walk (value > 0 ? value : value * 0.6f);
      else if (logic.m_mode == M_GLIDER)
        session.glider ().set_speed_control (value);
      else {
        session.active_vehicle ().set_thrust (value);
        session.active_vehicle ().set_boost (logic.m_boost_input,
                                             logic.m_go_input);
      }
    }

    void set_boost (GameSession& session, float value) {
      GameLogicState& logic = session.logic ();
      const float previous = logic.m_boost_input;
      logic.m_boost_input = std::max (0.0f, std::min (1.0f, value));
      if (logic.m_mode == M_FOOT) {
        if (logic.m_boost_input > 0.1f && previous <= 0.1f)
          session.walker ().jump ();
      } else if (logic.m_mode == M_GLIDER) {
        session.glider ().set_flare (logic.m_boost_input > 0.1f);
      } else {
        session.active_vehicle ().set_boost (logic.m_boost_input,
                                             logic.m_go_input);
      }
    }

    void deploy_glider (GameSession& session, const map::HeightMap& terrain) {
      if (!session.can_deploy_glider (terrain))
        return;
      const Vec3 position = session.bike ().position ();
      const Vec3 heading = session.bike ().orientation ();
      const velocity_t inherited = session.bike ().physical_velocity ();
      session.bike ().set_thrust (0);
      session.bike ().set_yaw (0 * u::deg);
      session.bike ().set_boost (0, 0);
      session.glider ().launch (moppe::position (position + Vec3 (0, 2.4f, 0)),
                                inherited,
                                heading,
                                true);
      GameLogicState& logic = session.logic ();
      logic.m_mode = M_GLIDER;
      session.glider ().set_turn (logic.m_turn_input);
      session.glider ().set_speed_control (logic.m_go_input);
      session.glider ().set_flare (logic.m_boost_input > 0.1f);
      sync_attached_bike (session);
    }

    void use_glider_control (GameSession& session,
                             const map::HeightMap& terrain) {
      if (session.logic ().m_mode == M_GLIDER) {
        session.glider ().drop_bike ();
        return;
      }
      deploy_glider (session, terrain);
    }

    void finish_glide (GameSession& session) {
      GameLogicState& logic = session.logic ();
      if (session.glider ().bike_attached ()) {
        sync_attached_bike (session);
        session.glider ().drop_bike ();
        logic.m_mode = M_BIKE;
        set_turn (session, logic.m_turn_input);
        set_go (session, logic.m_go_input);
        set_boost (session, 0);
        return;
      }

      const Vec3 position = session.glider ().position ();
      session.walker ().spawn (moppe::position (position + Vec3 (0, 0.15f, 0)),
                               session.glider ().heading ());
      logic.m_mode = M_FOOT;
      set_turn (session, logic.m_turn_input);
      set_go (session, logic.m_go_input);
      set_boost (session, 0);
    }

    void toggle_mount (GameSession& session) {
      GameLogicState& logic = session.logic ();
      if (logic.m_mode == M_GLIDER)
        return;

      if (logic.m_mode != M_FOOT) {
        // Step off to the side of whatever we're driving.
        mov::Vehicle& vehicle = session.active_vehicle ();
        const Vec3 heading = vehicle.orientation ();
        const Vec3 side (heading[2], 0, -heading[0]);
        session.walker ().spawn (
          moppe::position (vehicle.position () +
                           side * (logic.m_mode == M_CAR ? 2.4f : 1.8f)),
          heading);
        vehicle.set_thrust (0);
        vehicle.set_yaw (0 * u::deg);
        vehicle.set_boost (0, 0);
        logic.m_mode = M_FOOT;
        set_turn (session, logic.m_turn_input);
        set_go (session, logic.m_go_input);
        return;
      }

      // On foot: bike first, then our parked car, then grand theft.
      if (length2 (session.walker ().position () -
                   session.bike ().position ()) < 5.0f * 5.0f) {
        session.bike ().set_thrust (0);
        session.bike ().set_yaw (0 * u::deg);
        logic.m_mode = M_BIKE;
        set_turn (session, logic.m_turn_input);
        set_go (session, logic.m_go_input);
        set_boost (session, logic.m_boost_input);
        return;
      }

      if (logic.m_car_exists &&
          length2 (session.walker ().position () - session.car ().position ()) <
            6.0f * 6.0f) {
        session.car ().set_thrust (0);
        session.car ().set_yaw (0 * u::deg);
        logic.m_mode = M_CAR;
        set_turn (session, logic.m_turn_input);
        set_go (session, logic.m_go_input);
        set_boost (session, logic.m_boost_input);
      }
    }

    void apply_input_frame (GameSession& session,
                            const map::HeightMap& terrain,
                            const InputFrame& input) {
      set_turn (session, input_value (input.turn));
      set_go (session, input_value (input.drive));
      set_boost (session, input_value (input.boost));

      if (input.deploy_glider)
        use_glider_control (session, terrain);
      if (input.toggle_mount) {
        if (session.can_deploy_glider (terrain) || session.can_drop_bike ())
          use_glider_control (session, terrain);
        else
          toggle_mount (session);
      }
      if (input.cycle_camera) {
        GameLogicState& logic = session.logic ();
        logic.m_cam_mode = (CamMode)((logic.m_cam_mode + 1) % 3);
        if (logic.m_cam_mode == CAM_HELMET)
          logic.m_fp_eye = session.camera ().position ();
      }
    }
  }

  GameSession::GameSession (const WorldParams& world,
                            const map::RandomHeightMap& terrain,
                            const map::Surface& surface)
      : m_bike (world.spawn_position (),
                45 * u::deg,
                terrain,
                2600 * u::N,
                30 * u::kW,
                150 * u::kg),
        m_car (world.spawn_position (),
               45 * u::deg,
               terrain,
               14 * u::kN,
               100 * u::kW,
               900 * u::kg),
        m_glider (surface), m_camera (18 * u::deg, 6.5f * u::m) {}

  mov::Vehicle& GameSession::active_vehicle () noexcept {
    return m_logic.m_mode == M_CAR ? m_car : m_bike;
  }

  const mov::Vehicle& GameSession::active_vehicle () const noexcept {
    return m_logic.m_mode == M_CAR ? m_car : m_bike;
  }

  Vec3 GameSession::subject_position () const {
    if (m_logic.m_mode == M_FOOT)
      return m_walker.position ();
    if (m_logic.m_mode == M_GLIDER)
      return m_glider.position ();
    return active_vehicle ().position ();
  }

  Vec3 GameSession::subject_heading () const {
    if (m_logic.m_mode == M_FOOT)
      return m_walker.heading ();
    if (m_logic.m_mode == M_GLIDER)
      return m_glider.heading ();
    return active_vehicle ().orientation ();
  }

  float GameSession::subject_speed_kmh () const {
    if (m_logic.m_mode == M_FOOT)
      return 0.0f;
    if (m_logic.m_mode == M_GLIDER)
      return m_glider.airspeed ().numerical_value_in (u::m / u::s) * 3.6f;
    return length (active_vehicle ().velocity ()) * 3.6f;
  }

  bool GameSession::can_deploy_glider (const map::HeightMap& terrain) const {
    if (m_logic.m_mode != M_BIKE || !m_bike.airborne ())
      return false;
    const Vec3 position = m_bike.position ();
    const float ground = terrain.interpolated_height (position[0], position[2]);
    return position[1] - ground > 3.0f;
  }

  bool GameSession::can_drop_bike () const {
    return m_logic.m_mode == M_GLIDER && m_glider.bike_attached ();
  }

  void GameSession::clear_controls () {
    set_turn (*this, 0.0f);
    set_go (*this, 0.0f);
    set_boost (*this, 0.0f);
  }

  GameSession::State GameSession::state () const {
    return { m_logic,           m_bike.state (),   m_car.state (),
             m_glider.state (), m_walker.state (), m_camera.state (),
             m_stars.state (),  m_dust.state () };
  }

  void GameSession::restore (const State& state) {
    m_logic = state.logic;
    m_bike.restore (state.vehicle);
    m_car.restore (state.car);
    m_glider.restore (state.glider);
    m_walker.restore (state.walker);
    m_camera.restore (state.camera);
    m_stars.restore (state.stars);
    m_dust.restore (state.dust);
  }

  GameSessionAdvanceResult
  advance_game_session (const GameSessionAdvanceContext& context,
                        GameSession& session,
                        const InputFrame& input,
                        seconds_t dt) {
    const float elapsed = dt.numerical_value_in (u::s);
    GameLogicState& logic = session.logic ();
    session.bike ().set_water_level (context.world.water_level);
    session.car ().set_water_level (context.world.water_level);
    session.bike ().set_obstacles (&context.obstacles);
    session.car ().set_obstacles (&context.obstacles);

    apply_input_frame (session, context.terrain, input);

    if (!session.can_drop_bike ())
      session.bike ().update (dt);
    if (logic.m_car_exists)
      session.car ().update (dt);
    if (logic.m_mode == M_GLIDER) {
      const bool landed = session.glider ().update (dt);
      if (session.glider ().bike_attached ())
        sync_attached_bike (session);
      if (landed)
        finish_glide (session);
    }
    if (logic.m_mode == M_FOOT)
      session.walker ().update (
        dt, context.terrain, context.obstacles, context.world);

    const Vec3 vehicle_position = session.subject_position ();
    mov::Vehicle& vehicle = session.active_vehicle ();

    // Parked vehicles' impacts shouldn't linger until remount.
    if (logic.m_mode != M_BIKE) {
      session.bike ().pop_impact ();
      session.bike ().pop_fall_drop ();
    }
    if (logic.m_car_exists && logic.m_mode != M_CAR) {
      session.car ().pop_impact ();
      session.car ().pop_fall_drop ();
    }

    const bool in_water =
      vehicle_position[1] < meters_value (context.world.water_level) + 1.0f;
    const bool driving = logic.m_mode == M_BIKE || logic.m_mode == M_CAR;

    // Long jumps become score events after three seconds. Keep the last
    // airborne time locally because Vehicle clears its timer on touchdown.
    if (driving && vehicle.airtime () > 0.0f) {
      logic.m_jump_airtime = vehicle.airtime ();
      logic.m_landed_age += elapsed;
    } else {
      if (driving && logic.m_jump_airtime >= 3.0f) {
        logic.m_landed_airtime = logic.m_jump_airtime;
        logic.m_landed_points = (int)std::round (100.0f * logic.m_jump_airtime *
                                                 logic.m_jump_airtime);
        logic.m_score += logic.m_landed_points;
        logic.m_landed_age = 0.0f;
      }
      logic.m_jump_airtime = 0.0f;
      logic.m_landed_age += elapsed;
    }
    const DisplayColor dust_color (0.60f, 0.52f, 0.40f);
    const DisplayColor clod_color (0.42f, 0.34f, 0.24f);
    const DisplayColor spray_color (0.85f, 0.92f, 1.0f);
    const Vec3 forward = session.subject_heading ();
    const Vec3 rear_wheel =
      vehicle_position - forward * 1.4f + Vec3 (0, -0.7f, 0);

    // Drift kicks up dirt from the rear wheel (or spray).
    if (driving && vehicle.grounded () && vehicle.drift_speed () > 6.0f) {
      const int count = std::min (4, (int)(vehicle.drift_speed () * 0.2f));
      session.dust ().emit (moppe::position (rear_wheel),
                            velocity (vehicle.velocity () * 0.15f),
                            count,
                            in_water ? spray_color : dust_color);
    }

    // Roost: hard throttle sprays an arc of dirt clods backward off the rear
    // knobby, heaviest when the engine wins against the ground.
    if (driving && vehicle.grounded () && !in_water &&
        vehicle.thrust () > 0.6f) {
      const float speed = length (vehicle.velocity ());
      const float slip = scalar_value (vehicle.thrust ()) *
                         (1.0f - std::min (1.0f, speed / 30.0f));
      if (slip > 0.15f) {
        Dust::Style roost;
        roost.size = 0.45f * u::m;
        roost.lifetime = 0.9f * u::s;
        roost.downward_acceleration =
          12.0f * isq::acceleration[u::m / pow<2> (u::s)];
        roost.spread = 0.5f * one;
        session.dust ().emit (moppe::position (rear_wheel),
                              velocity (forward * (-6.0f - 14.0f * slip) +
                                        Vec3 (0, 3.5f + 3.0f * slip, 0)),
                              1 + (int)(slip * 3.0f),
                              clod_color,
                              roost);
      }
    }

    // Jet embers: hot additive sparks stream out of the nozzles while the
    // jets burn, arcing down and dying fast.
    if (driving && vehicle.boost_level () > 0.05f) {
      std::uniform_real_distribution<float> chance (0.0f, 1.0f);
      const probability_t spark (34.0f / u::s * vehicle.boost_level () *
                                 (elapsed * u::s));
      if (chance (logic.m_fx_rng) < scalar_value (spark)) {
        Dust::Style ember;
        ember.size = 0.15f * u::m;
        ember.lifetime = 0.45f * u::s;
        ember.downward_acceleration =
          6.0f * isq::acceleration[u::m / pow<2> (u::s)];
        ember.spread = 0.3f * one;
        ember.additive = true;
        session.dust ().emit (moppe::position (vehicle_position -
                                               forward * 0.5f +
                                               Vec3 (0, -0.5f, 0)),
                              velocity (vehicle.velocity () * 0.5f -
                                        forward * 2.0f + Vec3 (0, -4.0f, 0)),
                              1,
                              DisplayColor (1.0f, 0.55f, 0.18f),
                              ember);
      }
    }

    // Exhaust smoke: faint gray puffs rise off the muffler while the
    // throttle is open.
    if (driving && abs (vehicle.thrust ()) > 0.3f && logic.m_mode == M_BIKE) {
      std::uniform_real_distribution<float> chance (0.0f, 1.0f);
      const probability_t puff (14.0f / u::s * (elapsed * u::s));
      if (chance (logic.m_fx_rng) < scalar_value (puff)) {
        Dust::Style smoke;
        smoke.size = 0.35f * u::m;
        smoke.lifetime = 0.8f * u::s;
        smoke.downward_acceleration =
          -2.5f * isq::acceleration[u::m / pow<2> (u::s)];
        smoke.spread = 0.25f * one;
        session.dust ().emit (moppe::position (vehicle_position -
                                               forward * 1.2f +
                                               Vec3 (0, -0.4f, 0)),
                              velocity (vehicle.velocity () * 0.25f),
                              1,
                              DisplayColor (0.45f, 0.45f, 0.48f),
                              smoke);
      }
    }

    // Wading fast throws up a bow wave.
    if (driving && in_water && length (vehicle.velocity ()) > 15.0f)
      session.dust ().emit (
        moppe::position (vehicle_position + Vec3 (0, -0.5f, 0)),
        velocity (vehicle.velocity () * 0.3f),
        3,
        spray_color);

    // Hard landings shake the camera and burst dirt outward: a low pancake of
    // dust plus a ring of ballistic clods.
    const float impact = driving ? vehicle.pop_impact () : 0.0f;
    if (impact > 8.0f) {
      logic.m_shake = std::min (0.28f, 0.010f * impact);
      logic.m_shake_time = 0.0f;
      session.dust ().emit (
        moppe::position (vehicle_position + Vec3 (0, -0.7f, 0)),
        velocity (vehicle.velocity () * 0.2f),
        12,
        in_water ? spray_color : dust_color);
      if (!in_water) {
        Dust::Style burst;
        burst.size = 0.5f * u::m;
        burst.lifetime = 1.1f * u::s;
        burst.downward_acceleration =
          10.0f * isq::acceleration[u::m / pow<2> (u::s)];
        burst.spread = 1.4f * one;
        session.dust ().emit (
          moppe::position (vehicle_position + Vec3 (0, -0.6f, 0)),
          velocity (vehicle.velocity () * 0.15f +
                    Vec3 (0, 2.0f + 0.15f * impact, 0)),
          (int)std::min (10.0f, impact * 0.5f),
          clod_color,
          burst);
      }
    }

    GameSessionAdvanceResult result;
    // Crashes hurt; health trickles back slowly. Falls from above a hundred
    // metres are simply fatal -- house rule.
    if (impact > 9.0f)
      logic.m_health -= (impact - 9.0f) * 4.5f;
    if (driving && vehicle.pop_fall_drop () > 100.0f)
      logic.m_health = 0.0f;
    logic.m_health = std::min (100.0f, logic.m_health + 1.5f * elapsed);
    if (logic.m_health <= 0.0f) {
      session.dust ().emit (moppe::position (vehicle_position),
                            velocity (Vec3 (0, 6, 0)),
                            40,
                            DisplayColor (1.0f, 0.5f, 0.1f));
      --logic.m_lives;
      if (logic.m_lives <= 0) {
        logic.m_game_over = true;
      } else {
        // Halfway through the hearts, the game offers its sympathies out
        // loud. The app realizes this platform effect after the advance.
        result.say_ouchies = logic.m_lives == 5;

        // Respawn where you crashed, upright on the ground.
        const float ground = context.terrain.interpolated_height (
          vehicle_position[0], vehicle_position[2]);
        vehicle.reset (
          Vec3 (vehicle_position[0], ground + 1.2f, vehicle_position[2]));
        logic.m_health = 100.0f;
        logic.m_shake = 1.0f;
        logic.m_shake_time = 0.0f;
      }
    }

    // Star pickups sparkle gold and top up the boost reserve.
    {
      const int picked =
        session.stars ().update (vehicle_position, logic.m_total_time, elapsed);
      if (picked > 0) {
        Dust::Style sparkle;
        sparkle.size = 0.38f * u::m;
        sparkle.lifetime = 0.85f * u::s;
        sparkle.downward_acceleration =
          -1.5f * isq::acceleration[u::m / pow<2> (u::s)];
        sparkle.spread = 1.7f * one;
        sparkle.additive = true;
        session.dust ().emit (moppe::position (session.stars ().last_pos ()),
                              velocity (Vec3 (0, 4, 0)),
                              32,
                              DisplayColor (1.0f, 0.72f, 0.12f),
                              sparkle);
        Dust::Style flash;
        flash.size = 0.9f * u::m;
        flash.lifetime = 0.35f * u::s;
        flash.spread = 0.25f * one;
        flash.additive = true;
        session.dust ().emit (moppe::position (session.stars ().last_pos ()),
                              velocity (Vec3 ()),
                              5,
                              DisplayColor (1.0f, 0.95f, 0.55f),
                              flash);
        if (logic.m_mode != M_GLIDER)
          vehicle.replenish_boost (0.25f * picked);
      }
    }

    if (driving)
      logic.m_odometer += length (vehicle.velocity ()) * elapsed;

    session.dust ().update (dt);
    logic.m_shake_time += elapsed;
    logic.m_shake *= decay (7.0f / u::s, elapsed * u::s);

    if (logic.m_cam_mode == CAM_HELMET) {
      // Ride inside the rider's head; lightly smoothed so terrain bumps do
      // not rattle the eyeballs.
      Vec3 eye, look;
      if (logic.m_mode == M_FOOT) {
        eye = session.walker ().position () +
              Vec3 (0, 1.55f / context.landscape_scale_y, 0);
        look = session.walker ().heading ();
      } else if (logic.m_mode == M_GLIDER) {
        eye = session.glider ().position () -
              Vec3 (0, 0.75f / context.landscape_scale_y, 0);
        look = session.glider ().heading ();
      } else {
        eye = vehicle.position () +
              Vec3 (0, 0.95f / context.landscape_scale_y, 0) +
              vehicle.orientation () * (0.4f / context.landscape_scale_x);
        look = vehicle.orientation ();
      }
      logic.m_fp_eye =
        logic.m_fp_eye +
        (eye - logic.m_fp_eye) * smoothing_alpha (25.0f / u::s, elapsed * u::s);
      session.camera ().place (logic.m_fp_eye, logic.m_fp_eye + look * 10.0f);
    } else {
      session.camera ().set_landscape_scale (context.landscape_scale_x,
                                             context.landscape_scale_y);
      const float flip = logic.m_cam_mode == CAM_FRONT ? -1.0f : 1.0f;
      if (logic.m_mode == M_FOOT)
        session.camera ().update (
          moppe::position (session.walker ().position () +
                           Vec3 (0, 1.0f / context.landscape_scale_y, 0)),
          session.walker ().heading () * flip,
          velocity (Vec3 ()),
          dt);
      else if (logic.m_mode == M_GLIDER)
        session.camera ().update (session.glider ().physical_position (),
                                  session.glider ().heading () * flip,
                                  session.glider ().physical_velocity (),
                                  dt);
      else
        session.camera ().update (vehicle.physical_position (),
                                  vehicle.orientation () * flip,
                                  vehicle.physical_velocity (),
                                  dt);
      session.camera ().limit (context.terrain);
    }

    // Speed widens the field of view a touch.
    const float kmh = (driving || logic.m_mode == M_GLIDER)
                        ? session.subject_speed_kmh ()
                        : 0.0f;
    const float fov_target =
      std::min (1.0f, std::max (0.0f, (kmh - 70.0f) / 180.0f));
    logic.m_fov_k += (fov_target - logic.m_fov_k) *
                     smoothing_alpha (5.0f / u::s, elapsed * u::s);

    return result;
  }
}

### 14. Assistant

Yes — the Git integration is working on my end. I can see the repository as **`moppe`** (#3CV92Z), enumerate its directories and files, and retrieve full source-file contents.

I checked:

- Root build/configuration: `CMakeLists.txt`, `README.md`, `AGENTS.md`
- Main architecture: `moppe/terrain/`, `map/`, `mov/`, `game/`, `render/`, `platform/`, shaders, and the standalone `atelier/`
- Test suite structure across terrain, map, game, spatial, and atelier
- Project documentation, including the engine atlas and active project ledger

The repo is a C++23/CMake project for a generated-world motorcycle game: portable terrain/program logic → completed-world construction → mutable session → per-frame presentation → Metal/WebGPU-capable renderer. The CMake target separation matches that architecture, e.g. `moppe_terrain`, `moppe_world`, `moppe_simulation`, `moppe_scene`, and `moppe_app` (#3CV92Z).

I also successfully read actual implementation files, including:

- `moppe/terrain/world_recipe.cc`: recipe creation keeps a program’s seed consistent with the world seed and propagates the water datum into applicable terrain transforms.
- `moppe/game/game_session.cc`: input/mounting/glider control, simulation update, water/spray and dust effects, damage/respawn, pickups, and camera behavior are all accessible.

One limitation of the current integration: I can browse and read the synchronized repository source, but I do **not** currently have a shell or Git-status/history interface exposed here, so I cannot independently run `git status`, inspect branches/commits, build, or execute tests from this chat.
