diff --git a/crates/shared/src/protocol.rs b/crates/shared/src/protocol.rs index 399d73c..570e998 100644 --- a/crates/shared/src/protocol.rs +++ b/crates/shared/src/protocol.rs @@ -1,6 +1,4 @@ //! Network protocol types and constants. -//! -//! This module defines the pure, serde-serializable messages used for network communication between the client and server. It contains no networking logic or async dependencies. use serde::{Deserialize, Serialize}; @@ -42,9 +40,9 @@ pub struct HandshakeAck { pub protocol_version: u32, /// Human-readable server build string. pub server_build: String, - /// Packs the world requires, each with an optional download source. Empty in M1. + /// Packs the world requires, each with an optional download source. May include `PackTier::Resource` entries (a server resource pack), which are delivered one-way and applied client-side rather than strict-matched; a consumer must branch on tier (or `PackTier::requires_strict_match`) before treating an entry as a match requirement. pub world_packs: Vec, - /// Packs the client is missing relative to the server, each with an optional download source. Empty in M1. + /// Packs the client is missing relative to the server, each with an optional download source. As with `world_packs`, `PackTier::Resource` entries are delivered, not matched. pub missing_packs: Vec, /// Which stream carries which purpose for this session. pub stream_layout: StreamLayout, @@ -77,6 +75,8 @@ pub enum RejectReason { ProtocolMismatch, /// Client is missing required packs or has incompatible versions. PackMismatch, + /// Client declined or failed to fetch a server resource pack the server marked required. + ResourcePackDeclined, /// Client failed to authenticate. AuthFailed, /// Client is banned from the server. @@ -96,8 +96,6 @@ pub struct PlayerIdentity { } /// Reference to a content pack (resource pack, data pack, or Lua mod) as it appears in a modlist exchanged during the handshake. -/// -/// Identity is the pair (`id`, `content_hash`): `id` names the pack and `content_hash` is the authoritative value compared when deciding whether two installations agree. `version` is informational (display, logs, upgrade prompts) and is **not** the match key — two builds sharing a version but differing in contents are distinct packs. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct PackRef { /// Namespaced content identifier of the pack (`namespace:id`). Charset validation is deferred to the modlist-matching concept (out of M1 scope). @@ -114,7 +112,7 @@ pub struct PackRef { /// Classification of a content pack, determining the handshake matching rule applied to it. Inferred from folder contents, not self-declared: `assets/`-only is a resource pack, `data/`-only is a data pack, presence of `scripts/` is a Lua mod. #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] pub enum PackTier { - /// Client-only asset overlay (`assets/` only). Never streamed by the server, never matched. + /// Client-side asset overlay (`assets/` only). Never strict-matched between peers. A server may push one server resource pack of its own, delivered one-way and applied on top of the client's local pack stack; enforcement of a `required` server pack is apply-or-reject at the client, not a peer hash-match. Resource, /// Declarative content (`data/` only). Must match exactly between peers. Data, @@ -126,7 +124,7 @@ pub enum PackTier { } impl PackTier { - /// Returns whether a pack of this tier must match byte-for-byte between client and server for the connection to be accepted. Resource packs are never matched; data packs and non-`client_only` mods must match exactly (see Load order § modlist matching). + /// Returns whether a pack of this tier must match byte-for-byte between client and server for the connection to be accepted. Resource packs are never matched; data packs and non-`client_only` mods must match exactly. A `false` here does not imply the server never sends the pack, a server resource pack is delivered one-way despite not being part of bidirectional matching. #[must_use] pub fn requires_strict_match(self) -> bool { match self { @@ -144,6 +142,8 @@ pub struct RequiredPack { pub pack: PackRef, /// Optional HTTP(S) URL to fetch the pack from, bypassing the QUIC asset stream for large downloads. `None` means fetch over the asset stream. pub download_url: Option, + /// Whether the connection is rejected if the client cannot obtain and apply this pack. For data/mod tiers this is always `true` (they are mandatory for a correct session). For a `PackTier::Resource` entry (a server resource pack) it distinguishes an *optional* overlay the client may decline and keep playing (`false`) from a *required* one whose decline or fetch failure rejects the connection (`true`). + pub required: bool, } /// Optional protocol feature bits. diff --git a/docs/packs.md b/docs/packs.md index fb8af51..3fae72e 100644 --- a/docs/packs.md +++ b/docs/packs.md @@ -6,7 +6,9 @@ Two distinct, orthogonal systems. They are kept separate and are not collapsed i Client-side asset overlays: textures, sounds, models, fonts, language files. No logic. -A pack is a directory tree mirroring `/assets/` that overrides files by path. The renderer/asset loader resolves logical asset IDs against a stack of pack roots (base game → installed packs by priority) and the topmost hit wins. The server has no involvement. Ownership sits with the asset pipeline (in `client`, or a sibling `assets` crate if it grows). Pack authors never touch Lua. +A pack is a directory tree mirroring `/assets/` that overrides files by path. The renderer/asset loader resolves logical asset IDs against a stack of pack roots (base game → installed packs by priority) and the topmost hit wins. Ownership sits with the asset pipeline (in `client`, or a sibling `assets` crate if it grows). Pack authors never touch Lua. + +A client's own resource packs are a purely local choice; the server has no say over them and they are never part of gameplay modlist matching. The **one** exception is a **server resource pack**: a server may push a single cosmetic overlay of its own (a themed / total-conversion server) to connecting clients. It is a one-way server → client push, applied on top of the client's local stack, and enforced per the server's choice — *optional* packs the client may decline and keep playing, a *required* pack the client declines or fails to fetch rejects the connection. It is still `assets/`-only (no `data/`, no `scripts/`), so it can never affect authoritative state. Fetch and enforcement semantics live in the vault's `Architecture/Load order.md` § Streaming. ## Data packs