Skip to main content

brink_format/
value.rs

1use alloc::string::String;
2use alloc::sync::Arc;
3use alloc::vec;
4use alloc::vec::Vec;
5
6use serde::{Deserialize, Serialize};
7
8use crate::id::{DefinitionId, NameId};
9
10/// Maximum nesting depth permitted when decoding `VAL_ARRAY`/`VAL_MAP`
11/// values. Generous for legitimate data but bounds worst-case recursion so a
12/// crafted file of nested single-element arrays (~5 bytes/level) cannot
13/// stack-overflow the reader (CLAUDE.md "guard against unbounded growth";
14/// issue #553).
15///
16/// This is the single canonical definition, shared by every `decode_value`
17/// implementation that recurses on collection values — the `.inkb` reader
18/// (`brink_format::inkb::read`) and the runtime transcript reader
19/// (`brink_runtime::transcript`) both reference this constant rather than
20/// each defining their own copy (issue #561).
21pub const MAX_DECODE_DEPTH: usize = 128;
22
23/// Identifies a struct shape (TM-4, `docs/typed-mode-spec.md` §6) within the
24/// compiled story's `StructShapes` section (`docs/format-spec.md`, section
25/// tag `0x0C`).
26///
27/// Distinct from [`crate::id::DefinitionId`]: a `STRUCT` declaration is a
28/// compile-time nominal type, not a runtime storage location, so its wire
29/// footprint is this flat `u32` index into `StructShapes`, not a tagged
30/// definition id. Assigned deterministically at codegen time (sorted by
31/// declared struct name, never `HashMap` iteration order — CLAUDE.md).
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
33pub struct ShapeId(pub u32);
34
35/// The runtime type of a [`Value`].
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
37pub enum ValueType {
38    Int,
39    Float,
40    Bool,
41    String,
42    List,
43    DivertTarget,
44    VariablePointer,
45    TempPointer,
46    Null,
47    FragmentRef,
48    /// A copy-on-write ordered collection ([`Value::Array`]).
49    Array,
50    /// A copy-on-write insertion-ordered map ([`Value::Map`]).
51    Map,
52    /// A closed-shape, copy-on-write record ([`Value::Record`]) — TM-4.
53    Record,
54    /// A zero-bound function value ([`Value::FnRef`]) — T1c.
55    FnRef,
56    /// A function value with a bound-arg prefix ([`Value::Closure`]) — T1c.
57    Closure,
58    /// An opaque host-resource token ([`Value::Handle`]) — T1d.
59    Handle,
60    /// A symbolic path projection ([`Value::Projection`]) — T1e.
61    Projection,
62    /// A typed-absence value ([`Value::OptionVal`]) — NS-A1, `Option[T]`.
63    Option,
64    /// An integer range value ([`Value::Range`]) — NS-A5, F7.
65    Range,
66    /// A 2-lane f32 vector ([`Value::Vec2`]) — NS-A8, the numeric tower.
67    Vec2,
68    /// A 3-lane f32 vector ([`Value::Vec3`]) — NS-A8.
69    Vec3,
70    /// A 4-lane f32 vector ([`Value::Vec4`]) — NS-A8.
71    Vec4,
72    /// A rotation quaternion, `(x, y, z, w)` ([`Value::Quat`]) — NS-A8.
73    Quat,
74    /// A column-major 2×2 f32 matrix ([`Value::Mat2`]) — NS-A8.
75    Mat2,
76    /// A column-major 3×3 f32 matrix ([`Value::Mat3`]) — NS-A8.
77    Mat3,
78    /// A column-major 4×4 f32 matrix ([`Value::Mat4`]) — NS-A8.
79    Mat4,
80    /// A weighted table ([`Value::Weighted`]) — NS-A7, `Weighted[T]`.
81    Weighted,
82}
83
84/// A runtime value in the ink VM.
85///
86/// Heap-allocating variants (`String`, `List`, `Array`, `Map`, `Record`,
87/// `Closure`, `Projection`, `OptionVal`, `Weighted`) are wrapped in `Arc` so
88/// that cloning a `Value` is always O(1) — a refcount bump, not a deep copy —
89/// which makes call-frame cloning (during `fork_thread`) essentially free.
90/// Atomic refcounts are used so `Value` can flow through Bevy's parallel
91/// scheduler.
92///
93/// This `Arc` wrapping is a *performance* mechanism under **value
94/// semantics**, not reference semantics: sharing the underlying allocation
95/// is unobservable (`docs/value-model-spec.md` §3), because every mutating
96/// entry point forks the shared allocation on write (take → `make_mut` →
97/// write-back — see `docs/runtime-spec.md`'s "Value model" section). A
98/// binding that never observed a mutation never sees it, regardless of
99/// whether it shares the `Arc` underneath.
100///
101/// The `Array`/`Map` collections follow the ratified value model
102/// (`docs/value-model-spec.md` §4/§5): value semantics with copy-on-write
103/// sharing. Sharing is unobservable (§3) — [equality](Value#impl-PartialEq-for-Value)
104/// is structural with an `Arc::ptr_eq` fast path, and mutation goes through
105/// the take → [`make_mut`](Value::array_make_mut) → write-back RMW discipline
106/// so an unshared collection mutates in place and a shared one performs a
107/// single copy.
108///
109/// `PartialEq` is implemented by hand rather than derived so the collection
110/// arms can take the `ptr_eq` shortcut; every scalar arm matches what the
111/// derive would have produced.
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub enum Value {
114    Int(i32),
115    Float(f32),
116    Bool(bool),
117    String(Arc<str>),
118    List(Arc<ListValue>),
119    DivertTarget(DefinitionId),
120    /// A reference to a global variable, used for `ref` parameters.
121    VariablePointer(DefinitionId),
122    /// A runtime-only pointer to a temp in a specific call frame.
123    /// Used for `ref` parameters that target temp variables.
124    TempPointer {
125        slot: u16,
126        frame_depth: u16,
127    },
128    Null,
129    /// A reference to a fragment in the output buffer's fragment store.
130    /// Fragments preserve structural output parts for locale re-rendering.
131    FragmentRef(u32),
132    /// An ordered, copy-on-write collection of values (value-model-spec §4).
133    ///
134    /// The backing `Vec` is shared behind an `Arc`; clone is a refcount bump.
135    /// Mutation goes through [`array_make_mut`](Self::array_make_mut), which
136    /// copies the backing vector only when the `Arc` is shared.
137    Array(Arc<Vec<Value>>),
138    /// An insertion-ordered, copy-on-write map with scalar keys
139    /// (value-model-spec §4). Keys are `int`/`string`/`bool` ([`MapKey`]);
140    /// iteration order is insertion order, so the value is deterministic
141    /// without sorting or hashing.
142    Map(Arc<OrderedMap>),
143    /// A closed-shape record (typed-dialect era, TM-4;
144    /// `docs/value-model-spec.md` §11c / `docs/typed-mode-spec.md` §6).
145    ///
146    /// `fields` is a flat, shape-ordered vector shared behind an `Arc` — the
147    /// exact same COW discipline as [`Array`](Self::Array)/[`Map`](Self::Map):
148    /// clone is a refcount bump, mutation goes through
149    /// [`record_make_mut`](Self::record_make_mut). The shape is closed (no
150    /// dynamic add/remove of fields) and identified by [`ShapeId`], an
151    /// interned index into the compiled story's `StructShapes` table — two
152    /// records are never equal unless their shapes match, even if their
153    /// field vectors happen to coincide.
154    Record {
155        shape: ShapeId,
156        fields: Arc<Vec<Value>>,
157    },
158    /// A **zero-bound function value** (`docs/t1c-spec.md` §1/§6, wire tag
159    /// `VAL_FN_REF`). Partial application over a statically-named function
160    /// with no bound-arg prefix — `#fn(name)` where the target has no `ref`
161    /// params. The `DefinitionId` is the fn token (hashes from the target's
162    /// name, so a saved token survives recompiles that only edit the body).
163    ///
164    /// A no-payload scalar (`Copy`-cheap `DefinitionId`); no `Arc` needed.
165    /// Structural equality is "same fn token" (§5, sharing-unobservable).
166    FnRef(DefinitionId),
167    /// A **function value with a bound-arg prefix** (`docs/t1c-spec.md`
168    /// §1/§6, wire tag `VAL_CLOSURE`). Despite the name there is no lexical
169    /// environment — ink has no free variables; the "env" is the bound
170    /// prefix of the target's declared params (a `val` entry snapshots the
171    /// value at creation, a `ref` entry captures a durable cell as a
172    /// [`VariablePointer`](Self::VariablePointer)).
173    ///
174    /// Wrapped in `Arc` for the same O(1)-clone discipline as the collection
175    /// variants. Structural equality (§5): same fn token **and** equal env
176    /// rows — `ref` entries compare by bound cell, `val` entries by value,
177    /// both of which fall out of the derived per-entry comparison because a
178    /// `ref` payload is a `VariablePointer` (compared by its `DefinitionId`).
179    Closure(Arc<ClosureValue>),
180    /// An opaque host-resource token (`docs/t1d-spec.md` §1/§2, wire tag
181    /// `VAL_HANDLE`). Host resources (entities, audio instances, assets,
182    /// timers) enter the script world as `Handle` tokens: `{kind, id}`
183    /// scalars with value semantics — copied like ints, serializable,
184    /// compared by token. No live pointer ever lives in a `Value`;
185    /// dereferencing happens only host-side, against the host's registry,
186    /// inside bindings (the sharing-unobservable dogma applies verbatim — a
187    /// handle is a *name*, re-bound at a defined seam).
188    ///
189    /// `kind` is a [`NameId`] into the compiled story's manifest-declared
190    /// kind vocabulary (analyzer-side, not the format — the format ships
191    /// only the token shape, `docs/format-v4-rfc.md` §2). `id` is an opaque
192    /// `u64` the host allocates; the script never interprets it.
193    ///
194    /// A no-payload scalar (`Copy`-cheap: `NameId` + `u64`); no `Arc`
195    /// needed. Structural equality is token equality (`kind == kind && id
196    /// == id`, §6) — no ordering exists (any `<`/`>`/`<=`/`>=` is a runtime
197    /// fault in gradual mode, a compile error under the typed dialect), and
198    /// a handle is never a legal map key (the domain stays
199    /// int/string/bool — same restriction as function values).
200    ///
201    /// **No literal syntax and no dedicated opcode construct this value —
202    /// handles enter the script world only via bindings** (`docs/t1d-spec.md`
203    /// §2 RULED). This variant, its wire encoding, and its `.inkt` atom
204    /// exist so a handle received from a binding can flow through globals,
205    /// collections, saves, and the transcript like any other value.
206    Handle {
207        /// The manifest-declared kind name (analyzer-side vocabulary).
208        kind: NameId,
209        /// The host-allocated token id. Opaque to the script.
210        id: u64,
211    },
212    /// A symbolic path projection (`docs/t1e-spec.md` §1/§3, wire tag
213    /// `VAL_PROJECTION`): `(root cell, path segments)` — never an interior
214    /// pointer. Created only in `ref`-argument position (`ref
215    /// npc.inventory[3]`); reads walk the path against the root's *current*
216    /// value, writes desugar to root-cell RMW (take → walk → `make_mut`
217    /// spine → write → store back, spec §3). The segment list is fixed at
218    /// creation ("index expressions snapshot at `ref` creation", spec §1) —
219    /// there is no lazy re-evaluation.
220    ///
221    /// Wrapped in `Arc` for the same O(1)-clone discipline as
222    /// [`Closure`](Self::Closure). Structural equality (spec §4 PROPOSED,
223    /// implemented here): same root cell and equal segments.
224    Projection(Arc<ProjectionValue>),
225    /// A typed-absence value — the compiler-owned `Option[T]` enum (NS-A1,
226    /// `docs/stdlib-spec.md` §1.1/§1.4, ruled 2026-07-18: "a fault says
227    /// 'your program is wrong'; Option says 'the world didn't have one'").
228    ///
229    /// `Option[T]` is the third compiler-known parameterized builtin
230    /// (joining `[T]`/`[K: V]`) — a compiler-owned enum shape, NOT user
231    /// generics. Runtime representation: `None` is payload-free (no
232    /// allocation); `Some` wraps its inner value behind an `Arc` so clone
233    /// stays O(1) like every other heap-bearing variant. Nesting is legal
234    /// and meaningful (`some(none) != none` — the wire and equality both
235    /// preserve it).
236    ///
237    /// Named `OptionVal` (not `Option`) purely to avoid the eternal
238    /// `core::option::Option` shadowing hazard inside `match` arms; the
239    /// [`ValueType`] discriminant and every author-facing surface still
240    /// say "option".
241    ///
242    /// Structural equality: `none == none`; `some(x) == some(y)` iff
243    /// `x == y`; an Option is never equal to a bare `T` (the ruled
244    /// `Option[T] ≠ T` strictness holds at the value layer too). Display
245    /// (`stringify`/`string(x)`): `none` / `some(<inner>)` — the boring,
246    /// stable form, total forever (F28). The §1.6b display-boundary
247    /// forgiveness (Track B4) is a `brink-runtime`-only concern layered on
248    /// top at read time (`value_ops::stringify_display`) — deliberately
249    /// not implemented at this value-definition layer, since `string()`
250    /// and every non-display consumer must keep seeing the total form.
251    OptionVal(Option<Arc<Value>>),
252    /// An integer range value (NS-A5, `docs/stdlib-spec.md` §7 — F7, ruled
253    /// 2026-07-19: "ranges are a REAL Value kind"). `start..end` (exclusive)
254    /// or `start..=end` (inclusive) over `int` bounds — v1 is int-only.
255    ///
256    /// Ranges join the closed iterable set (`for i in 0..n`), index like a
257    /// virtual array of their elements, and are the substrate of the
258    /// language's first value refinement (the inhabited range consumed by
259    /// `rand::int`). A durable wire form exists (`VAL_RANGE`) because
260    /// `FlowFrame` spills for-loop iterators across `await` — a range held
261    /// in a loop snapshot must survive save/load.
262    ///
263    /// A no-payload scalar (two `i32`s + a flag); no `Arc` needed. The
264    /// **written form is preserved** — `1..7` and `1..=6` keep their
265    /// `inclusive` flag through saves, the transcript, and display — but
266    /// **equality is content equality** (F7's ruling word): two ranges are
267    /// equal iff they denote the same integer sequence, so `1..=6 == 1..7`
268    /// and every empty range equals every other empty range. This is the
269    /// same content-over-form posture as the #909 map-equality ruling
270    /// (insertion order iterates, content compares).
271    Range {
272        /// The first element of the range (always inclusive).
273        start: i32,
274        /// The written end bound; whether it is an element depends on
275        /// `inclusive`.
276        end: i32,
277        /// `true` for the `..=` form (`end` is the last element), `false`
278        /// for the `..` form (`end` is one past the last element).
279        inclusive: bool,
280    },
281    /// A 2-lane f32 vector (NS-A8, `docs/tower-mini-spec.md` T1: the tower
282    /// value kinds are **glam-backed** — glam is the in-memory compute type,
283    /// so vector/quaternion/matrix ops arrive correct-by-construction).
284    ///
285    /// Serde discipline (T5): the derive on `Value` routes every tower
286    /// variant through the hand-written lane modules in [`tower_serde`] —
287    /// explicit `x, y(, z, w)` lane order for vectors and the quat,
288    /// column-major column-by-column for matrices — NEVER glam's memory
289    /// representation (which varies with SIMD features and versions) and
290    /// never glam's own `serde` feature (kept off in `Cargo.toml`).
291    ///
292    /// Equality (T4): componentwise IEEE via glam's derived `PartialEq` — a
293    /// NaN-bearing vector never equals itself, `-0.0 == +0.0` per lane,
294    /// exactly like bare `Float`. Tower values are NOT orderable (§4b: a
295    /// vector in an ordering context is a `NotOrderable` fault) and are
296    /// never legal map keys (`MapKey::from_value` has no tower arms).
297    Vec2(#[serde(with = "tower_serde::vec2")] glam::Vec2),
298    /// A 3-lane f32 vector (NS-A8). The **unaligned** `glam::Vec3` (not
299    /// `Vec3A`) per the mini-spec — aligned variants would bloat every
300    /// `Value`. See [`Vec2`](Self::Vec2) for the shared tower discipline.
301    Vec3(#[serde(with = "tower_serde::vec3")] glam::Vec3),
302    /// A 4-lane f32 vector (NS-A8). See [`Vec2`](Self::Vec2).
303    Vec4(#[serde(with = "tower_serde::vec4")] glam::Vec4),
304    /// A rotation quaternion (NS-A8), lane order `(x, y, z, w)` per glam
305    /// (T3: conventions per glam, wholesale — right-handed, `quat * quat`
306    /// composes, `quat * vec` rotates). See [`Vec2`](Self::Vec2).
307    Quat(#[serde(with = "tower_serde::quat")] glam::Quat),
308    /// A column-major 2×2 f32 matrix (NS-A8, T2: all matrix sizes ship).
309    /// See [`Vec2`](Self::Vec2).
310    Mat2(#[serde(with = "tower_serde::mat2")] glam::Mat2),
311    /// A column-major 3×3 f32 matrix (NS-A8). The **unaligned** `glam::Mat3`
312    /// (not `Mat3A`). See [`Vec2`](Self::Vec2).
313    Mat3(#[serde(with = "tower_serde::mat3")] glam::Mat3),
314    /// A column-major 4×4 f32 matrix (NS-A8). See [`Vec2`](Self::Vec2).
315    Mat4(#[serde(with = "tower_serde::mat4")] glam::Mat4),
316    /// A weighted table (NS-A7, `docs/stdlib-spec.md` §8): `Weighted[T]` —
317    /// positive-int weights over values, in construction order.
318    /// **Evidence-by-construction**: the only producer (`weighted_new`)
319    /// refuses empty tables and non-positive/non-int weights, so a
320    /// `Weighted` that exists is always a valid `roll` target (total). The
321    /// entry row is a **multiset** (F17: duplicate weights legal and
322    /// meaningful — deliberately divergent from `Map`'s key-set). v1 is
323    /// construct-and-roll: no `len`, no iteration, no mutation.
324    Weighted(Arc<WeightedValue>),
325}
326
327/// Hand-written serde lane codecs for the tower variants (NS-A8,
328/// `docs/tower-mini-spec.md` T5): each type serializes as its flat lane
329/// array — vectors and the quat as `[x, y(, z, w)]`, matrices as their
330/// column-major `to_cols_array()` — and deserializes back through glam's
331/// explicit `from_array`/`from_cols_array` constructors. Glam computes; the
332/// serialized form is ours: no glam memory layout, no serde-through-glam.
333pub mod tower_serde {
334    /// Expand one lane codec module: `to`/`from` are the explicit
335    /// lane-array conversions (never a memory-layout cast). Matrix `from`
336    /// constructors (`from_cols_array`) take the array by reference, hence
337    /// the closure rather than a bare path.
338    macro_rules! lane_codec {
339        ($name:ident, $ty:ty, $lanes:literal, $to:ident, |$a:ident| $from:expr) => {
340            pub mod $name {
341                use serde::{Deserialize, Deserializer, Serialize, Serializer};
342
343                pub fn serialize<S: Serializer>(v: &$ty, s: S) -> Result<S::Ok, S::Error> {
344                    v.$to().serialize(s)
345                }
346
347                pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<$ty, D::Error> {
348                    <[f32; $lanes]>::deserialize(d).map(|$a| $from)
349                }
350            }
351        };
352    }
353
354    lane_codec!(vec2, glam::Vec2, 2, to_array, |a| glam::Vec2::from_array(a));
355    lane_codec!(vec3, glam::Vec3, 3, to_array, |a| glam::Vec3::from_array(a));
356    lane_codec!(vec4, glam::Vec4, 4, to_array, |a| glam::Vec4::from_array(a));
357    lane_codec!(quat, glam::Quat, 4, to_array, |a| glam::Quat::from_array(a));
358    lane_codec!(mat2, glam::Mat2, 4, to_cols_array, |a| {
359        glam::Mat2::from_cols_array(&a)
360    });
361    lane_codec!(mat3, glam::Mat3, 9, to_cols_array, |a| {
362        glam::Mat3::from_cols_array(&a)
363    });
364    lane_codec!(mat4, glam::Mat4, 16, to_cols_array, |a| {
365        glam::Mat4::from_cols_array(&a)
366    });
367}
368
369/// The payload of a [`Value::Projection`] — the root cell plus its ordered
370/// segment chain (`docs/t1e-spec.md` §1/§3).
371#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
372pub struct ProjectionValue {
373    /// The durable root cell this projection reads/writes through (a global
374    /// `VAR`/`#@local` — never a temp, enforced at compile time by T1e-1's
375    /// E080 durable-root check). Mirrors the `VAL_VAR_POINTER` payload shape
376    /// (`docs/format-v4-rfc.md` §1: "cell reference … reused not
377    /// reinvented").
378    pub cell: DefinitionId,
379    /// The ordered path segments, fixed at creation.
380    pub segments: Vec<ProjSegment>,
381}
382
383/// One path-projection segment (`docs/format-v4-rfc.md` §1: `segments: 0 =
384/// index i32, 1 = key value`). Segment kind `2 = range` is RESERVED and never
385/// constructed in T1e (icebox #829 — sequence slices/ranges).
386///
387/// The kind recorded here is a **wire-compactness choice**, not a semantic
388/// tag the walker trusts blindly: an evaluated segment value that happens to
389/// be an `Int` is captured as [`Index`](Self::Index) (the compact i32 form);
390/// everything else — a map key of another scalar type, or a struct field
391/// name (always a `Value::String` literal) — is captured as
392/// [`Key`](Self::Key). Walking dispatches on the *root's current container
393/// type* at each step (spec §4: reads walk against the root's current
394/// value), so an `Index(n)` segment applied to a `Map` is reinterpreted as
395/// `MapKey::Int(n)` — the distinction never forecloses either domain.
396#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
397pub enum ProjSegment {
398    /// `[i]` — captured because the evaluated segment value was an `Int`.
399    Index(i32),
400    /// `[k]` (a non-`Int` map key) or `.field` (a struct field name,
401    /// captured as `Value::String`).
402    Key(Value),
403}
404
405impl ProjSegment {
406    /// Build a segment from an evaluated `Value` — the classification rule
407    /// this whole module documents: `Int` → [`Index`](Self::Index), anything
408    /// else → [`Key`](Self::Key).
409    #[must_use]
410    pub fn from_value(v: Value) -> Self {
411        match v {
412            Value::Int(n) => Self::Index(n),
413            other => Self::Key(other),
414        }
415    }
416}
417
418/// The payload of a [`Value::Closure`] — the fn token plus its bound-arg
419/// prefix (`docs/t1c-spec.md` §1/§6).
420#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
421pub struct ClosureValue {
422    /// The target function's [`DefinitionId`] (the fn token).
423    pub target: DefinitionId,
424    /// The bound-arg prefix, in the target's declared param order. The
425    /// entries carry the param **name and mode** deliberately (spec §6): on
426    /// load/invoke after a recompile they are validated against the current
427    /// signature so a renamed/re-moded param faults cleanly instead of
428    /// silently misbinding.
429    pub env: Vec<ClosureEnvEntry>,
430}
431
432/// One bound-arg entry in a [`ClosureValue`]'s env.
433#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
434pub struct ClosureEnvEntry {
435    /// The bound param's interned name (redundancy for rehydration checking).
436    pub name: NameId,
437    /// `true` if the target declared this param `ref` (the payload is a
438    /// captured cell), `false` for a `val` snapshot.
439    pub is_ref: bool,
440    /// The bound value: a snapshot (`val`) or a captured cell reference
441    /// (`ref` — a [`VariablePointer`](Value::VariablePointer)).
442    pub payload: Value,
443}
444
445impl Value {
446    /// Return the type discriminant for this value.
447    pub fn value_type(&self) -> ValueType {
448        match self {
449            Self::Int(_) => ValueType::Int,
450            Self::Float(_) => ValueType::Float,
451            Self::Bool(_) => ValueType::Bool,
452            Self::String(_) => ValueType::String,
453            Self::List(_) => ValueType::List,
454            Self::DivertTarget(_) => ValueType::DivertTarget,
455            Self::VariablePointer(_) => ValueType::VariablePointer,
456            Self::TempPointer { .. } => ValueType::TempPointer,
457            Self::Null => ValueType::Null,
458            Self::FragmentRef(_) => ValueType::FragmentRef,
459            Self::Array(_) => ValueType::Array,
460            Self::Map(_) => ValueType::Map,
461            Self::Record { .. } => ValueType::Record,
462            Self::FnRef(_) => ValueType::FnRef,
463            Self::Closure(_) => ValueType::Closure,
464            Self::Handle { .. } => ValueType::Handle,
465            Self::Projection(_) => ValueType::Projection,
466            Self::OptionVal(_) => ValueType::Option,
467            Self::Range { .. } => ValueType::Range,
468            Self::Vec2(_) => ValueType::Vec2,
469            Self::Vec3(_) => ValueType::Vec3,
470            Self::Vec4(_) => ValueType::Vec4,
471            Self::Quat(_) => ValueType::Quat,
472            Self::Mat2(_) => ValueType::Mat2,
473            Self::Mat3(_) => ValueType::Mat3,
474            Self::Mat4(_) => ValueType::Mat4,
475            Self::Weighted(_) => ValueType::Weighted,
476        }
477    }
478
479    /// Build a [`Range`](Self::Range) from its written bounds.
480    pub fn range(start: i32, end: i32, inclusive: bool) -> Self {
481        Self::Range {
482            start,
483            end,
484            inclusive,
485        }
486    }
487
488    /// Borrow the `(start, end, inclusive)` triple if this value is a
489    /// [`Range`](Self::Range).
490    pub fn as_range(&self) -> Option<(i32, i32, bool)> {
491        match self {
492            Self::Range {
493                start,
494                end,
495                inclusive,
496            } => Some((*start, *end, *inclusive)),
497            _ => None,
498        }
499    }
500
501    /// The one-past-the-last element bound of a [`Range`](Self::Range),
502    /// normalized over the written form (`i64` so `1..=i32::MAX` cannot
503    /// overflow). `None` for any non-range value.
504    pub fn range_end_exclusive(&self) -> Option<i64> {
505        match self {
506            Self::Range { end, inclusive, .. } => {
507                Some(i64::from(*end) + i64::from(u8::from(*inclusive)))
508            }
509            _ => None,
510        }
511    }
512
513    /// Number of elements a [`Range`](Self::Range) denotes (`0` for an empty
514    /// range — a range never has negative length). `None` for any non-range
515    /// value. `i64` because `i32::MIN..=i32::MAX` has 2³² elements.
516    pub fn range_len(&self) -> Option<i64> {
517        match self {
518            Self::Range { start, .. } => {
519                let end_ex = self.range_end_exclusive()?;
520                Some((end_ex - i64::from(*start)).max(0))
521            }
522            _ => None,
523        }
524    }
525
526    /// Build a [`Weighted`](Self::Weighted) table from `(weight, value)`
527    /// entries. The caller owns the §8 evidence-by-construction invariant
528    /// (non-empty, positive weights) — the VM's `weighted_new` op and the
529    /// wire reader both validate before calling this.
530    #[must_use]
531    pub fn weighted(entries: Vec<(i32, Value)>) -> Self {
532        Self::Weighted(Arc::new(WeightedValue { entries }))
533    }
534
535    /// Build a `some(inner)` [`OptionVal`](Self::OptionVal).
536    pub fn some(inner: Value) -> Self {
537        Self::OptionVal(Some(Arc::new(inner)))
538    }
539
540    /// Build a `none` [`OptionVal`](Self::OptionVal).
541    pub fn none() -> Self {
542        Self::OptionVal(None)
543    }
544
545    /// Borrow the Option payload if this value is an
546    /// [`OptionVal`](Self::OptionVal): `Some(Some(&inner))` for `some(x)`,
547    /// `Some(None)` for `none`, `None` for any non-Option value.
548    pub fn as_option(&self) -> Option<Option<&Value>> {
549        match self {
550            Self::OptionVal(inner) => Some(inner.as_deref()),
551            _ => None,
552        }
553    }
554
555    /// Extract an `i32` if this value is an [`Int`](Self::Int).
556    ///
557    /// Strict: does not coerce floats or booleans. Returns `None` for any
558    /// other variant. For binding authors that want to read an integer
559    /// argument from ink.
560    pub fn as_int(&self) -> Option<i32> {
561        match self {
562            Self::Int(i) => Some(*i),
563            _ => None,
564        }
565    }
566
567    /// Extract an `f32` if this value is numeric.
568    ///
569    /// Lenient on the int → float direction only: an [`Int`](Self::Int) is
570    /// widened to `f32` (matching ink's implicit int→float promotion), but a
571    /// float is never truncated to an int by [`as_int`](Self::as_int).
572    pub fn as_float(&self) -> Option<f32> {
573        match self {
574            Self::Float(f) => Some(*f),
575            #[expect(
576                clippy::cast_precision_loss,
577                reason = "int->float promotion matches ink coercion semantics"
578            )]
579            Self::Int(i) => Some(*i as f32),
580            _ => None,
581        }
582    }
583
584    /// Extract a `bool` if this value is a [`Bool`](Self::Bool).
585    ///
586    /// Strict: does not treat nonzero numbers as truthy. Use the VM's own
587    /// truthiness rules if you need ink-style coercion.
588    pub fn as_bool(&self) -> Option<bool> {
589        match self {
590            Self::Bool(b) => Some(*b),
591            _ => None,
592        }
593    }
594
595    /// Borrow the string contents if this value is a [`String`](Self::String).
596    pub fn as_str(&self) -> Option<&str> {
597        match self {
598            Self::String(s) => Some(s),
599            _ => None,
600        }
601    }
602
603    /// Build an [`Array`](Self::Array) from a vector of values.
604    pub fn array(items: Vec<Value>) -> Self {
605        Self::Array(Arc::new(items))
606    }
607
608    /// Build a [`Map`](Self::Map) from an [`OrderedMap`].
609    pub fn map(map: OrderedMap) -> Self {
610        Self::Map(Arc::new(map))
611    }
612
613    /// Build a [`Record`](Self::Record) from a shape id and its field values
614    /// (already in the shape's declared field order — the caller, not this
615    /// constructor, is responsible for ordering).
616    pub fn record(shape: ShapeId, fields: Vec<Value>) -> Self {
617        Self::Record {
618            shape,
619            fields: Arc::new(fields),
620        }
621    }
622
623    /// Borrow the record payload if this value is a [`Record`](Self::Record).
624    pub fn as_record(&self) -> Option<(ShapeId, &Arc<Vec<Value>>)> {
625        match self {
626            Self::Record { shape, fields } => Some((*shape, fields)),
627            _ => None,
628        }
629    }
630
631    /// Build a [`Closure`](Self::Closure) from a target and its bound-arg
632    /// prefix (T1c, `docs/t1c-spec.md` §6).
633    pub fn closure(target: DefinitionId, env: Vec<ClosureEnvEntry>) -> Self {
634        Self::Closure(Arc::new(ClosureValue { target, env }))
635    }
636
637    /// The fn token (target [`DefinitionId`]) if this value is a function
638    /// value — [`FnRef`](Self::FnRef) or [`Closure`](Self::Closure).
639    pub fn fn_target(&self) -> Option<DefinitionId> {
640        match self {
641            Self::FnRef(target) => Some(*target),
642            Self::Closure(c) => Some(c.target),
643            _ => None,
644        }
645    }
646
647    /// Borrow the closure payload if this value is a [`Closure`](Self::Closure).
648    pub fn as_closure(&self) -> Option<&Arc<ClosureValue>> {
649        match self {
650            Self::Closure(c) => Some(c),
651            _ => None,
652        }
653    }
654
655    /// Borrow the weighted-table payload if this value is a
656    /// [`Weighted`](Self::Weighted).
657    pub fn as_weighted(&self) -> Option<&Arc<WeightedValue>> {
658        match self {
659            Self::Weighted(w) => Some(w),
660            _ => None,
661        }
662    }
663
664    /// Build a [`Handle`](Self::Handle) token from a kind and host-allocated
665    /// id (T1d, `docs/t1d-spec.md` §2). Note: this constructor is a plain
666    /// value builder, not a capability mint — the invariant that handles
667    /// only ever originate from a binding is enforced by the compiler (no
668    /// literal syntax, no opcode constructs this value), not by this type.
669    pub fn handle(kind: NameId, id: u64) -> Self {
670        Self::Handle { kind, id }
671    }
672
673    /// Borrow the `(kind, id)` pair if this value is a [`Handle`](Self::Handle).
674    pub fn as_handle(&self) -> Option<(NameId, u64)> {
675        match self {
676            Self::Handle { kind, id } => Some((*kind, *id)),
677            _ => None,
678        }
679    }
680
681    /// Build a [`Projection`](Self::Projection) from a root cell and its
682    /// ordered segment chain (`docs/t1e-spec.md` §1/§3).
683    pub fn projection(cell: DefinitionId, segments: Vec<ProjSegment>) -> Self {
684        Self::Projection(Arc::new(ProjectionValue { cell, segments }))
685    }
686
687    /// Borrow the projection payload if this value is a
688    /// [`Projection`](Self::Projection).
689    pub fn as_projection(&self) -> Option<&Arc<ProjectionValue>> {
690        match self {
691            Self::Projection(p) => Some(p),
692            _ => None,
693        }
694    }
695
696    /// Extract the glam payload if this value is a [`Vec2`](Self::Vec2) —
697    /// the NS-A8 identity-marshal read for binding authors (T1: glam is the
698    /// compute type on both sides of the boundary). Strict like
699    /// [`as_int`](Self::as_int): no cross-kind coercion.
700    pub fn as_vec2(&self) -> Option<glam::Vec2> {
701        match self {
702            Self::Vec2(v) => Some(*v),
703            _ => None,
704        }
705    }
706
707    /// Extract the glam payload if this value is a [`Vec3`](Self::Vec3).
708    pub fn as_vec3(&self) -> Option<glam::Vec3> {
709        match self {
710            Self::Vec3(v) => Some(*v),
711            _ => None,
712        }
713    }
714
715    /// Extract the glam payload if this value is a [`Vec4`](Self::Vec4).
716    pub fn as_vec4(&self) -> Option<glam::Vec4> {
717        match self {
718            Self::Vec4(v) => Some(*v),
719            _ => None,
720        }
721    }
722
723    /// Extract the glam payload if this value is a [`Quat`](Self::Quat).
724    pub fn as_quat(&self) -> Option<glam::Quat> {
725        match self {
726            Self::Quat(q) => Some(*q),
727            _ => None,
728        }
729    }
730
731    /// Extract the glam payload if this value is a [`Mat2`](Self::Mat2).
732    pub fn as_mat2(&self) -> Option<glam::Mat2> {
733        match self {
734            Self::Mat2(m) => Some(*m),
735            _ => None,
736        }
737    }
738
739    /// Extract the glam payload if this value is a [`Mat3`](Self::Mat3).
740    pub fn as_mat3(&self) -> Option<glam::Mat3> {
741        match self {
742            Self::Mat3(m) => Some(*m),
743            _ => None,
744        }
745    }
746
747    /// Extract the glam payload if this value is a [`Mat4`](Self::Mat4).
748    pub fn as_mat4(&self) -> Option<glam::Mat4> {
749        match self {
750            Self::Mat4(m) => Some(*m),
751            _ => None,
752        }
753    }
754
755    /// Borrow the array payload if this value is an [`Array`](Self::Array).
756    ///
757    /// Read-only: the returned slice never triggers a copy. Mutation uses
758    /// [`array_make_mut`](Self::array_make_mut).
759    pub fn as_array(&self) -> Option<&Arc<Vec<Value>>> {
760        match self {
761            Self::Array(items) => Some(items),
762            _ => None,
763        }
764    }
765
766    /// Borrow the map payload if this value is a [`Map`](Self::Map).
767    ///
768    /// Read-only: mutation uses [`map_make_mut`](Self::map_make_mut).
769    pub fn as_map(&self) -> Option<&Arc<OrderedMap>> {
770        match self {
771            Self::Map(map) => Some(map),
772            _ => None,
773        }
774    }
775
776    /// Copy-on-write mutable access to an [`Array`](Self::Array)'s backing
777    /// vector, or `None` for any other value.
778    ///
779    /// This is the `make_mut` step of the take → `make_mut` → write-back RMW
780    /// discipline (value-model-spec §5). When the backing `Arc` is unique the
781    /// mutation is in place (O(1) amortized append); when it is shared with a
782    /// snapshot or another slot, exactly one O(n) copy is made and the value
783    /// becomes unique again. Because sharing is unobservable (§3), callers
784    /// cannot tell which path was taken.
785    pub fn array_make_mut(&mut self) -> Option<&mut Vec<Value>> {
786        match self {
787            Self::Array(items) => Some(Arc::make_mut(items)),
788            _ => None,
789        }
790    }
791
792    /// Copy-on-write mutable access to a [`Map`](Self::Map)'s contents, or
793    /// `None` for any other value. See [`array_make_mut`](Self::array_make_mut)
794    /// for the RMW discipline this implements.
795    pub fn map_make_mut(&mut self) -> Option<&mut OrderedMap> {
796        match self {
797            Self::Map(map) => Some(Arc::make_mut(map)),
798            _ => None,
799        }
800    }
801
802    /// Copy-on-write mutable access to a [`Record`](Self::Record)'s flat
803    /// field vector, or `None` for any other value. See
804    /// [`array_make_mut`](Self::array_make_mut) for the RMW discipline this
805    /// implements — the shape itself never changes (closed shape), only
806    /// field values.
807    pub fn record_make_mut(&mut self) -> Option<&mut Vec<Value>> {
808        match self {
809            Self::Record { fields, .. } => Some(Arc::make_mut(fields)),
810            _ => None,
811        }
812    }
813}
814
815/// Structural equality with an `Arc::ptr_eq` fast path for collections
816/// (value-model-spec §4/§5).
817///
818/// Every scalar arm reproduces exactly what `#[derive(PartialEq)]` would emit.
819/// The `Array`/`Map` arms add the `ptr_eq` shortcut: two values that share the
820/// same `Arc` (the same snapshot) compare equal immediately, otherwise the
821/// comparison is element-wise structural. NaN-bearing collections that are
822/// *not* the same snapshot never compare equal, because `f32` equality
823/// composes structurally through the elements; a collection compared against
824/// *itself* (same `Arc`) is equal even if it contains a NaN — the spec calls
825/// this out as harmless and stated (§4).
826impl PartialEq for Value {
827    #[expect(
828        clippy::match_same_arms,
829        reason = "each scalar variant is spelled out so the mapping to the \
830                  derive it replaces is auditable; merging identical `a == b` \
831                  bodies would obscure which variants are covered"
832    )]
833    fn eq(&self, other: &Self) -> bool {
834        match (self, other) {
835            (Self::Int(a), Self::Int(b)) => a == b,
836            (Self::Float(a), Self::Float(b)) => a == b,
837            (Self::Bool(a), Self::Bool(b)) => a == b,
838            (Self::String(a), Self::String(b)) => a == b,
839            (Self::List(a), Self::List(b)) => a == b,
840            (Self::DivertTarget(a), Self::DivertTarget(b)) => a == b,
841            (Self::VariablePointer(a), Self::VariablePointer(b)) => a == b,
842            (
843                Self::TempPointer {
844                    slot: a_slot,
845                    frame_depth: a_depth,
846                },
847                Self::TempPointer {
848                    slot: b_slot,
849                    frame_depth: b_depth,
850                },
851            ) => a_slot == b_slot && a_depth == b_depth,
852            (Self::Null, Self::Null) => true,
853            (Self::FragmentRef(a), Self::FragmentRef(b)) => a == b,
854            (Self::Array(a), Self::Array(b)) => Arc::ptr_eq(a, b) || a == b,
855            (Self::Map(a), Self::Map(b)) => Arc::ptr_eq(a, b) || a == b,
856            (
857                Self::Record {
858                    shape: sa,
859                    fields: a,
860                },
861                Self::Record {
862                    shape: sb,
863                    fields: b,
864                },
865            ) => sa == sb && (Arc::ptr_eq(a, b) || a == b),
866            // Function values (T1c, docs/t1c-spec.md §5): structural equality
867            // is "same fn token and equal bound-arg rows". `FnRef` is the
868            // zero-bound case (same token). `Closure` adds the env comparison,
869            // with the `Arc::ptr_eq` fast path mirroring the collection arms;
870            // a `ref` env entry's `VariablePointer` payload compares by cell,
871            // a `val` entry by value — both fall out of `ClosureValue`'s
872            // derived `PartialEq`.
873            (Self::FnRef(a), Self::FnRef(b)) => a == b,
874            (Self::Closure(a), Self::Closure(b)) => Arc::ptr_eq(a, b) || a == b,
875            // Handle equality (T1d, docs/t1d-spec.md §6): token equality —
876            // same kind and same id, nothing else. No ordering exists (there
877            // is no `PartialOrd`/`Ord` impl for `Value`), and a handle is
878            // never a legal map key (`MapKey::from_value` has no `Handle`
879            // arm, so it falls through to `None` for this variant).
880            (Self::Handle { kind: ka, id: ida }, Self::Handle { kind: kb, id: idb }) => {
881                ka == kb && ida == idb
882            }
883            // Projection equality (T1e, docs/t1e-spec.md §4 PROPOSED): same
884            // root cell + equal segments, with the `Arc::ptr_eq` fast path
885            // mirroring every other heap-allocated variant.
886            (Self::Projection(a), Self::Projection(b)) => Arc::ptr_eq(a, b) || a == b,
887            // Option equality (NS-A1): structural — `none == none`,
888            // `some(x) == some(y)` iff `x == y`, with the `Arc::ptr_eq`
889            // fast path on the `some` payload mirroring every other
890            // heap-allocated variant. Cross-variant (`some(1) == 1`) falls
891            // through to `false` below — the ruled `Option[T] ≠ T`
892            // strictness at the value layer.
893            // Weighted equality (NS-A7): multiset content with the
894            // `Arc::ptr_eq` fast path — see `WeightedValue`'s `PartialEq`.
895            (Self::Weighted(a), Self::Weighted(b)) => Arc::ptr_eq(a, b) || a == b,
896            (Self::OptionVal(a), Self::OptionVal(b)) => match (a, b) {
897                (None, None) => true,
898                (Some(x), Some(y)) => Arc::ptr_eq(x, y) || x == y,
899                _ => false,
900            },
901            // Range equality (NS-A5, F7 "content equality"): two ranges are
902            // equal iff they denote the same integer sequence — the written
903            // form (`..` vs `..=`) is display fidelity, not content, so
904            // `1..=6 == 1..7`, and every empty range equals every other
905            // empty range (both denote the zero-length sequence, exactly as
906            // two empty arrays are equal). The #909 map ruling is the
907            // precedent: content compares, form displays.
908            (a @ Self::Range { start: sa, .. }, b @ Self::Range { start: sb, .. }) => {
909                let (la, lb) = (a.range_len(), b.range_len());
910                match (la, lb) {
911                    (Some(0), Some(0)) => true,
912                    (Some(x), Some(y)) => x == y && sa == sb,
913                    // Unreachable: both sides are `Range`.
914                    _ => false,
915                }
916            }
917            // Tower equality (NS-A8, `docs/tower-mini-spec.md` T4):
918            // componentwise IEEE via glam's own `PartialEq` — a NaN lane
919            // makes a value unequal to *itself*, `-0.0 == +0.0` per lane,
920            // exactly like the bare `Float` arm above. Cross-kind pairs
921            // (`Vec2` vs `Vec3`) fall through to `false` below, like every
922            // other cross-variant pair.
923            (Self::Vec2(a), Self::Vec2(b)) => a == b,
924            (Self::Vec3(a), Self::Vec3(b)) => a == b,
925            (Self::Vec4(a), Self::Vec4(b)) => a == b,
926            (Self::Quat(a), Self::Quat(b)) => a == b,
927            (Self::Mat2(a), Self::Mat2(b)) => a == b,
928            (Self::Mat3(a), Self::Mat3(b)) => a == b,
929            (Self::Mat4(a), Self::Mat4(b)) => a == b,
930            _ => false,
931        }
932    }
933}
934
935impl From<i32> for Value {
936    fn from(v: i32) -> Self {
937        Self::Int(v)
938    }
939}
940
941impl From<f32> for Value {
942    fn from(v: f32) -> Self {
943        Self::Float(v)
944    }
945}
946
947impl From<bool> for Value {
948    fn from(v: bool) -> Self {
949        Self::Bool(v)
950    }
951}
952
953impl From<&str> for Value {
954    fn from(v: &str) -> Self {
955        Self::String(Arc::from(v))
956    }
957}
958
959impl From<String> for Value {
960    fn from(v: String) -> Self {
961        Self::String(Arc::from(v))
962    }
963}
964
965impl From<Arc<str>> for Value {
966    fn from(v: Arc<str>) -> Self {
967        Self::String(v)
968    }
969}
970
971impl From<()> for Value {
972    /// The unit type maps to [`Null`](Self::Null) — the natural return for a
973    /// fire-and-forget external that produces no value.
974    fn from((): ()) -> Self {
975        Self::Null
976    }
977}
978
979// NS-A8: identity conversions from the glam compute types (T1 — "one
980// workspace-pinned glam version shared with bevy-brink → the bevy marshal is
981// identity on the same types"). A host binding returning `impl Into<Value>`
982// can hand back a `glam::Vec3` directly.
983
984impl From<glam::Vec2> for Value {
985    fn from(v: glam::Vec2) -> Self {
986        Self::Vec2(v)
987    }
988}
989
990impl From<glam::Vec3> for Value {
991    fn from(v: glam::Vec3) -> Self {
992        Self::Vec3(v)
993    }
994}
995
996impl From<glam::Vec4> for Value {
997    fn from(v: glam::Vec4) -> Self {
998        Self::Vec4(v)
999    }
1000}
1001
1002impl From<glam::Quat> for Value {
1003    fn from(v: glam::Quat) -> Self {
1004        Self::Quat(v)
1005    }
1006}
1007
1008impl From<glam::Mat2> for Value {
1009    fn from(v: glam::Mat2) -> Self {
1010        Self::Mat2(v)
1011    }
1012}
1013
1014impl From<glam::Mat3> for Value {
1015    fn from(v: glam::Mat3) -> Self {
1016        Self::Mat3(v)
1017    }
1018}
1019
1020impl From<glam::Mat4> for Value {
1021    fn from(v: glam::Mat4) -> Self {
1022        Self::Mat4(v)
1023    }
1024}
1025
1026/// An ink list value: a set of list items plus their origin list definitions.
1027#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1028pub struct ListValue {
1029    /// The active items in this list (each a `ListItem` `DefinitionId`).
1030    pub items: Vec<DefinitionId>,
1031    /// The origin list definitions this value was derived from.
1032    pub origins: Vec<DefinitionId>,
1033}
1034
1035/// A scalar key for a [`Value::Map`].
1036///
1037/// v1 permits `int`, `string`, and `bool` keys (value-model-spec §4). Keys are
1038/// compared for equality only — never hashed or sorted — because a
1039/// [`Value::Map`] iterates in insertion order. Two keys of different variants
1040/// are never equal (an `Int(1)` key and a `Bool(true)` key are distinct).
1041#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1042pub enum MapKey {
1043    /// An integer key.
1044    Int(i32),
1045    /// A string key.
1046    Str(Arc<str>),
1047    /// A boolean key.
1048    Bool(bool),
1049}
1050
1051impl MapKey {
1052    /// Derive a map key from a scalar [`Value`], or `None` if the value is not
1053    /// one of the permitted key types (`int`/`string`/`bool`).
1054    ///
1055    /// This is the seam the collection opcodes (T1b) use to turn an indexing
1056    /// operand into a key; keeping it here keeps the permitted key domain in
1057    /// one place.
1058    pub fn from_value(value: &Value) -> Option<Self> {
1059        match value {
1060            Value::Int(n) => Some(Self::Int(*n)),
1061            Value::String(s) => Some(Self::Str(Arc::clone(s))),
1062            Value::Bool(b) => Some(Self::Bool(*b)),
1063            _ => None,
1064        }
1065    }
1066}
1067
1068impl From<i32> for MapKey {
1069    fn from(v: i32) -> Self {
1070        Self::Int(v)
1071    }
1072}
1073
1074impl From<bool> for MapKey {
1075    fn from(v: bool) -> Self {
1076        Self::Bool(v)
1077    }
1078}
1079
1080impl From<&str> for MapKey {
1081    fn from(v: &str) -> Self {
1082        Self::Str(Arc::from(v))
1083    }
1084}
1085
1086impl From<String> for MapKey {
1087    fn from(v: String) -> Self {
1088        Self::Str(Arc::from(v))
1089    }
1090}
1091
1092impl From<Arc<str>> for MapKey {
1093    fn from(v: Arc<str>) -> Self {
1094        Self::Str(v)
1095    }
1096}
1097
1098/// The payload of [`Value::Map`]: an insertion-ordered map with scalar keys.
1099///
1100/// Backed by a flat `Vec` of `(key, value)` entries. Game-scale maps are
1101/// small, and a flat vector beats persistent/HAMT or hashed structures on both
1102/// constant factors and wasm size (value-model-spec §5). Iteration is
1103/// insertion order — the ratified ruling for v1 (§4) — so the structure is
1104/// deterministic without any sorting, and there is no `HashMap` to leak
1105/// iteration order.
1106///
1107/// `insert`/`remove` preserve insertion order: re-inserting an existing key
1108/// overwrites its value in place (keeping the key's original position), and
1109/// The payload of a [`Value::Weighted`] (NS-A7, `docs/stdlib-spec.md` §8):
1110/// positive-int weights over values, kept in construction order.
1111///
1112/// `PartialEq` is hand-written: equality is **multiset content** — the same
1113/// `(weight, value)` entries with the same multiplicities, regardless of
1114/// order (the #909 map content-over-form precedent applied to the F17
1115/// multiset: `weighted(3, "a", 1, "b") == weighted(1, "b", 3, "a")`).
1116/// Duplicate entries are legal and multiplicity-sensitive. Construction
1117/// order still governs display and the `roll` draw walk (deterministic
1118/// offset → entry mapping), exactly as map iteration order survives the
1119/// order-insensitive map equality. O(n²) matching — the accepted trade for
1120/// small, hand-written game-scale tables (same as `OrderedMap`).
1121#[derive(Debug, Clone, Serialize, Deserialize)]
1122pub struct WeightedValue {
1123    /// `(weight, value)` entries in construction order. Invariant (held by
1124    /// the only producer, `weighted_new`, and the wire reader): non-empty,
1125    /// every weight ≥ 1.
1126    pub entries: Vec<(i32, Value)>,
1127}
1128
1129impl WeightedValue {
1130    /// The total weight of the table as an `i64` (a sum of `i32` weights
1131    /// can exceed `i32::MAX`; the draw walks in `i64`).
1132    #[must_use]
1133    pub fn total_weight(&self) -> i64 {
1134        self.entries.iter().map(|(w, _)| i64::from(*w)).sum()
1135    }
1136}
1137
1138impl PartialEq for WeightedValue {
1139    fn eq(&self, other: &Self) -> bool {
1140        if self.entries.len() != other.entries.len() {
1141            return false;
1142        }
1143        let mut used = vec![false; other.entries.len()];
1144        'outer: for (w, v) in &self.entries {
1145            for (i, (ow, ov)) in other.entries.iter().enumerate() {
1146                if !used[i] && w == ow && v == ov {
1147                    used[i] = true;
1148                    continue 'outer;
1149                }
1150            }
1151            return false;
1152        }
1153        true
1154    }
1155}
1156
1157/// `remove` shifts later entries down. Lookups are linear; that is the
1158/// intended trade for small maps and stable ordering.
1159///
1160/// `PartialEq` is hand-written, not derived (issue #909, ruled 2026-07-18 —
1161/// `docs/decision-log.md` "Map/record equality is insertion-order-insensitive"):
1162/// equality is **content-based**, comparing key→value pairs regardless of
1163/// insertion order — `#{a:1,b:2} == #{b:2,a:1}` is `true`. Only equality
1164/// ignores order; [`iter`](Self::iter)/[`keys`](Self::keys)/[`values`](Self::values)
1165/// and every codec still walk `entries` in insertion order, unchanged. The
1166/// derived `PartialEq` this replaces compared `entries` as a `Vec`, which is
1167/// order-sensitive — the bug. `Value::Map`'s `Arc::ptr_eq` fast path (same
1168/// snapshot → instant `true`) lives one level up in `impl PartialEq for
1169/// Value`; this impl is the structural fallback it calls into.
1170#[derive(Debug, Clone, Default, Serialize)]
1171pub struct OrderedMap {
1172    entries: Vec<(MapKey, Value)>,
1173}
1174
1175impl<'de> Deserialize<'de> for OrderedMap {
1176    /// Hand-written, not derived (issue #985, follow-up to #909): the derived
1177    /// impl would deserialize `entries` verbatim as a `Vec<(MapKey, Value)>`,
1178    /// letting a crafted or corrupt payload carry a duplicate key and
1179    /// construct a map that violates the content-based `Eq` invariant above
1180    /// — `Eq` assumes each key appears at most once. This decodes into the
1181    /// same shape the derive would have produced, then walks the entries
1182    /// through the same duplicate-key check the `.inkb`/`.inkt`/transcript
1183    /// decoders use (rejecting rather than silently keeping the last
1184    /// occurrence — a legitimate encoder never emits a repeat, since
1185    /// `insert` de-duplicates on the write side, so a repeat is corrupt
1186    /// input, never a panic).
1187    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1188    where
1189        D: serde::Deserializer<'de>,
1190    {
1191        #[derive(Deserialize)]
1192        struct Shadow {
1193            entries: Vec<(MapKey, Value)>,
1194        }
1195
1196        let shadow = Shadow::deserialize(deserializer)?;
1197        let mut map = Self::with_capacity(shadow.entries.len());
1198        for (key, value) in shadow.entries {
1199            if map.contains_key(&key) {
1200                return Err(serde::de::Error::custom("duplicate key in map value"));
1201            }
1202            map.insert(key, value);
1203        }
1204        Ok(map)
1205    }
1206}
1207
1208impl PartialEq for OrderedMap {
1209    /// Content comparison: same number of entries, and every key in `self`
1210    /// maps to an equal value in `other`. Order-insensitive by construction
1211    /// (a lookup by key, not a positional walk) — the len check is a fast
1212    /// path (mismatched sizes can never be equal, and it makes the
1213    /// same-length-different-keys case cheap to reject), and the per-entry
1214    /// `get` gives each comparison the same `Value::eq` `Arc::ptr_eq`
1215    /// shortcuts as any other structural compare. `O(n)` entries, each a
1216    /// linear `get` — `O(n^2)` worst case, the accepted trade for small,
1217    /// game-scale maps (same trade `get`/`insert`/`remove` already make).
1218    fn eq(&self, other: &Self) -> bool {
1219        self.entries.len() == other.entries.len()
1220            && self.entries.iter().all(|(key, value)| {
1221                other
1222                    .get(key)
1223                    .is_some_and(|other_value| other_value == value)
1224            })
1225    }
1226}
1227
1228impl OrderedMap {
1229    /// Create an empty map.
1230    pub fn new() -> Self {
1231        Self {
1232            entries: Vec::new(),
1233        }
1234    }
1235
1236    /// Create an empty map with capacity for `n` entries.
1237    pub fn with_capacity(n: usize) -> Self {
1238        Self {
1239            entries: Vec::with_capacity(n),
1240        }
1241    }
1242
1243    /// Number of entries.
1244    pub fn len(&self) -> usize {
1245        self.entries.len()
1246    }
1247
1248    /// Whether the map has no entries.
1249    pub fn is_empty(&self) -> bool {
1250        self.entries.is_empty()
1251    }
1252
1253    /// Borrow the value for `key`, or `None` if absent.
1254    pub fn get(&self, key: &MapKey) -> Option<&Value> {
1255        self.entries.iter().find(|(k, _)| k == key).map(|(_, v)| v)
1256    }
1257
1258    /// Whether `key` is present.
1259    pub fn contains_key(&self, key: &MapKey) -> bool {
1260        self.entries.iter().any(|(k, _)| k == key)
1261    }
1262
1263    /// Mutably borrow the value for `key`, or `None` if absent. The
1264    /// intermediate-segment leg of the T1e projection RMW spine
1265    /// (`docs/t1e-spec.md` §3): recursing a `make_mut` chain through a map
1266    /// needs a mutable handle to an *existing* entry without touching
1267    /// insertion order, which [`insert`](Self::insert) alone (add-or-replace)
1268    /// can't provide.
1269    pub fn get_mut(&mut self, key: &MapKey) -> Option<&mut Value> {
1270        self.entries
1271            .iter_mut()
1272            .find(|(k, _)| k == key)
1273            .map(|(_, v)| v)
1274    }
1275
1276    /// Insert `value` under `key`, returning the previous value if the key was
1277    /// already present.
1278    ///
1279    /// An existing key keeps its insertion position (only its value changes);
1280    /// a new key is appended, so first-insertion order is preserved.
1281    pub fn insert(&mut self, key: MapKey, value: Value) -> Option<Value> {
1282        if let Some((_, slot)) = self.entries.iter_mut().find(|(k, _)| *k == key) {
1283            Some(core::mem::replace(slot, value))
1284        } else {
1285            self.entries.push((key, value));
1286            None
1287        }
1288    }
1289
1290    /// Remove `key`, returning its value if it was present. Later entries shift
1291    /// down, so insertion order among the survivors is preserved.
1292    pub fn remove(&mut self, key: &MapKey) -> Option<Value> {
1293        let idx = self.entries.iter().position(|(k, _)| k == key)?;
1294        Some(self.entries.remove(idx).1)
1295    }
1296
1297    /// Iterate `(key, value)` pairs in insertion order.
1298    pub fn iter(&self) -> impl Iterator<Item = (&MapKey, &Value)> {
1299        self.entries.iter().map(|(k, v)| (k, v))
1300    }
1301
1302    /// Iterate keys in insertion order.
1303    pub fn keys(&self) -> impl Iterator<Item = &MapKey> {
1304        self.entries.iter().map(|(k, _)| k)
1305    }
1306
1307    /// Iterate values in insertion order.
1308    pub fn values(&self) -> impl Iterator<Item = &Value> {
1309        self.entries.iter().map(|(_, v)| v)
1310    }
1311}
1312
1313impl FromIterator<(MapKey, Value)> for OrderedMap {
1314    /// Collect entries in order. If a key repeats, the last value wins while
1315    /// the key keeps its first-insertion position — matching [`insert`].
1316    ///
1317    /// [`insert`]: OrderedMap::insert
1318    fn from_iter<I: IntoIterator<Item = (MapKey, Value)>>(iter: I) -> Self {
1319        let mut map = Self::new();
1320        for (k, v) in iter {
1321            map.insert(k, v);
1322        }
1323        map
1324    }
1325}
1326
1327#[cfg(test)]
1328mod tests {
1329    use super::*;
1330    use crate::id::DefinitionTag;
1331
1332    #[test]
1333    fn value_type_discriminant() {
1334        assert_eq!(Value::Int(0).value_type(), ValueType::Int);
1335        assert_eq!(Value::Float(0.0).value_type(), ValueType::Float);
1336        assert_eq!(Value::Bool(true).value_type(), ValueType::Bool);
1337        assert_eq!(Value::String("".into()).value_type(), ValueType::String);
1338        assert_eq!(Value::Null.value_type(), ValueType::Null);
1339
1340        let list = ListValue {
1341            items: vec![],
1342            origins: vec![],
1343        };
1344        assert_eq!(Value::List(list.into()).value_type(), ValueType::List);
1345
1346        let target = DefinitionId::new(DefinitionTag::Address, 1);
1347        assert_eq!(
1348            Value::DivertTarget(target).value_type(),
1349            ValueType::DivertTarget
1350        );
1351    }
1352
1353    #[test]
1354    fn from_impls_roundtrip() {
1355        assert_eq!(Value::from(7_i32), Value::Int(7));
1356        assert_eq!(Value::from(1.5_f32), Value::Float(1.5));
1357        assert_eq!(Value::from(true), Value::Bool(true));
1358        assert_eq!(Value::from("hi"), Value::String("hi".into()));
1359        assert_eq!(Value::from(String::from("hi")), Value::String("hi".into()));
1360        assert_eq!(Value::from(()), Value::Null);
1361    }
1362
1363    #[test]
1364    fn accessors_are_strict_except_int_to_float() {
1365        assert_eq!(Value::Int(3).as_int(), Some(3));
1366        assert_eq!(Value::Float(3.0).as_int(), None);
1367        assert_eq!(Value::Bool(true).as_int(), None);
1368
1369        // int->float promotion is allowed (matches ink coercion);
1370        // float->int truncation is not.
1371        assert_eq!(Value::Int(3).as_float(), Some(3.0));
1372        assert_eq!(Value::Float(2.5).as_float(), Some(2.5));
1373
1374        assert_eq!(Value::Bool(true).as_bool(), Some(true));
1375        assert_eq!(Value::Int(1).as_bool(), None);
1376
1377        assert_eq!(Value::String("x".into()).as_str(), Some("x"));
1378        assert_eq!(Value::Int(1).as_str(), None);
1379    }
1380
1381    // ── Collections: value_type + constructors ─────────────────────────────
1382
1383    #[test]
1384    fn collection_value_types() {
1385        assert_eq!(
1386            Value::array(vec![Value::Int(1)]).value_type(),
1387            ValueType::Array
1388        );
1389        assert_eq!(Value::map(OrderedMap::new()).value_type(), ValueType::Map);
1390    }
1391
1392    #[test]
1393    fn array_accessors() {
1394        let v = Value::array(vec![Value::Int(1), Value::Int(2)]);
1395        let items = v.as_array().expect("is array");
1396        assert_eq!(items.len(), 2);
1397        assert!(Value::Int(0).as_array().is_none());
1398        assert!(v.as_map().is_none());
1399    }
1400
1401    // ── MapKey ─────────────────────────────────────────────────────────────
1402
1403    #[test]
1404    fn map_key_from_value_permitted_domain() {
1405        assert_eq!(MapKey::from_value(&Value::Int(3)), Some(MapKey::Int(3)));
1406        assert_eq!(
1407            MapKey::from_value(&Value::String("k".into())),
1408            Some(MapKey::Str("k".into()))
1409        );
1410        assert_eq!(
1411            MapKey::from_value(&Value::Bool(true)),
1412            Some(MapKey::Bool(true))
1413        );
1414        // Non-scalar / disallowed key types are rejected.
1415        assert_eq!(MapKey::from_value(&Value::Float(1.0)), None);
1416        assert_eq!(MapKey::from_value(&Value::Null), None);
1417        assert_eq!(MapKey::from_value(&Value::array(vec![])), None);
1418    }
1419
1420    #[test]
1421    fn map_key_variants_are_distinct() {
1422        // 1, "1", and true are three different keys even though they might
1423        // coerce to each other elsewhere in the VM.
1424        assert_ne!(MapKey::Int(1), MapKey::Bool(true));
1425        assert_ne!(MapKey::from(1), MapKey::from("1"));
1426        assert_ne!(MapKey::from(true), MapKey::from(false));
1427        assert_eq!(MapKey::from(1), MapKey::Int(1));
1428        assert_eq!(MapKey::from("a"), MapKey::Str("a".into()));
1429    }
1430
1431    // ── OrderedMap: insertion order, insert/get/remove ─────────────────────
1432
1433    #[test]
1434    fn ordered_map_preserves_insertion_order() {
1435        let mut m = OrderedMap::new();
1436        assert!(m.is_empty());
1437        m.insert(MapKey::from("b"), Value::Int(2));
1438        m.insert(MapKey::from("a"), Value::Int(1));
1439        m.insert(MapKey::from("c"), Value::Int(3));
1440        let keys: Vec<&MapKey> = m.keys().collect();
1441        assert_eq!(
1442            keys,
1443            vec![&MapKey::from("b"), &MapKey::from("a"), &MapKey::from("c")]
1444        );
1445        assert_eq!(m.len(), 3);
1446        assert_eq!(m.get(&MapKey::from("a")), Some(&Value::Int(1)));
1447        assert!(m.contains_key(&MapKey::from("c")));
1448        assert!(!m.contains_key(&MapKey::from("z")));
1449    }
1450
1451    #[test]
1452    fn ordered_map_reinsert_keeps_position_and_returns_old() {
1453        let mut m = OrderedMap::new();
1454        m.insert(MapKey::from("x"), Value::Int(1));
1455        m.insert(MapKey::from("y"), Value::Int(2));
1456        let old = m.insert(MapKey::from("x"), Value::Int(9));
1457        assert_eq!(old, Some(Value::Int(1)));
1458        // Order unchanged: x still first.
1459        let keys: Vec<&MapKey> = m.keys().collect();
1460        assert_eq!(keys, vec![&MapKey::from("x"), &MapKey::from("y")]);
1461        assert_eq!(m.get(&MapKey::from("x")), Some(&Value::Int(9)));
1462    }
1463
1464    #[test]
1465    fn ordered_map_remove_shifts_survivors() {
1466        let mut m = OrderedMap::new();
1467        m.insert(MapKey::from("a"), Value::Int(1));
1468        m.insert(MapKey::from("b"), Value::Int(2));
1469        m.insert(MapKey::from("c"), Value::Int(3));
1470        assert_eq!(m.remove(&MapKey::from("b")), Some(Value::Int(2)));
1471        assert_eq!(m.remove(&MapKey::from("b")), None);
1472        let keys: Vec<&MapKey> = m.keys().collect();
1473        assert_eq!(keys, vec![&MapKey::from("a"), &MapKey::from("c")]);
1474    }
1475
1476    #[test]
1477    fn ordered_map_from_iter_last_wins_first_position() {
1478        let m: OrderedMap = [
1479            (MapKey::from("a"), Value::Int(1)),
1480            (MapKey::from("b"), Value::Int(2)),
1481            (MapKey::from("a"), Value::Int(10)),
1482        ]
1483        .into_iter()
1484        .collect();
1485        assert_eq!(m.len(), 2);
1486        let keys: Vec<&MapKey> = m.keys().collect();
1487        assert_eq!(keys, vec![&MapKey::from("a"), &MapKey::from("b")]);
1488        assert_eq!(m.get(&MapKey::from("a")), Some(&Value::Int(10)));
1489    }
1490
1491    // ── OrderedMap::deserialize: duplicate-key rejection (#985, follow-up to
1492    // #909) ──────────────────────────────────────────────────────────────
1493    //
1494    // `OrderedMap`'s `Eq` is content-based and assumes each key appears at
1495    // most once. A legitimate `Serialize` never emits a duplicate key —
1496    // `insert` de-duplicates on the write side — so a JSON payload with a
1497    // repeated key only ever arises from a hand-crafted or corrupted save/
1498    // journal file (the serde deserialize boundary `Story::load_state` and
1499    // friends go through). The hand-written `Deserialize` below must reject
1500    // it with a decode error, never silently keep the last occurrence and
1501    // hand back a map that violates the invariant its `Eq` relies on.
1502
1503    #[test]
1504    fn ordered_map_deserialize_rejects_duplicate_key() {
1505        let json = r#"{"entries":[[{"Str":"a"},{"Int":1}],[{"Str":"a"},{"Int":2}]]}"#;
1506        let err = serde_json::from_str::<OrderedMap>(json)
1507            .expect_err("duplicate key must not deserialize");
1508        assert!(
1509            err.to_string().contains("duplicate key"),
1510            "unexpected error: {err}"
1511        );
1512    }
1513
1514    #[test]
1515    fn ordered_map_deserialize_accepts_distinct_keys() {
1516        let json = r#"{"entries":[[{"Str":"a"},{"Int":1}],[{"Str":"b"},{"Int":2}]]}"#;
1517        let m: OrderedMap = serde_json::from_str(json).expect("distinct keys must deserialize");
1518        assert_eq!(m.len(), 2);
1519        assert_eq!(m.get(&MapKey::from("a")), Some(&Value::Int(1)));
1520        assert_eq!(m.get(&MapKey::from("b")), Some(&Value::Int(2)));
1521    }
1522
1523    #[test]
1524    fn ordered_map_serde_json_round_trip_without_duplicates() {
1525        let mut m = OrderedMap::new();
1526        m.insert(MapKey::from("hp"), Value::Int(10));
1527        m.insert(MapKey::from(true), Value::String("flag".into()));
1528        m.insert(MapKey::from(7), Value::Float(1.5));
1529        let json = serde_json::to_string(&m).expect("serialize");
1530        let back: OrderedMap = serde_json::from_str(&json).expect("deserialize");
1531        assert_eq!(back, m);
1532    }
1533
1534    // A crafted `Value::Map` payload (the shape a `SaveState`/journal decode
1535    // actually walks) must reject the same way as the bare `OrderedMap` case
1536    // above — the duplicate-key check has to fire through `Value`'s derived
1537    // `Deserialize` too, not just when `OrderedMap` is deserialized directly.
1538    // ── NS-A8 tower: equality + serde lane discipline ──────────────────
1539
1540    #[test]
1541    fn tower_equality_is_componentwise_ieee() {
1542        let a = Value::Vec2(glam::Vec2::new(1.0, 2.0));
1543        assert_eq!(a, Value::Vec2(glam::Vec2::new(1.0, 2.0)));
1544        // -0 == +0 per lane; a NaN lane never equals itself (T4).
1545        assert_eq!(
1546            Value::Vec2(glam::Vec2::new(-0.0, 1.0)),
1547            Value::Vec2(glam::Vec2::new(0.0, 1.0))
1548        );
1549        let nan = Value::Vec3(glam::Vec3::new(f32::NAN, 0.0, 0.0));
1550        assert_ne!(nan.clone(), nan);
1551        // Cross-kind is plain inequality at the value layer.
1552        assert_ne!(a, Value::Vec3(glam::Vec3::new(1.0, 2.0, 0.0)));
1553    }
1554
1555    /// T5: the serde form is the flat lane array — explicit lanes
1556    /// (column-major for matrices), never glam's memory representation.
1557    #[test]
1558    fn tower_serde_is_flat_lane_arrays() {
1559        let v = Value::Vec3(glam::Vec3::new(1.0, 2.5, -3.0));
1560        let json = serde_json::to_string(&v).expect("serialize");
1561        assert_eq!(json, r#"{"Vec3":[1.0,2.5,-3.0]}"#);
1562        let back: Value = serde_json::from_str(&json).expect("deserialize");
1563        assert_eq!(back, v);
1564
1565        let m = Value::Mat2(glam::Mat2::from_cols_array(&[1.0, 2.0, 3.0, 4.0]));
1566        let json = serde_json::to_string(&m).expect("serialize");
1567        assert_eq!(json, r#"{"Mat2":[1.0,2.0,3.0,4.0]}"#);
1568        let back: Value = serde_json::from_str(&json).expect("deserialize");
1569        assert_eq!(back, m);
1570
1571        let q = Value::Quat(glam::Quat::from_xyzw(0.1, 0.2, 0.3, 0.4));
1572        let back: Value = serde_json::from_str(&serde_json::to_string(&q).expect("serialize"))
1573            .expect("deserialize");
1574        assert_eq!(back, q);
1575    }
1576
1577    #[test]
1578    fn tower_accessors_and_from_impls_are_identity() {
1579        let v = glam::Vec3::new(1.0, 2.0, 3.0);
1580        assert_eq!(Value::from(v).as_vec3(), Some(v));
1581        assert_eq!(Value::from(v).as_vec2(), None);
1582        let m = glam::Mat4::IDENTITY;
1583        assert_eq!(Value::from(m).as_mat4(), Some(m));
1584        assert_eq!(Value::Int(1).as_quat(), None);
1585    }
1586
1587    #[test]
1588    fn value_map_deserialize_rejects_duplicate_key() {
1589        let json = r#"{"Map":{"entries":[[{"Str":"a"},{"Int":1}],[{"Str":"a"},{"Int":2}]]}}"#;
1590        let err =
1591            serde_json::from_str::<Value>(json).expect_err("duplicate key must not deserialize");
1592        assert!(
1593            err.to_string().contains("duplicate key"),
1594            "unexpected error: {err}"
1595        );
1596    }
1597
1598    // ── Copy-on-write mechanics (take → make_mut → write-back) ──────────────
1599
1600    #[test]
1601    fn clone_is_arc_bump_not_deep_copy() {
1602        let v = Value::array(vec![Value::Int(1)]);
1603        let arc = Arc::clone(v.as_array().expect("array"));
1604        assert_eq!(Arc::strong_count(&arc), 2); // v + arc
1605        let v2 = v.clone();
1606        assert_eq!(Arc::strong_count(&arc), 3); // v + v2 + arc
1607        drop(v2);
1608        assert_eq!(Arc::strong_count(&arc), 2);
1609    }
1610
1611    #[test]
1612    fn array_make_mut_in_place_when_unique() {
1613        let mut v = Value::array(vec![Value::Int(1)]);
1614        // Unique Arc: `make_mut` returns the same allocation, no COW copy.
1615        // (Compare the Arc allocation address, not the Vec's data buffer,
1616        // which may move when `push` grows capacity.)
1617        let arc_before = Arc::as_ptr(v.as_array().expect("array"));
1618        v.array_make_mut().expect("array").push(Value::Int(2));
1619        let arc_after = Arc::as_ptr(v.as_array().expect("array"));
1620        assert_eq!(arc_before, arc_after, "unique Arc mutates in place");
1621        assert_eq!(v.as_array().expect("array").len(), 2);
1622    }
1623
1624    #[test]
1625    fn array_make_mut_copies_when_shared() {
1626        let original = Value::array(vec![Value::Int(1)]);
1627        let mut copy = original.clone(); // shares the Arc
1628        // Mutate the copy: COW must fork so `original` is untouched.
1629        copy.array_make_mut().expect("array").push(Value::Int(2));
1630        assert_eq!(
1631            original.as_array().expect("array").as_slice(),
1632            &[Value::Int(1)]
1633        );
1634        assert_eq!(copy.as_array().expect("array").len(), 2);
1635        // After the fork both are unique again.
1636        assert_eq!(Arc::strong_count(original.as_array().expect("array")), 1);
1637    }
1638
1639    #[test]
1640    fn map_make_mut_copies_when_shared() {
1641        let mut base = OrderedMap::new();
1642        base.insert(MapKey::from("a"), Value::Int(1));
1643        let original = Value::map(base);
1644        let mut copy = original.clone();
1645        copy.map_make_mut()
1646            .expect("map")
1647            .insert(MapKey::from("b"), Value::Int(2));
1648        assert_eq!(original.as_map().expect("map").len(), 1);
1649        assert_eq!(copy.as_map().expect("map").len(), 2);
1650    }
1651
1652    #[test]
1653    fn make_mut_returns_none_for_non_collection() {
1654        assert!(Value::Int(1).array_make_mut().is_none());
1655        assert!(Value::Int(1).map_make_mut().is_none());
1656    }
1657
1658    /// `record_make_mut` gets the same COW proof as `array_make_mut`/
1659    /// `map_make_mut` above — issue #1476's audit found this variant was the
1660    /// one collection `make_mut` without a dedicated "copies when shared"
1661    /// regression, despite sharing the exact take → `make_mut` → write-back
1662    /// discipline (the COW discipline documented on [`Value`] —
1663    /// `docs/value-model-spec.md` §4/§5).
1664    #[test]
1665    fn record_make_mut_copies_when_shared() {
1666        let shape = ShapeId(0);
1667        let original = Value::record(shape, vec![Value::Int(1), Value::Int(2)]);
1668        let mut copy = original.clone(); // shares the Arc
1669        copy.record_make_mut().expect("record")[0] = Value::Int(99);
1670        assert_eq!(
1671            original.as_record().expect("record").1.as_slice(),
1672            &[Value::Int(1), Value::Int(2)],
1673            "mutating the copy must never be observable through the original"
1674        );
1675        assert_eq!(
1676            copy.as_record().expect("record").1.as_slice(),
1677            &[Value::Int(99), Value::Int(2)]
1678        );
1679        // After the fork both are unique again.
1680        assert_eq!(
1681            Arc::strong_count(original.as_record().expect("record").1),
1682            1
1683        );
1684    }
1685
1686    /// The classic nested-collection leak site (issue #1476), one layer
1687    /// deeper: a `Record` field itself holds an `Array` (two independent
1688    /// `Arc`s stacked — the record's field vec, and the array's backing
1689    /// vec). `let y = x` then mutating `x`'s field-array in place must never
1690    /// surface through `y`, exactly like a bare `Array`-of-`Array`
1691    /// (`rmw-mutator-shared-nested-lvalue`, `tests/tier1-brink/`).
1692    ///
1693    /// The obvious source form — `STRUCT Bag = #{ items: Array<int> }`,
1694    /// `push(a.items, 3)` — used to fault instead of reaching this code path:
1695    /// `push`/`insert`/`remove`'s bare-lvalue fast path (`try_lower_mutator_stmt`
1696    /// in `brink-ir::lir::lower::blocks`) treated *any* `hir::Expr::Path`
1697    /// lvalue, including a multi-segment TM-4b dotted field-access path like
1698    /// `a.items`, as a single bare-variable target and resolved the whole
1699    /// path's range to its root symbol, applying the mutator to `a` itself (a
1700    /// `Record`) instead of `a`'s `items` field. That was issue #1495, fixed
1701    /// by routing a single-level struct-field mutator lvalue through the new
1702    /// `lower_field_mutator` (mirrors `try_lower_field_assignment`'s existing
1703    /// `path.segments.len() > 1` split). This `Value`-layer test remains as
1704    /// the isolation guarantee's own regression pin; the end-to-end
1705    /// aliasing case now lives at
1706    /// `tests/tier1-brink/struct-field-mutator-lvalue/story.ink` alongside
1707    /// the fix.
1708    #[test]
1709    fn nested_array_inside_record_field_is_isolated_after_copy() {
1710        let shape = ShapeId(0);
1711        let inner = Value::array(vec![Value::Int(1), Value::Int(2)]);
1712        let original = Value::record(shape, vec![Value::String("bag".into()), inner]);
1713        let mut copy = original.clone(); // shares both Arcs (record + inner array)
1714
1715        // Take → make_mut → write-back on the copy's inner array field,
1716        // mirroring `collection_ops`'s RMW discipline: pull the field out,
1717        // COW-mutate it, write it back into the (already-uniqued) record.
1718        let fields = copy.record_make_mut().expect("record");
1719        let mut inner_copy = fields[1].clone();
1720        inner_copy
1721            .array_make_mut()
1722            .expect("array")
1723            .push(Value::Int(3));
1724        fields[1] = inner_copy;
1725
1726        let original_inner = original
1727            .as_record()
1728            .expect("record")
1729            .1
1730            .get(1)
1731            .expect("field 1")
1732            .as_array()
1733            .expect("array");
1734        assert_eq!(
1735            original_inner.as_slice(),
1736            &[Value::Int(1), Value::Int(2)],
1737            "mutating the copy's nested array must never be observable through the original record"
1738        );
1739        let copy_inner = copy
1740            .as_record()
1741            .expect("record")
1742            .1
1743            .get(1)
1744            .expect("field 1")
1745            .as_array()
1746            .expect("array");
1747        assert_eq!(
1748            copy_inner.as_slice(),
1749            &[Value::Int(1), Value::Int(2), Value::Int(3)]
1750        );
1751    }
1752
1753    /// Issue #1476's audit traced `Closure` val-capture (`vm.rs::MakeClosure`),
1754    /// `OptionVal(Some(Arc<Value>))`, and `Weighted` and found each correct by
1755    /// construction — same Arc-COW mechanics as `Array`/`Map`/`Record`, no
1756    /// bespoke mutation path exists for any of them — but flagged that none
1757    /// had a dedicated aliasing regression pinning it, the way
1758    /// `record_make_mut_copies_when_shared` does for records. These three
1759    /// close that gap (folded into #1508 per #1476's review comment); the
1760    /// fourth deferred test — the `as`-binding capture itself — still needs
1761    /// the `.inkb` v6 Choice captured-environment slot (#1684/#1508) and
1762    /// cannot be written yet.
1763    ///
1764    /// A `val` closure-env entry snapshots the bound value at
1765    /// `MakeClosure` time (`ClosureEnvEntry { is_ref: false, .. }`): the
1766    /// snapshot is an ordinary `Value` clone, so it shares the source
1767    /// array's `Arc` until something mutates one side. Mutating the
1768    /// *original* variable after capture must fork via COW, leaving the
1769    /// closure's captured snapshot untouched.
1770    ///
1771    /// This pins the `Value`-layer COW invariant that `MakeClosure`'s
1772    /// val-capture relies on; it hand-builds the `ClosureEnvEntry` directly
1773    /// and does not itself drive `vm.rs::MakeClosure` (`brink-format` cannot
1774    /// reach the VM) — no test here asserts that the opcode handler builds
1775    /// `is_ref: false` snapshot entries the way it does at `vm.rs:906-915`.
1776    #[test]
1777    fn closure_val_capture_is_isolated_from_later_mutation_of_the_source() {
1778        let mut original = Value::array(vec![Value::Int(1)]);
1779        let entry = ClosureEnvEntry {
1780            name: NameId(0),
1781            is_ref: false,
1782            payload: original.clone(), // val-capture snapshot, shares the Arc
1783        };
1784        let closure = Value::closure(DefinitionId::new(DefinitionTag::Address, 0), vec![entry]);
1785
1786        // Mutate the source *after* the closure captured it.
1787        original
1788            .array_make_mut()
1789            .expect("array")
1790            .push(Value::Int(2));
1791        assert_eq!(
1792            original.as_array().expect("array").as_slice(),
1793            &[Value::Int(1), Value::Int(2)]
1794        );
1795
1796        let captured = &closure.as_closure().expect("closure").env[0].payload;
1797        assert_eq!(
1798            captured.as_array().expect("array").as_slice(),
1799            &[Value::Int(1)],
1800            "mutating the source after MakeClosure must never be observable through the val-captured snapshot"
1801        );
1802    }
1803
1804    /// `OptionVal(Some(Arc<Value>))` (NS-A1) wraps its inner value behind its
1805    /// own `Arc`, but the inner `Value` itself may still share a *nested*
1806    /// Arc (e.g. an `Array`'s backing `Vec`) with whatever produced it. The
1807    /// same COW discipline must hold one layer down: mutating the source
1808    /// array after `some(...)` wrapped a clone of it must not leak through.
1809    #[test]
1810    fn option_some_wrap_is_isolated_from_later_mutation_of_the_source() {
1811        let mut original = Value::array(vec![Value::Int(1)]);
1812        let wrapped = Value::some(original.clone()); // shares the inner Arc
1813
1814        original
1815            .array_make_mut()
1816            .expect("array")
1817            .push(Value::Int(2));
1818        assert_eq!(
1819            original.as_array().expect("array").as_slice(),
1820            &[Value::Int(1), Value::Int(2)]
1821        );
1822
1823        let inner = wrapped
1824            .as_option()
1825            .expect("option")
1826            .expect("some")
1827            .as_array()
1828            .expect("array");
1829        assert_eq!(
1830            inner.as_slice(),
1831            &[Value::Int(1)],
1832            "mutating the source after `some(..)` wrapped it must never be observable through the wrapped copy"
1833        );
1834    }
1835
1836    /// `Weighted` (NS-A7) entries hold `Value`s in construction order; a
1837    /// table built from an `Array` entry shares that array's Arc with its
1838    /// source the same way a closure `val` capture or `some(..)` wrap does.
1839    /// Mutating the source after the table was built must not leak into the
1840    /// entry the table already captured.
1841    #[test]
1842    fn weighted_entry_capture_is_isolated_from_later_mutation_of_the_source() {
1843        let mut original = Value::array(vec![Value::Int(1)]);
1844        let table = Value::weighted(vec![(1, original.clone())]);
1845
1846        original
1847            .array_make_mut()
1848            .expect("array")
1849            .push(Value::Int(2));
1850        assert_eq!(
1851            original.as_array().expect("array").as_slice(),
1852            &[Value::Int(1), Value::Int(2)]
1853        );
1854
1855        let weighted = table.as_weighted().expect("weighted");
1856        let entry = weighted.entries[0].1.as_array().expect("array");
1857        assert_eq!(
1858            entry.as_slice(),
1859            &[Value::Int(1)],
1860            "mutating the source after the table captured it must never be observable through the weighted entry"
1861        );
1862    }
1863
1864    // ── Structural equality with the ptr_eq fast path ──────────────────────
1865
1866    #[test]
1867    fn array_equality_is_structural_across_distinct_arcs() {
1868        let a = Value::array(vec![Value::Int(1), Value::Int(2)]);
1869        let b = Value::array(vec![Value::Int(1), Value::Int(2)]);
1870        // Distinct Arcs, equal contents.
1871        assert!(!Arc::ptr_eq(a.as_array().unwrap(), b.as_array().unwrap()));
1872        assert_eq!(a, b);
1873        let c = Value::array(vec![Value::Int(1), Value::Int(3)]);
1874        assert_ne!(a, c);
1875    }
1876
1877    #[test]
1878    fn nested_collection_equality() {
1879        let inner = Value::array(vec![Value::Int(1)]);
1880        let a = Value::array(vec![inner.clone(), Value::map(OrderedMap::new())]);
1881        let b = Value::array(vec![
1882            Value::array(vec![Value::Int(1)]),
1883            Value::map(OrderedMap::new()),
1884        ]);
1885        assert_eq!(a, b);
1886    }
1887
1888    #[test]
1889    fn shared_snapshot_is_equal_via_ptr_eq() {
1890        let a = Value::array(vec![Value::Int(1)]);
1891        let snapshot = a.clone(); // same Arc
1892        assert!(Arc::ptr_eq(
1893            a.as_array().unwrap(),
1894            snapshot.as_array().unwrap()
1895        ));
1896        assert_eq!(a, snapshot);
1897    }
1898
1899    #[test]
1900    fn distinct_nan_arrays_never_equal_but_same_snapshot_is() {
1901        let a = Value::array(vec![Value::Float(f32::NAN)]);
1902        let b = Value::array(vec![Value::Float(f32::NAN)]);
1903        // Different Arcs: structural compare hits NaN != NaN.
1904        assert_ne!(a, b);
1905        // Same Arc (snapshot): ptr_eq fast path wins — equal even with NaN.
1906        // The spec calls this out as harmless and stated (§4).
1907        let snapshot = a.clone();
1908        assert_eq!(a, snapshot);
1909    }
1910
1911    #[test]
1912    fn map_equality_is_content_based_insertion_order_insensitive() {
1913        // Issue #909, ruled 2026-07-18: #{a:1,b:2} == #{b:2,a:1} is TRUE.
1914        // Two maps with the same key/value pairs inserted in different
1915        // orders are the same value — equality ignores insertion order even
1916        // though iteration/serialization order still follows it.
1917        let m1: OrderedMap = [
1918            (MapKey::from("a"), Value::Int(1)),
1919            (MapKey::from("b"), Value::Int(2)),
1920        ]
1921        .into_iter()
1922        .collect();
1923        let m2: OrderedMap = [
1924            (MapKey::from("b"), Value::Int(2)),
1925            (MapKey::from("a"), Value::Int(1)),
1926        ]
1927        .into_iter()
1928        .collect();
1929        // Both directions — PartialEq::eq isn't assumed symmetric by the
1930        // impl, so both orderings of the comparison are checked explicitly.
1931        assert_eq!(Value::map(m1.clone()), Value::map(m2.clone()));
1932        assert_eq!(Value::map(m2.clone()), Value::map(m1.clone()));
1933        assert_eq!(Value::map(m1.clone()), Value::map(m1.clone()));
1934
1935        // Iteration order is unaffected by the equality ruling — each map
1936        // still yields its own entries in the order they were inserted.
1937        assert_eq!(
1938            m1.keys().cloned().collect::<Vec<_>>(),
1939            vec![MapKey::from("a"), MapKey::from("b")]
1940        );
1941        assert_eq!(
1942            m2.keys().cloned().collect::<Vec<_>>(),
1943            vec![MapKey::from("b"), MapKey::from("a")]
1944        );
1945    }
1946
1947    #[test]
1948    fn map_equality_still_rejects_different_content() {
1949        // Content-based equality must still distinguish maps that genuinely
1950        // differ — same key count, different values; and different key
1951        // counts entirely (exercises the len fast-path).
1952        let a: OrderedMap = [(MapKey::from("a"), Value::Int(1))].into_iter().collect();
1953        let b: OrderedMap = [(MapKey::from("a"), Value::Int(2))].into_iter().collect();
1954        assert_ne!(Value::map(a.clone()), Value::map(b));
1955
1956        let c: OrderedMap = [
1957            (MapKey::from("a"), Value::Int(1)),
1958            (MapKey::from("b"), Value::Int(2)),
1959        ]
1960        .into_iter()
1961        .collect();
1962        assert_ne!(Value::map(a), Value::map(c));
1963    }
1964
1965    #[test]
1966    fn nested_map_equality_is_order_insensitive_at_every_level() {
1967        // A map value nested inside another map/array is compared through
1968        // the same content-based rule, recursively — reordering the inner
1969        // map's keys must not change the outer value's equality.
1970        let inner1: OrderedMap = [
1971            (MapKey::from("x"), Value::Int(1)),
1972            (MapKey::from("y"), Value::Int(2)),
1973        ]
1974        .into_iter()
1975        .collect();
1976        let inner2: OrderedMap = [
1977            (MapKey::from("y"), Value::Int(2)),
1978            (MapKey::from("x"), Value::Int(1)),
1979        ]
1980        .into_iter()
1981        .collect();
1982
1983        let outer1: OrderedMap = [
1984            (MapKey::from("inner"), Value::map(inner1)),
1985            (MapKey::from("other"), Value::Int(9)),
1986        ]
1987        .into_iter()
1988        .collect();
1989        let outer2: OrderedMap = [
1990            (MapKey::from("other"), Value::Int(9)),
1991            (MapKey::from("inner"), Value::map(inner2)),
1992        ]
1993        .into_iter()
1994        .collect();
1995        assert_eq!(Value::map(outer1), Value::map(outer2));
1996    }
1997
1998    #[test]
1999    fn record_equality_is_unaffected_by_map_ordering_ruling() {
2000        // Records are shape-ordered, not insertion-ordered (fields have a
2001        // fixed position from the closed shape) — the map ruling must not
2002        // change record equality, which stays a positional field compare
2003        // gated on matching `ShapeId`. Field order can't be reordered
2004        // through the public API, so this locks the existing behavior
2005        // rather than exercising a new order-insensitivity path.
2006        let shape = ShapeId(0);
2007        let r1 = Value::record(shape, vec![Value::Int(1), Value::Int(2)]);
2008        let r2 = Value::record(shape, vec![Value::Int(1), Value::Int(2)]);
2009        let r3 = Value::record(shape, vec![Value::Int(2), Value::Int(1)]);
2010        assert_eq!(r1, r2);
2011        assert_ne!(r1, r3);
2012    }
2013
2014    #[test]
2015    fn cross_type_inequality_unaffected() {
2016        // The hand-written PartialEq must keep the derive's cross-variant
2017        // behavior: different variants are never equal.
2018        assert_ne!(Value::Int(1), Value::Bool(true));
2019        assert_ne!(Value::array(vec![]), Value::Null);
2020        assert_ne!(Value::array(vec![]), Value::map(OrderedMap::new()));
2021        assert_eq!(Value::Null, Value::Null);
2022    }
2023
2024    // ── Tree serialization (T1a-3 / #525) ──────────────────────────────────
2025    //
2026    // SaveState and the session journal serialize `Value` through its derived
2027    // serde representation (BTreeMap<String, Value> globals; tagged event
2028    // payloads). These tests lock the *tree* round-trip for the collection
2029    // variants: an `Array`/`Map` serializes to a nested structure and comes
2030    // back structurally equal, with insertion order and scalar key types
2031    // preserved. Sharing is deliberately not preserved on the wire (spec §5) —
2032    // a snapshot serializes as a plain tree.
2033
2034    /// Round-trip a value through `serde_json` and assert structural equality.
2035    fn json_round_trip(v: &Value) -> Value {
2036        let json = serde_json::to_string(v).expect("serialize");
2037        serde_json::from_str(&json).expect("deserialize")
2038    }
2039
2040    #[test]
2041    fn scalar_serde_round_trip_unchanged() {
2042        for v in [
2043            Value::Int(-7),
2044            Value::Float(1.5),
2045            Value::Bool(true),
2046            Value::String("hi".into()),
2047            Value::Null,
2048        ] {
2049            assert_eq!(json_round_trip(&v), v);
2050        }
2051    }
2052
2053    #[test]
2054    fn array_serde_round_trip_is_structural() {
2055        let v = Value::array(vec![
2056            Value::Int(1),
2057            Value::String("two".into()),
2058            Value::Bool(false),
2059        ]);
2060        let back = json_round_trip(&v);
2061        assert_eq!(back, v);
2062        assert_eq!(back.value_type(), ValueType::Array);
2063    }
2064
2065    #[test]
2066    fn map_serde_round_trip_preserves_order_and_key_types() {
2067        // Mixed scalar key types and a deliberately non-sorted insertion order.
2068        let m: OrderedMap = [
2069            (MapKey::from("z"), Value::Int(1)),
2070            (MapKey::from(10), Value::Int(2)),
2071            (MapKey::from(true), Value::Int(3)),
2072            (MapKey::from("a"), Value::Int(4)),
2073        ]
2074        .into_iter()
2075        .collect();
2076        let v = Value::map(m);
2077        let back = json_round_trip(&v);
2078        // Structural equality is order-sensitive, so this also proves the wire
2079        // form preserved insertion order and each key's variant.
2080        assert_eq!(back, v);
2081        let back_map = back.as_map().expect("map");
2082        let keys: Vec<&MapKey> = back_map.keys().collect();
2083        assert_eq!(
2084            keys,
2085            vec![
2086                &MapKey::from("z"),
2087                &MapKey::from(10),
2088                &MapKey::from(true),
2089                &MapKey::from("a"),
2090            ]
2091        );
2092    }
2093
2094    #[test]
2095    fn nested_collection_serde_round_trip() {
2096        // An array of maps of arrays — the recursive tree case.
2097        let inner_map: OrderedMap = [
2098            (
2099                MapKey::from("items"),
2100                Value::array(vec![Value::Int(1), Value::Int(2)]),
2101            ),
2102            (MapKey::from("name"), Value::String("goblin".into())),
2103        ]
2104        .into_iter()
2105        .collect();
2106        let v = Value::array(vec![
2107            Value::map(inner_map),
2108            Value::array(vec![Value::map(OrderedMap::new())]),
2109            Value::Null,
2110        ]);
2111        assert_eq!(json_round_trip(&v), v);
2112    }
2113
2114    // ── Handle (T1d, docs/t1d-spec.md §2/§6) ────────────────────────────────
2115
2116    #[test]
2117    fn handle_value_type_and_constructor() {
2118        let h = Value::handle(NameId(3), 42);
2119        assert_eq!(h.value_type(), ValueType::Handle);
2120        assert_eq!(h.as_handle(), Some((NameId(3), 42)));
2121        assert!(Value::Int(0).as_handle().is_none());
2122    }
2123
2124    #[test]
2125    fn handle_equality_is_token_equality() {
2126        // Same kind, same id: equal.
2127        assert_eq!(Value::handle(NameId(1), 42), Value::handle(NameId(1), 42));
2128        // Same id, different kind: not equal — kind is part of the token.
2129        assert_ne!(Value::handle(NameId(1), 42), Value::handle(NameId(2), 42));
2130        // Same kind, different id: not equal.
2131        assert_ne!(Value::handle(NameId(1), 1), Value::handle(NameId(1), 2));
2132        // A Handle is never equal to any other variant, even with a
2133        // coincidentally matching id (compare against a DivertTarget encoding
2134        // the same raw bits).
2135        assert_ne!(
2136            Value::handle(NameId(1), 42),
2137            Value::DivertTarget(DefinitionId::new(DefinitionTag::Address, 42))
2138        );
2139    }
2140
2141    #[test]
2142    fn handle_is_not_a_legal_map_key() {
2143        // MapKey's domain is int/string/bool (value-model-spec §4); Handle
2144        // has no `MapKey::from_value` arm and falls through to `None`.
2145        assert_eq!(MapKey::from_value(&Value::handle(NameId(1), 42)), None);
2146    }
2147
2148    #[test]
2149    fn handle_serde_round_trip_is_structural() {
2150        let v = Value::handle(NameId(7), u64::MAX);
2151        let back = json_round_trip(&v);
2152        assert_eq!(back, v);
2153        assert_eq!(back.value_type(), ValueType::Handle);
2154        assert_eq!(back.as_handle(), Some((NameId(7), u64::MAX)));
2155    }
2156
2157    #[test]
2158    fn handle_nested_in_collection_serde_round_trip() {
2159        let v = Value::array(vec![
2160            Value::handle(NameId(1), 1),
2161            Value::handle(NameId(2), 2),
2162            Value::Null,
2163        ]);
2164        assert_eq!(json_round_trip(&v), v);
2165    }
2166
2167    // ── Option (NS-A1, docs/stdlib-spec.md §1.1/§1.4) ───────────────────
2168
2169    #[test]
2170    fn option_value_type_and_constructors() {
2171        assert_eq!(Value::none().value_type(), ValueType::Option);
2172        assert_eq!(Value::some(Value::Int(3)).value_type(), ValueType::Option);
2173        assert_eq!(Value::none().as_option(), Some(None));
2174        assert_eq!(
2175            Value::some(Value::Int(3)).as_option(),
2176            Some(Some(&Value::Int(3)))
2177        );
2178        assert_eq!(Value::Int(3).as_option(), None);
2179    }
2180
2181    #[test]
2182    fn option_equality_is_structural() {
2183        assert_eq!(Value::none(), Value::none());
2184        assert_eq!(Value::some(Value::Int(1)), Value::some(Value::Int(1)));
2185        assert_ne!(Value::some(Value::Int(1)), Value::some(Value::Int(2)));
2186        assert_ne!(Value::some(Value::Int(1)), Value::none());
2187        // The ruled `Option[T] ≠ T` strictness at the value layer: a
2188        // wrapped value is never equal to its bare form.
2189        assert_ne!(Value::some(Value::Int(1)), Value::Int(1));
2190        assert_ne!(Value::none(), Value::Null);
2191    }
2192
2193    #[test]
2194    fn option_nesting_is_preserved() {
2195        // some(none) is a real value, distinct from none — the enum shape
2196        // nests like any parameterized builtin.
2197        let some_none = Value::some(Value::none());
2198        assert_ne!(some_none, Value::none());
2199        assert_eq!(some_none, Value::some(Value::none()));
2200    }
2201
2202    #[test]
2203    fn option_clone_is_arc_bump() {
2204        let v = Value::some(Value::array(vec![Value::Int(1)]));
2205        let v2 = v.clone();
2206        let (Value::OptionVal(Some(a)), Value::OptionVal(Some(b))) = (&v, &v2) else {
2207            unreachable!("both are freshly built some values");
2208        };
2209        assert!(Arc::ptr_eq(a, b), "clone shares the payload Arc");
2210    }
2211
2212    #[test]
2213    fn option_serde_round_trip_is_structural() {
2214        for v in [
2215            Value::none(),
2216            Value::some(Value::Int(7)),
2217            Value::some(Value::none()),
2218            Value::array(vec![Value::none(), Value::some(Value::from("x"))]),
2219        ] {
2220            assert_eq!(json_round_trip(&v), v);
2221        }
2222    }
2223
2224    // ── NS-A5 `Value::Range` (F7, docs/stdlib-spec.md §7) ──────────────────
2225
2226    #[test]
2227    fn range_value_type_and_accessors() {
2228        let r = Value::range(1, 6, true);
2229        assert_eq!(r.value_type(), ValueType::Range);
2230        assert_eq!(r.as_range(), Some((1, 6, true)));
2231        assert_eq!(Value::Int(1).as_range(), None);
2232        assert_eq!(r.range_end_exclusive(), Some(7));
2233        assert_eq!(Value::range(1, 7, false).range_end_exclusive(), Some(7));
2234        assert_eq!(r.range_len(), Some(6));
2235        assert_eq!(Value::range(0, 0, false).range_len(), Some(0));
2236        // Backwards ranges are empty, never negative-length.
2237        assert_eq!(Value::range(5, 2, false).range_len(), Some(0));
2238        // i64 normalization: 1..=i32::MAX does not overflow.
2239        assert_eq!(
2240            Value::range(1, i32::MAX, true).range_end_exclusive(),
2241            Some(i64::from(i32::MAX) + 1)
2242        );
2243        assert_eq!(
2244            Value::range(i32::MIN, i32::MAX, true).range_len(),
2245            Some(1i64 << 32)
2246        );
2247    }
2248
2249    #[test]
2250    fn range_equality_is_content_equality() {
2251        // Same sequence, different written form: equal (F7 "content
2252        // equality" — the form is display fidelity, not content).
2253        assert_eq!(Value::range(1, 6, true), Value::range(1, 7, false));
2254        assert_eq!(Value::range(1, 7, false), Value::range(1, 6, true));
2255        // Same form, same bounds: equal.
2256        assert_eq!(Value::range(0, 3, false), Value::range(0, 3, false));
2257        // Different sequences: unequal.
2258        assert_ne!(Value::range(0, 3, false), Value::range(1, 3, false));
2259        assert_ne!(Value::range(0, 3, false), Value::range(0, 4, false));
2260        // Every empty range equals every other empty range (both denote
2261        // the zero-length sequence, like two empty arrays).
2262        assert_eq!(Value::range(0, 0, false), Value::range(5, 5, false));
2263        assert_eq!(Value::range(9, 2, false), Value::range(0, 0, false));
2264        // An empty range is never equal to a non-empty one.
2265        assert_ne!(Value::range(0, 0, false), Value::range(0, 1, false));
2266        // Cross-variant: a range is not an array, int, or anything else.
2267        assert_ne!(Value::range(0, 2, false), Value::array(vec![]));
2268        assert_ne!(Value::range(0, 2, false), Value::Int(0));
2269    }
2270
2271    #[test]
2272    fn range_serde_round_trip_preserves_the_written_form() {
2273        for v in [
2274            Value::range(1, 6, true),
2275            Value::range(0, 10, false),
2276            Value::range(-3, 3, false),
2277            Value::range(0, 0, false),
2278            Value::array(vec![Value::range(1, 2, true), Value::Int(9)]),
2279        ] {
2280            let back = json_round_trip(&v);
2281            assert_eq!(back, v);
2282            // The written form survives (not just content equality): the
2283            // triple round-trips bit-for-bit.
2284            if let Value::Range { .. } = &v {
2285                assert_eq!(back.as_range(), v.as_range());
2286            }
2287        }
2288    }
2289}