Skip to content
Open
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 .claude/skills/new-delve/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,13 @@ Symptom → tool:
- **Terrain/visual fixes beyond swapping prefabs**: `delvec edit` — the
spec-0017 map editor loop (edit script batch → replay → snapshot). Never
hand-patch `.nbt` or invent block edits outside it.
- **Ground the campaign means to sit at the waterline** (an ocean `horizon`
shoreline that wades, a bank rolled down past sea level): the stage-7 `flood`
verb (spec-0030, world-edits `dsl_version` 0.9.0) — declare the envelope the
ambient sea is admitted into and the compiler computes and builds the reach.
It is a claim, not a switch: `DW0364` still refuses every cell the sea does
not reach, an envelope the water never enters is `DW0394`, and water that
runs on past it is `DW0395`. Never author water by hand to clear a `DW0364`.
- **Handing a build to the owner to play**: mention the playtest note flow
(spec-0006: `/trigger dw.note` in-game, then `delve-harvest` →
`playtest-report.json`) — one line, human-optional.
Expand Down
298 changes: 298 additions & 0 deletions crates/compiler/src/edit.rs

Large diffs are not rendered by default.

75 changes: 50 additions & 25 deletions crates/compiler/src/emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,23 @@ pub fn build_with_warnings(
// spec-0016 §7 pacing lints, filled in by the nav stage below.
let mut pacing: Vec<delvewright_dsl::Diagnostic> = Vec::new();

// spec-0026 flood-level proof (DW0364): in a horizon with a flood level
// (ocean), every standable cell of every placed piece must sit above it —
// empirical assembled geometry, no `waterline_y` exemption (the #149
// class). Runs off the EDITED model when an edit script exists, since a
// stage-7 batch can carve floor. No-op (and no occupancy model built) for
// a floodless horizon, so void/flatland builds are untouched.
if crate::horizon::flood_level(&crate::horizon::of_campaign(plan.campaign)).is_some() {
let world = match &edit_replay {
Some(er) => crate::nav::World::from_occupancy(crate::assembled::occupancy_of(
er.assembled.blocks.clone(),
&er.assembled.open_gates,
)),
None => crate::nav::World::from_plan(plan, structures),
};
crate::nav::check_flood_level(plan, &world)?;
}

// spec-0021 container proof (DW0431). Runs off the assembled world — over
// the EDITED model when an edit script exists, since a stage-7 batch can
// legitimately be what puts the barrel there. Independent of nav, because a
Expand Down Expand Up @@ -15242,22 +15259,20 @@ fn emit_server(plan: &Plan, out: &mut BuildOutput) {
"easy"
}
});
// Horizon (DSL v0.6, spec-0013). `void` (default/absent) keeps the empty-layer
// superflat + `the_void` biome, byte-identical to v0.5. `ocean` swaps in a
// pinned bedrock/stone/water superflat: from the -64 build floor, 1+118+8
// layers top the water at y=62 (= sea level); areas are placed on that datum
// (`plan::OCEAN_BASE_Y` = 60) so island pieces read as land ringed by the sea. No structures (generate-structures=false) or mobs (gamerule
// spawn_mobs false); the sea is pure backdrop. The string is a fixed literal,
// so both horizons stay deterministic (ADR-0006).
let ocean = matches!(
plan.campaign.world.content.horizon,
Some(delvewright_dsl::Horizon::Ocean)
);
let generator_settings = if ocean {
"{\"biome\":\"minecraft:ocean\",\"layers\":[{\"block\":\"minecraft:bedrock\",\"height\":1},{\"block\":\"minecraft:stone\",\"height\":118},{\"block\":\"minecraft:water\",\"height\":8}]}"
} else {
"{\"biome\":\"minecraft:the_void\",\"layers\":[]}"
};
// Horizon (DSL v0.6 spec-0013; horizon library spec-0026). `void`
// (default/absent) keeps the empty-layer superflat + `the_void` biome,
// byte-identical to v0.5. `ocean` swaps in a pinned bedrock/stone/water
// superflat (1+118+8 layers from the -64 floor, water top y=62 = sea
// level); `flatland` a pinned bedrock/dirt/grass superflat (1+126+1
// layers, grass top y=63 — one block under the scene walk plane by the §2
// datum equation), `minecraft:plains` biome for vanilla's own grass tint.
// Areas are placed on the per-area datum (`plan::area_base_y`:
// walk_ref_y − the tileset's declared walk_y). No structures
// (generate-structures=false) or mobs (gamerule spawn_mobs false); the
// ambient is pure backdrop. The strings are fixed literals in
// `crate::horizon`, so every horizon stays deterministic (ADR-0006).
let horizon = crate::horizon::of_campaign(plan.campaign);
let generator_settings = crate::horizon::generator_settings(&horizon);
// server.properties (keys sorted for determinism).
let props: BTreeMap<&str, String> = BTreeMap::from([
("allow-nether", "false".to_string()),
Expand All @@ -15279,12 +15294,14 @@ fn emit_server(plan: &Plan, out: &mut BuildOutput) {
"# Generated by delvec for campaign {} (spec-0002 world strategy).\n",
plan.namespace
));
if ocean {
text.push_str(
match horizon.base {
delvewright_dsl::HorizonBase::Ocean => text.push_str(
"# Ocean superflat (spec-0013 backdrop) + fixed seed; created on first boot.\n",
);
} else {
text.push_str("# Void/superflat + fixed seed; the world is created on first boot.\n");
),
delvewright_dsl::HorizonBase::Flatland => text.push_str(
"# Flatland grass superflat (spec-0026 horizon) + fixed seed; created on first boot.\n",
),
_ => text.push_str("# Void/superflat + fixed seed; the world is created on first boot.\n"),
}
for (k, v) in &props {
text.push_str(&format!("{k}={v}\n"));
Expand All @@ -15299,12 +15316,20 @@ The server jar is NOT shipped (ADR-0010); it is fetched by version at run time.\
.to_vec(),
);

let horizon_bullet = if ocean {
"- `level-type=minecraft:flat` + a pinned bedrock/stone/water `generator-settings`\n\
let horizon_bullet = match horizon.base {
delvewright_dsl::HorizonBase::Ocean => {
"- `level-type=minecraft:flat` + a pinned bedrock/stone/water `generator-settings`\n\
(sea level y=62, `minecraft:ocean` biome) ⇒ an island backdrop (spec-0013).\n"
} else {
"- `level-type=minecraft:flat` + `generator-settings` with an empty layer list and\n\
}
delvewright_dsl::HorizonBase::Flatland => {
"- `level-type=minecraft:flat` + a pinned bedrock/dirt/grass `generator-settings`\n\
(grass top y=63, `minecraft:plains` biome) ⇒ a bare grass plain flush with the\n\
scene walk plane (spec-0026).\n"
}
_ => {
"- `level-type=minecraft:flat` + `generator-settings` with an empty layer list and\n\
the `minecraft:the_void` biome ⇒ a void world.\n"
}
};
out.insert(
"server/README.md".to_string(),
Expand Down
184 changes: 184 additions & 0 deletions crates/compiler/src/horizon.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
//! The horizon model (spec-0026): one place where a campaign's resolved
//! stage-1 `horizon` becomes **declared physical facts** the rest of the
//! compiler reads — the placement datum, the flood level, the analytic ambient
//! and the emitted world generator. Nothing outside this module matches on
//! [`HorizonBase`] to decide world physics.
//!
//! ## The world model (spec-0026 §2)
//!
//! Each horizon declares:
//!
//! - **`walk_ref_y`** — the world y a placed piece's *walk plane* must land at.
//! The per-area placement datum is `walk_ref_y − walk_y`, where `walk_y` is
//! the tileset walk-plane convention declared in prefab metadata
//! ([`crate::registry::PrefabMeta::walk_y`]; missing in a non-void horizon is
//! `DW0367`, `crate::plan`). This supersedes spec-0013's single global
//! `OCEAN_BASE_Y` datum — the datum that made the #149 flooded-interior class
//! possible (an island-tileset constant applied to every tileset).
//! - **`flood_level`** — the world y at or below which a standable cell is
//! under the ambient water. Proved *empirically* against the assembled
//! geometry after placement (`DW0364`, [`crate::nav::check_flood_level`]) —
//! declarations position, proofs read reality.
//! - **ambient** — what a column the compiler modelled nothing into actually
//! contains ([`crate::nav::Ambient`]); the premise of the `DW0322`
//! boundary-safety proof.
//! - the emitted `server.properties` world generator (`level-type` +
//! `generator-settings`), the one channel the toolserver/delve images
//! consume (task #84 parity).
//!
//! ## Plug-in contract for surround generators (W-B/W-C slices)
//!
//! A surround-bearing base (`valley`, `summit`, the flatland seam band, the
//! sky archipelago solver hooks) plugs in here, not across the codebase:
//!
//! 1. implement the generator in its **own module** (e.g. `crate::valley`),
//! consuming the [`delvewright_dsl::ResolvedHorizon`] params and the
//! campaign seed, and emitting placed prefab tiles like any other piece;
//! 2. flip that base's arm in [`base_implemented`] to `true` — the DSL-layer
//! reserved-base rejection (`DW0141`, `delvewright_dsl::validate`) keys off
//! the same slice status and is deleted in the same PR;
//! 3. extend [`generator_settings`] / [`walk_ref_y`] / `nav::Ambient` with the
//! base's ambient facts (one `match` arm each — the compiler refuses to
//! build an unimplemented base long before those arms are reachable).
//!
//! Everything else (per-area datum, DW0364/DW0367, server emission plumbing)
//! is already base-generic and needs no edits.

use delvewright_dsl::{Campaign, HorizonBase, ResolvedHorizon, resolved_horizon};

/// The resolved horizon of a campaign (defaults applied; `void` when absent).
pub fn of_campaign(campaign: &Campaign) -> ResolvedHorizon {
resolved_horizon(&campaign.world.content.horizon)
}

/// The resolved base of a campaign's horizon.
pub fn base_of(campaign: &Campaign) -> HorizonBase {
of_campaign(campaign).base
}

/// Whether this delvec slice implements the base end-to-end (spec-0026
/// foundation: `void`, `ocean`, `flatland`). The DSL validation layer rejects
/// the others (`DW0141` reserved), so compiler paths may treat an
/// unimplemented base as unreachable-after-validation.
pub fn base_implemented(base: HorizonBase) -> bool {
match base {
HorizonBase::Void | HorizonBase::Ocean | HorizonBase::Flatland => true,
HorizonBase::Sky | HorizonBase::Valley | HorizonBase::Summit => false,
}
}

/// Sea level of the `ocean` horizon superflat (spec-0013): the pinned
/// bedrock/stone/water layer stack (1 + 118 + 8 from the -64 build floor) tops
/// the water at y=62. Emission pins the same stack in `generator-settings`.
pub const SEA_LEVEL: i32 = 62;

/// Height of the `ocean` horizon superflat's water layer (spec-0013) — the `8`
/// in the pinned `generator-settings` stack emission writes
/// (`emit::emit_server`). Ambient water occupies `SEA_LEVEL - 7 ..= SEA_LEVEL`.
pub const OCEAN_WATER_LAYERS: i32 = 8;

/// Y of the topmost ambient **solid** block of the `ocean` horizon superflat:
/// the sea floor (stone) directly under the water layers, at 54. The ambient
/// model boundary safety reasons about (`nav::Sea`) starts here — below it the
/// world is stone all the way to bedrock, which is why an ocean world has no
/// void column anywhere.
pub const SEA_FLOOR_TOP_Y: i32 = SEA_LEVEL - OCEAN_WATER_LAYERS;

/// Y of the `flatland` horizon's grass surface (spec-0026 §1): the pinned
/// bedrock/dirt/grass superflat (1 + 126 + 1 layers from the -64 build floor)
/// tops its grass at y=63 — **exactly one block under the scene walk plane**
/// ([`FLATLAND_WALK_REF_Y`]), the §3 zero-height-difference seam by datum
/// equation, never by blending.
pub const FLATLAND_SURFACE_Y: i32 = 63;

/// The `ocean` walk reference (spec-0026 §2): sea level + 1, the vanilla-normal
/// beach relationship — a walk plane one block above the top water block.
pub const OCEAN_WALK_REF_Y: i32 = SEA_LEVEL + 1;

/// The `flatland` walk reference: the grass surface + 1. Numerically equal to
/// `plan::BASE_Y` (64), so a walk_y=0 tileset sits exactly where `void` puts
/// every area — flatland relocates nothing, it just fills the world in.
pub const FLATLAND_WALK_REF_Y: i32 = FLATLAND_SURFACE_Y + 1;

/// The world y a placed piece's walk plane must land at under this horizon
/// (spec-0026 §2), or `None` for `void` — the one base with no physical
/// reference plane, where areas keep the historical `plan::BASE_Y` origin.
///
/// `sky` reads its `float_y` param; `valley`/`summit` derive from their
/// surround geometry and land with their generator slices (unreachable behind
/// [`base_implemented`] until then).
pub fn walk_ref_y(h: &ResolvedHorizon) -> Option<i32> {
match h.base {
HorizonBase::Void => None,
HorizonBase::Ocean => Some(OCEAN_WALK_REF_Y),
HorizonBase::Flatland => Some(FLATLAND_WALK_REF_Y),
HorizonBase::Sky => Some(h.float_y),
// Valley: gap floor + 1; Summit: plateau top + 1. The gap floor /
// plateau shell land with their surround generators (W-B/W-C); until
// then validation refuses these bases (`base_implemented`).
HorizonBase::Valley | HorizonBase::Summit => None,
}
}

/// The world y at or below which a standable cell is flooded by the ambient
/// (spec-0026 §2 hazard facts): `ocean` 62, every other base none. Proved
/// empirically by `DW0364` (`crate::nav::check_flood_level`) with **no
/// waterline exemption** — the check reads assembled geometry, never metadata.
pub fn flood_level(h: &ResolvedHorizon) -> Option<i32> {
match h.base {
HorizonBase::Ocean => Some(SEA_LEVEL),
_ => None,
}
}

/// The pinned `generator-settings` JSON for the horizon's ambient superflat
/// (fixed literals — deterministic emission, ADR-0006). The `void` literal is
/// byte-identical to every pre-0.6 campaign's; `ocean` to every v0.6 one.
pub fn generator_settings(h: &ResolvedHorizon) -> &'static str {
match h.base {
HorizonBase::Ocean => {
"{\"biome\":\"minecraft:ocean\",\"layers\":[{\"block\":\"minecraft:bedrock\",\"height\":1},{\"block\":\"minecraft:stone\",\"height\":118},{\"block\":\"minecraft:water\",\"height\":8}]}"
}
HorizonBase::Flatland => {
"{\"biome\":\"minecraft:plains\",\"layers\":[{\"block\":\"minecraft:bedrock\",\"height\":1},{\"block\":\"minecraft:dirt\",\"height\":126},{\"block\":\"minecraft:grass_block\",\"height\":1}]}"
}
// `sky` will emit its declared backdrop's generator (spec-0026 §4, the
// sky slice); until then it is unreachable behind `base_implemented`.
// `valley`/`summit` ambients are void below their tile skirts.
HorizonBase::Void | HorizonBase::Sky | HorizonBase::Valley | HorizonBase::Summit => {
"{\"biome\":\"minecraft:the_void\",\"layers\":[]}"
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use delvewright_dsl::horizon_defaults;

/// The flatland datum equation (spec-0026 §3): grass top is exactly one
/// block under the walk reference, and the layer arithmetic agrees with
/// the emitted literal (1 + 126 + 1 layers from the −64 floor → top 63).
#[test]
fn flatland_datum_equation() {
assert_eq!(FLATLAND_SURFACE_Y, -64 + 1 + 126 + 1 - 1);
assert_eq!(FLATLAND_WALK_REF_Y, FLATLAND_SURFACE_Y + 1);
assert_eq!(FLATLAND_WALK_REF_Y, crate::plan::BASE_Y);
}

/// The ocean facts are the spec-0013 pins, unchanged by the datum rework.
#[test]
fn ocean_facts_unchanged() {
let h = ResolvedHorizon::of_base(HorizonBase::Ocean);
assert_eq!(walk_ref_y(&h), Some(63));
assert_eq!(flood_level(&h), Some(62));
assert_eq!(SEA_FLOOR_TOP_Y, 54);
}

/// Sky's walk reference is its `float_y` param (default 160).
#[test]
fn sky_walk_ref_is_float_y() {
let h = ResolvedHorizon::of_base(HorizonBase::Sky);
assert_eq!(walk_ref_y(&h), Some(horizon_defaults::FLOAT_Y));
}
}
1 change: 1 addition & 0 deletions crates/compiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ pub mod edit;
pub mod emit;
pub mod flow;
pub mod gates;
pub mod horizon;
pub mod integrity;
pub mod light;
pub mod load;
Expand Down
4 changes: 2 additions & 2 deletions crates/compiler/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -963,8 +963,8 @@ fn edited_assembled(
/// The `ocean`-horizon sea level to draw as a background plane, or `None` for a
/// `void`-horizon campaign (see `snapshot::SEA_PLANE_NOTE`).
fn sea_level_of(campaign: &delvewright_dsl::Campaign) -> Option<i32> {
match campaign.world.content.horizon {
Some(delvewright_dsl::Horizon::Ocean) => Some(delvewright_compiler::plan::SEA_LEVEL),
match delvewright_compiler::horizon::base_of(campaign) {
delvewright_dsl::HorizonBase::Ocean => Some(delvewright_compiler::plan::SEA_LEVEL),
_ => None,
}
}
Expand Down
Loading
Loading