Skip to content
Table of contents

nxsand: A GPU grid for falling sand

nxsand is a falling-sand simulation for Switch with a desktop development target. Materials live in an integer grid. Simulation passes update that grid, and a separate rendering path turns material identifiers into the displayed image.

Hardware and graphics
  • Updated
  • C++ · 80.07%
Documented components
2Explore modules
Source documents
3Read the instructions
Repository languagesC++ 80.07%7

Shares calculated from GitHub Linguist bytes.

C++80.07%
80.07%526,697 bytes
GLSL10%
10%65,789 bytes
Python4.44%
4.44%29,193 bytes
PowerShell3.32%
3.32%21,830 bytes
Language%Bytes
C++80.07%526,697
GLSL10%65,789
Python4.44%29,193
PowerShell3.32%21,830
Makefile1.63%10,750
Shell0.31%2,016
1 more languages
Just0.23%1,491

1. Overview

1.1. Why it exists

Cellular automaton sand physics simulation engine running on libnx with touch and controller input.

1.2. Best fit

Interactive physics simulations on handheld console touchscreens.

2. Use cases

Falling-sand sandbox for Nintendo Switch homebrew, simulated on the GPU with OpenGL ES 3.0, with desktop builds.

Use-case diagram for nxsand: Paint materials on the grid; Watch materials react; Save and load worlds; Tune performance settings; Import a Powder Toy stamp; Build and test the NROnxsandPaint materials on the gridWatch materials reactSave and load worldsTune performance settingsImport a Powder Toy stampBuild and test the NROPlayerHomebrew developer«system»GPU (OpenGL ES 3.0)
People on the left, external systems on the right; each oval is a use case backed by the files cited below.
Paint materials on the grid
PlayerGPU (OpenGL ES 3.0)

Touch, Joy-Con or mouse input becomes brush strokes (source/sim/brush_stroke.cpp) written into the grid by shaders/paint.frag; the X button opens the material ring (source/ui/material_wheel.cpp).

Watch materials react
PlayerGPU (OpenGL ES 3.0)

A Margolus cellular automaton runs on ping-pong GL_R8UI textures in shaders/sim.frag or sim.comp, with reactions such as lava plus water giving stone in shaders/sim_common.glsl and the sim_rules_body.glsl it includes.

Save and load worlds
Player

source/save/save.cpp stores up to three slots as JSON with base64 grids in sdmc:/switch/nxsand/ (./nxsand_save/ on desktop); Y or F5 quick-saves.

Tune performance settings
PlayerGPU (OpenGL ES 3.0)

settings.json and the Engine menu set sim resolution, substeps, active tiles, fragment or compute backend and the Battery Saver, Balanced or Quality presets.

Import a Powder Toy stamp
Homebrew developer

nx::importTptStampJson in source/save/tpt_stamp_import.cpp maps The Powder Toy element IDs to NXSand materials and scales the stamp into the grid.

Build and test the NRO
Homebrew developer

make builds build/NXSand.nro with devkitPro; make desktop and make test build the SDL2 desktop version and the CPU reference tests in tests/; CI does the same in .github/workflows/native-nro.yml.

2.1. Scenario: Build NXSand and play a first world

  1. Install devkitPro, then run dkp-pacman -S switch-dev switch-sdl2 switch-mesa switch-glm switch-freetype switch-harfbuzz.
  2. Run make at the repository root to get build/NXSand.nro.
  3. Copy it to sdmc:/switch/NXSand.nro and launch it from the Homebrew Menu.
  4. Press X to pick Lava, paint with A, then switch to Water and pour it on the lava to see stone and smoke form.
  5. Press Y to quick-save the world into sdmc:/switch/nxsand/.

Files readREADME.mddocs/PHYSICS.mddocs/SETTINGS.mddocs/TPT_IMPORT.mdMakefileshaders/sim_common.glslshaders/paint.fragsource/save/save.cppsource/save/tpt_stamp_import.cppsource/sim/brush_stroke.cppsource/ui/material_wheel.cppsource/game/engine_settings.cpp.github/workflows/native-nro.yml

3. Getting started

These commands come from the repository documentation. They were not run during this review.

README.md · Reference versions (June 2026)
dkp-pacman -Q libnx switch-sdl2
dkp-pacman -Syu    # pull newer libnx / switch-sdl2 when available
README.md · 2. Install Switch libraries
dkp-pacman -S switch-dev switch-sdl2 switch-mesa switch-glm switch-freetype switch-harfbuzz
README.md · 3. Build the NRO
make
README.md · Desktop (optional)
make desktop
./build/NXSand
make test         # CPU reference tests, no GPU
README.md · Desktop (optional)
pacman -S --needed mingw-w64-x86_64-gcc mingw-w64-x86_64-make mingw-w64-x86_64-SDL2 mingw-w64-x86_64-freetype mingw-w64-x86_64-angleproject

4. Repository references

Excerpts from the repository’s guides, in their original language. Links open the inspected source revision.

Prerequisites and first run

4.1. Install on Switch (players)

