1010//! See PR #279 outlook E3 + grammar-landscape.md §9.
1111//!
1212//! META-AGENT: `pub mod verb_table;` to mod.rs.
13+ //!
14+ //! ## Tense modulation (G4 loose end)
15+ //!
16+ //! Earlier seed broadcast 12 family priors across all 12 tenses, producing a
17+ //! degenerate 12-unique-value table with zero tense x family interaction. The
18+ //! refactor introduces `SlotPriorDelta` + `SlotPrior::combine` and a
19+ //! `tense_modifier(Tense)` function so each cell becomes
20+ //! `final = base.combine(tense_modifier(tense))`.
21+ //!
22+ //! Modifiers are linguistically grounded in standard English grammar
23+ //! (Quirk, Greenbaum, Leech & Svartvik, *A Comprehensive Grammar of the
24+ //! English Language*, Longman 1985, sections 4.21-4.27 on tense / aspect /
25+ //! mood):
26+ //!
27+ //! - Perfect aspects (Perfect, Pluperfect, FuturePerfect) emphasise
28+ //! completion and therefore temporal anchoring -> `temporal +0.15`.
29+ //! - Continuous (progressive) aspects emphasise an ongoing process ->
30+ //! `temporal +0.10`, `modal -0.05` (less anchored, less modal weight).
31+ //! - Imperative is a timeless directive command -> `temporal -0.20`,
32+ //! `modal +0.20`.
33+ //! - Potential (irrealis / possibility mood; this enum's stand-in for the
34+ //! Subjunctive) emphasises possibility -> `temporal -0.10`, `modal +0.25`,
35+ //! `kausal -0.05` (cause is hypothetical).
36+ //! - Habitual is recurring-as-timeless -> `temporal -0.10`, `modal +0.05`.
37+ //! - Default (Present, Past, Future) leaves the base prior untouched.
38+ //!
39+ //! All resulting axes are clamped to [0.0, 1.0] in `SlotPrior::combine`.
1340
1441use crate :: grammar:: role_keys:: Tense ;
1542
@@ -46,6 +73,66 @@ impl SlotPrior {
4673 pub const fn uniform ( ) -> Self {
4774 Self { temporal : 0.5 , kausal : 0.5 , modal : 0.5 , lokal : 0.5 , instrument : 0.5 }
4875 }
76+
77+ /// Apply a tense-driven delta to each axis and clamp the result to
78+ /// `[0.0, 1.0]`. This is how the broadcast-flat 12 priors per family
79+ /// gain tense x family interaction (G4 loose end).
80+ pub fn combine ( self , delta : SlotPriorDelta ) -> Self {
81+ fn clamp ( x : f32 ) -> f32 {
82+ if x < 0.0 { 0.0 } else if x > 1.0 { 1.0 } else { x }
83+ }
84+ Self {
85+ temporal : clamp ( self . temporal + delta. temporal ) ,
86+ kausal : clamp ( self . kausal + delta. kausal ) ,
87+ modal : clamp ( self . modal + delta. modal ) ,
88+ lokal : clamp ( self . lokal + delta. lokal ) ,
89+ instrument : clamp ( self . instrument + delta. instrument ) ,
90+ }
91+ }
92+ }
93+
94+ /// Additive delta applied to a `SlotPrior` per tense. Each axis is summed
95+ /// with the base prior and clamped via `SlotPrior::combine`. Default = no
96+ /// change (all zeros).
97+ #[ derive( Debug , Clone , Copy , Default ) ]
98+ pub struct SlotPriorDelta {
99+ pub temporal : f32 ,
100+ pub kausal : f32 ,
101+ pub modal : f32 ,
102+ pub lokal : f32 ,
103+ pub instrument : f32 ,
104+ }
105+
106+ /// Tense-driven modifier table. Linguistic grounding: Quirk et al.
107+ /// *Comprehensive Grammar of the English Language* sections 4.21-4.27.
108+ /// See module-level doc comment for the per-tense rationale.
109+ pub fn tense_modifier ( tense : Tense ) -> SlotPriorDelta {
110+ use Tense :: * ;
111+ match tense {
112+ // Perfect aspects emphasise completion -> temporal anchoring.
113+ Perfect | Pluperfect | FuturePerfect => SlotPriorDelta {
114+ temporal : 0.15 , kausal : 0.0 , modal : 0.0 , lokal : 0.0 , instrument : 0.0 ,
115+ } ,
116+ // Continuous (progressive) aspects emphasise ongoing process.
117+ PresentContinuous | PastContinuous | FutureContinuous => SlotPriorDelta {
118+ temporal : 0.10 , kausal : 0.0 , modal : -0.05 , lokal : 0.0 , instrument : 0.0 ,
119+ } ,
120+ // Imperative: timeless directive -> suppresses temporal, amplifies modal.
121+ Imperative => SlotPriorDelta {
122+ temporal : -0.20 , kausal : 0.0 , modal : 0.20 , lokal : 0.0 , instrument : 0.0 ,
123+ } ,
124+ // Potential (irrealis / subjunctive role): possibility -> modal up,
125+ // kausal slightly down (cause is hypothetical), temporal slightly down.
126+ Potential => SlotPriorDelta {
127+ temporal : -0.10 , kausal : -0.05 , modal : 0.25 , lokal : 0.0 , instrument : 0.0 ,
128+ } ,
129+ // Habitual: recurring-as-timeless.
130+ Habitual => SlotPriorDelta {
131+ temporal : -0.10 , kausal : 0.0 , modal : 0.05 , lokal : 0.0 , instrument : 0.0 ,
132+ } ,
133+ // Present, Past, Future: unmarked tense, no modifier.
134+ Present | Past | Future => SlotPriorDelta :: default ( ) ,
135+ }
49136}
50137
51138/// 144-cell lookup: rows = `VerbFamily`, columns = `Tense`. Indexing is
@@ -87,89 +174,37 @@ impl VerbRoleTable {
87174/// The numbers are *priors*, not facts: a future PR replaces them
88175/// with corpus-derived statistics. Mark this `// starter â tune empirically`
89176/// in any consumer that depends on specific values.
90- pub fn default_table ( ) -> VerbRoleTable {
91- let mut t = VerbRoleTable :: new_uniform ( ) ;
92-
93- // --- Change verbs: high Temporal + Modal ---
94-
95- // BECOMES: state-change, strongly temporal + modal
96- let becomes = SlotPrior { temporal : 0.9 , kausal : 0.2 , modal : 0.7 , lokal : 0.3 , instrument : 0.2 } ;
97- for tense in Tense :: ALL {
98- t. set ( VerbFamily :: Becomes , tense, becomes) ;
99- }
100-
101- // DISSOLVES: destruction-as-change, high temporal + modal
102- let dissolves = SlotPrior { temporal : 0.85 , kausal : 0.3 , modal : 0.7 , lokal : 0.25 , instrument : 0.2 } ;
103- for tense in Tense :: ALL {
104- t. set ( VerbFamily :: Dissolves , tense, dissolves) ;
105- }
106-
107- // ABSTRACTS: conceptual transformation, high modal + temporal
108- let abstracts = SlotPrior { temporal : 0.7 , kausal : 0.25 , modal : 0.85 , lokal : 0.15 , instrument : 0.2 } ;
109- for tense in Tense :: ALL {
110- t. set ( VerbFamily :: Abstracts , tense, abstracts) ;
111- }
112-
113- // MIRRORS: reflection/symmetry, temporal + modal + lokal
114- let mirrors = SlotPrior { temporal : 0.75 , kausal : 0.2 , modal : 0.7 , lokal : 0.6 , instrument : 0.15 } ;
115- for tense in Tense :: ALL {
116- t. set ( VerbFamily :: Mirrors , tense, mirrors) ;
117- }
118-
119- // --- Action verbs: high Kausal + Temporal ---
120-
121- // CAUSES: strong causal agency, high kausal + instrument
122- let causes = SlotPrior { temporal : 0.4 , kausal : 0.95 , modal : 0.4 , lokal : 0.3 , instrument : 0.5 } ;
123- for tense in Tense :: ALL {
124- t. set ( VerbFamily :: Causes , tense, causes) ;
125- }
126-
127- // PREVENTS: blocking action, high kausal + temporal
128- let prevents = SlotPrior { temporal : 0.7 , kausal : 0.9 , modal : 0.4 , lokal : 0.25 , instrument : 0.35 } ;
129- for tense in Tense :: ALL {
130- t. set ( VerbFamily :: Prevents , tense, prevents) ;
177+ /// Base prior for a `VerbFamily` (pre-tense-modulation). The full per-cell
178+ /// prior is `base_prior(family).combine(tense_modifier(tense))`.
179+ pub fn base_prior ( family : VerbFamily ) -> SlotPrior {
180+ match family {
181+ // --- Change verbs: high Temporal + Modal ---
182+ VerbFamily :: Becomes => SlotPrior { temporal : 0.9 , kausal : 0.2 , modal : 0.7 , lokal : 0.3 , instrument : 0.2 } ,
183+ VerbFamily :: Dissolves => SlotPrior { temporal : 0.85 , kausal : 0.3 , modal : 0.7 , lokal : 0.25 , instrument : 0.2 } ,
184+ VerbFamily :: Abstracts => SlotPrior { temporal : 0.7 , kausal : 0.25 , modal : 0.85 , lokal : 0.15 , instrument : 0.2 } ,
185+ VerbFamily :: Mirrors => SlotPrior { temporal : 0.75 , kausal : 0.2 , modal : 0.7 , lokal : 0.6 , instrument : 0.15 } ,
186+ // --- Action verbs: high Kausal + Temporal ---
187+ VerbFamily :: Causes => SlotPrior { temporal : 0.4 , kausal : 0.95 , modal : 0.4 , lokal : 0.3 , instrument : 0.5 } ,
188+ VerbFamily :: Prevents => SlotPrior { temporal : 0.7 , kausal : 0.9 , modal : 0.4 , lokal : 0.25 , instrument : 0.35 } ,
189+ VerbFamily :: Transforms => SlotPrior { temporal : 0.8 , kausal : 0.85 , modal : 0.35 , lokal : 0.3 , instrument : 0.6 } ,
190+ // --- State verbs: high Modal, low Temporal ---
191+ VerbFamily :: Supports => SlotPrior { temporal : 0.2 , kausal : 0.35 , modal : 0.85 , lokal : 0.2 , instrument : 0.3 } ,
192+ VerbFamily :: Contradicts => SlotPrior { temporal : 0.15 , kausal : 0.7 , modal : 0.9 , lokal : 0.15 , instrument : 0.1 } ,
193+ VerbFamily :: Refines => SlotPrior { temporal : 0.3 , kausal : 0.4 , modal : 0.8 , lokal : 0.2 , instrument : 0.35 } ,
194+ VerbFamily :: Grounds => SlotPrior { temporal : 0.25 , kausal : 0.3 , modal : 0.75 , lokal : 0.85 , instrument : 0.2 } ,
195+ // --- Discovery / enablement: high Kausal + Lokal ---
196+ VerbFamily :: Enables => SlotPrior { temporal : 0.35 , kausal : 0.8 , modal : 0.4 , lokal : 0.7 , instrument : 0.45 } ,
131197 }
198+ }
132199
133- // TRANSFORMS: active change, high kausal + temporal + instrument
134- let transforms = SlotPrior { temporal : 0.8 , kausal : 0.85 , modal : 0.35 , lokal : 0.3 , instrument : 0.6 } ;
135- for tense in Tense :: ALL {
136- t. set ( VerbFamily :: Transforms , tense, transforms) ;
137- }
138-
139- // --- State verbs: high Modal, low Temporal ---
140-
141- // SUPPORTS: epistemic backing, high modal
142- let supports = SlotPrior { temporal : 0.2 , kausal : 0.35 , modal : 0.85 , lokal : 0.2 , instrument : 0.3 } ;
143- for tense in Tense :: ALL {
144- t. set ( VerbFamily :: Supports , tense, supports) ;
145- }
146-
147- // CONTRADICTS: logical opposition, high modal + kausal
148- let contradicts = SlotPrior { temporal : 0.15 , kausal : 0.7 , modal : 0.9 , lokal : 0.15 , instrument : 0.1 } ;
149- for tense in Tense :: ALL {
150- t. set ( VerbFamily :: Contradicts , tense, contradicts) ;
151- }
152-
153- // REFINES: iterative improvement, high modal, moderate kausal
154- let refines = SlotPrior { temporal : 0.3 , kausal : 0.4 , modal : 0.8 , lokal : 0.2 , instrument : 0.35 } ;
155- for tense in Tense :: ALL {
156- t. set ( VerbFamily :: Refines , tense, refines) ;
157- }
158-
159- // GROUNDS: anchoring to context, high lokal + modal
160- let grounds = SlotPrior { temporal : 0.25 , kausal : 0.3 , modal : 0.75 , lokal : 0.85 , instrument : 0.2 } ;
161- for tense in Tense :: ALL {
162- t. set ( VerbFamily :: Grounds , tense, grounds) ;
163- }
164-
165- // --- Discovery/enablement verbs: high Kausal + Lokal ---
166-
167- // ENABLES: facilitation, high kausal + lokal
168- let enables = SlotPrior { temporal : 0.35 , kausal : 0.8 , modal : 0.4 , lokal : 0.7 , instrument : 0.45 } ;
169- for tense in Tense :: ALL {
170- t. set ( VerbFamily :: Enables , tense, enables) ;
200+ pub fn default_table ( ) -> VerbRoleTable {
201+ let mut t = VerbRoleTable :: new_uniform ( ) ;
202+ for family in VerbFamily :: ALL {
203+ let base = base_prior ( family) ;
204+ for tense in Tense :: ALL {
205+ t. set ( family, tense, base. combine ( tense_modifier ( tense) ) ) ;
206+ }
171207 }
172-
173208 t
174209}
175210
@@ -250,8 +285,11 @@ mod tests {
250285
251286 #[ test]
252287 fn refines_state_verb_modal ( ) {
288+ // Tense::Present is unmarked (no modifier) so the family-level base
289+ // prior is preserved. (Under tense modulation, Perfect adds +0.15 to
290+ // temporal, which would push Refines.temporal from 0.3 to 0.45.)
253291 let t = default_table ( ) ;
254- let p = t. lookup ( VerbFamily :: Refines , Tense :: Perfect ) ;
292+ let p = t. lookup ( VerbFamily :: Refines , Tense :: Present ) ;
255293 assert ! ( p. modal > 0.7 , "Refines should have high modal" ) ;
256294 assert ! ( p. temporal < 0.4 , "Refines should have low temporal" ) ;
257295 assert ! ( count_non_uniform( & p) >= 2 ) ;
@@ -315,8 +353,11 @@ mod tests {
315353
316354 #[ test]
317355 fn dissolves_change_verb_temporal_modal ( ) {
356+ // Use Tense::Present (unmarked) so the family base prior is preserved.
357+ // Imperative would suppress temporal by 0.20 (0.85 -> 0.65 < 0.7) and
358+ // amplify modal — those are tested in `test_imperative_suppresses_temporal`.
318359 let t = default_table ( ) ;
319- let p = t. lookup ( VerbFamily :: Dissolves , Tense :: Imperative ) ;
360+ let p = t. lookup ( VerbFamily :: Dissolves , Tense :: Present ) ;
320361 assert ! ( p. temporal > 0.7 , "Dissolves should have high temporal" ) ;
321362 assert ! ( p. modal > 0.6 , "Dissolves should have elevated modal" ) ;
322363 assert ! ( count_non_uniform( & p) >= 2 ) ;
@@ -334,4 +375,83 @@ mod tests {
334375 ) ;
335376 }
336377 }
378+
379+ // --- Tense modulation tests (G4 loose end: priors must vary across tenses
380+ // within a family; broadcast-flat 12-priors-across-12-tenses produces
381+ // zero tense×family interaction). ---
382+
383+ /// Failing-test-first: Perfect aspect (completion → temporal anchoring
384+ /// per Quirk et al. CGEL §4.21–4.27) must yield strictly higher temporal
385+ /// prior than the unmarked Past for the same family.
386+ #[ test]
387+ fn test_perfect_amplifies_temporal_within_family ( ) {
388+ let t = default_table ( ) ;
389+ let perfect = t. lookup ( VerbFamily :: Causes , Tense :: Perfect ) ;
390+ let past = t. lookup ( VerbFamily :: Causes , Tense :: Past ) ;
391+ assert ! (
392+ perfect. temporal > past. temporal,
393+ "Perfect should amplify temporal over Past for Causes; got perfect={} past={}" ,
394+ perfect. temporal, past. temporal
395+ ) ;
396+ }
397+
398+ /// Imperative (timeless command) suppresses temporal in favour of modal.
399+ #[ test]
400+ fn test_imperative_suppresses_temporal ( ) {
401+ let t = default_table ( ) ;
402+ let imperative = t. lookup ( VerbFamily :: Causes , Tense :: Imperative ) ;
403+ let present = t. lookup ( VerbFamily :: Causes , Tense :: Present ) ;
404+ assert ! (
405+ imperative. temporal < present. temporal,
406+ "Imperative should suppress temporal vs Present for Causes; got imp={} pres={}" ,
407+ imperative. temporal, present. temporal
408+ ) ;
409+ assert ! (
410+ imperative. modal > present. modal,
411+ "Imperative should amplify modal vs Present for Causes; got imp={} pres={}" ,
412+ imperative. modal, present. modal
413+ ) ;
414+ }
415+
416+ /// Subjunctive equivalent — this enum has Potential (irrealis/possibility
417+ /// mood), which fills the Subjunctive role. Potential should amplify modal
418+ /// over Present.
419+ #[ test]
420+ fn test_subjunctive_amplifies_modal ( ) {
421+ let t = default_table ( ) ;
422+ let potential = t. lookup ( VerbFamily :: Supports , Tense :: Potential ) ;
423+ let present = t. lookup ( VerbFamily :: Supports , Tense :: Present ) ;
424+ assert ! (
425+ potential. modal > present. modal,
426+ "Potential (subjunctive role) should amplify modal vs Present for Supports; \
427+ got pot={} pres={}",
428+ potential. modal, present. modal
429+ ) ;
430+ }
431+
432+ /// Sanity: continuous aspects amplify temporal but less than perfect.
433+ /// Use `Causes` (temporal base 0.4) so neither modifier saturates at 1.0.
434+ #[ test]
435+ fn test_continuous_amplifies_temporal_less_than_perfect ( ) {
436+ let t = default_table ( ) ;
437+ let cont = t. lookup ( VerbFamily :: Causes , Tense :: PresentContinuous ) ;
438+ let perf = t. lookup ( VerbFamily :: Causes , Tense :: Perfect ) ;
439+ let pres = t. lookup ( VerbFamily :: Causes , Tense :: Present ) ;
440+ assert ! ( cont. temporal > pres. temporal, "Continuous > Present temporal" ) ;
441+ assert ! ( perf. temporal > cont. temporal, "Perfect > Continuous temporal" ) ;
442+ }
443+
444+ /// Sanity: clamp to [0,1] holds even when base prior is near saturation.
445+ #[ test]
446+ fn test_combine_clamps_to_unit_interval ( ) {
447+ let t = default_table ( ) ;
448+ // Causes has kausal=0.95 base; no tense modifier touches kausal,
449+ // but Perfect adds +0.15 to temporal where Causes.temporal=0.4 → 0.55.
450+ let p = t. lookup ( VerbFamily :: Causes , Tense :: Perfect ) ;
451+ assert ! ( p. temporal >= 0.0 && p. temporal <= 1.0 ) ;
452+ assert ! ( p. kausal >= 0.0 && p. kausal <= 1.0 ) ;
453+ assert ! ( p. modal >= 0.0 && p. modal <= 1.0 ) ;
454+ assert ! ( p. lokal >= 0.0 && p. lokal <= 1.0 ) ;
455+ assert ! ( p. instrument >= 0.0 && p. instrument <= 1.0 ) ;
456+ }
337457}
0 commit comments