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
2 changes: 2 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ jobs:
tools/input_rebind_verify.gd \
tools/photo_mode_verify.gd \
tools/player_target_verify.tscn \
tools/explosives_verify.tscn \
tools/fire_verify.tscn \
tools/worldgen_cactus_verify.gd \
tools/worldgen_tree_verify.gd \
tools/worldgen_cave_verify.gd \
Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ RedotCraft: a Minecraft-like voxel sandbox built with **Redot Engine** (Godot 4
- IDs are stored in `PackedByteArray`, so stay under 256. `TEXTURE_TINTS` applies per-texture tinting (grass, leaves, sand, ...).
- Cutout blocks (leaves, glass, torch) are antialiased with `alpha_to_coverage`, so their edge quality depends on MSAA being enabled.
- Water: `BLOCK_WATER` (16) is a source; IDs 28-34 are flowing levels 7..1, resolved through `BlockRegistry.is_water_id()`/`water_level()`/`water_id_for_level()`. `VoxelWorld` runs a 4 Hz cellular flow tick (`_water_tick`, 1024 cells/tick) seeded by breaks/places near water; flow replaces only air or weaker flowing water, falls when the cell below is air, and dries when unfed. Every change goes through `_record_edit()`, so the settled state is reapplied on chunk regeneration; `_edited_blocks` is mirrored into `_edits_by_chunk` so job snapshots and `TerrainGenerator.generate_data()` only see the chunk's own edits. Meshes are non-opaque, non-breakable, no collision and get the same `ARRAY_CUSTOM0` light attribute as blocks; flowing surfaces render at `_water_top(level)` and set vertex-color alpha so `water.gdshader` can calm the waves. `water.gdshader` samples `hint_screen_texture` for screen-space refraction (wave-distorted background with a faint chromatic split, carried through `EMISSION` so the refracted scene is not lit twice), `hint_depth_texture` for shore blending (shallow water is lighter, more transparent, and foams at the edge), and block light for warm glow.
- Fire: burning a block never replaces it. A `BlockRegistry.FLAG_FLAMMABLE` log/leaf/dry plant/mushroom enters the `_burning` state for `FUEL_BURN_TICKS` with its texture and collision intact, and `FireOverlay` (`world/fire_overlay.gd` + `fire_overlay.gdshader`/`smoke_overlay.gdshader`) clusters flame and smoke billboards on it — one MultiMesh per effect, animated entirely in-shader from per-instance phase/progress and rebuilt only on the 4 Hz `VoxelWorld._fire_tick`. Burning blocks crawl into flammable neighbours one at a time, paced by a per-block `FIRE_IGNITION_DELAY_TICKS` (4), `FIRE_SPREAD_PER_TICK` (1) on every `FIRE_SPREAD_PERIOD_TICKS` (2), plus a per-cell `FIRE_SPREAD_INTERVAL_TICKS` cooldown (~2 blocks/s), and can chain-detonate adjacent TNT/nukes. When the timer expires the block crumbles to air and that edit goes through `_record_edit()`, so the settled result survives chunk regeneration (mid-burn state does not persist). `BLOCK_FIRE` (70) is only the standalone cutout cross flame that flint-and-steel leaves on a non-flammable face (`FLAG_CUTOUT | FLAG_CROSS | FLAG_EMISSIVE`, animated by `block.gdshader`); it decays when starved/unsupported and can catch adjacent fuel. `use_flint_and_steel()` detonates an explosive, starts the clicked flammable block burning, or lights the clicked face. `tools/fire_verify.tscn` covers the lot.
- Underwater biomes: `OCEAN`/`DEEP_OCEAN` are split with `KELP_FOREST`, `SEAGRASS_MEADOW`, `CORAL_REEF`, `FROZEN_OCEAN` (BiomeCatalog IDs 15-18), chosen in `TerrainSampler._underwater_biome()` from depth, temperature, and patch noise; `TerrainSampler._ocean_floor_at()` shapes the shelf, slope, and abyss. `VoxelPopulator._decorate_underwater()` grows seabed plants from `DecorationCatalog._underwater_sets` (new blocks 52-58: coral substrate, seagrass, kelp, coral fan/branch, sponge, anemone) and only replaces water; the water mesh culls faces against `FLAG_CROSS` plants and `get_water_ambience()` treats a camera inside one as submerged, so foliage does not sit in glassy air pockets. `VoxelWorld.get_water_ambience()` + `Main._update_underwater()` grade the overlay, `DayNightCycle.set_underwater()` the fog/light/caustics global, and `AudioManager.set_underwater()` a Master low-pass; keep their throttled world queries out of per-frame hot paths.

## Weather / atmosphere
Expand Down Expand Up @@ -88,6 +89,7 @@ RedotCraft: a Minecraft-like voxel sandbox built with **Redot Engine** (Godot 4
- `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.
- `redot --headless --path . res://tools/fire_verify.tscn` checks flammability flags, fire ignition/spread/decay, chain detonation of explosives, the flint-and-steel face-light path, persistence reseeding, and the fire shader wiring. Scene-based for the same class_name reason as above.
- `tools/icon_check.gd` needs a rendering display and fails headless.
- Benchmarks: `worldgen_benchmark.gd`, `worldgen_mesh_benchmark.gd`, and `worldgen_stream_benchmark.gd` (the last is minutes long at 32 chunks).

Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ Current state: the TerraForged-inspired staged pipeline is live under `world/wor
## World / simulation
- [x] Water polish — flowing water levels (IDs 28-34) with a 4 Hz cellular spill/dry sim seeded by edits, falling cascades, settled state persisted through `_edited_blocks`; the mesher renders `_water_top(level)` surfaces with a flowing vertex-color flag. `water.gdshader` uses sharp, gently distorted scene refraction with chromatic split disabled, progressive depth absorption, and faint narrow shore foam.
- [ ] Block physics — gravity blocks (falling sand/gravel) and other environment reactions that must persist through saving
- [ ] Fire, TNT, and explosions — fire spread/decay, ignitable blocks, and explosion destruction routed through `_record_edit()` so settled state survives chunk regeneration
- [x] Fire, TNT, and explosions — TNT (5-block radius) and nuke (18-block radius) destruction already routes through `VoxelWorld.carve_sphere()`/`_record_edit()`. Burning works in three states and never swaps the block for fire: a `BlockRegistry.FLAG_FLAMMABLE` log/leaf/plant/mushroom enters `_burning` for `FUEL_BURN_TICKS` with its texture and collision intact while `FireOverlay` (`world/fire_overlay.gd` + `fire_overlay.gdshader`/`smoke_overlay.gdshader`, one MultiMesh per effect) draws animated flame and smoke billboards over it; it crawls into flammable neighbours one at a time, paced by a per-block `FIRE_IGNITION_DELAY_TICKS` (4), `FIRE_SPREAD_PER_TICK` (1) on `FIRE_SPREAD_PERIOD_TICKS` (2) and a per-cell `FIRE_SPREAD_INTERVAL_TICKS` cooldown (~2 blocks/s), and can chain-detonate adjacent TNT/nukes; when the timer expires the block crumbles to air and that edit goes through `_record_edit()`. `BLOCK_FIRE` (70) is only the standalone cross flame flint-and-steel leaves on a non-flammable face (`assets/placeholders/fire/fire.png` via `tools/gen_fire_texture.gd`; smoke puff likewise), decaying when starved/unsupported; `_seed_fire_edits()` re-arms persisted flames on commit. Flint and steel (`use_flint_and_steel()`) detonates an explosive, starts the clicked flammable block burning, or lights the clicked face. `tools/fire_verify.tscn` covers flammability, burn-in-place, spread, decay, chain detonation, flint and steel, persistence reseeding, and the shader wiring
- [ ] Buckets and fluid sources — water/lava buckets and source rules on top of the flowing levels 28-34 and `_water_tick`, plus lava/water interaction
- [ ] Growth and decay — grass spread, leaf decay, sapling growth, and crop farming
- [ ] Ambient life (deferred until the world-detail passes land) — fireflies at night, butterflies/birds by day, and subtle particle life with per-biome density and time-of-day gating; fireflies can reuse the emissive block-light path for glow
Expand Down
11 changes: 11 additions & 0 deletions assets/placeholders/fire/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Fire placeholder textures

`fire.png` (a 64 px transparent flame sheet) and `smoke.png` (a soft grey puff)
are generated specifically for RedotCraft by `tools/gen_fire_texture.gd` from a
fixed RNG seed. They contain no third-party artwork and are safe to redistribute
with the repository.

`fire.png` is the standalone `BLOCK_FIRE` cross flame, which `world/block.gdshader`
scrolls and warps on the fire layer. `smoke.png` is sampled by
`world/smoke_overlay.gdshader` for the burning-block smoke billboards; the flame
billboards use `fire.png` through `world/fire_overlay.gdshader`.
Binary file added assets/placeholders/fire/fire.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions assets/placeholders/fire/fire.png.import
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://bmmyojifiaj0i"
path="res://.godot/imported/fire.png-21315587809dcbb752fb58ffa6332c60.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://assets/placeholders/fire/fire.png"
dest_files=["res://.godot/imported/fire.png-21315587809dcbb752fb58ffa6332c60.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file added assets/placeholders/fire/smoke.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions assets/placeholders/fire/smoke.png.import
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://dw68i0bvxmwsv"
path="res://.godot/imported/smoke.png-49c8e5f32d90e33d8741bcbc95d5eba7.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://assets/placeholders/fire/smoke.png"
dest_files=["res://.godot/imported/smoke.png-49c8e5f32d90e33d8741bcbc95d5eba7.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
12 changes: 10 additions & 2 deletions game/main.gd
Original file line number Diff line number Diff line change
Expand Up @@ -724,10 +724,18 @@ func _on_block_placed(block_id: int) -> void:
func _on_item_used(item_id: int, block_position: Vector3i) -> void:
if item_id != ItemRegistry.ITEM_FLINT_AND_STEEL:
return
var result := world.trigger_explosive(block_position)
var result := world.use_flint_and_steel(block_position, player.target_normal)
if result.is_empty():
set_status("Flint and steel only ignites TNT or a nuke")
set_status("Flint and steel needs a solid face to light")
return
if result.has("blocked"):
set_status("Too wet to light" if result["blocked"] == "wet" else "Already burning")
return
if int(result.get("ignited", 0)) > 0:
AudioManager.play_block_place(BlockRegistry.BLOCK_FIRE, Vector3(block_position) + Vector3(0.5, 0.5, 0.5))
set_status("Lit fire")
return
AudioManager.play_block_break(BlockRegistry.BLOCK_TNT, Vector3(block_position) + Vector3(0.5, 0.5, 0.5))
set_status("Detonated %s · carved %d blocks" % [result["name"], result["removed"]])


Expand Down
Loading
Loading