README.md

You need a homebrew-ready Switch (setup guide).

  1. Get NXSand.nro — build it yourself (below), download the latest NXSand-switch zip from GitHub Actions (switch/NXSand.nro inside; optional NXSand.nsp forwarder when CI has SWITCH_PROD_KEYS), or use a release tag (Switch + Linux + Windows portable zips).
  2. Copy to the SD card: sdmc:/switch/NXSand.nro (unzip the artifact at the card root, or copy switch/NXSand.nro into switch/).
  3. Launch from the Homebrew Menu. Saves live in sdmc:/switch/nxsand/. Legacy sdmc:/switch/nxengine/ data is migrated on first launch when possible. Full install notes: docs/INSTALL.md.
Read the full instructions ↗

4.2. 1. Install devkitPro

README.md

OSInstall
WindowsDownload and run the installer from devkitPro installer releases. Use the MSYS2 shortcut it adds (e.g. “devkitPro MSYS2”) so make and dkp-pacman are on your PATH.
Linux / macOSFollow Getting Started on devkitpro.org.
Set DEVKITPRO if your environment does not (the installer usually does).
Read the full instructions ↗

4.3. 2. Install Switch libraries

README.md

In the devkitPro shell:
Read the full instructions ↗

4.4. Installing NXSand on Nintendo Switch

docs/INSTALL.md

NXSand ships as a homebrew .nro. You need a Switch setup that can run homebrew. For controls, settings, and architecture, see the full documentation site.
Read the full instructions ↗

4.5. Install on Switch

docs/INSTALL.md

  1. Copy NXSand.nro to sdmc:/switch/NXSand.nro (required).
  2. Install NXSand.nsp with your CFW tool (Tinfoil, DBI, Goldleaf, etc.).
  3. Launch NXSand from the Home menu. If the forwarder fails, launch the .nro from the homebrew menu instead. Forwarders need a CFW setup that allows unsigned NSP installs. Keep prod.keys private; never commit them.
Read the full instructions ↗
Operations and troubleshooting

4.6. Troubleshooting

docs/INSTALL.md

  • The app file must be sdmc:/switch/NXSand.nro, not inside the save folder.
  • Build and deploy: powershell -File scripts\build-native.ps1 → copy dist/switch/NXSand.nro to sdmc:/switch/NXSand.nro. Run powershell -File scripts\verify-nro.ps1 before copying.
  • Confirm the deployed NRO: delete sdmc:/switch/nxsand/launch.log, launch once, and check the latest block includes build: switch-sim-log-v11, switch: sim shader=sim.frag, romfs: sim.frag=… sim_rules_body.glsl=…, sim shader path: shaders/sim.frag, switch: default sim backend=fragment, and no preferring compute sim. Recopy from a fresh build-native.ps1 build if you still see sim_switch paths.
  • If the app opens then exits, NXSand shows a fatal text screen and writes sdmc:/switch/nxsand/launch.log when possible.
Read the full instructions ↗

4.7. When to update

docs/diagrams/README.md

  • sim-pipeline.mmd, sim-margolus-step.mmd: sim/render pass wiring, compute backend, active-tile scissor rules.
  • material-reactions.mmd, reaction-lava-water-quench.puml: shaders/sim.frag / sim_common.glsl reaction branches.
  • save-load-flow.puml: source/save/save.cpp JSON fields or paths.
  • game-scenes.mmd, brush-input-flow.mmd: menu flow, input mapping, PlayRegion.
  • core-runtime-classes.puml, source-modules.mmd: new top-level modules or ownership changes.
Read the full instructions ↗
Source documentation

3 documents read from 3 candidates.

5. Features and components 2

Simulation state
SimState holds dimensions, tick state and brush parameters. SimPipeline owns two textures and framebuffers and exposes paint, step, readback and upload operations. Ping-pong storage keeps the previous grid available while producing the next.
Backend and tests
The fragment path uses GL_R8UI; the compute path uses GL_R32UI for imageStore. Active-tile tracking and fallback state are exposed in the pipeline. The Makefile separates CPU tests, GPU tests and the Switch application build.

6. Architecture

Expand a component to follow its connections, or explore the full diagram.

System components Context
Mermaid Interactive diagram

System overview of the components and connections verified in the repository.

Static architecture view for nxsand
Static preview. The interactive diagram replaces it after Mermaid loads.
flowchart LR
node_player(("Player"))

subgraph group_runtime["Runtime"]
  node_entry["Platform entry<br/>[main.cpp]"]
  node_app["App frame loop<br/>[app.cpp]"]
  node_game_settings["Game settings<br/>[game_settings.hpp]"]
  node_engine_settings["Engine settings"]
end

subgraph group_simulation["Simulation"]
  node_sim_state["Simulation state<br/>[sim_state.hpp]"]
  node_materials["Materials<br/>[materials.hpp]"]
  node_physics["Physics parameters<br/>[physics_params.hpp]"]
  node_sim_pipeline["GPU simulation<br/>[sim_pipeline.cpp]"]
  node_sim_backend["Simulation backend<br/>[sim_backend.hpp]"]
