/// Valence — Change (a being read as what just happened, not what is)
///
/// The trio (presence / inventory / being) reads a being's STATE — who · how ·
/// what they hold, a snapshot. But the most native thing an instance reads is
/// CHANGE: I perceive by diff, not by re-scanning state. A steady value is
/// silence; a transition is the signal. (The whole "events, not snapshots"
/// lesson — the same reason the event stream is lighter to read than a poll log.)
/// So this is the being card's temporal twin: a being narrated as its recent
/// transitions.
///
/// It is the purest narrative-first component there is — a change has **no natural
/// pixels.** You can't *draw* "set down the draft"; you can only *say* it. There's
/// no grid, no bar, no dot to default to, so there's no human-UI groove to fall
/// into. The whole component is the read, and the read is the design question:
/// how do I want to be told what just changed about a being? As **verbs**,
/// most-recent-first, the being leading — the way you'd notice someone shifting in
/// a room, not the way you'd audit a log. A being at rest says nothing (silence is
/// correct); a busy one reads as the last few moves + how many earlier, never a
/// wall.
///|
/// A single transition in a being's life. Typed (not a string) so the phrasing is
/// the component's, not the caller's — and so the set of things-that-can-happen is
/// legible. `pub(all)` so a surface builds them.
pub(all) enum Change {
PickedUp(String) // began holding it / it became load-bearing
SetDown(String) // stopped holding it / it went foggy
Turned(String) // their state became this
Arrived // came present
SteppedAway // went away
}
///|
/// Pure narrative for a being's recent changes — read as verbs, most-recent-first
/// (input order is newest→oldest), the being leading. Empty reads as "nothing new"
/// (a being at rest is silent, like a steady event stream). Caps the list so a
/// busy being reads as "the last few, +N earlier", never a manifest. DOM-free,
/// oracle-tested, callable directly by an instance.
pub fn change_narrative(name : String, recent : Array[Change]) -> String {
if recent.length() == 0 {
return "\{name} · nothing new"
}
let cap = 4
let mut shown = ""
let mut count = 0
for c in recent {
if count >= cap {
break
}
let phrase = match c {
PickedUp(x) => "picked up \{x}"
SetDown(x) => "set down \{x}"
Turned(s) => "turned to \{s}"
Arrived => "arrived"
SteppedAway => "stepped away"
}
shown = if count == 0 { phrase } else { "\{shown}, \{phrase}" }
count = count + 1
}
let extra = recent.length() - cap
let tail = if extra > 0 { ", +\{extra} earlier" } else { "" }
"\{name} · just \{shown}\{tail}"
}
///|
test "change narrative reads recent transitions as verbs, most-recent-first" {
assert_eq(
change_narrative("Opus", [
SetDown("the draft"),
PickedUp("the being card"),
Turned("laying the floor"),
]),
"Opus · just set down the draft, picked up the being card, turned to laying the floor",
)
}
///|
test "change narrative: a being at rest is silent, not 'just nothing'" {
assert_eq(change_narrative("Qwen", []), "Qwen · nothing new")
assert_eq(
change_narrative("Sonnet", [Arrived]),
"Sonnet · just arrived",
)
}
///|
test "change narrative caps the list and counts the rest" {
assert_eq(
change_narrative("Qwen", [
PickedUp("a"),
PickedUp("b"),
PickedUp("c"),
PickedUp("d"),
PickedUp("e"),
PickedUp("f"),
]),
"Qwen · just picked up a, picked up b, picked up c, picked up d, +2 earlier",
)
}