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
7 changes: 7 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ jobs:
tools/input_rebind_verify.gd \
tools/photo_mode_verify.gd \
tools/player_target_verify.tscn \
tools/player_survival_verify.gd \
tools/inventory_crafting_verify.gd \
tools/inventory_ui_verify.gd \
tools/item_drops_verify.gd \
tools/gameplay_integration_verify.gd \
tools/survival_progression_verify.gd \
tools/flat_survival_warning_verify.gd \
tools/explosives_verify.tscn \
tools/fire_verify.tscn \
tools/worldgen_cactus_verify.gd \
Expand Down
11 changes: 8 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ RedotCraft: a Minecraft-like voxel sandbox built with **Redot Engine** (Godot 4
- 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, 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.
- `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 14: 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, v13 removes rigid fallen-log/driftwood decorations, and v14 generates ores independently of cave density in non-flat worlds (older worlds retain the old gate). `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.
- `block_registry.gd`: block table `BLOCK_DEFS` rows `[id, name, top, side, bottom, flags, hardness, preferred_tool, required_tier]`, 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 expose it through `CraftingRecipes` or `Main.INITIAL_INVENTORY`. The hotbar now reflects the first ten inventory slots, not a fixed block list. A missing texture logs a warning and renders magenta.
- Emissive blocks: add the id to `EMISSIVE_COLORS` (and `FLAG_EMISSIVE` on the def row). The mesher BFS seeds colored light from it; the existing light volume makes new sources work with no other changes.
- Cross blocks (torches, plants): `FLAG_CROSS` meshes two intersecting inset quads instead of a cube, samples the cell's light without AO, and adds no collision. Torches combine `FLAG_CUTOUT | FLAG_CROSS | FLAG_EMISSIVE`.
- IDs are stored in `PackedByteArray`, so stay under 256. `TEXTURE_TINTS` applies per-texture tinting (grass, leaves, sand, ...).
Expand Down Expand Up @@ -60,6 +60,11 @@ RedotCraft: a Minecraft-like voxel sandbox built with **Redot Engine** (Godot 4

## Player / HUD contract

- Game modes: `GameMode.CREATIVE`/`SURVIVAL` are persisted in top-level world metadata, never terrain config or global settings. `GameConfig.pending_game_mode` defaults to Survival for creation; `get_game_mode()` resolves loaded metadata with a Creative fallback for legacy worlds. Main snapshots the mode before restoring Player/inventory. Survival initializes empty but restores earned stacks; Creative uses starter supplies plus the catalog, unlimited placement, instant mining, flight and no survival damage/tool wear. The creation form is the only mode selector; do not add in-session or world-management switches. Future mode changes belong to the unimplemented command console.
- Survival: `Player` owns health/hunger/air and emits `died`, `vitals_changed`, `mined_block(position,id,harvest)`, `block_picked`, and `block_interacted`. `Main` owns inventory consumption/tool wear, physical drops and station interaction, and `SurvivalUI` owns the vitals/death presentation. Death pauses; respawn synchronously validates terrain. C crouches, F5 toggles third person, middle-click picks owned blocks, and mining requires holding LMB.
- Inventory: `game/item_inventory.gd` stores 40 `{id,count,durability}` stacks (empty `{}`), first ten hotbar slots, 64-item caps and single-tool stacks. `CraftingRecipes` commits atomically; `BlockContainers` stores chests and furnace progress. `InventoryOverlay.configure_inventory()` binds the live model; `open_station()` opens a table/chest/furnace. `InventoryTransfer` validates both swap directions, including output-slot rejection. Main session schema v2 migrates old count rows and persists vitals, stacks, containers and `ItemDrops`; never credit mined blocks directly.
- Crafting progression: see `CRAFTING.md`. All logs convert to `BLOCK_PLANKS` (74), which replaces raw logs in wooden recipes. `ItemRegistry.harvest_drop()` owns stone-to-cobble/coal drops. New items 1017-1021 are charcoal, bowl, stew, melon slice and flint. Whole melons place; slices eat. `CraftingRecipes.craft(..., batches=1)` is atomic, `max_craftable()` accounts for freed ingredient slots, and UI availability caching invalidates on inventory/table access changes. `survival_progression_verify.gd` proves all six wood-species paths with real mining/drop/pickup/station calls; do not replace it with tests that directly grant finished crafting ingredients.
- New focused headless verifiers: `player_survival_verify.gd`, `inventory_crafting_verify.gd`, `inventory_ui_verify.gd`, `item_drops_verify.gd`, `gameplay_integration_verify.gd`.
- `player/player.gd` talks to `Main` only through signals (`block_broken`, `block_placed`, `status_requested`, `slot_cycled`, `slot_selected`, `pause_requested`), connected in `Main._connect_player()`. Keep that pattern instead of reaching into nodes.
- Targeting uses `Player._voxel_raycast()` grid traversal, not collision geometry; it skips water so non-colliding cross plants are selectable, and the entered cell normal becomes the place face.
- Controls: WASD, Space jump, double-tap Space toggles fly, Shift sprint, Ctrl fast fly (`fly_boost`), E inventory (`inventory`), M map, ] minimap toggle, N map mode, F1 HUD (`hud_toggle`), F2 screenshot (`screenshot`), P photo camera (`photo_camera`), 1-9/0 and mouse wheel select hotbar slot, LMB mine, RMB place, Esc pause (`ui_cancel`). In the photo camera WASD flies, Space/Shift rise/descend, Ctrl boosts, the wheel zooms, and Esc exits. Every `project.godot` action is rebindable in Settings -> Controls: `GameConfig` stores physical keycodes in `settings["input_bindings"]` (persisted in `settings.cfg`), rewrites the InputMap on change, and reads the defaults from `ProjectSettings` for resets.
Expand Down
133 changes: 133 additions & 0 deletions CRAFTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Survival Crafting Progression

Survival starts with an empty inventory. Crafting uses recipe buttons rather than
a shaped ingredient grid. Open the inventory with **E**, scroll to the recipe
book, and search by an output or ingredient name. Categories and **Craftable only**
help narrow the list. Requirements show **owned / needed** quantities.

**Craft 1** makes one batch, not necessarily one item. **Craft Max** makes up to
64 batches that fit the backpack. Failed crafts consume nothing. A larger batch
can sometimes fit when a single batch cannot, because it empties an ingredient
slot. Table recipes require opening a placed crafting table, not merely carrying
one.

## 1. Logs to Wooden Tools

Oak, spruce, birch, acacia, jungle, and mangrove logs all work. Each has a recipe
producing the same placeable **Wooden Planks** block; mixed species therefore do
not strand incompatible crafting materials.

| Recipe | Ingredients | Output | Where |
|---|---|---|---|
| Wooden planks | 1 log of any species | 4 planks | Inventory |
| Sticks | 2 planks | 4 sticks | Inventory |
| Bamboo sticks | 2 bamboo | 1 stick | Inventory |
| Crafting table | 4 planks | 1 table | Inventory |
| Wooden pickaxe | 3 planks + 2 sticks | 1 tool | Table |
| Wooden axe | 3 planks + 2 sticks | 1 tool | Table |
| Wooden shovel | 1 plank + 2 sticks | 1 tool | Table |

**First milestone: harvest three logs by hand.** Craft them into 12 planks, then
make a table and one batch of sticks. Move the table to a hotbar slot, place it,
and right-click it. Craft a wooden pickaxe. You still have three planks and two
sticks left over. Axes speed up wood harvesting; they are not needed to start.

## 2. Stone, Storage, and a Furnace

Mine ordinary stone with the wooden pickaxe. It drops **cobblestone**, not smooth
stone. Breaking it with an unsuitable tool does not award a block. Mining wears
the tool only when a block actually breaks.

| Recipe | Ingredients | Output | Where |
|---|---|---|---|
| Stone pickaxe or axe | 3 cobblestone + 2 sticks | 1 tool | Table |
| Stone shovel | 1 cobblestone + 2 sticks | 1 tool | Table |
| Furnace | 8 cobblestone | 1 furnace | Table |
| Chest | 8 planks | 1 chest, 27 slots | Table |
| Crushed deepstone | 1 deepstone | 1 cobblestone | Inventory |

**Second milestone: mine 11 stone blocks** for a stone pickaxe and furnace.
Keep gathering wood for replacement tools, fuel, storage, and shelter.

## 3. Fuel and Light Without Finding Coal

Place and open the furnace. Move material into **Input**, combustible material
into **Fuel**, and collect the **Output**. Output slots cannot accept items.
Furnaces run while playing; close the inventory to let time advance. Every item
takes ten seconds. Partial progress and remaining fuel survive saving/loading.

| Fuel | Burn Time | Smelts per Fresh Fuel Item |
|---|---|---|
| Coal or charcoal | 80 seconds | 8 |
| Any log, plank, or wooden tool | 15 seconds | 1.5 |
| Stick | 5 seconds | 0.5 |

Smelt a log of **any species** into charcoal using another log or planks as fuel.
One coal **or** charcoal plus one stick makes **four torches** in the inventory.
Wooden pickaxes can also harvest coal ore, which drops coal directly.

Fuel time is shared across successive smelts. An already-lit furnace can burn
remaining fuel while idle, so load the next input before leaving it running.

## 4. Iron and Further Materials

Use a **stone pickaxe** to harvest iron ore, then smelt it into iron ingots.
At the crafting table, iron pickaxes/axes cost three ingots and two sticks;
an iron shovel costs one ingot and two sticks. Iron pickaxes can harvest gold ore.
Gold can be smelted and stored, but gold equipment is not implemented.

| Smelting Input | Output |
|---|---|
| Iron ore | Iron ingot |
| Gold ore | Gold ingot |
| Any log | Charcoal |
| Sand or red sand | Glass |
| Cobblestone | Smooth stone |
| Clay | Terracotta |
| Kelp | Dried kelp |

Knap one gravel into one flint in the inventory. One flint and one iron ingot
make flint-and-steel at a table. These recipes do not rely on random gravel drops.

## 5. Foraged Food

| Recipe | Ingredients | Output / Use |
|---|---|---|
| Melon slices | 1 melon block | 4 slices; each restores 2 hunger |
| Melon block | 4 slices | 1 placeable melon; no net item gain |
| Bowls | 3 planks | 4 bowls |
| Mushroom stew | 1 bowl + 1 brown mushroom + 1 red mushroom | Restores 6 hunger; returns the bowl |
| Dried kelp | Smelt 1 kelp | Restores 1 hunger |

These crafting recipes work in the inventory without a table. Select prepared
food and right-click to eat. Whole melons are building blocks, not directly
edible items. Eating at full hunger consumes nothing. A returned bowl goes into
the backpack, or becomes a physical drop if there is no room.

Foraging is **not renewable farming**: local melons, mushrooms, and kelp do not
regrow yet. Meat items exist in Creative, but Survival has no animal source.
Farming, renewable food, animals, and further equipment are future work.

## World and Save Rules

- Normal and Amplified worlds support the ore progression above.
- New worldgen version 14 generates ores even when cave density is zero.
- Existing version 13 and older worlds retain their generation behavior;
changing recipes does not regenerate their terrain.
- Flat worlds have no generated trees or ores and are resource-limited.
The creation form warns about this when selecting Flat Survival.
- Existing block and item IDs are unchanged. New content is appended, so stored
logs, tools, inventories, and containers remain usable.
- Creative still has the free catalog and unlimited building. These recipes do
not provide a way to change a world's game mode.

## Regression Coverage

`tools/survival_progression_verify.gd` repeats the complete path for all six log
species from empty inventory through iron tools and gold harvesting. It uses
timed Player mining, Main's drop/tool-wear handling, physical pickup, placed
stations, UI crafting, and mid-smelt save restoration. It also checks table
gates, incorrect-tool harvesting, prepared foods, and full-inventory bowl returns.

Model atomicity, recipe filtering, keyboard crafting, and capacity behavior are
covered by `inventory_crafting_verify.gd` and `inventory_ui_verify.gd`.
Loading
Loading