end

subgraph group_presentation["Presentation"]
  node_render["World renderer"]
  node_shader_program["Shader programs<br/>[shader_program.cpp]"]
  node_font["Font atlas<br/>[font_atlas.cpp]"]
  node_audio["Audio<br/>[tone_audio.cpp]"]
end

subgraph group_interaction["Interaction"]
  node_input["Input handling<br/>[input.cpp]"]
  node_menu["Menus<br/>[menu.cpp]"]
  node_layout["UI layout<br/>[layout.hpp]"]
  node_theme["UI theme<br/>[theme.hpp]"]
end

subgraph group_persistence["Persistence"]
  node_save["World saves<br/>[save.cpp]"]
  node_save_paths["Save storage<br/>[save_paths.cpp]"]
  node_settings_io["Settings storage<br/>[settings_io.cpp]"]
end

node_player -->|"launches"| node_entry
node_entry -->|"starts"| node_app
node_app -->|"renders"| node_render
node_app -->|"reads"| node_input
node_app -->|"coordinates"| node_menu
node_app -->|"applies settings"| node_engine_settings
node_app -->|"uses settings"| node_game_settings
node_sim_pipeline -->|"updates grid"| node_sim_state
node_sim_pipeline -->|"simulates materials"| node_materials
node_sim_pipeline -->|"uses parameters"| node_physics
node_sim_pipeline -->|"selects backend"| node_sim_backend
node_render -->|"uses shaders"| node_shader_program
node_render -->|"draws grid"| node_sim_state
node_render -->|"colors materials"| node_materials
node_menu -->|"edits settings"| node_engine_settings
node_menu -->|"uses text"| node_font
node_menu -->|"draws interface"| node_render
node_layout -->|"uses styles"| node_theme
node_input -->|"maps controls"| node_layout
node_audio -->|"reads preferences"| node_game_settings
node_save -.->|"uses storage"| node_save_paths
node_settings_io -->|"uses storage"| node_save_paths
group_runtime --> group_simulation
group_runtime --> group_persistence
class node_entry,node_app,node_game_settings,node_engine_settings toneBlue
class node_sim_state,node_materials,node_physics,node_sim_pipeline,node_sim_backend toneAmber
class node_render,node_shader_program,node_font,node_audio toneMint
class node_input,node_menu,node_layout,node_theme toneRose
class node_save,node_save_paths,node_settings_io,node_player toneIndigo

A Nintendo Switch falling-sand sandbox with desktop builds for iteration. Its frame loop coordinates GPU simulation, rendering, input, menus, persistence and settings around a live material grid.

Components
21
Connections
28
Evidence-backed links
15/28
Text version of the diagram

The diagram merges parallel links between groups, so it shows 24 of 28. Full list:

  • Player → Platform entry (launches)
  • Platform entry → App frame loop (starts)
  • App frame loop → GPU simulation (ticks)
  • App frame loop → World renderer (renders)
  • App frame loop → Input handling (reads)
  • App frame loop → Menus (coordinates)
  • App frame loop → World saves (requests saves)
  • App frame loop → Settings storage (loads settings)
  • App frame loop → Engine settings (applies settings)
  • App frame loop → Game settings (uses settings)
  • App frame loop → Simulation state (holds state)
  • App frame loop → Physics parameters (holds parameters)
  • GPU simulation → Simulation state (updates grid)
  • GPU simulation → Materials (simulates materials)
  • GPU simulation → Physics parameters (uses parameters)
  • GPU simulation → Simulation backend (selects backend)
  • World renderer → Shader programs (uses shaders)
  • World renderer → Simulation state (draws grid)
  • World renderer → Materials (colors materials)
  • Menus → Engine settings (edits settings)
  • Menus → Font atlas (uses text)
  • Menus → World renderer (draws interface)
  • UI layout → UI theme (uses styles)
  • Input handling → UI layout (maps controls)
  • Audio → Game settings (reads preferences)
  • World saves → Save storage (uses storage)
  • Settings storage → Save storage (uses storage)
  • Engine settings → Settings storage (persists preferences)
Follow one path through the code

Follow paintDisk into grid modification, then step into the selected backend. readTexture identifies the current grid for rendering. Save readback must also account for OpenGL row orientation rather than copying a displayed screenshot.

7. Technical dossier

Built from the source at commit af70da3 (138 files). Each item comes from a pattern match in the code, so open the linked file before relying on it.

7.1. Layers and modules

Folders are sorted into layers by name, from the user interface down to data access. Because the sorting uses names only, the diagram says nothing about calls between layers.

