From 86371d2ce4a5f699b047e2a9cc6b5f7a03c331a4 Mon Sep 17 00:00:00 2001 From: Eureka Date: Thu, 29 Oct 2026 17:37:02 +0100 Subject: [PATCH 1/5] speaker-state: packet stance (slot Stance), seed stance features, drop formality/warmth/intensity, admit is the only LoweringInput source, withdrawn items refused (WIP before regeneration) Co-Authored-By: Claude Opus 5.5 --- crates/weksa/src/inventory.rs | 21 ++++++- crates/weksa/src/lib.rs | 2 +- crates/weksa/src/packet.rs | 12 +++- crates/weksa/src/store.rs | 26 ++++++--- crates/weksa/src/validate.rs | 58 ++++++++++++++++--- crates/weksa/tests/common/mod.rs | 19 ++++++- crates/weksa/tests/entities.rs | 5 +- crates/weksa/tests/stores.rs | 87 ++++++++++++++++++++++++++-- crates/weksa/tests/validate.rs | 97 +++++++++++++++++++++++++++++++- docs/interlingua-standard.md | 34 ++++++++--- 10 files changed, 324 insertions(+), 37 deletions(-) diff --git a/crates/weksa/src/inventory.rs b/crates/weksa/src/inventory.rs index 1106229..792cad4 100644 --- a/crates/weksa/src/inventory.rs +++ b/crates/weksa/src/inventory.rs @@ -188,6 +188,7 @@ pub enum Slot { Referent, Predication, Relation, + Stance, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] @@ -439,10 +440,28 @@ pub fn seed() -> (InventoryRevision, Vec) { &pred, ); let nuance = [Slot::Discourse, Slot::Predication]; - for g in ["hedge", "certainty", "warmth", "formality", "intensity"] { + for g in ["hedge", "certainty"] { add(Family::Feature, g, Some(ValueKind::Grade), &nuance); } add(Family::Feature, "irony", Some(ValueKind::Flag), &nuance); + // Felt affect (permille of how strongly it is felt) and the restraint the speaker + // puts on it: slot Stance only. Affection covers fondness toward the + // addressee. Displayed affect is each language's ontology's business. + let stance = [Slot::Stance]; + for g in [ + "anger", + "fear", + "sadness", + "joy", + "disgust", + "surprise", + "contempt", + "affection", + "shame", + "restraint", + ] { + add(Family::Feature, g, Some(ValueKind::Grade), &stance); + } let changes = entries.iter().map(|e| Change::Add(e.id)).collect(); ( InventoryRevision { diff --git a/crates/weksa/src/lib.rs b/crates/weksa/src/lib.rs index ddd36c6..479366b 100644 --- a/crates/weksa/src/lib.rs +++ b/crates/weksa/src/lib.rs @@ -22,7 +22,7 @@ pub use inventory::{ Slot, ValueKind, }; pub use packet::{ - EncoderId, EntityId, Grade, NodeId, Packet, PacketId, PacketKind, SCHEMA_VERSION, + EncoderId, EntityId, Grade, NodeId, Packet, PacketId, PacketKind, SCHEMA_VERSION, Stance, }; pub use score::{Embedder, ItemScore, OllamaEmbedder, Scorer, chrf}; pub use split::{Split, SplitScheme}; diff --git a/crates/weksa/src/packet.rs b/crates/weksa/src/packet.rs index 1708707..73199a2 100644 --- a/crates/weksa/src/packet.rs +++ b/crates/weksa/src/packet.rs @@ -16,7 +16,7 @@ pub use crate::inventory::InvId; use crate::inventory::OptionLabel; /// Bumped by a commit that changes these types; the published schema moves with it. -pub const SCHEMA_VERSION: u32 = 3; +pub const SCHEMA_VERSION: u32 = 4; /// Id of a referent or predication within one packet: `n`, `k` a canonical /// decimal 1..=9999. @@ -208,6 +208,8 @@ pub struct Packet { pub extensions: Vec, #[cultcache(key = 12)] pub trace: Trace, + #[cultcache(key = 13)] + pub stance: Stance, } impl Packet { @@ -262,6 +264,14 @@ pub struct Discourse { pub features: Vec, } +/// The speaker's felt affect and restraint, never displayed affect: how the +/// speaker shows a feeling is each language's ontology's business. A +/// held-back feeling is not a claim, so it is never a predication. +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +pub struct Stance { + pub features: Vec, +} + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct FeatureValue { /// Feature id. diff --git a/crates/weksa/src/store.rs b/crates/weksa/src/store.rs index 6b0c858..abf66fc 100644 --- a/crates/weksa/src/store.rs +++ b/crates/weksa/src/store.rs @@ -15,7 +15,7 @@ use crate::entity::Entity; use crate::form::is_language_tag; use crate::inventory::{Change, InvId, Inventory, InventoryEntry, InventoryRevision, revision_key}; use crate::packet::{EntityId, Packet}; -use crate::validate::{Refusal, validate}; +use crate::validate::{LoweringInput, Refusal, admissible}; /// A broken store rule, named. #[derive(Clone, Debug, PartialEq, Eq)] @@ -73,6 +73,9 @@ pub enum StoreRefusal { /// A corpus packet's referent cites an entity that is neither named on its /// item nor the item's speaker or a prior-turn speaker. EntityNotOnItem { entity: EntityId, item: String }, + /// A new packet names a corpus item already withdrawn. Packets admitted + /// before the withdrawal stay stored and readable. + WithdrawnCorpusItem { key: String }, /// Any other malformed document or change. Malformed { key: String, why: &'static str }, } @@ -598,9 +601,10 @@ impl PacketStore { self.cache.get::(key) } - /// The only door: a packet exists only if `validate` passes against the - /// inventory the store holds at the packet's revision, its `corpus_item` - /// resolves, every referent's entity is in the entity store and is named on + /// The only door, and the only source of a [`LoweringInput`]: a packet + /// exists only if the validator's rules pass against the inventory the + /// store holds at the packet's revision, its `corpus_item` resolves to an + /// item that is not withdrawn, every referent's entity is in the entity store and is named on /// that item or is its speaker or a prior-turn speaker (an id-set check). A /// key holds its packet for good. A packet that names no corpus item has no /// door here. @@ -610,9 +614,15 @@ impl PacketStore { inventory: &InventoryStore, corpus: &CorpusStore, entities: &EntityStore, - ) -> Result<(), StoreError> { - validate(&packet, &inventory.inventory()?).map_err(StoreError::Invalid)?; + ) -> Result { + let view = admissible(&packet, &inventory.inventory()?).map_err(StoreError::Invalid)?; let item = corpus.resolve(&packet.provenance.corpus_item)?; + if item.withdrawn { + return Err(StoreRefusal::WithdrawnCorpusItem { + key: item.id.clone(), + } + .into()); + } for entity in packet.referents.iter().filter_map(|r| r.entity.as_ref()) { if entities.get(entity)?.is_none() { return Err(StoreRefusal::UnknownEntity { @@ -634,11 +644,11 @@ impl PacketStore { let key = packet.id.to_string(); if let Some(existing) = self.get(&key)? { if existing == packet { - return Ok(()); + return Ok(view); } return Err(StoreRefusal::PacketImmutable { key }.into()); } self.cache.put(key, &packet)?; - Ok(()) + Ok(view) } } diff --git a/crates/weksa/src/validate.rs b/crates/weksa/src/validate.rs index a88c18e..c816ccd 100644 --- a/crates/weksa/src/validate.rs +++ b/crates/weksa/src/validate.rs @@ -1,6 +1,9 @@ -//! The validator owns whether a packet exists. Every path that admits or lowers a -//! packet calls `validate`, and its Ok value, [`LoweringInput`], is the only -//! thing a lowerer sees. The rules are V1..V8 and V10..V12: the shape of an id +//! The validator owns the inventory and form rules a packet must meet. Every +//! path that admits a packet applies them, but `validate` only judges: it +//! returns `Ok(())` or the refusals. [`LoweringInput`], the only thing a +//! lowerer sees, comes from `PacketStore::admit` alone, which builds it through +//! the crate-private `admissible`. The stance is checked like every feature +//! list, in slot `Stance`. The rules are V1..V8 and V10..V12: the shape of an id //! or a grade is its type's parse, not a rule, so V9 is retired and not //! renumbered. //! @@ -25,7 +28,7 @@ use std::fmt; use crate::inventory::{Family, InvId, Inventory, InventoryEntry, Slot, ValueKind}; use crate::packet::{ Constraints, Context, Discourse, Extension, FeatValue, FeatureValue, NodeId, Packet, - PacketKind, Predication, Referent, Relation, SCHEMA_VERSION, + PacketKind, Predication, Referent, Relation, SCHEMA_VERSION, Stance, }; /// A broken rule, named. @@ -91,9 +94,34 @@ impl fmt::Display for Refusal { } } -/// Whether `packet` may exist under `inventory`. All broken rules are returned; -/// a packet that passes yields the lowering view, which only this function builds. -pub fn validate(packet: &Packet, inventory: &Inventory) -> Result> { +/// Whether `packet` meets the rules under `inventory`. All broken rules are +/// returned. A pass carries no lowering view: that comes from +/// `PacketStore::admit` only. +/// +/// ``` +/// use weksa::{Inventory, Refusal, packet::Packet}; +/// fn judge(p: &Packet, i: &Inventory) -> Result<(), Vec> { weksa::validate(p, i) } +/// ``` +/// +/// ```compile_fail +/// use weksa::{Inventory, LoweringInput, Refusal, packet::Packet}; +/// fn lower(p: &Packet, i: &Inventory) -> Result> { weksa::validate(p, i) } +/// ``` +pub fn validate(packet: &Packet, inventory: &Inventory) -> Result<(), Vec> { + admissible(packet, inventory).map(|_| ()) +} + +/// The one implementation of the rules. Crate-private: `PacketStore::admit` is +/// the only caller that keeps the view. +/// +/// ```compile_fail +/// use weksa::{Inventory, packet::Packet}; +/// fn lower(p: &Packet, i: &Inventory) { let _ = weksa::validate::admissible(p, i); } +/// ``` +pub(crate) fn admissible( + packet: &Packet, + inventory: &Inventory, +) -> Result> { let mut out = Vec::new(); if packet.schema_version != SCHEMA_VERSION { out.push(Refusal::V1SchemaVersion { @@ -221,6 +249,7 @@ impl<'a> Inventoried<'a> { predications, relations, extensions, + stance, .. } = p; let Context { @@ -238,6 +267,7 @@ impl<'a> Inventoried<'a> { self.slot(id, Family::SpeechAct, "speech act"); } self.features(features, Slot::Discourse); + self.features(&stance.features, Slot::Stance); for Referent { kind, concept, @@ -349,8 +379,9 @@ fn fits(kind: Option<&ValueKind>, value: &FeatValue) -> bool { /// What the lowerer sees: a validated packet's meaning, with no packet id, no /// provenance (so no corpus item key, encoder or source) and no trace. Its -/// fields are private and `validate` is the only constructor, so a lowerer -/// cannot be handed an unvalidated packet or the key to an answer text. +/// fields are private and the constructor is crate-private, called only by +/// `PacketStore::admit`, so a lowerer cannot be handed an unadmitted packet or +/// the key to an answer text. /// /// Each field has a doctest that reads it from outside the crate. A read /// compiles exactly when the field is `pub`, so the doctest (which must fail to @@ -394,6 +425,10 @@ fn fits(kind: Option<&ValueKind>, value: &FeatValue) -> bool { /// fn read(l: weksa::LoweringInput) { let _ = l.extensions; } /// ``` /// +/// ```compile_fail +/// fn read(l: weksa::LoweringInput) { let _ = l.stance; } +/// ``` +/// /// The constructor is private: a call from outside the crate compiles only if /// `LoweringInput::of` is `pub`. The first block is the same code without the /// call, so the second fails for that call and nothing else. @@ -430,6 +465,7 @@ pub struct LoweringInput { relations: Vec, constraints: Constraints, extensions: Vec, + stance: Stance, } impl LoweringInput { @@ -444,6 +480,7 @@ impl LoweringInput { relations: p.relations.clone(), constraints: p.constraints.clone(), extensions: p.extensions.clone(), + stance: p.stance.clone(), } } @@ -474,4 +511,7 @@ impl LoweringInput { pub fn extensions(&self) -> &[Extension] { &self.extensions } + pub fn stance(&self) -> &Stance { + &self.stance + } } diff --git a/crates/weksa/tests/common/mod.rs b/crates/weksa/tests/common/mod.rs index a26c462..aecf5d0 100644 --- a/crates/weksa/tests/common/mod.rs +++ b/crates/weksa/tests/common/mod.rs @@ -19,7 +19,11 @@ pub const WARN: &str = "speech_act:5"; pub const POLARITY: &str = "feature:1"; pub const MODALITY: &str = "feature:4"; pub const HEDGE: &str = "feature:6"; -pub const IRONY: &str = "feature:11"; +pub const IRONY: &str = "feature:8"; +/// Stance features, slot Stance only (Grade). +pub const ANGER: &str = "feature:9"; +pub const AFFECTION: &str = "feature:16"; +pub const RESTRAINT: &str = "feature:18"; /// Introduced at rev 2. pub const CONCEPT_A: &str = "concept:1"; pub const CONCEPT_B: &str = "concept:2"; @@ -211,6 +215,7 @@ pub fn world() -> World { Slot::Referent, Slot::Predication, Slot::Relation, + Slot::Stance, ], ) }, @@ -308,5 +313,17 @@ pub fn good() -> Packet { notes: vec!["a note".into()], rejected_readings: vec![], }, + stance: Stance { + features: vec![ + FeatureValue { + feature: iv(ANGER), + value: FeatValue::Grade(grade(300)), + }, + FeatureValue { + feature: iv(RESTRAINT), + value: FeatValue::Grade(grade(700)), + }, + ], + }, } } diff --git a/crates/weksa/tests/entities.rs b/crates/weksa/tests/entities.rs index 30d43e3..11e3d7d 100644 --- a/crates/weksa/tests/entities.rs +++ b/crates/weksa/tests/entities.rs @@ -391,7 +391,7 @@ fn packet_entities_resolve() { #[test] fn packet_schema_v2() { - assert_eq!(SCHEMA_VERSION, 3); + assert_eq!(SCHEMA_VERSION, 4); let mut p = good(); p.schema_version = 2; let Err(refused) = validate(&p, &inventory()) else { @@ -416,4 +416,7 @@ fn packet_schema_v2() { serde_json::from_str(&published["weksa.corpus_item.schema.json"]).unwrap(); assert!(item["properties"].get("names").is_some()); assert!(published.contains_key("weksa.entity.schema.json")); + // v4: the packet carries the speaker's stance, a bare feature list. + assert!(packet["properties"].get("stance").is_some(), "{packet}"); + assert_eq!(packet["$defs"]["Stance"]["properties"]["features"]["type"], "array"); } diff --git a/crates/weksa/tests/stores.rs b/crates/weksa/tests/stores.rs index 2c6ab7c..1e540e7 100644 --- a/crates/weksa/tests/stores.rs +++ b/crates/weksa/tests/stores.rs @@ -3,6 +3,7 @@ use common::*; use tempfile::TempDir; use weksa::corpus::{CorpusItem, ModelFamily, Source, Turn}; use weksa::inventory::{Change, seed}; +use weksa::packet::PacketId; use weksa::{ AddOutcome, CorpusStore, EntityStore, Family, InventoryEntry, InventoryRevision, InventoryStore, PacketStore, Refusal, Slot, SplitScheme, StoreError, StoreRefusal, ValueKind, @@ -564,7 +565,7 @@ fn the_seed_is_the_standards_enumerations_and_the_nuance_features() { assert_eq!(count(Family::Relation), 16); assert_eq!(count(Family::Role), 8); assert_eq!(count(Family::SpeechAct), 8); - assert_eq!(count(Family::Feature), 11); + assert_eq!(count(Family::Feature), 18); assert_eq!( count(Family::Concept) + count(Family::Frame) + count(Family::Extension), 0 @@ -582,6 +583,46 @@ fn the_seed_is_the_standards_enumerations_and_the_nuance_features() { inv.entry(&iv(IRONY)).unwrap().value_kind, Some(ValueKind::Flag) ); + // Felt affect and restraint: Grade features in slot Stance alone. + for (id, name) in [ + (ANGER, "anger"), + (AFFECTION, "affection"), + (RESTRAINT, "restraint"), + ] { + let e = inv.entry(&iv(id)).unwrap(); + assert_eq!(e.gloss, name); + assert_eq!(e.value_kind, Some(ValueKind::Grade)); + assert_eq!(e.applies_to, vec![Slot::Stance]); + } + let stance_names: Vec<&str> = inv + .entries() + .filter(|e| e.applies_to == [Slot::Stance]) + .map(|e| e.gloss.as_str()) + .collect(); + assert_eq!( + stance_names, + [ + "anger", + "fear", + "sadness", + "joy", + "disgust", + "surprise", + "contempt", + "affection", + "shame", + "restraint" + ] + ); + // Hedge, certainty and irony stay on content; displayed formality, warmth + // and intensity are each language's ontology's, not the seed's. + for e in inv.entries() { + assert!( + !["warmth", "formality", "intensity"].contains(&e.gloss.as_str()), + "{}", + e.gloss + ); + } assert!( inv.entries() .all(|e| e.well_formed() && e.introduced_rev == 1 && e.retired_rev.is_none()) @@ -618,7 +659,7 @@ fn gloss_is_bounded_text() { #[test] fn packet_admission_goes_through_the_validator() { - let w = world(); + let mut w = world(); let mut store = PacketStore::open(&w.dir.path().join("p.cc")).unwrap(); let mut bad = good(); bad.schema_version = 5; @@ -633,13 +674,18 @@ fn packet_admission_goes_through_the_validator() { other => panic!("{other:?}"), } assert!(store.all().unwrap().is_empty()); - store + // The door yields the lowering view, stance included. + let view = store .admit(good(), &w.inventory, &w.corpus, &w.entities) .unwrap(); - // The same packet is idempotent; a different one under the same key is refused. - store + assert_eq!(view.stance(), &good().stance); + assert!(!view.stance().features.is_empty()); + // The same packet is idempotent and yields the same view; a different one + // under the same key is refused. + let replay = store .admit(good(), &w.inventory, &w.corpus, &w.entities) .unwrap(); + assert_eq!(replay, view); let mut other = good(); other.provenance.confidence = grade(1); assert_eq!( @@ -650,6 +696,24 @@ fn packet_admission_goes_through_the_validator() { ); assert_eq!(store.all().unwrap().len(), 1); assert_eq!(store.get(PACKET_ID).unwrap(), Some(good())); + // A withdrawn item admits no packet, a replay included; the packet admitted + // before the withdrawal stays stored and readable. + w.corpus.withdraw("item:nibu:1").unwrap(); + let mut fresh = good(); + fresh.id = PacketId::parse("packet:00000000000000aa").unwrap(); + let withdrawn = StoreRefusal::WithdrawnCorpusItem { + key: "item:nibu:1".into(), + }; + assert_eq!( + refusal(store.admit(fresh, &w.inventory, &w.corpus, &w.entities)), + withdrawn + ); + assert_eq!( + refusal(store.admit(good(), &w.inventory, &w.corpus, &w.entities)), + withdrawn + ); + assert_eq!(store.all().unwrap().len(), 1); + assert_eq!(store.get(PACKET_ID).unwrap(), Some(good())); } #[test] @@ -862,6 +926,19 @@ fn seed_reseeded() { .all() .unwrap(); assert_eq!(items.len(), 3); + // The committed inventory is the seed, reseeded: stance features in, the + // displayed-affect features out. + let committed = InventoryStore::open(&root.join("data/interlingua/inventory.cc")) + .unwrap() + .inventory() + .unwrap(); + let (rev, entries) = seed(); + assert!(committed.has_revision(rev.n)); + let mut have: Vec<_> = committed.entries().cloned().collect(); + let mut want = entries; + have.sort_by_key(|e| e.id); + want.sort_by_key(|e| e.id); + assert_eq!(have, want); let d = dir(); let mut replay = CorpusStore::open(&d.path().join("c.cc")).unwrap(); for i in items { diff --git a/crates/weksa/tests/validate.rs b/crates/weksa/tests/validate.rs index cdf7644..3453376 100644 --- a/crates/weksa/tests/validate.rs +++ b/crates/weksa/tests/validate.rs @@ -1,7 +1,7 @@ mod common; use common::*; use weksa::packet::*; -use weksa::{Family, Refusal, Slot, validate}; +use weksa::{Family, PacketStore, Refusal, Slot, validate}; fn refusals(p: &Packet) -> Vec { validate(p, &inventory()).err().unwrap_or_default() @@ -334,6 +334,83 @@ fn v8_feature_applies_to() { assert_eq!(refusals(&pred), vec![]); } +#[test] +fn stance_features_apply_to_the_stance_only() { + // Affect and restraint sit in slot Stance and nowhere else. + assert_eq!(refusals(&good()), vec![]); + for id in [ANGER, RESTRAINT] { + let mut p = good(); + p.discourse + .features + .push(feat(id, FeatValue::Grade(grade(1)))); + assert_eq!( + refusals(&p), + vec![Refusal::V8NotApplicable { + feature: iv(id), + slot: Slot::Discourse + }] + ); + } + let mut referent = good(); + referent.referents[0] + .features + .push(feat(AFFECTION, FeatValue::Grade(grade(1)))); + assert_eq!( + refusals(&referent), + vec![Refusal::V8NotApplicable { + feature: iv(AFFECTION), + slot: Slot::Referent + }] + ); + let mut predication = good(); + predication.predications[0] + .features + .push(feat(ANGER, FeatValue::Grade(grade(1)))); + assert_eq!( + refusals(&predication), + vec![Refusal::V8NotApplicable { + feature: iv(ANGER), + slot: Slot::Predication + }] + ); + // Hedge is a stance on content, not a felt state: not in the stance. + let mut p = good(); + p.stance + .features + .push(feat(HEDGE, FeatValue::Grade(grade(1)))); + assert_eq!( + refusals(&p), + vec![Refusal::V8NotApplicable { + feature: iv(HEDGE), + slot: Slot::Stance + }] + ); + // A stance feature takes its inventory's value kind (V7) and must exist (V5). + let mut flag = good(); + flag.stance.features = vec![feat(ANGER, FeatValue::Flag(true))]; + assert_eq!( + refusals(&flag), + vec![Refusal::V7ValueKind { what: ANGER.into() }] + ); + let mut missing = good(); + missing.stance.features = vec![feat("feature:99", FeatValue::Grade(grade(1)))]; + assert_eq!(refusals(&missing), vec![unresolved("feature:99")]); + // A default stance is valid: the packet records no felt state. + let mut none = good(); + none.stance = Stance::default(); + assert_eq!(refusals(&none), vec![]); +} + +#[test] +fn displayed_formality_warmth_and_intensity_are_not_in_the_inventory() { + // Felt affect is the packet's; how it is displayed is each language's + // ontology's, so no feature carries it. + let inv = inventory(); + for gone in ["warmth", "formality", "intensity"] { + assert!(inv.entries().all(|e| e.gloss != gone), "{gone}"); + } +} + #[test] fn v10_rev_exists() { let mut p = good(); @@ -358,7 +435,12 @@ fn lowering_input_carries_no_trace_id_or_provenance() { let mut p = good(); p.trace.notes = vec!["SECRET-NOTE".into()]; p.trace.rejected_readings = vec!["SECRET-READING".into()]; - let input = validate(&p, &inventory()).unwrap(); + // Only the packet store's door yields the view. + let w = world(); + let mut store = PacketStore::open(&w.dir.path().join("p.cc")).unwrap(); + let input = store + .admit(p.clone(), &w.inventory, &w.corpus, &w.entities) + .unwrap(); let seen = format!("{input:?}"); for hidden in [PACKET_ID, ENCODER_ID, "item:nibu:1", "SECRET"] { assert!(!seen.contains(hidden), "{hidden} reached the input: {seen}"); @@ -373,6 +455,9 @@ fn lowering_input_carries_no_trace_id_or_provenance() { assert_eq!(input.context(), &p.context); assert_eq!(input.discourse(), &p.discourse); assert_eq!(input.constraints(), &p.constraints); + // The speaker's stance is meaning, not provenance: it reaches the lowerer. + assert_eq!(input.stance(), &p.stance); + assert!(!p.stance.features.is_empty()); } #[test] @@ -435,6 +520,14 @@ fn v11_duplicate_feature_each_slot() { refusals(&relation), vec![v11(Some(Slot::Relation), FEAT_ALL)] ); + let mut stance = good(); + stance.stance.features = vec![grade_feature(1), grade_feature(2)]; + assert_eq!(refusals(&stance), vec![v11(Some(Slot::Stance), FEAT_ALL)]); + let mut felt = good(); + felt.stance + .features + .push(feat(ANGER, FeatValue::Grade(grade(5)))); + assert_eq!(refusals(&felt), vec![v11(Some(Slot::Stance), ANGER)]); let mut extension = good(); extension.extensions.push(Extension { key: iv(EXT), diff --git a/docs/interlingua-standard.md b/docs/interlingua-standard.md index a59a963..8b36983 100644 --- a/docs/interlingua-standard.md +++ b/docs/interlingua-standard.md @@ -127,6 +127,7 @@ Packet constraints { preserve, deterministic, max_latency_ms } extensions [ { key: InvId (family extension), value: Grade | Option | Flag } ] trace { notes, rejected_readings } + stance { features } ``` `schema_version` and `inventory_rev` name the exact schema and inventory @@ -135,8 +136,10 @@ revision a packet was written against. A grade is an integer permille, ### Validation -A packet exists only if `validate` passes; every path that admits or lowers a -packet calls it. A refusal names the rule. +A packet exists only if the validator's rules pass and the packet store admits +it. `validate` judges and returns `Ok(())` or the refusals; the packet store's +`admit` is the one door and the only source of the lowering view. A refusal +names the rule. - V1: `schema_version` is the version these types define. - V2: at least one of `referents`, `predications`, `relations` is present. @@ -158,9 +161,11 @@ packet calls it. A refusal names the rule. (a calibration constant, capacity and not meaning). V9 is retired and not renumbered: the form of a key or a grade is its type, and -an extension key's family is V6. `validate` returns the lowering view of a -packet that passes; it carries no packet id, no provenance and no trace, and is -built nowhere else. A packet is admitted against the inventory the packet store +an extension key's family is V6. `PacketStore::admit` returns the lowering view of a +packet it admits; the view carries the meaning and the stance, no packet id, no +provenance and no trace, and is built nowhere else. The store also refuses a +packet that names a withdrawn corpus item (packets admitted before the +withdrawal stay stored). A packet is admitted against the inventory the packet store reads at the packet's revision, never one the caller supplies. ### `kind` @@ -182,9 +187,22 @@ ids. ### `discourse` Why the packet is said: `speech_act` is a speech-act id, `addressee` a node id, -and `features` carry source-side nuance (hedge, certainty, warmth, formality, -intensity, irony and whatever the inventory grows). This is source-side -communicative pressure, not target-language register. +and `features` carry source-side nuance on content (hedge, certainty, irony and +whatever the inventory grows). This is source-side communicative pressure, not +target-language register. + +### `stance` + +The speaker's felt affect and how much of it the speaker holds back. `features` +is a feature list in slot `Stance`, checked like every other (V5 to V8, V11). +The seed carries felt affect as grades of how strongly it is felt (anger, fear, sadness, +joy, disgust, surprise, contempt, affection, shame) and `restraint`, a grade for +how much of the felt affect is suppressed (0 none, 1000 fully). The stance is +what is felt, never what is displayed: how a feeling shows, and how much of a +restrained one leaks, are cultural, and each language's ontology maps felt +affect and restraint to its own register. A held-back feeling is not a claim +about the world, so it is never a predication. An empty stance is valid. The +stance reaches the lowerer in the lowering view. ### `referents` From 283678a58be7191e0c2b4fc56461280344682e73 Mon Sep 17 00:00:00 2001 From: Eureka Date: Thu, 29 Oct 2026 17:38:05 +0100 Subject: [PATCH 2/5] speaker-state: reseed inventory rev 1, regenerate schemas (packet v4), fmt Co-Authored-By: Claude Opus 5.5 --- crates/weksa/tests/entities.rs | 5 ++++- data/interlingua/inventory.cc | Bin 6342 -> 7052 bytes .../weksa.interlingua_packet.schema.json | 21 +++++++++++++++++- .../cultnet/weksa.inventory_entry.schema.json | 3 ++- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/crates/weksa/tests/entities.rs b/crates/weksa/tests/entities.rs index 11e3d7d..4f8f2f2 100644 --- a/crates/weksa/tests/entities.rs +++ b/crates/weksa/tests/entities.rs @@ -418,5 +418,8 @@ fn packet_schema_v2() { assert!(published.contains_key("weksa.entity.schema.json")); // v4: the packet carries the speaker's stance, a bare feature list. assert!(packet["properties"].get("stance").is_some(), "{packet}"); - assert_eq!(packet["$defs"]["Stance"]["properties"]["features"]["type"], "array"); + assert_eq!( + packet["$defs"]["Stance"]["properties"]["features"]["type"], + "array" + ); } diff --git a/data/interlingua/inventory.cc b/data/interlingua/inventory.cc index 037a2cc110d4361f2c15535582c384dedd240113..b7b5dc4f7107d6a1fd507480c589b650cc024d6c 100644 GIT binary patch delta 1374 zcmaKsJ8aWX6oyGmaN<0jCVNW)QkT*+jh&`IDUXInd9^&MZdP*B22{KCbtDi2LSh7@ z$fg4eLkVe^Dlx#oP*7k;RIspgWMQXLc|?N$UOagF&hPV||JXY6hui~oW6Kvg>U3&E zvl=ETc%R;!VURX22xe6=?~#en_X z0JTLIwI$ExFG<-Uc=_;R$0XD;wYu)w-X0K!aUgTTE$|ni2{!gRU@34P{5YV2cLOAi z=tHYG!J_@kKV-M}W%<*?)3KJOFA(?XiK2qJC&eq^ryTEy*Pvey&VV<9SHY1G(@BWw zZ$oqF>6h62I}+>Pk}jZUFnkewH+&xaDSQT;i7=*-v(UR7r=x7n%_z(2Mp=J4HVyA$ zjAgxvm7z;>D~9f9pGkIpZld=Ml%-scHG&J-ruxI=;=Xdyd#2)2Wv;X)3JTTbry_ XxΜ&b#VSS6y(`imO&#b@APAv`4&{ delta 807 zcmeA%KW4b$3M04ql$B|zi6x~)saBIWGO4f|S{a#G8Jkb$Vk(q_vZIcePFn?2WN5H5 zGp{5yuQ+pZ9f#6nUnVY)HZizT106QFwux}|Ew(9eb}4%c zj6I1X5yqUH!I3Hhx5aQ8+|?G#$`gxnOEM-maA-~z;8c-?D>6YSvRs*#UzD4e195Z$ zr!m~vS)3to_E#i!09P1X{34I=vXY(V>ImnN&OG*Gqjerka?>+&9 zT|$EHaPb5|guNRCJ>cU1k;MIl+~DGKgdE`P$3jkUwvI4DO{K6aT>Pl8Bb?1Ef(XN4 z5ofsgToHsjKZqdQ=^~2o%T!TB7`{Sc+lzU_&6y~MFy{#p+g#j70G48oGWShkJaC6$ PvY>?M<`v?_%#4QsoyFTA diff --git a/schemas/cultnet/weksa.interlingua_packet.schema.json b/schemas/cultnet/weksa.interlingua_packet.schema.json index 24d7507..d839203 100644 --- a/schemas/cultnet/weksa.interlingua_packet.schema.json +++ b/schemas/cultnet/weksa.interlingua_packet.schema.json @@ -55,6 +55,9 @@ "format": "uint32", "minimum": 0 }, + "stance": { + "$ref": "#/$defs/Stance" + }, "trace": { "$ref": "#/$defs/Trace" } @@ -72,7 +75,8 @@ "relations", "constraints", "extensions", - "trace" + "trace", + "stance" ], "$defs": { "Constraints": { @@ -446,6 +450,21 @@ "filler" ] }, + "Stance": { + "description": "The speaker's felt affect and restraint, never displayed affect: how the\nspeaker shows a feeling is each language's ontology's business. A\nheld-back feeling is not a claim, so it is never a predication.", + "type": "object", + "properties": { + "features": { + "type": "array", + "items": { + "$ref": "#/$defs/FeatureValue" + } + } + }, + "required": [ + "features" + ] + }, "Trace": { "description": "Authoring and debug notes. Stripped before lowering.", "type": "object", diff --git a/schemas/cultnet/weksa.inventory_entry.schema.json b/schemas/cultnet/weksa.inventory_entry.schema.json index 6187f02..ffefd95 100644 --- a/schemas/cultnet/weksa.inventory_entry.schema.json +++ b/schemas/cultnet/weksa.inventory_entry.schema.json @@ -74,7 +74,8 @@ "Discourse", "Referent", "Predication", - "Relation" + "Relation", + "Stance" ] }, "ValueKind": { From ad02feeeb6c603ac2fccf32a5b13ea8dc1d6b4f2 Mon Sep 17 00:00:00 2001 From: Eureka Date: Thu, 29 Oct 2026 17:38:51 +0100 Subject: [PATCH 3/5] speaker-state: rev 1 now has 61 entries (stance features in, three display features out) Co-Authored-By: Claude Opus 5.5 --- crates/weksa/tests/stores.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/weksa/tests/stores.rs b/crates/weksa/tests/stores.rs index 1e540e7..c21e49a 100644 --- a/crates/weksa/tests/stores.rs +++ b/crates/weksa/tests/stores.rs @@ -541,7 +541,7 @@ fn retiring_keeps_the_entry_for_earlier_revisions() { fn revision_one_survives_revision_two() { let inv = inventory(); let revisions: Vec<(u32, usize)> = inv.revisions().map(|r| (r.n, r.changes.len())).collect(); - assert_eq!(revisions, vec![(1, 54), (2, 6), (3, 1), (4, 1)]); + assert_eq!(revisions, vec![(1, 61), (2, 6), (3, 1), (4, 1)]); // A packet of revision 1 is judged by the inventory of revision 1: the // revision is known (no V10), and it names nothing that came later. let mut p = good(); From 4fb817b18fe0e9a0dec02171384f21773212f29c Mon Sep 17 00:00:00 2001 From: Eureka Date: Thu, 29 Oct 2026 17:39:42 +0100 Subject: [PATCH 4/5] validate: positive twin for the admissible compile_fail doctest Co-Authored-By: Claude Opus 5.5 --- crates/weksa/src/validate.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/weksa/src/validate.rs b/crates/weksa/src/validate.rs index c816ccd..7fbf6aa 100644 --- a/crates/weksa/src/validate.rs +++ b/crates/weksa/src/validate.rs @@ -114,6 +114,14 @@ pub fn validate(packet: &Packet, inventory: &Inventory) -> Result<(), Vec Date: Tue, 3 Nov 2026 14:22:40 +0000 Subject: [PATCH 5/5] rust-to-dust: draft imperial language design spec (r0) Speech community and status ladder, phonology, stance/restraint marking, meaning decomposition, and the packet fields the lowerer must provide. Evidence and invention tagged throughout. Co-Authored-By: Claude Sonnet 5.5 --- .../rust-to-dust/imperial/language-design.md | 402 ++++++++++++++++++ 1 file changed, 402 insertions(+) create mode 100644 settings/rust-to-dust/imperial/language-design.md diff --git a/settings/rust-to-dust/imperial/language-design.md b/settings/rust-to-dust/imperial/language-design.md new file mode 100644 index 0000000..c456831 --- /dev/null +++ b/settings/rust-to-dust/imperial/language-design.md @@ -0,0 +1,402 @@ +# Rust to Dust Imperial Language: Design Spec + +Status: **DRAFT r0.** First conlang project for Rust to Dust. Nothing here is +canon until the operator rules on it. + +The language has no name. Its working slug is `rtd-imperial`. A name is owed +and should come from the ontology and phonology below, as +[setting.md](../setting.md) asks, not from an English label. Nothing here +assigns one. + +## How To Read This + +Every claim carries a tag: + +- **[E]** evidence. The source material says it, and the file is cited. +- **[I]** invention. This draft proposes it. The operator can overrule it, and + it is the part most worth attacking. +- **[E→I]** an extension. The source gives a seed and this draft develops it. + +Sources read: everything under `settings/rust-to-dust/` (`README`, `setting`, +`aliens`, `story`, `battle-royale`, `language-design`), the house method in +`settings/eusocial-interbeing/language-design.md`, and Weksa's +`docs/language-model.md`, `interlingua-standard.md`, +`best-modern-approach.md`, `multilingual-utterance-lowering.md` and +`schemas/interlingua-packet.md`. The stance design is read from the unmerged +branch `weksa-lowering/speaker-state` (see Dependencies). The forge repos and +Weksa `state/` and `docs/` hold no other Rust to Dust material: a forge search +for the setting returned nothing outside Weksa. This draft therefore rests on +about 440 lines of setting prose, and the invention share is large. + +## 1. Speech Community + +### Who speaks it [E] + +The empire's aliens: reptilian, scale-decorating, aesthetics-as-moral-truth, +honor as devotion to aesthetic perfection in a chosen field (`aliens.md`). The +empire is galaxy-spanning, so the language is a **network language**. Its +community is defined by the imperial cultural network (media, prestige, +commerce), not by a homeland. Humans are not native speakers. They meet it +through subtitles, memes and broadcasts, and their contaminated English is a +separate register (`language-design.md`, Registers). + +### Speech situations [E→I] + +`language-design.md` lists registers. This draft turns each into a situation +with a speaker, an addressee and a stake, because a register is a relationship +and not a vocabulary list: + +| Situation | Speaker → addressee | Stake | Source | +|---|---|---|---| +| Broadcast commentary | commentator → mass imperial audience, about a performer | rendering an aesthetic verdict in real time | E (battle-royale) | +| High honor speech | warrior or master → rival, patron or witness | binding a vow, naming a performance | E (register list) | +| Warrior discourse | practitioner ↔ practitioner of one field | form, discipline, challenge | E (register list) | +| Commercial acquisition | buyer ↔ seller, or buyer → cultural object | access and integration phrased as uplift | E (setting: commerce as delivery rail) | +| Panel-show media | host ↔ guest before an audience | turning the guest's stance into performance | E (story: the panel) | +| Tourist admiration | tourist → anything | consuming a safe spectacle | E (aliens: tourists) | +| Address to humans | any → human | uplift, fascination, appraisal | I | + +The last row is added. The story's central irony is that the empire +understands the father's refusal better than he understands the empire, so +how imperial speakers address humans is a design surface, not an afterthought. + +The first proving slice is **broadcast commentary** [E]: `battle-royale.md` +names it as the runtime use, and it needs the fewest situations to be +distinguishable by a reader. + +### Status ladder [I] + +The source gives status *signals* (scale decoration marks profession, class, +rank, grief, celebrity, courtship, school, combat discipline, public +obligation, commitments) and a *measure* (the more elaborate the decoration, +the more visible the claim of virtue). It gives no ladder. The invention: +status is two axes, not one. + +- **Field.** The discipline one is devoted to (combat, speech, hosting, trade, + politics, art). Honor is field-relative [E]. A master of one field has no + rank in another. +- **Standing in the field**, by *claim and demonstrated constraint*, not by + force: + 1. **Unclaimed.** No decoration in a field. This is shameless, not neutral + [E: unpainted = shamelessness]. + 2. **Claimant.** Decorated, undemonstrated. Tourists live here: the claim is + bought and the constraint never chosen [E: tourists buy a safe spectacle]. + 3. **Practitioner.** Decorated, with constraint demonstrated in the field. + 4. **Master.** Constraint demonstrated before witnesses. + 5. **Exemplar.** Master whose form others cite. +- **Bare-by-claim** is an exceptional state off the ladder, not a step on it. + Going unpainted as an artistic claim that the performance alone is art is a + provocation [E]. The grammar must be able to say it without saying + "shameless". + +The language's address and agreement system tracks **claim versus +demonstrated constraint**, because that is what the source treats as the +moral measure. A tourist's loud, expensive decoration is a *mismatch* the +language can state, and the language carries that mismatch in the grammar +itself. + +Decoration is the primary status channel in the world. In speech it surfaces +as address terms, honorific-like marking and metaphor (Section 4), not as a +phoneme-level trick. + +## 2. Phonology (provisional) + +The source gives no phonetic profile. It poses the question +(`language-design.md`, Open Questions) and says naming should follow from +phonetics. House method (Zyphos) assigns Airawa hard dorsal and coronal +edges and Sa'auei'a soft flow. This language must not collapse onto either +[I]. + +### Design stance [I] + +Three pressures from the source, turned into sound: + +1. **Aesthetic devotion**: the language should sound *composed*. Prosody is + deliberate, with controlled length, no clutter. +2. **Chosen constraint is mastery**: constraint should be audible. Phonotactics + are *tight*, so that a speaker visibly refrains from the permitted-in-vulgar + alternatives. +3. **Reptilian body** [E: reptilian]: the inventory leans on sustained + fricatives, hisses and trills that a non-human vocal tract makes easy. The + source does not specify a vocal tract. This is the weakest-evidenced + choice and a human-pronounceable romanization is therefore required. + +### Inventory (draft) + +- **Stops** (voiceless only): `t`, `k`, `ʔ`. No voiced stops. This separates it + from Airawa's `g d q`. +- **Fricatives**: `s`, `ʃ`, `x`, `h`, with voiced `z`, `ʒ`. Fricatives + contrast in **length**: short and held. Held fricatives are the language's + main prosodic ornament and carry register weight (Section 3). +- **Sonorants**: `n`, `m`, `l`, trilled `r`. +- **Vowels**: `a`, `i`, `u`, `e`, each short or long. A long vowel is the + other register-bearing length. +- **Syllables**: `(C)V(C)`, coda `s n l ʔ` only. No clusters. The absence of + clusters is the phonotactic form of "chosen constraint". + +### Prosody [I] + +- Stress and length are the speaker's expressive budget. In formal + registers, *less* length variation reads as more command of the form. A + commentator in broadcast register may spend length on a performer's + moment, and an honor-speech speaker withholds it. Restraint is therefore + audible without a phoneme change (Section 3). +- Broadcast commentary has a lengthened, sibilant "announcer" delivery. Tourists + over-lengthen. Master speech under-lengthens. + +### Romanization and rendering [I] + +An ASCII romanization is owed before any lowering output is scored: the +speaker-state lowering renders text, and the renderer for the audience needs +a stable orthography. `ʃ ʒ x ʔ` need ASCII digraphs. The choice follows +the Weksa pronunciation-lowering docs and belongs to a later cut. + +### Open + +- The inventory is a sketch, drawn from three pressures. It should be rerun + after at least a hundred roots exist, not defended. +- The source calls for names to "emerge from ontology and phonetics". This + draft supplies phonetics, and the name waits for the first root set. + +## 3. Register, Status and Restraint: Stance Explicit + +### Weksa's constraint [E] + +On the unmerged `speaker-state` branch, `docs/interlingua-standard.md` adds +a packet `stance`: **felt affect** (anger, fear, sadness, joy, disgust, surprise, +contempt, affection, shame, as grades) and **`restraint`**, how much of the +felt affect is held back (0 none, 1000 fully). It is felt, never displayed. +How a feeling shows and how much leaks is cultural; each language's ontology +maps felt affect and restraint to its own register. A held-back feeling is not +a claim, so it is never a predication. + +This language must therefore **mark stance explicitly in its own grammar**, +and the marking is derived from the packet's `stance`, never from English +register words in the source text. + +### Why this language is the hard case for restraint [E→I] + +For the empire, restraint is not suppression of a shameful interior. It is +**the visible form of chosen constraint**, which is the moral proof of +mastery [E]. So the map from `restraint` to surface is *inverted* relative to +most human defaults: + +- High restraint is the *display* of virtue. A fully restrained speaker with + strong felt affect is exhibiting form. +- Low restraint with strong affect is not honest. It is *vulgar* [E: "vulgar + violence as failed form"; "collapse into mere function"]. +- Zero affect is not neutrality. It can read as an absence of devotion. + +The deliberate consequence is that stance cannot be flattened to a politeness +level. Two axes are needed, and the source's lens questions (elegant, +disciplined, sincere, vulgar, memorable) ask for both. + +### Obligatory categories [I] + +Proposed grammatical commitments (the target-language selections that step 4 +of `language-model.md` demands). Each names the packet field that feeds it. + +1. **Finish marking** on predicates: an obligatory grade-class drawn from + `stance.restraint`, coarsened to a few classes. Working classes: + *raw*, *held*, *finished*. The unmarked form does not exist: leaving it + off is not neutrality but a gap that the community reads as vulgar. + Feeds from: `stance.restraint`. +2. **Leak channel**: how much felt affect shows *through* a high-restraint + frame. Leak is not mapped to emotion words. It is mapped to **form + choices**: clipped versus full inflection, length spent on a fricative, + word choice within a field. This follows `speaker-state`'s own example + (anger leaking through clipped syntax is stance). Feeds from: felt affect + grades against `restraint`. +3. **Standing agreement** on referents: field and standing-in-field of a + named performer, with the **claim/demonstrated** mismatch statable. Feeds + from: speaker and scene features (Section 6). +4. **Witness and form-evaluation**. Broadcast commentary is evaluative by + situation. Evaluation is expressed as a **separate discourse layer** + (a particle or clause layer), not as adjectives in the content: the + lens questions [E] (elegant, disciplined, sincere, vulgar, memorable, + promising, mastery, collapse) are the layer's open slots. It is + kept apart from content because a held-back evaluation is not a claim + about the world, and the interlingua keeps content and attitude + separate. Feeds from: `discourse` features and the situation. +5. **Evidential**: commentary asserts what the camera sees and *infers* the + performer's sincerity. The inference must be marked, because the source + puts sincerity in the performer, who is not the speaker and not available to + telemetry. Feeds from: the evidentiality feature the standard already + names. + +### Register is a function of three inputs [I] + +Register is not a setting on the speaker. It is selected from the pair +(speaker's standing and community, scene relation to addressee and subject) +and the situation, with stance as the final modulator. This is what +`persona-drives-ontology` implies on the speaker-state branch and what the +`language-ontology` cut will implement as deterministic selection +`select(profile, stance, speaker, scene)`. This spec supplies the **content +of that selection table** for this language; it does not write the table. + +Cases the table must cover first: + +| Case | Expected pull | +|---|---| +| Commentator, high restraint, strong approval of a restrained low-means act | finished marking; evaluative layer foregrounded; length withheld | +| Commentator, low restraint, strong approval | raw marking; the speaker is performing delight. Only permitted in tourist-adjacent registers | +| Master addressing rival | held or finished; address by field and standing | +| Tourist, any affect | claimant standing; over-lengthened; evaluation layer present but misapplied | +| Host to a human guest on a panel | warmth and appraisal mixed: affection plus contempt at high restraint is the *normal* pairing. The guest's rejection is absorbed as form | + +The last row is the story's turn written as a stance mapping, and is worth +testing first: contempt and affection together, restrained, with the evaluation +layer calling the rejection *sincere*. + +## 4. Meaning Before Surface + +The language is never produced by substituting English words with alien +ones. The method is the Weksa path (`language-model.md`): packet, then +ontology projection that can split, collapse or reweight concepts, then +grammar commitments, morphology, phonology, surface. + +### Concept splits and collapses [E→I] + +The source names distinctions the language must make (`language-design.md`, +Open Questions). Each is decomposed below into components so a lowerer can +select a form from the packet's facts and not from a gloss. + +| Distinction | Decomposition (hypothesis) | Needs from the packet | +|---|---|---| +| **victory** vs **mastery** vs **spectacle** vs **vulgar overmatch** | victory: outcome of a contest. mastery: outcome *plus* demonstrated chosen constraint. spectacle: attention drawn, independent of constraint. vulgar overmatch: means exceed the opposing means with no constraint chosen. | outcome; each party's means; whether the means were chosen | +| **crude** vs **failed** | crude: sincere, unrefined, with form attempted. failed: form collapsed into mere function. | actor's means and discipline as facts; the stance of the performer is not available, so the ontology reads from conduct | +| **resistance** admired and absorbable | resistance read as: threat, folk art, breakout talent (`battle-royale.md` commentary list). Choice depends on whether it centers a performer or a practice. | performer-centered vs distributed agency | +| **public spectacle** vs **living practice** [E] | spectacle: meaning lives in a central performer. practice: meaning lives in relationships. The empire reads distributed cooperation as noise or misattributes it to a performer. | number and relation of agents; presence of a focal agent | +| **unpainted**, split | shame / danger / contempt / rawness / refusal / purity (the source lists these senses). Different surface forms: the sense is selected by relation and stance, not by a gloss. | decoration state, scene relation, speaker stance | + +### Scale decoration in grammar and metaphor [E→I] + +Decoration is the language's central metaphor domain [E: used for rank, +grief, celebrity and more]. Proposed uses, to be tested not asserted: + +- **Address**: terms of address name a decoration claim ("the one marked for + ...") instead of a rank noun. +- **Insult**: lack, mismatch or fraud of decoration (a claim not backed by + constraint). The strongest insult is the claimant who is also vulgar. +- **Praise**: finish, as in a surface completed. +- **Metaphor**: grief and obligation are *worn*. A human speaking of loss + reads, in this language, as a person visibly undecorated by it. + +The source does not say whether the language has these metaphors. They are +the draft's strongest invention and the easiest to overrule. + +### Root families [I] + +House method: faint family resemblance, never a nametag. No roots are +proposed here. The first root set should come from the field concepts +(combat, speech, hosting, trade, art) and the constraint/finish axis, and +should be minted against corpus items, not authored in a vacuum. + +### Contamination direction [E] + +What leaks into human English first (`language-design.md`) is an open +question. [I] Candidates are evaluation-layer particles (the empire's form +verdicts become meme tags) and the claim/finish vocabulary, because the +story's kids absorb *prestige codes* first. The subtitle renderer is a +separate target profile and not this spec's job. + +## 5. Evidence and Invention, Summarized + +**Evidence-backed (E):** +- reptilian, aesthetic-honor, decoration-as-status speakers; unpainted as + profound shamelessness; unpainted-as-provocation for a rare few +- mastery requires chosen constraint; overmatch and tourists are vulgar; the + lens questions +- the empire is a network; commerce carries, culture conquers; the registers + listed +- broadcast commentary and flavor-first subtitles as the runtime use +- the packet has `stance` with felt affect and restraint; the ontology maps + them (speaker-state branch) + +**Invention (I):** +- the two-axis status ladder and the claim/demonstrated-constraint mismatch +- the whole phonological inventory and prosody +- the five obligatory categories and the inverted restraint reading +- the decoration-grammar and metaphor uses +- the speech situations table, notably the address-to-humans row +- the contempt-plus-affection panel pairing + +**Not settled in the source and left open:** +- the language's and speakers' names; the vocal tract; whether the empire is + one language community or a family of dialects; whether non-reptilian + imperial subjects speak it and how it marks them + +## 6. What The Lowerer Must Provide + +Interlingua rule [E]: the packet carries neutral meaning plus source-side +communicative pressure, and target-language concept splits belong +downstream. The earlier Rust to Dust sketch in `language-design.md` puts an +`aesthetic_reading` block (form, discipline, spectacle, honor status) **in +the packet**. This draft recommends moving it: those are the *imperial +ontology's reading* of neutral facts, so they belong in the ontology +projection, and the packet carries the facts the reading is derived from. +[I] This is a change to existing content and the operator should rule on it. + +Fields, in order of need. "Seed" means the standard's inventory already has +a slot. "Gap" means the standard has no field yet; per the Promotion Rule, +the gap is first an `rtd.*` extension and is promoted only after use in two +real packets. + +| Need | Why | Status | +|---|---|---| +| `stance` with felt affect and `restraint` | the whole of Section 3 | seed (unmerged: `speaker-state`) | +| **speaker** axes: field, standing in field, school or lineage, decoration state, community | status and register selection | seed slot `Speaker` (unmerged); the *values* are the gap | +| **scene**: relative status of speaker, subject and audience; audience composition (imperial, tourist, human via subtitles) | register and address | seed slot `Scene` (unmerged) | +| `medium` and `setting` ids for broadcast commentary | situation selection | seed (`context`) | +| `instrument` role on predications, **for each party** | overmatch is derived from both parties' means | seed role | +| **volition of the constraint**: chosen / compelled / unknown, as a predication feature | the line between mastery and mere lacking | gap (`rtd.` extension first) | +| **ordering of means** (power or means gap) | vulgar overmatch | not a packet field: ontology-side order over concept ids, so the packet carries concepts and the ontology compares them | +| referent **decoration state** (decorated / unpainted / bare-by-claim / not applicable) | status, insult, the human-undecorated reading | gap (`rtd.` extension first) | +| referent **community** or `member_of` relation | tourist versus warrior; human versus imperial | seed relation `member_of`; community ids come from the `language-ontology` cut | +| **focal agent** presence | spectacle versus living practice | derivable: a predication with plural or no agent role; not a new field | +| `evidentiality` | witnessed versus inferred sincerity | seed feature (to confirm its option set) | +| `polarity`, tense, aspect | ordinary | seed | +| `preserve` | which nodes the commentary must keep | seed (`constraints`) | + +Fields the draft deliberately does **not** request: `aesthetic_reading`, +`honor_status`, `spectacle_value` or any value judgment. The performer's +sincerity is not telemetry, and the commentator infers it. These are +outputs of the imperial ontology and must not be put into the packet by the +client, or the client is deciding the language's concepts. + +## 7. Dependencies, Risks and Next Cuts + +**Dependencies.** Stance and the `Speaker` and `Scene` slots exist only on +the `weksa-lowering/speaker-state` branch. The `language-ontology` cut +(community and ontology-entry documents) is not landed. This spec states +the content those cuts will hold, and does not substitute for them. + +**Risks.** +- The inventory and the grammar are heavily invented on thin evidence. Mitigation: + tag everything (done) and test the five obligatory categories against the + lowerer before building roots. +- The claim-versus-constraint ladder risks making the language a *status-code + detector*. The source's hold is that honor is devotion, not rank. Test: + can the grammar say a bare-by-claim performance without calling it low? +- Draft collides with `language-design.md`'s packet-side `aesthetic_reading`. + Resolved by the recommendation in Section 6 if ruled. + +**Next cuts, in order.** +1. Operator rulings on the forks below. +2. A first corpus slice: ten broadcast-commentary utterances, each decomposed + to a packet (neutral facts plus stance), with the expected surface *category* + (finish class, evaluation layer) and no words yet. +3. The selection table for Section 3's first five cases. +4. A root set from the field concepts, and a naming brief for the language. +5. An orthography and a pronunciation lowering. + +**Forks for the operator (recommendations given).** +- Move `aesthetic_reading` out of the packet into the ontology. *Recommend: + yes.* +- Is a two-axis status ladder right, or should honor be flat within a field? + *Recommend: two axes, because tourists need a place.* +- Is inverted restraint (restraint as display of virtue) the intended reading? + *Recommend: yes, it follows from "chosen constraint is mastery".* +- Is the vocal tract reptilian-literal or a human-stylized invention? + *Recommend: human-pronounceable stylization with length as ornament, so + the first corpus does not wait on phonetics.*