Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,15 @@ jobs:
- name: Run worldgen core checks
env:
CHECK_SHARD: Worldgen core
run: bash .github/scripts/run_checks.sh tools/worldgen_verify.gd
run: |
bash .github/scripts/run_checks.sh \
tools/worldgen_verify.gd \
tools/worldgen_water_verify.gd \
tools/worldgen_ore_verify.gd \
tools/worldgen_structure_verify.gd \
tools/worldgen_poi_verify.gd \
tools/worldgen_audit_verify.gd \
tools/worldgen_config_sweep_verify.gd

- name: Upload check logs
if: failure()
Expand Down Expand Up @@ -129,7 +137,9 @@ jobs:
tools/worldgen_biome_verify.gd \
tools/worldgen_island_verify.gd \
tools/worldgen_river_verify.gd \
tools/worldgen_lod_verify.gd
tools/worldgen_lod_verify.gd \
tools/worldgen_spline_verify.gd \
tools/worldgen_elevated_hydrology_verify.gd

- name: Upload check logs
if: failure()
Expand All @@ -155,7 +165,14 @@ jobs:
env:
CHECK_SHARD: Streaming
CHECK_TIMEOUT_SECONDS: "600"
run: bash .github/scripts/run_checks.sh tools/stream_full_verify.gd
run: |
bash .github/scripts/run_checks.sh \
tools/stream_full_verify.gd \
tools/stream_soak_verify.gd \
tools/lod_mode_verify.gd \
tools/chunk_data_compression_verify.gd \
tools/world_storage_verify.gd \
tools/light_invalidation_verify.gd

- name: Upload check logs
if: failure()
Expand Down
13 changes: 8 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AGENTS.md

RedotCraft: a Minecraft-like voxel sandbox built with **Redot Engine** (Godot 4 fork, v26.2), GDScript only. There is no test framework, linter, typechecker, or CI. Verification is headless scripts under `tools/` (see Verification), then running the game and reading output.
RedotCraft: a Minecraft-like voxel sandbox built with **Redot Engine** (Godot 4 fork, v26.2), GDScript only. There is no unit-test framework, linter, or typechecker. Verification is headless scripts under `tools/` (see Verification), then running the game and reading output; the same checks are sharded in GitHub Actions.

## Engine & tooling