Mermaid Layered view
flowchart TB
  subgraph L_presentation["Presentation"]
    direction TB
    presentation_0["ui<br/>30 files"]
  end
  subgraph L_application["Application services"]
    direction TB
    application_0["scripts<br/>18 files"]
    application_1["gpu<br/>4 files"]
  end
  subgraph L_domain["Domain and business rules"]
    direction TB
    domain_0["game<br/>8 files"]
  end
  subgraph L_infrastructure["Data and infrastructure"]
    direction TB
    infrastructure_0["platform/audio<br/>10 files"]
    infrastructure_1["platform/input<br/>7 files"]
    infrastructure_2["platform<br/>4 files"]
    infrastructure_3["gpu<br/>2 files"]
  end
  subgraph L_core["Core source"]
    direction TB
    core_0["gpu<br/>13 files"]
    core_1["save<br/>13 files"]
    core_2["sim<br/>13 files"]
    core_3["shaders<br/>3 files"]
    core_more["+2"]
  end
  subgraph L_tests["Tests"]
    direction TB
    tests_0["tests<br/>21 files"]
  end
  subgraph L_hosting["Build, CI and hosting"]
    direction TB
    hosting_0[".github<br/>4 files"]
    hosting_1["(root)<br/>2 files"]
  end
  L_presentation ~~~ L_application
  L_application ~~~ L_domain
  L_domain ~~~ L_infrastructure
  L_infrastructure ~~~ L_core
  L_core ~~~ L_tests
  L_core ~~~ L_hosting
Modules and their main layer
ModuleLayerFilesTypesExtensions
uiPresentation309.hpp .cpp
gpuCore source1917.hpp .cpp
scriptsApplication services180.ps1 .py .sh
saveCore source131.hpp .cpp
simCore source137.hpp .cpp
platform/audioData and infrastructure103.cpp .hpp
gameDomain and business rules810.cpp .hpp
platform/inputData and infrastructure74.hpp .cpp
platformData and infrastructure40.cpp .hpp
shadersCore source30.glsl
third_party/gladCore source30.h .c
third_party/nlohmannCore source10.hpp

7.2. C4 model

From system context to code declarations. Each level reflects the available source evidence.

UML 1 · System context
1 · System context

The widest C4 view: who uses the project and which outside systems and data stores it relies on. Rendered with uml-mcp.

PlantUML source
@startuml
!include <C4/C4_Context>
LAYOUT_LEFT_RIGHT()
Person(P_cli, "CLI user")
System(SYS, "nxsand", "nxsand is a falling-sand simulation for Switch with a desktop develop…")
System_Ext(E0, "Kroki")
Rel(P_cli, SYS, "Uses")
Rel(SYS, E0, "Calls")
@enduml
UML 2 · Containers
2 · Containers

One level closer: the parts that run separately inside the project, their main language and how calls flow between them. Rendered with uml-mcp.

PlantUML source
@startuml
!include <C4/C4_Container>
LAYOUT_LEFT_RIGHT()
Person(P_cli, "CLI user")
System_Boundary(SYS, "nxsand") {
  Container(APP, "nxsand", "C++")
}
System_Ext(E0, "Kroki")
Rel(P_cli, APP, "Uses")
Rel(APP, E0, "Calls")
@enduml
UML 3 · Components and layers
3 · Components and layers

Modules grouped by source-layer classification. Boundaries show organization, not runtime isolation or call relationships. Rendered with uml-mcp.

PlantUML source
@startuml
!include <C4/C4_Component>
LAYOUT_TOP_DOWN()
System_Boundary(SYS, "nxsand") {
Boundary(L0, "Presentation", "layer") {
Component(M0_0, "ui", "C++", "30 files")
}
Boundary(L1, "Application services", "layer") {
Component(M1_0, "scripts", ".ps1, Python, .sh", "18 files")
Component(M1_1, "gpu", "C++", "4 files")
}
Boundary(L2, "Domain and business rules", "layer") {
Component(M2_0, "game", "C++", "8 files")
}
Boundary(L3, "Data and infrastructure", "layer") {
Component(M3_0, "platform/audio", "C++", "10 files")
Component(M3_1, "platform/input", "C++", "7 files")
Component(M3_2, "platform", "C++", "4 files")
Component(M3_3, "gpu", "C++", "2 files")
}
Boundary(L4, "Core source", "layer") {
Component(M4_0, "gpu", "C++", "13 files")
Component(M4_1, "save", "C++", "13 files")
Component(M4_2, "sim", "C++", "13 files")
Component(M4_3, "shaders", ".glsl", "3 files")
Component(M4_4, "third_party/glad", "C/C++, C", "3 files")
Component(M4_5, "third_party/nlohmann", "C++", "1 files")
}
Boundary(L5, "Tests", "layer") {
Component(M5_0, "tests", "C++", "21 files")
}
Boundary(L6, "Build, CI and hosting", "layer") {
Component(M6_0, ".github", ".yml, .json", "4 files")
Component(M6_1, "(root)", "Makefile, justfile", "2 files")
}
}
@enduml
UML 4 · Code
4 · Code

Selected declarations, fields and methods grouped by source file. No inheritance or call edges are inferred. Rendered with uml-mcp.

