From e2436f6e2456644a477f1158ca2cba31d0c499c2 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 11 Jul 2026 20:00:19 +0000 Subject: [PATCH] Improve roads, cabins, forests, and woodland communities --- README.md | 19 ++ worldgen-c/include/worldgen.h | 6 +- worldgen-c/src/main.c | 4 +- worldgen-c/src/worldgen.c | 479 +++++++++++++++++++++++++++---- worldgen-c/tools/worldgen_scan.c | 11 + 5 files changed, 459 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 80422b3..ea4320d 100644 --- a/README.md +++ b/README.md @@ -74,11 +74,30 @@ Features - Redwoods are enabled automatically. The biome classifier marks high-rainfall, higher-relief areas as redwood forest, then places titan trees, dirt/mulch floor cover, tall grass, and occasional fallen logs. +- General forests grow as broad, coherent oak, maple, birch, pine, spruce, + walnut, and cypress stands with occasional companion species. Seeded + clearings, sparse field trees, regenerating edges, mature interiors, variable + tree height, and irregular crown spacing keep the distribution from resolving + into a uniform grid. - Cabins are enabled automatically. With `--trails`, cabins only try to spawn on chunks crossed by the trail network so their spur paths can connect to roads. Without `--trails`, cabins can still spawn, but they are rarer and unconnected. +- Cabin generation includes compact homesteads, longhouses, two-story houses, + L-shaped cabins, cross-gabled lodges, mixed-height tower lodges, and rambling + multi-wing cabins. Roof direction, wing height, material palette, windows, + porches, chimneys, decay, and path width vary by design. +- Seeded woodland-community regions raise the local cabin frequency and give + their roads permission to bridge narrowly bounded water at exact sea level. + Eligible crossings must have nearby dry banks on both sides; deep water, + broad lakes, and open ocean remain impassable. Bridges use raised spruce + decks, timber supports, and intermittent edge posts rather than gravel fill. - Trails are optional because they need a prepass over the target area. Use `--trails` when you want roads and better-connected cabins. +- Long roads search a broad terrain corridor and prefer valley floors, gentle + contour-following grades, low ridge crossings, and stable side slopes. Deep + water remains impassable; shallow water is crossed only when the detour cost + is greater, keeping fords and causeways short. Short cabin spurs use looser + engineering constraints so they can still reach the main network. Minecraft Version ----------------- diff --git a/worldgen-c/include/worldgen.h b/worldgen-c/include/worldgen.h index dbe1dc9..750c2c9 100644 --- a/worldgen-c/include/worldgen.h +++ b/worldgen-c/include/worldgen.h @@ -71,7 +71,9 @@ BLOCK_CLAY = 58, BLOCK_SEAGRASS = 59, BLOCK_IRON_BARS_NS = 60, - BLOCK_IRON_BARS_EW = 61 + BLOCK_IRON_BARS_EW = 61, + BLOCK_SPRUCE_STAIRS_N = 62, + BLOCK_SPRUCE_STAIRS_S = 63 }; struct trail_segment; @@ -112,5 +114,7 @@ void worldgen_prepass(worldgen_ctx *ctx, int min_x, int max_x, int min_z, int ma void worldgen_free_trails(worldgen_ctx *ctx); int worldgen_debug_biome(worldgen_ctx *ctx, int x, int z); double worldgen_debug_redwood_mask(worldgen_ctx *ctx, int x, int z); +double worldgen_debug_community_strength(worldgen_ctx *ctx, int x, int z); +int worldgen_debug_bridge_site(worldgen_ctx *ctx, int x, int z, int dir_x, int dir_z); #endif diff --git a/worldgen-c/src/main.c b/worldgen-c/src/main.c index 6f62579..e904e16 100644 --- a/worldgen-c/src/main.c +++ b/worldgen-c/src/main.c @@ -166,7 +166,9 @@ static const block_state BLOCK_STATE_TABLE[] = { [BLOCK_CLAY] = {"minecraft:clay", NULL, 0}, [BLOCK_SEAGRASS] = {"minecraft:seagrass", NULL, 0}, [BLOCK_IRON_BARS_NS] = {"minecraft:iron_bars", PROPS_IRON_BARS_NS, 5}, - [BLOCK_IRON_BARS_EW] = {"minecraft:iron_bars", PROPS_IRON_BARS_EW, 5} + [BLOCK_IRON_BARS_EW] = {"minecraft:iron_bars", PROPS_IRON_BARS_EW, 5}, + [BLOCK_SPRUCE_STAIRS_N] = {"minecraft:spruce_stairs", PROPS_STAIRS_N, 4}, + [BLOCK_SPRUCE_STAIRS_S] = {"minecraft:spruce_stairs", PROPS_STAIRS_S, 4} }; static const block_state *get_block_state(uint16_t id) { diff --git a/worldgen-c/src/worldgen.c b/worldgen-c/src/worldgen.c index d46b995..dd9c8f8 100644 --- a/worldgen-c/src/worldgen.c +++ b/worldgen-c/src/worldgen.c @@ -10,7 +10,8 @@ #define TRAIL_NODE_SPACING 1600.0 #define TRAIL_CELL_SIZE 16.0 -#define TRAIL_MARGIN 96.0 +#define TRAIL_MARGIN 256.0 +#define TRAIL_LANDFORM_RADIUS 64 #define TRAIL_WIDTH 5 typedef enum { @@ -50,6 +51,7 @@ typedef struct trail_segment { static int column_height(worldgen_ctx *ctx, int x, int z); +static double worley_distance(int x, int z, double scale, uint32_t seed); // --------------------------------------------------------------------------- // RNG (deterministic, cheap) @@ -215,6 +217,46 @@ static double land_value(worldgen_ctx *ctx, int x, int z) { return value; } +/* Broad, seed-stable pockets where cabins form recognizable communities. + * Roads and cabin spawning share this field, so bridge permissions occur in + * the same woods where the extra local traffic is actually generated. */ +static double woodland_community_strength(worldgen_ctx *ctx, int x, int z) { + double cell = worley_distance(x + 18000, z - 26000, 0.00105, + 0xC011AB1Eu ^ (uint32_t)ctx->world_seed); + double cluster = smoothstep01((0.62 - cell) / 0.32); + double texture = simplex_noise2(&ctx->noise, (x - 31000) * 0.0018, + (z + 22000) * 0.0018) * 0.5 + 0.5; + return clamp01(cluster * 0.72 + texture * 0.28); +} + +static int is_dry_sea_level_bank(worldgen_ctx *ctx, int x, int z) { + column_data bank = get_column_data(ctx, x, z); + if (bank.has_water && bank.height < bank.water_surface) return 0; + return bank.height >= ctx->sea_level - 1 && bank.height <= ctx->sea_level + 6; +} + +static int small_community_water_crossing(worldgen_ctx *ctx, int x, int z, int dir_x, int dir_z) { + column_data water = get_column_data(ctx, x, z); + if (!water.has_water || water.water_surface != ctx->sea_level || water.height >= water.water_surface) return 0; + if (water.water_surface - water.height > 6) return 0; + if (woodland_community_strength(ctx, x, z) < 0.56) return 0; + if (dir_x == 0 && dir_z == 0) return 0; + double len = sqrt((double)dir_x * dir_x + (double)dir_z * dir_z); + double ux = (double)dir_x / len; + double uz = (double)dir_z / len; + int bank_forward = 0, bank_backward = 0; + for (int distance = 4; distance <= 48; distance += 4) { + int fx = x + (int)llround(ux * distance); + int fz = z + (int)llround(uz * distance); + int bx = x - (int)llround(ux * distance); + int bz = z - (int)llround(uz * distance); + if (!bank_forward && is_dry_sea_level_bank(ctx, fx, fz)) bank_forward = 1; + if (!bank_backward && is_dry_sea_level_bank(ctx, bx, bz)) bank_backward = 1; + if (bank_forward && bank_backward) return 1; + } + return 0; +} + static void append_trail_segment(worldgen_ctx *ctx, int ax, int az, int bx, int bz, int *points, int count) { /* Drop near-parallel segments that hug existing roads to keep the network from bunching up. */ const double min_parallel_distance = 72.0; @@ -597,6 +639,14 @@ double worldgen_debug_redwood_mask(worldgen_ctx *ctx, int x, int z) { return redwood_biome_mask(ctx, x, z, height, local_relief(ctx, x, z, height)); } +double worldgen_debug_community_strength(worldgen_ctx *ctx, int x, int z) { + return woodland_community_strength(ctx, x, z); +} + +int worldgen_debug_bridge_site(worldgen_ctx *ctx, int x, int z, int dir_x, int dir_z) { + return small_community_water_crossing(ctx, x, z, dir_x, dir_z); +} + static void block_list_init(block_list *list) { list->items = NULL; list->count = 0; @@ -1067,6 +1117,20 @@ static double old_growth_grove_mask(worldgen_ctx *ctx, int x, int z) { return clamp01(cluster * 0.7 + noise * 0.3); } +static double forest_clearing_mask(worldgen_ctx *ctx, int x, int z) { + double cell = worley_distance(x - 23000, z + 17000, 0.0032, + 0xC1EA4123u ^ (uint32_t)ctx->world_seed); + double opening = smoothstep01((0.28 - cell) / 0.16); + return opening; +} + +static double forest_maturity_mask(worldgen_ctx *ctx, int x, int z, double cover) { + double age = simplex_noise2(&ctx->noise, (x + 44000) * 0.0011, (z - 36000) * 0.0011) * 0.5 + 0.5; + double disturbance = simplex_noise2(&ctx->noise, (x - 15000) * 0.0045, (z + 26000) * 0.0045) * 0.5 + 0.5; + double interior = smoothstep01((cover - 0.48) / 0.32); + return clamp01(age * 0.5 + interior * 0.38 + disturbance * 0.12); +} + static int can_place_tree(worldgen_ctx *ctx, int x, int y, int z, int trunk_height, int radius, int crown_extra) { int ground_block = sample_block(ctx, x, y - 1, z); if (ground_block != BLOCK_GRASS && ground_block != BLOCK_SNOW && ground_block != BLOCK_DIRT) return 0; @@ -1857,6 +1921,39 @@ static const int TREE_POOL_OLD_GROWTH[] = { TREE_SPRUCE_TIERS, TREE_PINE_CROWN }; +static double tree_species_stand(worldgen_ctx *ctx, int x, int z, double ox, double oz) { + double broad = simplex_noise2(&ctx->noise, (x + ox) * 0.0022, (z + oz) * 0.0022) * 0.5 + 0.5; + double detail = simplex_noise2(&ctx->noise, (x - oz) * 0.006, (z + ox) * 0.006) * 0.5 + 0.5; + return broad * 0.78 + detail * 0.22; +} + +/* Nearby trees usually share a dominant species. A small companion-tree + * chance below preserves mixed forests without producing species confetti. */ +static tree_species dominant_tree_species(worldgen_ctx *ctx, const column_data *data, int x, int z) { + double rain = rainfall_field(ctx, x, z); + int altitude = data->height - ctx->sea_level; + tree_species best = TREE_SPECIES_OAK; + double best_score = -1.0; +#define CONSIDER_SPECIES(species_id, score_value) do { \ + double candidate_score = (score_value); \ + if (candidate_score > best_score) { best_score = candidate_score; best = (species_id); } \ +} while (0) + if (data->biome == BIOME_WEST_KY_COALFIELDS) { + CONSIDER_SPECIES(TREE_SPECIES_OAK, tree_species_stand(ctx, x, z, 11000, -7000) + 0.12); + CONSIDER_SPECIES(TREE_SPECIES_WALNUT, tree_species_stand(ctx, x, z, -19000, 13000) + rain * 0.08); + CONSIDER_SPECIES(TREE_SPECIES_MAPLE, tree_species_stand(ctx, x, z, 27000, 21000) + rain * 0.16); + CONSIDER_SPECIES(TREE_SPECIES_CYPRESS, tree_species_stand(ctx, x, z, -33000, -17000) + rain * 0.28 - 0.12); + } else { + CONSIDER_SPECIES(TREE_SPECIES_OAK, tree_species_stand(ctx, x, z, 11000, -7000) - altitude * 0.004); + CONSIDER_SPECIES(TREE_SPECIES_MAPLE, tree_species_stand(ctx, x, z, 27000, 21000) + rain * 0.12); + CONSIDER_SPECIES(TREE_SPECIES_BIRCH, tree_species_stand(ctx, x, z, -8000, 31000) + altitude * 0.006); + CONSIDER_SPECIES(TREE_SPECIES_PINE, tree_species_stand(ctx, x, z, 39000, -29000) + altitude * 0.008); + CONSIDER_SPECIES(TREE_SPECIES_SPRUCE, tree_species_stand(ctx, x, z, -41000, 37000) + altitude * 0.011 + rain * 0.08); + } +#undef CONSIDER_SPECIES + return best; +} + static const tree_archetype *choose_tree_archetype(worldgen_ctx *ctx, const column_data *data, int world_x, int world_z, rng_state *rng) { int altitude = data->height - ctx->sea_level; const tree_archetype *fallback = &TREE_TYPES[TREE_OAK_ROUND]; @@ -1879,10 +1976,18 @@ static const tree_archetype *choose_tree_archetype(worldgen_ctx *ctx, const colu return &TREE_TYPES[TREE_POOL_OLD_GROWTH[idx]]; } if (data->biome == BIOME_WEST_KY_COALFIELDS) { + if (rng_next_f64(rng) < 0.82) { + const tree_archetype *stand_tree = choose_species_variant(dominant_tree_species(ctx, data, world_x, world_z), rng); + if (stand_tree) return stand_tree; + } size_t pool_size = sizeof(TREE_POOL_WEST) / sizeof(TREE_POOL_WEST[0]); size_t idx = (size_t)rng_range_inclusive(rng, 0, (int)pool_size - 1); return &TREE_TYPES[TREE_POOL_WEST[idx]]; } + if (rng_next_f64(rng) < 0.82) { + const tree_archetype *stand_tree = choose_species_variant(dominant_tree_species(ctx, data, world_x, world_z), rng); + if (stand_tree) return stand_tree; + } const int *pool = (altitude > 30) ? TREE_POOL_EAST_HIGH : TREE_POOL_EAST_LOW; size_t pool_size = (altitude > 30) ? sizeof(TREE_POOL_EAST_HIGH) / sizeof(TREE_POOL_EAST_HIGH[0]) : sizeof(TREE_POOL_EAST_LOW) / sizeof(TREE_POOL_EAST_LOW[0]); @@ -1891,9 +1996,13 @@ static const tree_archetype *choose_tree_archetype(worldgen_ctx *ctx, const colu return choice ? choice : fallback; } -static void generate_tree(worldgen_ctx *ctx, const tree_archetype *arch, int x, int y, int z, rng_state *rng, block_list *out) { +static void generate_tree(worldgen_ctx *ctx, const tree_archetype *arch, int x, int y, int z, + double maturity, rng_state *rng, block_list *out) { if (!arch) return; int height = rng_range_inclusive(rng, arch->min_height, arch->max_height); + double height_scale = 0.62 + clamp01(maturity) * 0.38; + height = (int)llround((double)height * height_scale); + if (height < 4) height = 4; if (!can_place_tree(ctx, x, y, z, height, arch->space_radius, arch->canopy_extra)) { if (!can_place_tree(ctx, x, y, z, height, arch->space_radius - 1 >= 1 ? arch->space_radius - 1 : 1, arch->canopy_extra)) return; } @@ -2114,6 +2223,8 @@ typedef struct { int offset_z; int half_w; int half_l; + int roof_axis; + int stories; } cabin_rect; #define CABIN_MAX_RECTS 4 @@ -2139,18 +2250,36 @@ typedef struct { int door_rect_index; int has_ladder; int path_width; + int porch_depth; + int has_chimney; } cabin_blueprint; static const cabin_rect CABIN_RECT_SMALL[] = { - {0, 0, 2, 3} + {0, 0, 2, 3, 0, 0} }; static const cabin_rect CABIN_RECT_LONG[] = { - {0, 0, 3, 4} + {0, 0, 3, 4, 0, 0} }; static const cabin_rect CABIN_RECT_TWO_STORY[] = { - {0, 0, 3, 3} + {0, 0, 3, 3, 1, 0} +}; + +static const cabin_rect CABIN_RECT_L_SHAPED[] = { + {-1, 0, 2, 3, 0, 1}, {2, 1, 2, 1, 1, 1} +}; + +static const cabin_rect CABIN_RECT_CROSS_GABLE[] = { + {0, 0, 2, 4, 0, 1}, {0, 0, 3, 2, 1, 1} +}; + +static const cabin_rect CABIN_RECT_TOWER_LODGE[] = { + {-1, 0, 2, 3, 0, 1}, {2, 1, 1, 1, 1, 2} +}; + +static const cabin_rect CABIN_RECT_RAMBLING[] = { + {0, 0, 2, 3, 0, 1}, {-2, -2, 1, 2, 1, 1}, {2, 2, 1, 2, 1, 1} }; static const cabin_blueprint CABIN_BLUEPRINTS[] = { @@ -2174,7 +2303,9 @@ static const cabin_blueprint CABIN_BLUEPRINTS[] = { 0.08, 0, 0, - 2 + 2, + 1, + 1 }, { CABIN_RECT_LONG, @@ -2196,7 +2327,9 @@ static const cabin_blueprint CABIN_BLUEPRINTS[] = { 0.1, 0, 0, - 3 + 3, + 2, + 1 }, { CABIN_RECT_TWO_STORY, @@ -2218,7 +2351,53 @@ static const cabin_blueprint CABIN_BLUEPRINTS[] = { 0.08, 0, 1, - 2 + 2, + 1, + 1 + }, + { + .rects = CABIN_RECT_L_SHAPED, + .rect_count = sizeof(CABIN_RECT_L_SHAPED) / sizeof(CABIN_RECT_L_SHAPED[0]), + .wall_height = 4, .stories = 1, + .floor_block = BLOCK_OAK_PLANKS, .roof_block = BLOCK_SPRUCE_PLANKS, + .log_x_block = BLOCK_SPRUCE_LOG_X, .log_z_block = BLOCK_SPRUCE_LOG_Z, .corner_log = BLOCK_OAK_LOG, + .stair_e_block = BLOCK_SPRUCE_STAIRS_E, .stair_w_block = BLOCK_SPRUCE_STAIRS_W, + .window_block = BLOCK_GLASS_PANE, .foundation_block = BLOCK_STONE, + .log_decay = 0.025, .plank_decay = 0.12, .window_chance = 0.62, .door_decay = 0.04, + .door_rect_index = 0, .has_ladder = 0, .path_width = 3, .porch_depth = 2, .has_chimney = 1 + }, + { + .rects = CABIN_RECT_CROSS_GABLE, + .rect_count = sizeof(CABIN_RECT_CROSS_GABLE) / sizeof(CABIN_RECT_CROSS_GABLE[0]), + .wall_height = 4, .stories = 1, + .floor_block = BLOCK_SPRUCE_PLANKS, .roof_block = BLOCK_OAK_PLANKS, + .log_x_block = BLOCK_OAK_LOG_X, .log_z_block = BLOCK_OAK_LOG_Z, .corner_log = BLOCK_OAK_LOG, + .stair_e_block = BLOCK_SPRUCE_STAIRS_E, .stair_w_block = BLOCK_SPRUCE_STAIRS_W, + .window_block = BLOCK_GLASS, .foundation_block = BLOCK_STONE_BRICKS, + .log_decay = 0.015, .plank_decay = 0.08, .window_chance = 0.7, .door_decay = 0.02, + .door_rect_index = 1, .has_ladder = 0, .path_width = 3, .porch_depth = 2, .has_chimney = 1 + }, + { + .rects = CABIN_RECT_TOWER_LODGE, + .rect_count = sizeof(CABIN_RECT_TOWER_LODGE) / sizeof(CABIN_RECT_TOWER_LODGE[0]), + .wall_height = 4, .stories = 1, + .floor_block = BLOCK_SPRUCE_PLANKS, .roof_block = BLOCK_SPRUCE_PLANKS, + .log_x_block = BLOCK_OAK_LOG_X, .log_z_block = BLOCK_OAK_LOG_Z, .corner_log = BLOCK_OAK_LOG, + .stair_e_block = BLOCK_SPRUCE_STAIRS_E, .stair_w_block = BLOCK_SPRUCE_STAIRS_W, + .window_block = BLOCK_GLASS_PANE, .foundation_block = BLOCK_STONE, + .log_decay = 0.02, .plank_decay = 0.1, .window_chance = 0.72, .door_decay = 0.03, + .door_rect_index = 0, .has_ladder = 1, .path_width = 2, .porch_depth = 1, .has_chimney = 1 + }, + { + .rects = CABIN_RECT_RAMBLING, + .rect_count = sizeof(CABIN_RECT_RAMBLING) / sizeof(CABIN_RECT_RAMBLING[0]), + .wall_height = 3, .stories = 1, + .floor_block = BLOCK_OAK_PLANKS, .roof_block = BLOCK_SPRUCE_PLANKS, + .log_x_block = BLOCK_SPRUCE_LOG_X, .log_z_block = BLOCK_SPRUCE_LOG_Z, .corner_log = BLOCK_OAK_LOG, + .stair_e_block = BLOCK_SPRUCE_STAIRS_E, .stair_w_block = BLOCK_SPRUCE_STAIRS_W, + .window_block = BLOCK_GLASS_PANE, .foundation_block = BLOCK_STONE, + .log_decay = 0.035, .plank_decay = 0.16, .window_chance = 0.58, .door_decay = 0.06, + .door_rect_index = 0, .has_ladder = 0, .path_width = 2, .porch_depth = 2, .has_chimney = 1 } }; @@ -2297,7 +2476,7 @@ static int cabin_site_height_range(const cabin_blueprint *bp, int chunk_origin_x static void build_cabin_roof(const cabin_blueprint *bp, int chunk_x, int chunk_z, chunk_data *chunk, int rect_center_x, int rect_center_z, int half_w, int half_l, - int roof_base_y, rng_state *rng) { + int roof_axis, int roof_base_y, rng_state *rng) { int chunk_origin_x = chunk_x * CHUNK_SIZE; int chunk_origin_z = chunk_z * CHUNK_SIZE; int overhang = 1; @@ -2305,14 +2484,18 @@ static void build_cabin_roof(const cabin_blueprint *bp, int chunk_x, int chunk_z int x1 = rect_center_x + half_w; int z0 = rect_center_z - half_l; int z1 = rect_center_z + half_l; - int layers = half_w + 3; + int layers = (roof_axis ? half_l : half_w) + 3; for (int layer = 0; layer < layers; ++layer) { - int start_x = (x0 - overhang) + layer; - int end_x = (x1 + overhang) - layer; - if (start_x > end_x) break; + int start_cross = (roof_axis ? z0 : x0) - overhang + layer; + int end_cross = (roof_axis ? z1 : x1) + overhang - layer; + if (start_cross > end_cross) break; int y = roof_base_y + layer + 1; - for (int wz = z0 - overhang; wz <= z1 + overhang; ++wz) { - for (int wx = start_x; wx <= end_x; ++wx) { + int run0 = (roof_axis ? x0 : z0) - overhang; + int run1 = (roof_axis ? x1 : z1) + overhang; + for (int run = run0; run <= run1; ++run) { + for (int cross = start_cross; cross <= end_cross; ++cross) { + int wx = roof_axis ? run : cross; + int wz = roof_axis ? cross : run; if (rng && bp->plank_decay > 0.0 && rng_next_f64(rng) < bp->plank_decay * 0.2) continue; int lx = wx - chunk_origin_x; int lz = wz - chunk_origin_z; @@ -2320,18 +2503,19 @@ static void build_cabin_roof(const cabin_blueprint *bp, int chunk_x, int chunk_z set_block_with_height(chunk, lx, lz, y, bp->roof_block); } } - for (int wz = z0 - overhang; wz <= z1 + overhang; ++wz) { - int east_x = end_x + 1; - int west_x = start_x - 1; - int lx_e = east_x - chunk_origin_x; - int lx_w = west_x - chunk_origin_x; - int lz = wz - chunk_origin_z; - if (lx_e >= 0 && lx_e < CHUNK_SIZE && lz >= 0 && lz < CHUNK_SIZE) { - set_block_with_height(chunk, lx_e, lz, y, bp->stair_w_block); - } - if (lx_w >= 0 && lx_w < CHUNK_SIZE && lz >= 0 && lz < CHUNK_SIZE) { - set_block_with_height(chunk, lx_w, lz, y, bp->stair_e_block); - } + for (int run = run0; run <= run1; ++run) { + int wx0 = roof_axis ? run : start_cross - 1; + int wz0 = roof_axis ? start_cross - 1 : run; + int wx1 = roof_axis ? run : end_cross + 1; + int wz1 = roof_axis ? end_cross + 1 : run; + int lx0 = wx0 - chunk_origin_x, lz0 = wz0 - chunk_origin_z; + int lx1 = wx1 - chunk_origin_x, lz1 = wz1 - chunk_origin_z; + uint16_t low_stair = roof_axis ? BLOCK_SPRUCE_STAIRS_S : bp->stair_e_block; + uint16_t high_stair = roof_axis ? BLOCK_SPRUCE_STAIRS_N : bp->stair_w_block; + if (lx0 >= 0 && lx0 < CHUNK_SIZE && lz0 >= 0 && lz0 < CHUNK_SIZE) + set_block_with_height(chunk, lx0, lz0, y, low_stair); + if (lx1 >= 0 && lx1 < CHUNK_SIZE && lz1 >= 0 && lz1 < CHUNK_SIZE) + set_block_with_height(chunk, lx1, lz1, y, high_stair); } } } @@ -2375,18 +2559,7 @@ static void build_cabin_rect(worldgen_ctx *ctx, const cabin_blueprint *bp, const int x1 = rect_center_x + rect->half_w; int z0 = rect_center_z - rect->half_l; int z1 = rect_center_z + rect->half_l; - int clear_top = base_floor_y + bp->wall_height * bp->stories + 6; - - for (int wz = z0 - 2; wz <= z1 + 2; ++wz) { - for (int wx = x0 - 2; wx <= x1 + 2; ++wx) { - int lx = wx - chunk_origin_x; - int lz = wz - chunk_origin_z; - if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) continue; - for (int y = base_floor_y; y <= clear_top && y < CHUNK_HEIGHT; ++y) { - set_block_with_height(chunk, lx, lz, y, BLOCK_AIR); - } - } - } + int rect_stories = rect->stories > 0 ? rect->stories : bp->stories; uint16_t foundation_block = bp->foundation_block ? bp->foundation_block : BLOCK_STONE; for (int wz = z0; wz <= z1; ++wz) { @@ -2414,7 +2587,7 @@ static void build_cabin_rect(worldgen_ctx *ctx, const cabin_blueprint *bp, const int ladder_hole_x = INT_MIN; int ladder_hole_z = INT_MIN; - for (int story = 0; story < bp->stories; ++story) { + for (int story = 0; story < rect_stories; ++story) { int story_floor = base_floor_y + story * bp->wall_height; if (story_floor >= CHUNK_HEIGHT - 4) break; for (int wz = z0 + 1; wz <= z1 - 1; ++wz) { @@ -2521,7 +2694,7 @@ static void build_cabin_rect(worldgen_ctx *ctx, const cabin_blueprint *bp, const } } - if (bp->has_ladder && story < bp->stories - 1) { + if (bp->has_ladder && story < rect_stories - 1) { int ladder_x = rect_center_x; int ladder_z = rect_center_z; uint16_t ladder_block = BLOCK_LADDER_S; @@ -2549,8 +2722,9 @@ static void build_cabin_rect(worldgen_ctx *ctx, const cabin_blueprint *bp, const } } - int roof_base = base_floor_y + bp->wall_height * bp->stories; - build_cabin_roof(bp, chunk_x, chunk_z, chunk, rect_center_x, rect_center_z, rect->half_w, rect->half_l, roof_base, rng); + int roof_base = base_floor_y + bp->wall_height * rect_stories; + build_cabin_roof(bp, chunk_x, chunk_z, chunk, rect_center_x, rect_center_z, + rect->half_w, rect->half_l, rect->roof_axis, roof_base, rng); int attic_y = roof_base; for (int wz = z0 + 1; wz <= z1 - 1; ++wz) { @@ -2578,6 +2752,41 @@ static void build_cabin_rect(worldgen_ctx *ctx, const cabin_blueprint *bp, const update_columns_for_rect(columns, chunk_origin_x, chunk_origin_z, x0, x1, z0, z1, roof_base + 2); } +static void build_cabin_porch_and_chimney(const cabin_blueprint *bp, const cabin_rect *door_rect, + int chunk_x, int chunk_z, chunk_data *chunk, int rect_center_x, int rect_center_z, + int base_floor_y, int door_x, int door_z, int door_side) { + int origin_x = chunk_x * CHUNK_SIZE; + int origin_z = chunk_z * CHUNK_SIZE; + int out_x = door_side == 2 ? -1 : door_side == 3 ? 1 : 0; + int out_z = door_side == 0 ? -1 : door_side == 1 ? 1 : 0; + int side_x = out_z; + int side_z = out_x; + for (int depth = 1; depth <= bp->porch_depth; ++depth) { + for (int side = -1; side <= 1; ++side) { + int wx = door_x + out_x * depth + side_x * side; + int wz = door_z + out_z * depth + side_z * side; + int lx = wx - origin_x, lz = wz - origin_z; + if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) continue; + set_block_with_height(chunk, lx, lz, base_floor_y, bp->floor_block); + set_block_with_height(chunk, lx, lz, base_floor_y + 4, bp->roof_block); + if (depth == bp->porch_depth && (side == -1 || side == 1)) { + for (int y = base_floor_y + 1; y <= base_floor_y + 3; ++y) + set_block_with_height(chunk, lx, lz, y, bp->corner_log); + } + } + } + if (!bp->has_chimney) return; + int chimney_x = rect_center_x + (door_side == 2 ? door_rect->half_w - 1 : -door_rect->half_w + 1); + int chimney_z = rect_center_z + (door_side == 0 ? door_rect->half_l - 1 : -door_rect->half_l + 1); + int lx = chimney_x - origin_x, lz = chimney_z - origin_z; + if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) return; + int rect_stories = door_rect->stories > 0 ? door_rect->stories : bp->stories; + int roof_top = base_floor_y + bp->wall_height * rect_stories + + (door_rect->roof_axis ? door_rect->half_l : door_rect->half_w) + 4; + for (int y = base_floor_y + 1; y <= roof_top && y < CHUNK_HEIGHT; ++y) + set_block_with_height(chunk, lx, lz, y, BLOCK_STONE_BRICKS); +} + static int try_place_cabin(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_data *chunk, column_data columns[CHUNK_SIZE][CHUNK_SIZE], unsigned char occupancy[CHUNK_SIZE][CHUNK_SIZE], int center_x, int center_z, const cabin_blueprint *bp, rng_state *rng) { @@ -2592,6 +2801,7 @@ static int try_place_cabin(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_da int rect_z1[CABIN_MAX_RECTS]; int min_h = INT_MAX; int max_h = INT_MIN; + int max_structure_height = 0; for (size_t i = 0; i < bp->rect_count; ++i) { const cabin_rect *rect = &bp->rects[i]; int rcx = center_x + rect->offset_x; @@ -2608,6 +2818,10 @@ static int try_place_cabin(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_da rect_x1[i] = x1; rect_z0[i] = z0; rect_z1[i] = z1; + int rect_stories = rect->stories > 0 ? rect->stories : bp->stories; + int roof_rise = (rect->roof_axis ? rect->half_l : rect->half_w) + 4; + int structure_height = bp->wall_height * rect_stories + roof_rise; + if (structure_height > max_structure_height) max_structure_height = structure_height; if (rect_overlaps_mask(occupancy, chunk_origin_x, chunk_origin_z, x0, x1, z0, z1, 3)) { return 0; } @@ -2636,7 +2850,7 @@ static int try_place_cabin(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_da if (!cabin_site_height_range(bp, chunk_origin_x, chunk_origin_z, columns, center_x, center_z, 2, &pad_min_h, &pad_max_h)) return 0; if (pad_max_h - pad_min_h > 2) return 0; int base_floor_y = min_h + 1; - if (base_floor_y + bp->wall_height * bp->stories + 6 >= CHUNK_HEIGHT) return 0; + if (base_floor_y + max_structure_height >= CHUNK_HEIGHT) return 0; int door_rect_index = clamp_int(bp->door_rect_index, 0, (int)bp->rect_count - 1); int door_side = rng_range_inclusive(rng, 0, 3); @@ -2670,6 +2884,19 @@ static int try_place_cabin(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_da door_z = clamp_int(door_z + door_offset, min_door_z, max_door_z); } + /* Clear the complete compound footprint before building any wing. Clearing + * per rectangle would erase walls and roofs where wings overlap. */ + for (size_t i = 0; i < bp->rect_count; ++i) { + for (int wz = rect_z0[i] - 2; wz <= rect_z1[i] + 2; ++wz) { + for (int wx = rect_x0[i] - 2; wx <= rect_x1[i] + 2; ++wx) { + int lx = wx - chunk_origin_x, lz = wz - chunk_origin_z; + if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) continue; + for (int y = base_floor_y; y <= base_floor_y + max_structure_height && y < CHUNK_HEIGHT; ++y) + set_block_with_height(chunk, lx, lz, y, BLOCK_AIR); + } + } + } + for (size_t i = 0; i < bp->rect_count; ++i) { int has_door = (int)i == door_rect_index; build_cabin_rect(ctx, bp, &bp->rects[i], chunk_x, chunk_z, chunk, columns, @@ -2677,6 +2904,9 @@ static int try_place_cabin(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_da door_x, door_z, door_side, has_door, rng); mark_rect_occupancy(occupancy, chunk_origin_x, chunk_origin_z, rect_x0[i], rect_x1[i], rect_z0[i], rect_z1[i], 3); } + build_cabin_porch_and_chimney(bp, &bp->rects[door_rect_index], chunk_x, chunk_z, chunk, + rect_center_x[door_rect_index], rect_center_z[door_rect_index], + base_floor_y, door_x, door_z, door_side); connect_cabin_to_trail(ctx, chunk_x, chunk_z, columns, chunk, door_x, door_z, door_side, bp->path_width); @@ -2688,7 +2918,10 @@ static void generate_chunk_cabins(worldgen_ctx *ctx, int chunk_x, int chunk_z, c rng_state rng; rng_seed(&rng, seed ^ 0xCAB1A5u); double noise = simplex_noise2(&ctx->noise, (chunk_x * CHUNK_SIZE + 5000) * 0.0006, (chunk_z * CHUNK_SIZE - 5000) * 0.0006) * 0.5 + 0.5; - double spawn_chance = 0.015 + noise * 0.02; + int chunk_center_x = chunk_x * CHUNK_SIZE + CHUNK_SIZE / 2; + int chunk_center_z = chunk_z * CHUNK_SIZE + CHUNK_SIZE / 2; + double community = woodland_community_strength(ctx, chunk_center_x, chunk_center_z); + double spawn_chance = 0.008 + noise * 0.015 + smoothstep01((community - 0.48) / 0.34) * 0.085; if (ctx->enable_trails && !chunk_contains_trail(ctx, chunk_x, chunk_z)) { return; /* only spawn cabins where a road crosses the chunk, so the spur can hook in */ } @@ -2725,6 +2958,7 @@ static void generate_chunk_cabins(worldgen_ctx *ctx, int chunk_x, int chunk_z, c int world_cx = chunk_x * CHUNK_SIZE + local_cx; int world_cz = chunk_z * CHUNK_SIZE + local_cz; double score = land_value(ctx, world_cx, world_cz); + score += woodland_community_strength(ctx, world_cx, world_cz) * 0.35; int flat_min_h = INT_MAX; int flat_max_h = INT_MIN; if (!cabin_site_height_range(bp, chunk_x * CHUNK_SIZE, chunk_z * CHUNK_SIZE, columns, @@ -2956,6 +3190,35 @@ static int column_has_manmade_surface(chunk_data *chunk, int chunk_x, int chunk_ return 0; } +static void tree_grid_candidate(worldgen_ctx *ctx, int gx, int gz, int grid, int *out_x, int *out_z) { + uint64_t seed = (uint64_t)ctx->world_seed + (uint64_t)gx * 341873128712ULL + (uint64_t)gz * 132897987541ULL; + rng_state rng; + rng_seed(&rng, seed); + *out_x = gx * grid + rng_range_inclusive(&rng, 0, grid - 1); + *out_z = gz * grid + rng_range_inclusive(&rng, 0, grid - 1); +} + +/* Matérn-style priority thinning gives deterministic, irregular spacing and + * prevents adjacent grid cells from producing colliding crowns. */ +static int tree_wins_spacing(worldgen_ctx *ctx, int gx, int gz, int grid, int x, int z, double spacing) { + int reach = (int)ceil(spacing / (double)grid) + 1; + uint32_t priority = hash_coords(gx, gz, (uint32_t)ctx->world_seed ^ 0x71EE5A9Du); + double spacing2 = spacing * spacing; + for (int ox = -reach; ox <= reach; ++ox) { + for (int oz = -reach; oz <= reach; ++oz) { + if (ox == 0 && oz == 0) continue; + int ngx = gx + ox, ngz = gz + oz; + int nx, nz; + tree_grid_candidate(ctx, ngx, ngz, grid, &nx, &nz); + double dx = (double)(nx - x), dz = (double)(nz - z); + if (dx * dx + dz * dz >= spacing2) continue; + uint32_t other = hash_coords(ngx, ngz, (uint32_t)ctx->world_seed ^ 0x71EE5A9Du); + if (other < priority || (other == priority && (ngx < gx || (ngx == gx && ngz < gz)))) return 0; + } + } + return 1; +} + static void generate_chunk_trees(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_data *chunk, block_list *out) { const int grid = 5; const int margin = 4; @@ -2981,8 +3244,8 @@ static void generate_chunk_trees(worldgen_ctx *ctx, int chunk_x, int chunk_z, ch rng_state rng; rng_seed(&rng, seed); - int candidate_x = gx * grid + rng_range_inclusive(&rng, 0, grid - 1); - int candidate_z = gz * grid + rng_range_inclusive(&rng, 0, grid - 1); + int candidate_x, candidate_z; + tree_grid_candidate(ctx, gx, gz, grid, &candidate_x, &candidate_z); if (candidate_x < chunk_min_x || candidate_x > chunk_max_x || candidate_z < chunk_min_z || candidate_z > chunk_max_z) { continue; @@ -3024,21 +3287,32 @@ static void generate_chunk_trees(worldgen_ctx *ctx, int chunk_x, int chunk_z, ch } if (altitude_factor <= 0.0) continue; + double base_cover = tree_density_mask(ctx, candidate_x, candidate_z); + double clearing = forest_clearing_mask(ctx, candidate_x, candidate_z); + double cover = clamp01(base_cover * (1.0 - clearing * 0.92)); + double maturity = forest_maturity_mask(ctx, candidate_x, candidate_z, cover); double spawn_prob; if (is_redwood) { double rain = rainfall_field(ctx, candidate_x, candidate_z); spawn_prob = clamp01(0.25 + stand_density * 0.65 + (rain - 0.5) * 0.2) * altitude_factor; + maturity = clamp01(0.55 + stand_density * 0.45); } else { - double density = tree_density_mask(ctx, candidate_x, candidate_z); + double density = cover; if (is_old_growth) { density = clamp01(0.25 + grove_mask * 0.75); + maturity = clamp01(0.72 + grove_mask * 0.28); } - spawn_prob = 0.7 * (0.55 + 0.45 * density) * altitude_factor; + /* Openings retain a few isolated field trees, while forest interiors + * become substantially denser than their regenerating edges. */ + spawn_prob = (0.035 + density * 0.78) * altitude_factor; if (is_old_growth) { spawn_prob = clamp01(0.25 + grove_mask * 0.7) * altitude_factor; } } if (rng_next_f64(&rng) > spawn_prob) continue; + double spacing = is_redwood ? (10.0 + (1.0 - stand_density) * 4.0) + : (4.5 + (1.0 - cover) * 4.5 + maturity * 1.5); + if (!tree_wins_spacing(ctx, gx, gz, grid, candidate_x, candidate_z, spacing)) continue; block_list tmp; block_list_init(&tmp); @@ -3052,7 +3326,7 @@ static void generate_chunk_trees(worldgen_ctx *ctx, int chunk_x, int chunk_z, ch block_list_free(&tmp); continue; } - generate_tree(ctx, arch, candidate_x, base_y, candidate_z, &rng, &tmp); + generate_tree(ctx, arch, candidate_x, base_y, candidate_z, maturity, &rng, &tmp); if (tmp.count == 0) { block_list_free(&tmp); continue; @@ -3109,6 +3383,34 @@ static uint32_t trail_segment_hash(int ax, int az, int bx, int bz, uint32_t seed return hash_coords(mix_x, mix_z, seed); } +typedef struct { + double valley_depth; + double ridge_exposure; + double gradient_x; + double gradient_z; +} trail_landform; + +/* Read the broad landform rather than reacting to individual blocks. This + * distinguishes sheltered valley floors from ridges and steep side slopes. */ +static trail_landform sample_trail_landform(worldgen_ctx *ctx, int x, int z, double height) { + const int r = TRAIL_LANDFORM_RADIUS; + double east = (double)column_height(ctx, x + r, z); + double west = (double)column_height(ctx, x - r, z); + double south = (double)column_height(ctx, x, z + r); + double north = (double)column_height(ctx, x, z - r); + double se = (double)column_height(ctx, x + r, z + r); + double nw = (double)column_height(ctx, x - r, z - r); + double ne = (double)column_height(ctx, x + r, z - r); + double sw = (double)column_height(ctx, x - r, z + r); + double mean = (east + west + south + north + se + nw + ne + sw) / 8.0; + trail_landform land; + land.valley_depth = mean - height; + land.ridge_exposure = fmax(0.0, height - mean); + land.gradient_x = (east - west) / (2.0 * r); + land.gradient_z = (south - north) / (2.0 * r); + return land; +} + static int should_connect_trail_nodes(worldgen_ctx *ctx, int node_x0, int node_z0, int node_x1, int node_z1) { if (node_x0 == node_x1 && node_z0 == node_z1) return 0; uint32_t seg_hash = trail_segment_hash(node_x0, node_z0, node_x1, node_z1, (uint32_t)ctx->world_seed ^ 0xB37D4A91u); @@ -3120,9 +3422,10 @@ static int should_connect_trail_nodes(worldgen_ctx *ctx, int node_x0, int node_z int mid_ix = (int)llround(mid_x); int mid_iz = (int)llround(mid_z); column_data mid_column = get_column_data(ctx, mid_ix, mid_iz); - double ridge_bias = clamp01((double)(mid_column.height - ctx->sea_level) / 40.0); + trail_landform midpoint_land = sample_trail_landform(ctx, mid_ix, mid_iz, (double)mid_column.height); + double valley_bias = clamp01((midpoint_land.valley_depth + 4.0) / 16.0); if (mid_column.has_water && mid_column.height <= mid_column.water_surface) { - ridge_bias -= 0.35; + valley_bias -= 0.35; } double dir_x = bx - ax; double dir_z = bz - az; @@ -3167,7 +3470,7 @@ static int should_connect_trail_nodes(worldgen_ctx *ctx, int node_x0, int node_z base += 0.07; } base += (alignment - 0.5) * 0.2; - base += (ridge_bias - 0.5) * 0.15; + base += (valley_bias - 0.5) * 0.15; double texture = simplex_noise2(&ctx->noise, mid_x * 0.0012, mid_z * 0.0012) * 0.5 + 0.5; base += (texture - 0.5) * 0.15; if (base < 0.02) base = 0.02; @@ -3321,6 +3624,12 @@ static int build_trail_path(worldgen_ctx *ctx, double ax, double az, double bx, if (grid_w < 2) grid_w = 2; if (grid_h < 2) grid_h = 2; int total = grid_w * grid_h; + double route_dx = bx - ax; + double route_dz = bz - az; + double route_length = sqrt(route_dx * route_dx + route_dz * route_dz); + /* Long inter-settlement roads are engineered more conservatively than + * short cabin spurs, which may accept steeper and more direct approaches. */ + double road_scale = clamp01((route_length - 240.0) / 900.0); double *heights = (double *)malloc((size_t)total * sizeof(double)); if (!heights) return 0; for (int gz = 0; gz < grid_h; ++gz) { @@ -3381,14 +3690,23 @@ static int build_trail_path(worldgen_ctx *ctx, double ax, double az, double bx, int ni = nz * grid_w + nx; if (used[ni]) continue; double next_height = heights[ni]; - double slope = fabs(next_height - current_height); - if (slope > 3.0) continue; int wx = (int)llround(min_x + nx * TRAIL_CELL_SIZE); int wz = (int)llround(min_z + nz * TRAIL_CELL_SIZE); column_data cd = get_column_data(ctx, wx, wz); + double water_penalty = 0.0; if (cd.has_water && next_height <= cd.water_surface) { - continue; /* trails should never be routed through water */ + if (!small_community_water_crossing(ctx, wx, wz, neighbors[n][0], neighbors[n][1])) continue; + next_height = (double)cd.water_surface + 1.0; + water_penalty = 8.0; /* worthwhile only when it materially shortens a community road */ } + int current_wx = (int)llround(min_x + cx * TRAIL_CELL_SIZE); + int current_wz = (int)llround(min_z + cz * TRAIL_CELL_SIZE); + column_data current_cd = get_column_data(ctx, current_wx, current_wz); + if (current_cd.has_water && current_cd.height < current_cd.water_surface) + current_height = (double)current_cd.water_surface + 1.0; + double slope = fabs(next_height - current_height); + double max_step = 4.0 - road_scale; + if (slope > max_step) continue; double tree_penalty = 0.0; if (cd.biome == BIOME_REDWOOD_FOREST) { double stand = redwood_tree_presence(ctx, wx, wz); @@ -3396,7 +3714,20 @@ static int build_trail_path(worldgen_ctx *ctx, double ax, double az, double bx, tree_penalty = stand; } double base_cost = (neighbors[n][0] == 0 || neighbors[n][1] == 0) ? 1.0 : 1.41421356237; - double cost = base_cost * (1.0 + slope * 6.0 + tree_penalty * 4.5) + tree_penalty * 6.5; + trail_landform land = sample_trail_landform(ctx, wx, wz, next_height); + double valley_bonus = clamp01(land.valley_depth / 18.0) * (0.4 + road_scale * 0.2); + double ridge_penalty = clamp01(land.ridge_exposure / 22.0) * (1.4 + road_scale * 1.8); + double move_x = (double)neighbors[n][0] / base_cost; + double move_z = (double)neighbors[n][1] / base_cost; + double contour_climb = fabs(move_x * land.gradient_x + move_z * land.gradient_z); + double cross_relief = fabs(-move_z * land.gradient_x + move_x * land.gradient_z) * + (2.0 * TRAIL_LANDFORM_RADIUS); + double contour_penalty = contour_climb * (5.0 + road_scale * 5.0); + double earthwork_penalty = clamp01((cross_relief - 10.0) / 28.0) * (0.8 + road_scale * 1.2); + double grade_penalty = slope * (5.5 + road_scale * 3.5); + double terrain_cost = 1.0 + grade_penalty + ridge_penalty + contour_penalty + + earthwork_penalty + tree_penalty * 4.5 + water_penalty; + double cost = base_cost * terrain_cost * (1.0 - valley_bonus) + tree_penalty * 6.5; if (dist[current] + cost < dist[ni]) { dist[ni] = dist[current] + cost; prev[ni] = current; @@ -3556,6 +3887,26 @@ static void place_trail_column(chunk_data *out, column_data columns[CHUNK_SIZE][ columns[local_x][local_z].height = target_height; } +static void place_trail_bridge_column(chunk_data *out, column_data columns[CHUNK_SIZE][CHUNK_SIZE], + int chunk_x, int chunk_z, int world_x, int world_z, int deck_y, int railing) { + int lx = world_x - chunk_x * CHUNK_SIZE; + int lz = world_z - chunk_z * CHUNK_SIZE; + if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) return; + if (deck_y < 2 || deck_y >= CHUNK_HEIGHT - 4) return; + for (int y = 1; y < CHUNK_HEIGHT; ++y) + if (is_cabin_structure_block(out->blocks[y][lx][lz])) return; + out->blocks[deck_y][lx][lz] = BLOCK_SPRUCE_PLANKS; + for (int y = deck_y + 1; y <= deck_y + 3; ++y) out->blocks[y][lx][lz] = BLOCK_AIR; + uint32_t support_hash = hash_coords(world_x, world_z, 0xB21D6E5u); + if ((support_hash % 5u) == 0u) { + int bed = columns[lx][lz].height; + for (int y = bed + 1; y < deck_y; ++y) out->blocks[y][lx][lz] = BLOCK_OAK_LOG; + } + if (railing && (support_hash % 3u) == 0u) out->blocks[deck_y + 1][lx][lz] = BLOCK_OAK_LOG; + columns[lx][lz].height = deck_y; + out->heightmap[lx][lz] = (uint16_t)(deck_y + (railing ? 1 : 0)); +} + static void carve_trail_pad(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_data *out, column_data columns[CHUNK_SIZE][CHUNK_SIZE], int center_x, int center_z, int radius, int target_height) { if (radius < 1) radius = 1; (void)ctx; @@ -3565,6 +3916,8 @@ static void carve_trail_pad(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_d if (dx * dx + dz * dz > radius_sq) continue; int wx = center_x + dx; int wz = center_z + dz; + column_data data = get_column_data(ctx, wx, wz); + if (data.has_water && data.height < data.water_surface) continue; place_trail_column(out, columns, chunk_x, chunk_z, wx, wz, target_height); } } @@ -3596,7 +3949,8 @@ static void carve_trail_span(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_ int wx = (int)llround(px); int wz = (int)llround(pz); column_data data = get_column_data(ctx, wx, wz); - int target = data.height; + int is_bridge = small_community_water_crossing(ctx, wx, wz, x1 - x0, z1 - z0); + int target = is_bridge ? data.water_surface + 1 : data.height; if (last_height != INT32_MIN) { double blended = carry_weight * (double)last_height + (1.0 - carry_weight) * (double)target; target = (int)llround(blended); @@ -3614,6 +3968,15 @@ static void carve_trail_span(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_ double oz = pz + nz * w; int fx = (int)llround(ox); int fz = (int)llround(oz); + column_data cross_data = get_column_data(ctx, fx, fz); + if (cross_data.has_water && cross_data.height < cross_data.water_surface) { + if (small_community_water_crossing(ctx, fx, fz, x1 - x0, z1 - z0)) { + int edge = fabs(w) >= half_width - 0.55; + place_trail_bridge_column(out, columns, chunk_x, chunk_z, fx, fz, + cross_data.water_surface + 1, edge); + } + continue; + } place_trail_column(out, columns, chunk_x, chunk_z, fx, fz, target); } } diff --git a/worldgen-c/tools/worldgen_scan.c b/worldgen-c/tools/worldgen_scan.c index 328606a..5094b94 100644 --- a/worldgen-c/tools/worldgen_scan.c +++ b/worldgen-c/tools/worldgen_scan.c @@ -10,6 +10,7 @@ static int is_cabin_block(uint16_t block) { block == BLOCK_OAK_LOG_X || block == BLOCK_OAK_LOG_Z || block == BLOCK_SPRUCE_LOG_X || block == BLOCK_SPRUCE_LOG_Z || block == BLOCK_GLASS_PANE || block == BLOCK_GLASS || block == BLOCK_SPRUCE_STAIRS_E || block == BLOCK_SPRUCE_STAIRS_W || + block == BLOCK_SPRUCE_STAIRS_N || block == BLOCK_SPRUCE_STAIRS_S || block == BLOCK_LADDER_N || block == BLOCK_LADDER_S || block == BLOCK_LADDER_E || block == BLOCK_LADDER_W || block == BLOCK_SPRUCE_DOOR_N_LOWER || block == BLOCK_SPRUCE_DOOR_N_UPPER || block == BLOCK_SPRUCE_DOOR_S_LOWER || block == BLOCK_SPRUCE_DOOR_S_UPPER || @@ -78,6 +79,8 @@ int main(int argc, char **argv) { long cabin_columns = 0; long gravel_columns = 0; long cabin_gravel_columns = 0; + long bridge_deck_columns = 0; + long eligible_bridge_columns = 0; long tall_log_columns = 0; long max_oak_log_run = 0; long biome_columns[4] = {0, 0, 0, 0}; @@ -96,8 +99,11 @@ int main(int argc, char **argv) { if (biome >= 0 && biome < 4) ++biome_columns[biome]; double redwood_mask = worldgen_debug_redwood_mask(&ctx, wx, wz); if (redwood_mask > max_redwood_mask) max_redwood_mask = redwood_mask; + if (worldgen_debug_bridge_site(&ctx, wx, wz, 1, 0) || + worldgen_debug_bridge_site(&ctx, wx, wz, 0, 1)) ++eligible_bridge_columns; int has_cabin = 0; int has_gravel = 0; + int has_bridge_deck = 0; int current_run = 0; int best_run = 0; for (int y = 1; y < CHUNK_HEIGHT; ++y) { @@ -109,6 +115,8 @@ int main(int argc, char **argv) { if (block == BLOCK_GRAVEL) { has_gravel = 1; } + if (block == BLOCK_SPRUCE_PLANKS && y > 1 && chunk.blocks[y - 1][lx][lz] == BLOCK_WATER) + has_bridge_deck = 1; if (block == BLOCK_OAK_LOG) { ++current_run; if (current_run > best_run) best_run = current_run; @@ -120,6 +128,7 @@ int main(int argc, char **argv) { if (best_run >= 40) ++tall_log_columns; if (has_cabin) ++cabin_columns; if (has_gravel) ++gravel_columns; + if (has_bridge_deck) ++bridge_deck_columns; if (has_cabin && has_gravel) { ++cabin_gravel_columns; } @@ -133,6 +142,8 @@ int main(int argc, char **argv) { printf("cabin_columns=%ld\n", cabin_columns); printf("gravel_columns=%ld\n", gravel_columns); printf("cabin_gravel_columns=%ld\n", cabin_gravel_columns); + printf("bridge_deck_columns=%ld\n", bridge_deck_columns); + printf("eligible_bridge_columns=%ld\n", eligible_bridge_columns); printf("tall_oak_log_columns=%ld\n", tall_log_columns); printf("max_oak_log_run=%ld\n", max_oak_log_run); printf("biome_west_ky=%ld\n", biome_columns[0]);