Expand All @@ -23,10 +23,12 @@ RedotCraft: a Minecraft-like voxel sandbox built with **Redot Engine** (Godot 4

- Terrain pipeline architecture and tuning guardrails: `world/worldgen/README.md`.

- `voxel_defs.gd`: CHUNK_SIZE 16, WORLD_HEIGHT 128, SEA_LEVEL 32, collision layer 1, padded-neighbor face/AO tables. Shared by the mesher and clouds.
- `voxel_world.gd`: streams chunks around the player (default render_distance 10, unload +2); full detail extends to the render distance (`lod_distance = min(render_distance, MAX_FULL_DETAIL_DISTANCE)` where the cap is 32, so only Extreme distances >32 use LOD). Distant full chunks are committed without collision (`COLLISION_DISTANCE` = 6) and rebuilt on approach to add physics. Full detail everywhere is expensive: RD 32 loads in ~2.5 minutes of worker time and holds ~211 MB of voxel data. `Chunk.lod` tracks the mode; jobs carry it and stale transitions are requeued (also checked against `_stream_center` on commit, like edit versions). Chunk generation and meshing run on `WorkerThreadPool` threads that may only read immutable state (`BlockRegistry`, `TerrainGenerator`, `ChunkMesher`); worker concurrency is half the logical cores (4-8), and only the main thread touches scene nodes. LOD chunks store compact per-column top/sub/water arrays (~3 KB) instead of full voxel data; `break_block()`, `place_block()`, `get_block_world()`, and `_water_place()` refuse LOD chunks. Player edits are kept in `_edited_blocks` and re-applied when chunks regenerate.
- `voxel_defs.gd`: CHUNK_SIZE 16, WORLD_HEIGHT 192, SEA_LEVEL 48, collision layer 1, padded-neighbor face/AO tables. Shared by the mesher and clouds.
- `voxel_world.gd`: streams chunks around the player (default render_distance 10, unload +2). Terrain Detail defaults to Full Detail (`lod_distance = render_distance`) at every distance, including Extreme; opt-in Balanced LOD caps the full-detail radius at `COLLISION_DISTANCE + 2` (8) and uses compact chunks outside it. Extreme Full Detail can take minutes and several GB but never silently enables LOD. Distant full chunks omit collision until approach. Player movement is swept through resident chunk coordinates and stops just inside the truly unloaded boundary; visible LOD/full chunks remain traversable while their approach collision rebuild catches up. All movement pauses while the current chunk's collision is unavailable, and downward motion is suppressed on the entry tick so boosted flight cannot descend through a pending floor. Missing collision is discovered before scheduling, ordered nearest-first, and may use one bounded urgent worker slot so stale distant jobs cannot occupy every normal slot. `Chunk.lod` tracks the mode; jobs carry it and stale transitions are requeued (also checked against `_stream_center` on commit, like edit versions). Chunk generation and meshing run on `WorkerThreadPool` threads that may only read immutable state (`BlockRegistry`, `TerrainGenerator`, `ChunkMesher`); normal worker concurrency is half the logical cores (4-8), plus the one urgent near-player slot, and only the main thread touches scene nodes. LOD chunks store compact per-column top/sub/water arrays (~3 KB) instead of full voxel data; edits refuse LOD chunks. The full-chunk palette/RLE codec remains verifier-covered, but live cold-chunk compaction is disabled: repeated neighbor decoding caused flight stutter and could feed a stale short snapshot to remeshing. Balanced LOD is the supported memory-saving path.
- Stream-center rebuilds enumerate nearest-first rings without sorting the full square, completed collision results commit before distant visuals, and a single-cell edit queues its owner ahead of neighbor-light remeshes. Physics bodies/shapes exist only inside the collision radius; distant visual chunks do not register empty bodies with the physics server.
- Persistence: `world/world_storage.gd` stores versioned metadata and deterministic sparse edits in ZSTD region files. Region I/O and edit hydration stay on the main thread; workers receive immutable per-chunk snapshots. The clean-region cache is capped at 16, dirty regions cannot be evicted, unloaded disk-backed edit buckets are released, and temp/backup replacement preserves a valid recovery backup. Save metadata includes player/inventory/slot/spawn/time/moon/weather; `Main` autosaves every 30 seconds and flushes on exit/new world.
- Spawning: `TerrainGenerator.find_spawn_position()` only knows terrain height, so `VoxelWorld.find_safe_spawn()` searches outward in the generated chunk data for a column whose topmost block is solid (not leaves) with two air cells above; `Main` calls it after `setup_player()`. It depends on which chunk jobs have committed, so the result varies run to run — pin `GameConfig.world["seed"]` and use `get_spawn_position()` (deterministic) for reproducible captures.
- `terrain_generator.gd`: FastNoiseLite climate biomes (15, climate-selected), caves, ores, tree stamping. Deterministic per seed, no mutable state after `configure()`. `generate_data(pos, edits, lod)` returns either full voxel data or compact LOD column arrays (`lod_solid_y`/`lod_solid_id`/`lod_sub_id`/`lod_water_y`/`lod_water_level`) with no `data` allocation; LOD columns bake real tree crowns from in-field anchors, mirror the floor-patch pass, and add low scrub so distance matches the full-detail ring.
- `terrain_generator.gd`: FastNoiseLite climate biomes, caves, data-driven ores, and tree/structure stamping. Deterministic per seed, no mutable state after `configure()`. `WorldGenConfig.CURRENT_VERSION` is 13: v8 keeps legacy cave classification, v9 adds dripstone caves/highland boulders, v10 preserves the original default-off spline terrain/elevated hydrology experiments, v11 adds climate variants, 2D spline profiles, routed elevated hydrology, organic cave regions, large trees, improved lava basins, region POIs, and soft ceiling behavior, v12 replaces the frequent artificial cobblestone outcrop props with low stone pebbles, and v13 removes rigid fallen-log/driftwood decorations. `generate_data(pos, edits, lod)` returns either full voxel data or compact LOD columns; both modes consume the same authoritative inland-water field.
- `chunk_mesher.gd`: produces one ArrayMesh per chunk, a separate water mesh, and ConcavePolygonShape3D collision (the last two only for full-detail chunks). `build_lod()` instead takes the compact distance columns and emits one top quad per column plus vertical runs of side quads down to the neighbor's top, sampling compact per-edge top arrays (`LodNeighbors`); heights match the full mesh so seams stay closed, and light is full sky. LOD block faces get cheap occlusion instead of a light volume: top faces darken under taller cardinal neighbors, sides darken with depth, and sides below the soil layer use stone. Full-detail chunks receive `NeighborSample.from_lod()` samples for bordering distance chunks and expand them into the light volume on the worker thread. Face shading, ambient occlusion, and voxel light are baked per vertex. Before meshing, a 3x3-chunk block volume is assembled and flood-filled on the worker thread: the generator's per-column heightmap (`GenResult.heights`) seeds a sky-light pass, then a lateral BFS carries it indoors, and an RGB block-light BFS runs when emissive blocks are present. Per-vertex light is packed into `ARRAY_CUSTOM0` (`ARRAY_CUSTOM_RGBA_FLOAT`: block RGB + sky level) and the texture-array layer into `ARRAY_CUSTOM1` (`ARRAY_CUSTOM_R_FLOAT`); `world/block.gdshader` samples `vec3(UV, layer)`, multiplies sky light into albedo, and adds block light as emission. Any custom-attribute mesh needs the matching `Mesh.ARRAY_FORMAT_CUSTOM*` flags in `arrays_to_mesh()`, and `world/block.gdshader` replaces the old StandardMaterial3D.
- `block_registry.gd`: block table `BLOCK_DEFS` rows `[id, name, top, side, bottom, flags]`, packed into a runtime `Texture2DArray` (one 64px layer per texture; per-image tinting, edge fix-up, and mipmaps). `layer_for(block_id, face)` returns the layer the mesher writes per vertex.
- To add a block: append a row using an existing texture filename from `assets/placeholders/zigcraft/default/`, then add it to `Main.HOTBAR` / `INITIAL_INVENTORY` if it should be placeable. A missing texture logs a warning and renders magenta.
Expand Down Expand Up @@ -54,6 +56,7 @@ RedotCraft: a Minecraft-like voxel sandbox built with **Redot Engine** (Godot 4
- `soft_shadows` sets directional/positional PCF quality plus `Sun.light_angular_distance` (0 when off, giving hard but stable edges); `taa` sets `Viewport.use_taa` (FSR2 below 100% scale takes over temporal AA automatically); `fsr_scale` at 100% disables FSR2 and renders native; `_apply_graphics()` also forces `Viewport.anisotropic_filtering_level = ANISOTROPY_16X`.
- Adding a graphics key: add it to every `GRAPHICS_PRESETS` entry. `GameConfig.load_settings()` only copies saved values for keys that already exist in the preset, so existing user configs automatically pick up the preset default for new keys.
- Frame pacing lives in Display settings (`vsync`, `fps_cap`, `dynamic_resolution`, `dynamic_resolution_target`); `GameConfig.apply_frame_pacing()` pushes vsync and `Engine.max_fps` at load and on change, and `Main._update_frame_pacing()` steps `Viewport.scaling_3d_scale` toward the target via the pure `GameConfig.dynamic_resolution_scale()` (50% floor, never above the configured `fsr_scale`; the vsync refresh and FPS cap are part of the budget). Pacing keys survive because `load_settings()` copies every key that exists in `DEFAULT_SETTINGS`.
- Terrain Detail is `lod_mode` in Display settings: 0 Full Detail (default), 1 Balanced LOD. Apply it through `VoxelWorld.set_lod_mode()` so loaded chunks transition safely.

## Player / HUD contract

Expand Down Expand Up @@ -85,7 +88,7 @@ RedotCraft: a Minecraft-like voxel sandbox built with **Redot Engine** (Godot 4

- Parse check after editing scripts or scenes: `redot --editor --headless --path . --quit` (a nonzero "error" count means a script/scene fails to load).
- Headless verifiers, from the repo root (`redot --headless --path . --script res://tools/<file>`):
`worldgen_verify.gd` (parity, seams, continuity, roughness), `worldgen_biome_verify.gd` (region size, vegetation density), `worldgen_river_verify.gd` (river width, banks, floodplain, bed), `worldgen_island_verify.gd` (detached large/small island landmasses), `worldgen_lod_verify.gd` (LOD parity, canopies, patches, shading), `worldgen_tree_verify.gd`, `worldgen_cactus_verify.gd`, `audio_verify.gd` (bank/buses/mappings), `weather_verify.gd` (biome precipitation, snow/mist fields, lightning schedule, wind-sway wiring), `ui_flow_verify.gd` (menu flows, cancel order, focus), `ui_scale_verify.gd` (window content scale, text multiplier, live theme refresh), `input_rebind_verify.gd` (action coverage, apply/reset, conflicts, persistence, capture flow), `stream_full_verify.gd` (no LOD inside render distance, lazy collision), `photo_mode_verify.gd` (HUD toggle state, free-camera handoff/look/movement, player artifact and UI-layer hiding, input bindings, screenshot naming), `frame_pacing_verify.gd` (pacing defaults, vsync/FPS application, dynamic-resolution policy), `shadow_proxy_verify.gd` (leaf wind marker, shader `IN_SHADOW_PASS` wiring).
`worldgen_verify.gd` (parity, seams, continuity, roughness), `worldgen_biome_verify.gd` (region size, vegetation density), `worldgen_river_verify.gd` (river width, banks, floodplain, bed), `worldgen_island_verify.gd` (detached large/small island landmasses), `worldgen_lod_verify.gd` (LOD parity, canopies, patches, shading), `worldgen_water_verify.gd` (aquifers), `worldgen_ore_verify.gd`, `worldgen_structure_verify.gd`, `worldgen_poi_verify.gd`, `worldgen_audit_verify.gd`, `worldgen_config_sweep_verify.gd`, `worldgen_spline_verify.gd`, `worldgen_elevated_hydrology_verify.gd`, `worldgen_tree_verify.gd`, `worldgen_cactus_verify.gd`, `world_storage_verify.gd`, `lod_mode_verify.gd`, `chunk_data_compression_verify.gd`, `stream_soak_verify.gd`, `light_invalidation_verify.gd`, `audio_verify.gd`, `weather_verify.gd`, `ui_flow_verify.gd`, `ui_scale_verify.gd`, `input_rebind_verify.gd`, `stream_full_verify.gd`, `photo_mode_verify.gd`, `frame_pacing_verify.gd`, `shadow_proxy_verify.gd`.
- `redot --headless --path . res://tools/player_target_verify.tscn` is scene-based because autoload identifiers are unavailable to `--script` harnesses; use a `.tscn` whenever a test references `GameConfig`/`AudioManager`.
- `redot --headless --path . res://tools/explosives_verify.tscn` checks flint-and-steel activation, TNT/nuke radii, spherical edit persistence, unbreakable blocks, and item-only placement rejection.
- `redot --path . res://tools/shadow_proxy_measure.tscn` needs a rendering display: it loads the real scene with a flat canopy, sweeps the sun, and prints the shadow-edge temporal MAD for the leaf shadow proxy off/off-control/on with and without TAA.
Expand Down
Loading
Loading