PlantUML source
@startuml
skinparam backgroundColor transparent
skinparam shadowing false
skinparam roundCorner 10
skinparam defaultFontName Arial
skinparam defaultFontSize 13
skinparam ArrowColor #4e6070
skinparam ArrowFontColor #3d4f5f
skinparam titleFontSize 14
skinparam actorBorderColor #006a62
skinparam actorBackgroundColor #e9f3f4
skinparam usecaseBorderColor #006a62
skinparam usecaseBackgroundColor #e9f3f4
skinparam componentBorderColor #006a62
skinparam componentBackgroundColor #e9f3f4
skinparam interfaceBorderColor #006a62
skinparam interfaceBackgroundColor #ffffff
skinparam nodeBorderColor #4e6070
skinparam nodeBackgroundColor #f4f8fb
skinparam databaseBorderColor #006a62
skinparam databaseBackgroundColor #ffffff
skinparam cloudBorderColor #4e6070
skinparam cloudBackgroundColor #f0f5fa
skinparam artifactBorderColor #006a62
skinparam artifactBackgroundColor #ffffff
skinparam rectangleBorderColor #b0c2d3
skinparam activityBorderColor #006a62
skinparam activityBackgroundColor #e9f3f4
skinparam partitionBorderColor #b0c2d3
skinparam stereotypeCBackgroundColor #ffffff
top to bottom direction
hide empty members
package "nxsand" {
package "source/game/app.hpp" as F0 {
class "App" as T0_0 {
  shutdown()
  frame()
  setSimGridSize()
  applyRuntimeSettings()
  applyRuntimeSettingsLight()
  applyRuntimeSettingsHeavy()
}
}
package "source/gpu/active_tiles.hpp" as F1 {
class "ActiveTileMap" as T1_0 {
  reset()
  wakeAll()
  sleepAll()
  wakeFromGridTopDown()
  wakeBoundsPx()
  rememberBounds()
}
class "ActiveTileRun" as T1_1 {
  std
}
}
package "source/gpu/perf_stats.hpp" as F2 {
class "PerfStats" as T2_0 {
  beginFrame()
  beginSim()
  endSim()
  beginPaint()
  endPaint()
  beginWorldRender()
}
}
package "source/gpu/font_atlas.hpp" as F3 {
class "FontAtlas" as T3_0 {
  isReady()
  shutdown()
  invalidateGlTexture()
  resetGlResources()
  textWidth()
  drawText()
}
}
package "source/gpu/shader_program.hpp" as F4 {
class "ShaderProgram" as T4_0 {
  loadFromFiles()
  loadComputeFromFile()
  use()
  uniformLocation()
  readFile()
  compile()
}
}
package "source/platform/input/menu_repeat.hpp" as F5 {
class "MenuRepeatState" as T5_0 {
  reset()
  tick()
  tickAxis()
}
}
package "source/gpu/shader_program.cpp" as F6 {
class "SwitchLinkHeartbeat" as T6_0 {
  start()
  finish()
}
}
package "source/ui/menu.hpp" as F7 {
class "MenuLayout" as T7_0 {
  s
  panelX
  panelY
  panelW
  panelH
  rowH
  listY0
  titleY
}
}
package "source/sim/physics_gpu.hpp" as F8 {
class "PhysicsParamsGPU" as T8_0 {
  fire_speed
  fire_smokeRate
  fire_ignitePlant
  fire_igniteOil
  fire_spreadRate
  smoke_fadeRate
  smoke_driftRate
  water_flowRate
}
}
package "source/gpu/shader_cache.cpp" as F9 {
class "ShaderCacheHeader" as T9_0 {
  magic
  version
  keyHash
  binaryFormat
  payloadSize
}
}
package "source/gpu/sim_pipeline.hpp" as F10 {
class "SimPassRect" as T10_0 {
  simShader
  computeShader
  paintShader
  activeTiles
}
}
package "source/gpu/render_pipeline.hpp" as F11 {
class "UiVertex" as T11_0 {
  std
  bloomBrightShader
  bloomBlurShader
  bloomCompositeShader
}
}
package "source/sim/brush_stroke.hpp" as F12 {
class "BrushDab" as T12_0 {
  x
  y
  std
}
class "BrushStrokePlan" as T12_1 {
  std
}
}
package "source/gpu/font_atlas.cpp" as F13 {
class "FtLibrary" as T13_0 {
  std
  true
}
}
package "source/sim/physics_settings.hpp" as F14 {
class "ParamSpec" as T14_0 {
  id
  label
}
}
package "source/ui/layout.hpp" as F15 {
class "PlayRegion" as T15_0 {
  scaleX
  scaleY
}
}
package "source/game/game_settings.hpp" as F16 {
class "DebugSettings" as T16_0 {
  std
}
class "DisplaySettings" as T16_1 {
  std
}
class "GameSettings" as T16_2 {
  std
}
}
}
@enduml

7.3. Data flow

The calls from the GitDiagram graph, in the order you meet them from the entry points. GitDiagram generates the graph with AI; the file paths it cites were checked against the repository tree.

