From e3fcf96cb813bba065f9236d29825b59370a27a0 Mon Sep 17 00:00:00 2001 From: ogyrec Date: Tue, 26 May 2026 12:10:09 +0200 Subject: [PATCH] Document terrain interaction contract v2 --- README.md | 6 + crates/freven_block_api/src/lib.rs | 98 +++++++ crates/freven_world_api/README.md | 5 + docs/GUEST_CONTRACT_v1.md | 5 + docs/README.md | 4 + docs/SDK_DISTRIBUTION.md | 5 +- docs/TERRAIN_INTERACTION_CONTRACT_v2.md | 330 ++++++++++++++++++++++++ 7 files changed, 452 insertions(+), 1 deletion(-) create mode 100644 docs/TERRAIN_INTERACTION_CONTRACT_v2.md diff --git a/README.md b/README.md index 5205886..f7910c1 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,12 @@ That document explains how a game such as Vanilla can provide a friendly blocktype/worldproperty workflow while the engine still consumes the generic canonical content graph. +For the long-term voxel terrain break/place intent contract, see +[docs/TERRAIN_INTERACTION_CONTRACT_v2.md](docs/TERRAIN_INTERACTION_CONTRACT_v2.md). +That document defines the SDK-level action identity, ray, hit, prediction, +server-validation, and reconciliation semantics that replace legacy +target-position-only terrain interaction behavior for rc10. + The current long-term direction is: - **engine/platform layer**: neutral runtime/platform substrate diff --git a/crates/freven_block_api/src/lib.rs b/crates/freven_block_api/src/lib.rs index f0a5ad1..ab6fe78 100644 --- a/crates/freven_block_api/src/lib.rs +++ b/crates/freven_block_api/src/lib.rs @@ -65,6 +65,104 @@ pub struct ClientCursorHit { pub distance_m: f32, } +/// Long-term terrain break/place action kind. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum TerrainInteractionKindV2 { + Break, + Place, +} + +/// Client prediction transaction id for a terrain interaction. +/// +/// This id is scoped by the surrounding `(level_id, stream_epoch, action_seq)` +/// action identity. It is not a global terrain edit id. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TerrainPredictionTransactionIdV2(pub u64); + +/// Identity and dependency boundary for Terrain Interaction Contract v2. +/// +/// This is SDK vocabulary for builtin/block interaction surfaces. It is not yet +/// a serialized action payload schema by itself. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TerrainInteractionIdentityV2 { + pub level_id: u32, + pub stream_epoch: u32, + pub input_seq: u32, + pub action_seq: Option, + pub kind: TerrainInteractionKindV2, + pub prediction_tx: TerrainPredictionTransactionIdV2, + pub depends_on: Vec, +} + +/// Client-sampled terrain interaction ray. +/// +/// Servers may use this ray to validate expressed intent, but must validate it +/// against authoritative player/world state before accepting terrain edits. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct TerrainInteractionRayV2 { + pub ray_origin_m: [f32; 3], + pub ray_dir: [f32; 3], + pub max_distance_m: f32, + pub client_view_tick: Option, +} + +/// Face/contact hit semantics for Terrain Interaction Contract v2. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct TerrainInteractionHitV2 { + pub hit_block_pos: (i32, i32, i32), + pub hit_face: ClientBlockFace, + pub hit_point_m: Option<[f32; 3]>, +} + +/// Place-specific semantics for Terrain Interaction Contract v2. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct TerrainPlaceIntentV2 { + pub support_block_pos: (i32, i32, i32), + pub placement_pos: (i32, i32, i32), + pub block_id: BlockRuntimeId, + pub expected_placement_empty: bool, + pub expected_support_solid: bool, +} + +/// Non-serialized SDK vocabulary shape for voxel terrain break/place intent v2. +/// +/// The transport payload schema remains intentionally deferred until the owning +/// serialized action-payload module is selected. +#[derive(Debug, Clone, PartialEq)] +pub struct TerrainInteractionIntentV2 { + pub identity: TerrainInteractionIdentityV2, + pub ray: TerrainInteractionRayV2, + pub hit: TerrainInteractionHitV2, + pub place: Option, +} + +/// Deterministic terrain interaction rejection/mismatch categories. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum TerrainInteractionRejectReasonV2 { + StaleStream, + DuplicateAction, + MissingDependency, + InvalidSequence, + InvalidCoordinates, + InvalidRay, + OutOfReach, + RayMismatch, + HitMismatch, + FaceMismatch, + Occluded, + TargetNotLoaded, + TargetNotSolid, + TargetNotBreakable, + PlacementNotLoaded, + PlacementNotEmpty, + SupportNotSolid, + BlockIdNotAllowed, + PolicyDenied, + StateMismatch, + InternalError, +} + /// One predicted block edit hint (visual-only, not authoritative). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ClientPredictedEdit { diff --git a/crates/freven_world_api/README.md b/crates/freven_world_api/README.md index 3ae09e4..d52a219 100644 --- a/crates/freven_world_api/README.md +++ b/crates/freven_world_api/README.md @@ -57,6 +57,11 @@ Examples: These are composition points over block-owned families. They do not make `freven_world_api` the owner of standard block gameplay semantics. +Terrain break/place action payloads should follow the semantic contract in +`../../docs/TERRAIN_INTERACTION_CONTRACT_v2.md`. `freven_world_api` owns the +action identity/envelope fields and opaque payload carriage, not a hidden +target-position-only validation fallback. + ## Current state Stage 4.5 ownership is: diff --git a/docs/GUEST_CONTRACT_v1.md b/docs/GUEST_CONTRACT_v1.md index 9dc22b3..ff1ee33 100644 --- a/docs/GUEST_CONTRACT_v1.md +++ b/docs/GUEST_CONTRACT_v1.md @@ -118,6 +118,11 @@ Current hosting policy: - `ActionResult.output` carries canonical runtime output families - rejected actions may carry message output, but must not carry command output +Terrain break/place interactions use the long-term semantic contract in +`TERRAIN_INTERACTION_CONTRACT_v2.md`. Contract v1 still carries action payloads +as opaque bytes; implementations must not treat legacy target-position-only +terrain payloads as an rc10 fallback once v2 is adopted. + ## Message path - Host sends `ClientMessageInput` / `ServerMessageInput` diff --git a/docs/README.md b/docs/README.md index d27db5d..525b925 100644 --- a/docs/README.md +++ b/docs/README.md @@ -58,6 +58,10 @@ Read them in this order: neutral runtime-loaded guest semantics - [GUEST_CONTRACT_v1.md](GUEST_CONTRACT_v1.md): canonical world-owned runtime-loaded guest semantics +- [TERRAIN_INTERACTION_CONTRACT_v2.md](TERRAIN_INTERACTION_CONTRACT_v2.md): + long-term terrain break/place intent contract for action identity, client + rays, hit/place semantics, prediction, server validation, diagnostics, and + reconciliation - [MOD_CONFIG_v1.md](MOD_CONFIG_v1.md): public mod config schema, experience/stack authoring, resolution, and guest delivery semantics - [WORLDGEN_PROVIDER_CONCURRENCY_v1.md](WORLDGEN_PROVIDER_CONCURRENCY_v1.md): diff --git a/docs/SDK_DISTRIBUTION.md b/docs/SDK_DISTRIBUTION.md index b52b048..91fb314 100644 --- a/docs/SDK_DISTRIBUTION.md +++ b/docs/SDK_DISTRIBUTION.md @@ -18,7 +18,10 @@ This repository (`frevenengine/freven-sdk`) is public-readable and contains: Reference gameplay (vanilla experience) lives in a separate repository (`frevenengine/freven-vanilla`). First-party gameplay helpers such as break/place payload codecs and humanoid input -now ship from `freven-vanilla`, not from this SDK repository. +now ship from `freven-vanilla`, not from this SDK repository. The long-term +terrain break/place intent semantics are documented in +`TERRAIN_INTERACTION_CONTRACT_v2.md`; legacy Vanilla payload helpers must migrate +to that contract rather than remain an rc10 fallback. ## Publishing plan diff --git a/docs/TERRAIN_INTERACTION_CONTRACT_v2.md b/docs/TERRAIN_INTERACTION_CONTRACT_v2.md new file mode 100644 index 0000000..5247869 --- /dev/null +++ b/docs/TERRAIN_INTERACTION_CONTRACT_v2.md @@ -0,0 +1,330 @@ +# Terrain Interaction Contract v2 + +Terrain Interaction Contract v2 is the Freven SDK contract for voxel terrain +break/place intent. It defines the long-term shared meaning that clients, +servers, prediction, diagnostics, Vanilla, and other voxel world stacks must +converge on before rc10 terrain interaction is considered stable. + +This document is a semantic contract. It does not, by itself, change the current +engine protocol or Vanilla runtime. Existing action transport still carries an +opaque action payload through `freven_world_guest::ActionInput.payload` and +`freven_world_api::ActionCmdView.payload` until a schema-owned payload module is +introduced. `freven_block_api` exposes non-serialized v2 vocabulary types for +builtin/block interaction surfaces; those types are not yet a wire payload +schema. Implementations must not preserve broken target-position-only v1 behavior +as a hidden fallback. + +## Ownership + +- `freven_world_guest` and `freven_world_api` own action identity, stream + envelope, dispatch, and opaque payload carriage. +- `freven_block_api` owns builtin / compile-time block-facing client interaction + helper types such as cursor hits, faces, predicted edits, and authoritative + action edits. +- `freven_block_guest` owns runtime-loaded block mutation/query/service payload + shapes. +- First-party Vanilla v1 break/place payload helpers remain legacy + Vanilla-owned helpers. They are not the rc10 contract and must not be treated + as a compatibility fallback once v2 is adopted. + +When a concrete serializable v2 payload is added, it should live in the +appropriate SDK-owned block/world contract surface and be referenced by action +payload bytes explicitly. Until then, this document is the normative field and +behavior design. + +## Goals + +- Carry one complete terrain interaction intent from client to server. +- Let client prediction and server validation reason about the same hit, action, + and dependency identity. +- Keep the server authoritative while avoiding false rejects caused by + block-center-only validation. +- Make rejection and mismatch outcomes diagnostic instead of opaque rollback-only + behavior. +- Reconcile exact predictions without deleting independent later predictions or + leaving ghost overlays. + +## Non-Goals + +- No engine, Vanilla, boot, or network runtime behavior is implemented here. +- No protocol change is made by this document alone. +- No fallback to v1 target-position-only validation is authorized. +- Prediction remains visual/presented state only; it is not collision truth and + not server authority. + +## Intent Shape + +Every terrain interaction intent carries the following fields. + +```text +TerrainInteractionIntentV2 { + identity: TerrainInteractionIdentity, + ray: TerrainInteractionRay, + hit: TerrainInteractionHit, + place: Option, +} +``` + +The `place` field is present only when `identity.kind = place`. + +## Intent Identity and Stream Boundary + +`TerrainInteractionIdentity`: + +```text +{ + level_id: u32, + stream_epoch: u32, + input_seq: u32, + action_seq: Option, + kind: break | place, + prediction_tx: TerrainPredictionTransactionId, + depends_on: Vec, +} +``` + +Rules: + +- `(level_id, stream_epoch)` is the stream boundary. A terrain intent never + applies across streams. +- `input_seq` is the client input timeline boundary for the interaction sample. +- `action_seq` is assigned by the client action layer when the command enters the + pending action stream. If a pre-submit payload cannot know `action_seq`, the + engine-owned pending record must bind the payload to the assigned `action_seq` + before network send and reconciliation. +- `prediction_tx` identifies the exact client prediction transaction created for + this intent. +- `depends_on` lists earlier prediction transactions whose effects are required + for this intent to be meaningful. +- Same-cell chains must express dependencies. A same-cell break followed by a + place depends on the break transaction instead of relying on ambient presented + state. + +## Client Interaction Ray + +`TerrainInteractionRay`: + +```text +{ + ray_origin_m: [f32; 3], + ray_dir: [f32; 3], + max_distance_m: f32, + client_view_tick: Option, +} +``` + +Rules: + +- `ray_dir` is normalized. Non-finite components, zero-length directions, and + non-positive or non-finite distances are invalid. +- The ray is the client's sampled interaction intent, not server truth. +- `client_view_tick` or an equivalent snapshot marker is optional and exists only + to aid diagnostics and replay. It does not make the client ray trusted. +- The server validates intent against authoritative world state and the + authoritative player interaction origin. The client ray may be compared against + that origin/orientation within policy tolerance, but never grants reach or + occlusion authority by itself. + +## Hit Semantics + +`TerrainInteractionHit`: + +```text +{ + hit_block_pos: (i32, i32, i32), + hit_face: pos_x | neg_x | pos_y | neg_y | pos_z | neg_z, + hit_point_m: Option<[f32; 3]>, +} +``` + +Rules: + +- `hit_block_pos` is the support/target block intersected by the interaction ray. +- `hit_face` is the face contacted by the ray. +- `hit_point_m`, when present, is the world-space contact point on or very near + the contacted face. +- Validation must use face/contact-aware checks. It must not rely only on + visibility to the block center, because valid edge and face interactions can + fail center-only visibility. +- Coordinates must be finite where floating point is used and sane for the + active level bounds. + +## Break Semantics + +For `kind = break`: + +- `hit_block_pos` is the block requested for removal. +- The target cell must be loaded, non-air, breakable by current game rules, and + first-solid for the validated authoritative ray under the selected interaction + shape policy. +- The result is an authoritative terrain edit or a deterministic rejection + reason. A local prediction rollback without a reason is not a valid expected + failure mode. + +## Place Semantics + +`TerrainPlaceIntent`: + +```text +{ + support_block_pos: (i32, i32, i32), + placement_pos: (i32, i32, i32), + block_id: BlockRuntimeId, + expected_placement_empty: bool, + expected_support_solid: bool, +} +``` + +Rules: + +- `support_block_pos` must match `hit_block_pos`. +- `placement_pos` is derived from `support_block_pos + hit_face.normal`. +- The placement cell is expected to be empty in authoritative validation, unless + a future explicit replacement policy says otherwise. +- The support block is expected to be solid/supporting according to block + gameplay metadata, not merely visually opaque. +- The requested `block_id` must be allowed for the player, inventory, game mode, + and current server policy. +- A place that depends on a same-cell break must list the break prediction + transaction in `depends_on`. The server may accept the chain only if the + dependency is accepted or can be validated as an atomic/ordered same-cell + sequence by the action stream. + +## Prediction + +Client prediction rules: + +- The client may apply visual/presented prediction immediately after local + admission. +- Prediction is never collision truth and never server truth. +- Every predicted terrain edit is linked to the exact + `(level_id, stream_epoch, action_seq, prediction_tx)` identity. +- A rejected or mismatched transaction removes that exact transaction and all + dependent transactions. +- Independent later predictions survive exact close of an earlier accepted, + rejected, or mismatched transaction. +- Prediction overlays must be represented as transactions over authoritative + terrain, not as detached ghost state. + +Local admission may reject impossible or unsafe requests before send, but local +admission is not a server rejection and must be reported separately. + +## Server Validation + +The server remains authoritative. For every received v2 terrain intent it +validates the same contract against authoritative state: + +- stream identity: active or recently valid `(level_id, stream_epoch)`; +- sequence identity: action sequence ordering, duplicate detection, and + dependency availability; +- coordinates: finite ray fields and sane world-cell positions; +- authoritative interaction origin and reach; +- ray/face/contact consistency within policy tolerance; +- first-solid/support block under authoritative world state; +- placement cell emptiness for place interactions; +- support block solidity/support policy; +- occlusion using face/contact-aware visibility rather than block-center-only + visibility; +- allowed block id, inventory, mode, permissions, cooldowns, and gameplay rules. + +The server must not trust the client ray, hit, face, or placement cell without +validation. It uses them to validate the user's expressed intent, not as proof. + +## Rejection and Mismatch Reasons + +Expected failures return deterministic categories suitable for diagnostics: + +- `stale_stream` +- `duplicate_action` +- `missing_dependency` +- `invalid_sequence` +- `invalid_coordinates` +- `invalid_ray` +- `out_of_reach` +- `ray_mismatch` +- `hit_mismatch` +- `face_mismatch` +- `occluded` +- `target_not_loaded` +- `target_not_solid` +- `target_not_breakable` +- `placement_not_loaded` +- `placement_not_empty` +- `support_not_solid` +- `block_id_not_allowed` +- `policy_denied` +- `state_mismatch` +- `internal_error` + +`internal_error` is reserved for unexpected faults and must not be used for +ordinary validation failures. Implementations may carry additional structured +details, but the category must remain stable enough for tests, logs, and manual +smoke diagnostics. + +## Reconciliation + +Action acceptance and terrain settlement are separate phases: + +- An accepted `ActionResult` means the server accepted the action command. It is + not final terrain settle. +- The authoritative `WorldDelta` that contains the terrain edit closes the exact + prediction transaction. +- A rejected or mismatched result removes the exact prediction transaction and + its dependents. +- A matching authoritative world delta closes only the transaction it proves. + Later independent predictions remain pending. +- A world delta that contradicts a predicted edit produces a mismatch close for + that transaction and dependents, then applies authoritative state. +- Clients must not retain ghost overlays after close, reject, mismatch, stream + transition, or dependency removal. + +## Compatibility and Migration + +v1 target-position-only terrain interaction semantics are legacy and non-rc10. +They are useful only as historical context for current bugs: + +- target pos / face / block id are insufficient to express interaction intent; +- server-side reconstruction from authoritative world plus player position loses + the client's sampled ray/contact semantics; +- block-center visibility causes false rejects; +- prediction cannot reconcile exact same-cell chains without transaction + identity and dependencies. + +The rc10 migration boundary is: + +- adopt v2 as the only valid break/place terrain interaction contract; +- remove hidden target-position-only validation fallback; +- treat any temporary v1 path as explicitly legacy/non-rc10; +- expose v2 rejection/mismatch categories instead of opaque rollback-only + behavior. + +## SDK Integration Notes + +Existing SDK action fields already cover part of the v2 envelope: + +- `freven_world_guest::ActionInput.level_id` +- `freven_world_guest::ActionInput.stream_epoch` +- `freven_world_guest::ActionInput.action_seq` +- `freven_world_guest::ActionInput.at_input_seq` +- `freven_world_api::ActionCmdView.level_id` +- `freven_world_api::ActionCmdView.stream_epoch` +- `freven_world_api::ActionCmdView.seq` +- `freven_world_api::ActionCmdView.at_input_seq` +- `freven_world_api::ClientActionResultEvent.action_seq` + +The missing SDK-owned schema is the concrete v2 terrain interaction payload +carried inside the opaque action payload bytes. `freven_block_api` currently +provides non-serialized vocabulary stubs: + +- `TerrainInteractionKindV2` +- `TerrainPredictionTransactionIdV2` +- `TerrainInteractionIdentityV2` +- `TerrainInteractionRayV2` +- `TerrainInteractionHitV2` +- `TerrainPlaceIntentV2` +- `TerrainInteractionIntentV2` +- `TerrainInteractionRejectReasonV2` + +A serialized schema should be introduced only when the owning crate/module is +selected, and then wired consistently through runtime-loaded and builtin action +paths.