Mermaid Sequence diagram
sequenceDiagram
  autonumber
  participant P0 as Audio
  participant P1 as Game settings
  participant P2 as Player
  participant P3 as Platform entry
  participant P4 as App frame loop
  participant P5 as GPU simulation
  participant P6 as World renderer
  participant P7 as Input handling
  participant P8 as Menus
  participant P9 as World saves
  participant P10 as Settings storage
  participant P11 as Engine settings
  participant P12 as Simulation state
  participant P13 as Physics parameters
  participant P14 as Materials
  P0->>P1: reads preferences
  P2->>P3: launches
  P3->>P4: starts
  P4->>P5: ticks
  P4->>P6: renders
  P4->>P7: reads
  P4->>P8: coordinates
  P4->>P9: requests saves
  P4->>P10: loads settings
  P4->>P11: applies settings
  P4->>P1: uses settings
  P4->>P12: holds state
  P4->>P13: holds parameters
  P5->>P12: updates grid
  P5->>P14: simulates materials
  P5->>P13: uses parameters
Steps as text (+4)
  1. Audio to Game settings reads preferences
  2. Player to Platform entry launches
  3. Platform entry to App frame loop starts
  4. App frame loop to GPU simulation ticks
  5. App frame loop to World renderer renders
  6. App frame loop to Input handling reads
  7. App frame loop to Menus coordinates
  8. App frame loop to World saves requests saves
  9. App frame loop to Settings storage loads settings
  10. App frame loop to Engine settings applies settings
  11. App frame loop to Game settings uses settings
  12. App frame loop to Simulation state holds state
  13. App frame loop to Physics parameters holds parameters
  14. GPU simulation to Simulation state updates grid
  15. GPU simulation to Materials simulates materials
  16. GPU simulation to Physics parameters uses parameters
  17. GPU simulation to Simulation backend selects backend
  18. World renderer to Shader programs uses shaders
  19. World renderer to Simulation state draws grid
  20. World renderer to Materials colors materials
  21. Input handling to UI layout maps controls
  22. Menus to Engine settings edits settings
  23. Menus to Font atlas uses text
  24. Menus to World renderer draws interface

7.4. Public interface

Outside code can use everything listed here. The project also declares 51 public types.

Command line 9

Entry points

7.5. Private interface

These names are internal by the language’s conventions: a leading underscore, private, static or pub(crate).

static (file-local) function
74
anonymous namespace
19
underscore helper
9

7.6. Services

Service 2

7.7. Entities and data model 36

Mermaid Entity class diagram
classDiagram
  direction LR
  class E0["MenuLayout"] {
    +float s
    +float panelX
    +float panelY
    +float panelW
    +float panelH
    +float rowH
  }
  class E1["PhysicsParamsGPU"] {
    +float fire_speed
    +float fire_smokeRate
    +float fire_ignitePlant
    +float fire_igniteOil
    +float fire_spreadRate
    +float smoke_fadeRate
  }
  class E2["ShaderCacheHeader"] {
    +char magic
    +uint32_t version
    +uint64_t keyHash
    +uint32_t binaryFormat
    +uint32_t payloadSize
  }
  class E3["SimPassRect"] {
    +ShaderProgram simShader
    +ShaderProgram computeShader
    +ShaderProgram paintShader
    +ActiveTileMap activeTiles
  }
  class E4["UiVertex"] {
    +:vector std
    +ShaderProgram bloomBrightShader
    +ShaderProgram bloomBlurShader
    +ShaderProgram bloomCompositeShader
  }
  class E5["BrushDab"] {
    +int x
    +int y
    +:vector std
  }
  class E6["FtLibrary"] {
    +:ifstream std
    +return true
  }
  class E7["ParamSpec"] {
    +char id
    +char label
  }
  class E8["PlayRegion"] {
    +int scaleX
    +int scaleY
  }
  class E9["DebugSettings"] {
    +:pair std
  }

An arrow links a field to the entity its type names.

EntityKindFirst fields
MenuLayoutData structs, panelX, panelY, panelW, panelH, rowH, listY0, titleY
PhysicsParamsGPUData structfire_speed, fire_smokeRate, fire_ignitePlant, fire_igniteOil, fire_spreadRate, smoke_fadeRate, smoke_driftRate, water_flowRate
ShaderCacheHeaderData structmagic, version, keyHash, binaryFormat, payloadSize
SimPassRectData structsimShader, computeShader, paintShader, activeTiles
UiVertexData structstd, bloomBrightShader, bloomBlurShader, bloomCompositeShader
BrushDabData structx, y, std
FtLibraryData structstd, true
ParamSpecData structid, label
Show 6 more entities
PlayRegionData structscaleX, scaleY
DebugSettingsData structstd
DisplaySettingsData structstd
GameSettingsData structstd
ActiveTileRunData structstd
BrushStrokePlanData structstd

7.8. Business logic

Once UI code and the services and entities above are set aside, these classes have the most methods.

App Domain and business rules, 37 methods
shutdown, frame, setSimGridSize, applyRuntimeSettings, applyRuntimeSettingsLight, applyRuntimeSettingsHeavy
ActiveTileMap Core source, 23 methods
reset, wakeAll, sleepAll, wakeFromGridTopDown, wakeBoundsPx, rememberBounds
PerfStats Core source, 12 methods
beginFrame, beginSim, endSim, beginPaint, endPaint, beginWorldRender
FontAtlas Core source, 11 methods
isReady, shutdown, invalidateGlTexture, resetGlResources, textWidth, drawText
ShaderProgram Core source, 8 methods
loadFromFiles, loadComputeFromFile, use, uniformLocation, readFile, compile
MenuRepeatState Data and infrastructure, 3 methods
reset, tick, tickAxis
SwitchLinkHeartbeat Core source, 2 methods
start, finish

7.9. External integrations

HTTP clients

urllib

Hosts named in the code

7.10. Stack and dependencies

Native (Make) Makefile 12 runtime

DEVKITPRO

Open the manifest

External dependencies

7.11. CI/CD pipeline

3 GitHub Actions workflows, 11 jobs. Each box is a job; double-bordered boxes publish or deploy, and arrows follow needs.

Mermaid Pipeline flow
flowchart LR
  subgraph wf0["Documentation"]
    direction LR
    wf0_build[["build<br/>ubuntu-latest"]]
    wf0_deploy[["deploy<br/>ubuntu-latest"]]
  end
  subgraph wf1["Build NXSand.nro"]
    direction LR
    wf1_unit_tests["CPU unit tests<br/>ubuntu-latest"]
    wf1_switch[["Switch NRO<br/>ubuntu-latest"]]
    wf1_desktop["Desktop sanity build (Linux)<br/>ubuntu-latest"]
    wf1_desktop_windows["Desktop sanity build (Windows)<br/>windows-latest"]
  end
  subgraph wf2["Release NXSand"]
    direction LR
    wf2_unit_tests["CPU unit tests<br/>ubuntu-latest"]
    wf2_build_switch[["Package Switch NRO<br/>ubuntu-latest"]]
    wf2_build_linux[["Package Linux portable<br/>ubuntu-latest"]]
    wf2_build_windows[["Package Windows portable<br/>windows-latest"]]
    wf2_publish[["Publish GitHub release<br/>ubuntu-latest"]]
  end
  t0(["push · manual run"])
  t1(["push · pull request · manual run"])
  t2(["manual run"])
  d0[("GitHub Pages")]
  d1[("GitHub Releases")]
  t0 --> wf0_build
  wf0_build --> wf0_deploy
  wf0_deploy -.-> d0
  t1 --> wf1_unit_tests
  wf1_unit_tests --> wf1_switch
  wf1_unit_tests --> wf1_desktop
  wf1_unit_tests --> wf1_desktop_windows
  t2 --> wf2_unit_tests
  wf2_unit_tests --> wf2_build_switch
  wf2_unit_tests --> wf2_build_linux
  wf2_unit_tests --> wf2_build_windows
  wf2_build_switch --> wf2_publish
  wf2_build_linux --> wf2_publish
  wf2_build_windows --> wf2_publish
  wf2_publish -.-> d1
Workflows, triggers, jobs and targets
WorkflowTriggersJobsPublishes to
Documentation
.github/workflows/docs.yml
push, manual run
  • build ubuntu-latest
    actions/checkout · actions/setup-python · Install MkDocs · Build site · Upload Pages artifact
  • deploy ← build ubuntu-latest
    Deploy to GitHub Pages
GitHub Pages
Build NXSand.nro
.github/workflows/native-nro.yml
push, pull request, manual run
  • CPU unit tests ubuntu-latest
    actions/checkout · Install build tools · Build and run unit tests · Build and run GPU unit tests
  • Switch NRO ← unit-tests ubuntu-latest
    actions/checkout · Verify required portlibs · Build NRO and stage dist · Export NSP forwarder · Package switch zip · actions/upload-artifact
  • Desktop sanity build (Linux) ← unit-tests ubuntu-latest
    actions/checkout · Install SDL2 + GLESv2 + FreeType (Mesa) · Fetch UI font · Build desktop · GPU unit tests · Verify binary
  • Desktop sanity build (Windows) ← unit-tests windows-latest
    actions/checkout · msys2/setup-msys2 · Fetch UI font · Build desktop · Verify binary
—
Release NXSand
.github/workflows/release.yml
manual run
  • CPU unit tests ubuntu-latest
    actions/checkout · Install build tools · Build and run unit tests
  • Package Switch NRO ← unit-tests ubuntu-latest
    actions/checkout · Verify required portlibs · Build NRO · Export NSP forwarder · Package Switch zip · actions/upload-artifact
  • Package Linux portable ← unit-tests ubuntu-latest
    actions/checkout · Install SDL2 + GLESv2 + FreeType (Mesa) · Fetch UI font · Build desktop · Package Linux zip · actions/upload-artifact
  • Package Windows portable ← unit-tests windows-latest
    actions/checkout · msys2/setup-msys2 · Fetch UI font · Build desktop · Package Windows zip · actions/upload-artifact
  • Publish GitHub release ← build-switch, build-linux, build-windows ubuntu-latest
    actions/checkout · actions/download-artifact · List release artifacts · Skip publish under act · Publish GitHub release
GitHub Releases

7.12. Deployment

Mermaid Deployment diagram
flowchart LR
  repo[("GitHub repository")]
  ci[["GitHub Actions<br/>3 workflows"]]
  pub_GitHub_Pages[("GitHub Pages")]
  pub_GitHub_Releases[("GitHub Releases")]
  host_MkDocs_site("Documentation site (MkDocs)<br/>mkdocs.yml")
  repo -->|"push or pull request"| ci
  ci -->|"publishes"| pub_GitHub_Pages
  ci -->|"publishes"| pub_GitHub_Releases
  ci -.->|"configured"| host_MkDocs_site

Where it runs

Published to

7.13. Security

Controls

Environment variables 20

The page lists names without their values and underlines the ones that look like secrets.

NXENGINE_BOOT_LOG NXENGINE_SIM_H NXENGINE_SIM_W NXSAND_BOOT_LOG NXSAND_SIM_H NXSAND_SIM_W MINGW_PREFIX NXENGINE_DEBUG_GRID NXENGINE_DISABLE_AUDIO NXENGINE_LAUNCH_LOG NXENGINE_PALETTE_MODE NXENGINE_SWITCH_SWAP_FACE_XY NXENGINE_VERBOSE_LAUNCH_LOG NXSAND_DEBUG_GRID NXSAND_DISABLE_AUDIO NXSAND_FONT_PATH

7.14. Testing

Test files
21
Source files
129
Unit tests
21

No measured coverage report in the analyzed sources.

Where the tests are

tests 21

Nothing found for: Database, Monitoring.

8. Types and file structure

53 declared types, with 12 in the base-relationship overview.

Type diagram
classDiagram
direction TB
class t7ba1fb6b5b9e["SimPassRect"] {
  <<struct>>
}
class t658fbe1e623e["SimPipeline"] {
  SimPipeline()
  init()
  shutdown()
  clearAll()
}
class tb8626ee066b2["SimState"] {
  <<struct>>
}
class tfdb610a7db47["SimCompileAudioGuard"] {
  <<struct>>
  SimCompileAudioGuard()
}
class ta050db13a10c["App"] {
  init()
  shutdown()
  frame()
  setSimGridSize()
}
class tf962c5d4ce57["PerformanceSettings"] {
  <<struct>>
}
class tbdcb989e9dca["VisualSettings"] {
  <<struct>>
}
class tbb5d68fb65da["ControlSettings"] {
  <<struct>>
}
class t480673c07c2c["AudioSettings"] {
  <<struct>>
}
class t28451b24c3ad["AccessibilitySettings"] {
  <<struct>>
}
class t45aedf66edc0["DebugSettings"] {
  <<struct>>
}
class t4b3cad79f762["DisplaySettings"] {
  <<struct>>
}

Unresolved bases remain labelled; full signatures are not reconstructed.

9. Reference

135 / 135 eligible files read (240 in the tree); 0 oversized, 0 failed reads and 5 syntax diagnostics.

What the scan covers

Known dependency/build folders, credential files and binaries are excluded. The scan covers READMEs, recognized manifests and parser-supported source files.

Linguist statistics cover the whole repository and may include other languages.

11. Files to read 3

12. Glossary

Repository

The versioned source files, configuration, and history of a project.

README

The repository’s entry document, usually covering its purpose and initial setup.

Dependency

A library, tool, or service that another component needs to build or run.

Volume and bind mount

A volume is storage managed by the container platform; a bind mount exposes a chosen host path inside a container.

CLI

A text interface in which commands and arguments select operations. Exit codes let scripts detect success or failure.

Build

The steps that turn source files and dependencies into a runnable program or distributable artifact.

Continuous integration

Automated checks that run when source changes, such as building, testing, and linting. Passing checks cover only what those checks exercise.

GPU

A processor designed for parallel workloads, including graphics rendering and many numerical operations.

Shader

A program executed at a stage of a graphics or compute pipeline, for example to transform vertices or calculate pixel colors.

Backend

An implementation behind a shared interface. Depending on context, it can mean a server-side service or a graphics, input, or storage implementation.

13. References

  1. antoinebou12. (2026, November 5). nxsand. [Software repository] github.com.
  2. antoinebou12. (2026, November 5). nxsand: README. [Repository documentation] github.com.
  3. antoinebou12. (2026, November 5). source/gpu/sim_pipeline.hpp. [Source file] github.com.
  4. antoinebou12. (2026, November 5). source/sim/sim_state.hpp. [Source file] github.com.
  5. antoinebou12. (2026, November 5). Makefile. [Source file] github.com.
  6. Boucher, A. (2026, October 23). nxsand: A GPU grid for falling sand. [Project article] antoineboucher.info.

Sources without a publication date show the date they were last checked, November 5, 2026. File links retain the inspected revision where available.