@@ -79,15 +79,16 @@ use std::time::Instant;
7979use griff_constraint_lab:: fingering:: {
8080 best_hands, decode_positions, hand_problem, holdout_bucket, repeat_pairs, solve_hand, song_key,
8181 tab_lines, v1_cost, v1_problem, with_repeat_consistency, with_string_tiebreak, CutStats ,
82- HandModel , HandWeights , LineCut , TabLine , TechniqueEdge , HAND_VARS_PER_NOTE , V1_VARS_PER_NOTE ,
82+ HandModel , HandWeights , LineCut , TabLine , TechniqueEdge , TechniqueKind , HAND_VARS_PER_NOTE ,
83+ V1_VARS_PER_NOTE ,
8384} ;
8485use griff_constraint_lab:: ir:: VarId ;
8586use griff_constraint_lab:: optir:: {
8687 verify_agreement, verify_record, OptProblem , ProblemRecord , SolveRecord , Verdict ,
8788} ;
8889use griff_constraint_lab:: technique:: {
89- derived_direction, tap_aware_chain, tap_aware_cost, technique_chain, technique_cost ,
90- Continuity , LegatoDirection , TechniqueObjective ,
90+ derived_direction, direction_between , tap_aware_chain, tap_aware_cost, technique_chain,
91+ technique_cost , Continuity , LegatoDirection , TechniqueObjective ,
9192} ;
9293use griff_constraint_lab:: ties:: {
9394 lexicographic_path, optimum_set, path_matches, train_secondary, Chain , Example , Features ,
@@ -1792,52 +1793,60 @@ impl LegatoCensus {
17921793 let ( before, here) = ( tab. human [ i - 1 ] , tab. human [ i] ) ;
17931794 let same = before. string == here. string ;
17941795 let open = here. fret == 0 ;
1795- let edge = tab. edges [ i] ;
17961796 let direction = derived_direction ( & tab. pitches , i) ;
17971797 self . edges += 1 ;
1798- if edge. is_legato ( ) {
1799- self . legato . add ( same) ;
1800- match edge {
1801- TechniqueEdge :: HammerOn => self . hammer_on . add ( same) ,
1802- TechniqueEdge :: PullOff => self . pull_off . add ( same) ,
1803- TechniqueEdge :: Legato => self . legato_span . add ( same) ,
1804- TechniqueEdge :: Plain => { }
1805- }
1806- match direction {
1807- Some ( LegatoDirection :: Ascending ) => self . ascending . add ( same) ,
1808- Some ( LegatoDirection :: Descending ) => {
1809- self . descending . add ( same) ;
1810- self . descending_legato_open . add ( open) ;
1811- }
1812- Some ( LegatoDirection :: Unison ) => self . unison . add ( same) ,
1813- None => { }
1814- }
1815- } else {
1798+ let observed = tab
1799+ . edges
1800+ . iter ( )
1801+ . any ( |edge| edge. from == i - 1 && edge. to == i) ;
1802+ if !observed {
18161803 self . plain . add ( same) ;
18171804 if direction == Some ( LegatoDirection :: Descending ) {
18181805 self . descending_plain_open . add ( open) ;
18191806 }
18201807 }
18211808 if tab. tapped [ i - 1 ] {
18221809 self . out_of_tap += 1 ;
1823- if edge. is_legato ( ) {
1824- self . out_of_tap_legato . add ( same) ;
1825- match direction {
1826- Some ( LegatoDirection :: Ascending ) => self . out_of_tap_legato_ascending += 1 ,
1827- Some ( LegatoDirection :: Descending ) => {
1828- self . out_of_tap_legato_descending += 1 ;
1829- }
1830- Some ( LegatoDirection :: Unison ) => self . out_of_tap_legato_unison += 1 ,
1831- None => { }
1832- }
1833- self . out_of_tap_legato_open_target += u64:: from ( open) ;
1834- }
18351810 }
18361811 if tab. tapped [ i] {
18371812 self . into_tap += 1 ;
1838- if edge. is_legato ( ) {
1839- self . into_tap_legato . add ( same) ;
1813+ }
1814+ }
1815+ for edge in & tab. edges {
1816+ let before = tab. human [ edge. from ] ;
1817+ let here = tab. human [ edge. to ] ;
1818+ let same = before. string == here. string ;
1819+ let open = here. fret == 0 ;
1820+ let direction = direction_between ( & tab. pitches , edge. from , edge. to ) ;
1821+ self . legato . add ( same) ;
1822+ match edge. kind {
1823+ TechniqueKind :: HammerOn => self . hammer_on . add ( same) ,
1824+ TechniqueKind :: PullOff => self . pull_off . add ( same) ,
1825+ TechniqueKind :: Legato => self . legato_span . add ( same) ,
1826+ }
1827+ match direction {
1828+ Some ( LegatoDirection :: Ascending ) => self . ascending . add ( same) ,
1829+ Some ( LegatoDirection :: Descending ) => {
1830+ self . descending . add ( same) ;
1831+ self . descending_legato_open . add ( open) ;
1832+ }
1833+ Some ( LegatoDirection :: Unison ) => self . unison . add ( same) ,
1834+ None => { }
1835+ }
1836+ if tab. tapped [ edge. from ] {
1837+ self . out_of_tap_legato . add ( same) ;
1838+ match direction {
1839+ Some ( LegatoDirection :: Ascending ) => self . out_of_tap_legato_ascending += 1 ,
1840+ Some ( LegatoDirection :: Descending ) => {
1841+ self . out_of_tap_legato_descending += 1 ;
1842+ }
1843+ Some ( LegatoDirection :: Unison ) => self . out_of_tap_legato_unison += 1 ,
1844+ None => { }
18401845 }
1846+ self . out_of_tap_legato_open_target += u64:: from ( open) ;
1847+ }
1848+ if tab. tapped [ edge. to ] {
1849+ self . into_tap_legato . add ( same) ;
18411850 }
18421851 }
18431852 }
@@ -1855,9 +1864,11 @@ struct CensusRow {
18551864struct CensusReport {
18561865 schema : & ' static str ,
18571866 version : u32 ,
1858- /// Legato origins on the last note of a kept line (whole corpus, all
1859- /// formats): they project onto no edge .
1867+ /// Legato origins in kept lines with no later same-string note in the
1868+ /// imported voice .
18601869 dangling_legato : u64 ,
1870+ /// Resolved same-string targets that fall outside their origin's kept line.
1871+ cross_line_legato : u64 ,
18611872 rows : Vec < CensusRow > ,
18621873 corpus : CorpusFacts ,
18631874}
@@ -1918,7 +1929,9 @@ fn legato_census(corpus: Corpus, out: &Path) -> std::io::Result<()> {
19181929 . collect ( ) ;
19191930
19201931 let dangling = corpus. facts . cut_stats . dangling_legato ;
1921- println ! ( "\n legato origins ending a kept line (no edge): {dangling}" ) ;
1932+ let cross_line = corpus. facts . cut_stats . cross_line_legato ;
1933+ println ! ( "\n unresolved legato origins (no later same-string voice note): {dangling}" ) ;
1934+ println ! ( "resolved legato targets outside their origin's kept line: {cross_line}" ) ;
19221935 println ! ( "\n L1/L2 — P(same string | edge): share (edges). Legato kinds are imported; directions are derived from pitch.\n " ) ;
19231936 println ! ( "| split | family | population | legato edges | HammerOn | PullOff | Legato | plain edges (base rate) | ascending legato | descending legato | unison legato |" ) ;
19241937 println ! ( "|---|---|---|---|---|---|---|---|---|---|---|" ) ;
@@ -1986,8 +1999,9 @@ fn legato_census(corpus: Corpus, out: &Path) -> std::io::Result<()> {
19861999 }
19872000 let report = CensusReport {
19882001 schema : "griff.constraint-lab-legato-census" ,
1989- version : 1 ,
2002+ version : 2 ,
19902003 dangling_legato : dangling,
2004+ cross_line_legato : cross_line,
19912005 rows,
19922006 corpus : corpus. facts ,
19932007 } ;
@@ -2096,66 +2110,50 @@ struct LegatoLine {
20962110}
20972111
20982112fn cross_string_legato ( path : & [ FretboardPosition ] , edges : & [ TechniqueEdge ] ) -> u64 {
2099- path. windows ( 2 )
2100- . zip ( edges. iter ( ) . skip ( 1 ) )
2101- . filter ( |( pair, edge) | {
2102- edge. is_legato ( ) && pair. first ( ) . map ( |p| p. string ) != pair. last ( ) . map ( |p| p. string )
2103- } )
2113+ edges
2114+ . iter ( )
2115+ . filter ( |edge| path[ edge. from ] . string != path[ edge. to ] . string )
21042116 . count ( ) as u64
21052117}
21062118
2107- /// Human cross-string legato edges with enough local context to audit whether
2108- /// the imported origin should bind to the immediate next onset or to a later
2109- /// note on the origin string. This is diagnostic output only; it does not feed
2110- /// any objective.
2119+ /// Any human cross-string legato edges left after same-string projection.
2120+ /// This is a fail-closed regression manifest only; it does not feed any
2121+ /// objective and is expected to be empty on the research corpus.
21112122fn cross_string_legato_forensics ( tab : & TabLine ) -> Vec < serde_json:: Value > {
2112- ( 1 .. tab. human . len ( ) )
2113- . filter_map ( |i| {
2114- let edge = tab . edges [ i ] ;
2115- let from = tab. human [ i - 1 ] ;
2116- let immediate_to = tab. human [ i ] ;
2117- if !edge . is_legato ( ) || from. string == immediate_to . string {
2123+ tab. edges
2124+ . iter ( )
2125+ . filter_map ( | edge| {
2126+ let from = tab. human [ edge . from ] ;
2127+ let target = tab. human [ edge . to ] ;
2128+ if from. string == target . string {
21182129 return None ;
21192130 }
21202131
2121- let direction = match derived_direction ( & tab. pitches , i ) {
2132+ let direction = match direction_between ( & tab. pitches , edge . from , edge . to ) {
21222133 Some ( LegatoDirection :: Ascending ) => "ascending" ,
21232134 Some ( LegatoDirection :: Descending ) => "descending" ,
21242135 Some ( LegatoDirection :: Unison ) => "unison" ,
21252136 None => "unknown" ,
21262137 } ;
2127- let next_on_origin_string = ( ( i + 1 ) ..tab. human . len ( ) )
2128- . find ( |& j| tab. human [ j] . string == from. string )
2129- . map ( |j| {
2130- serde_json:: json!( {
2131- "index" : j,
2132- "notes_after_immediate" : j - i,
2133- "pitch" : tab. pitches[ j] . 0 ,
2134- "string" : tab. human[ j] . string,
2135- "fret" : tab. human[ j] . fret,
2136- "tapped" : tab. tapped[ j] ,
2137- } )
2138- } ) ;
21392138
21402139 Some ( serde_json:: json!( {
2141- "edge_into" : i,
2142- "kind" : format!( "{edge:?}" ) ,
2140+ "kind" : format!( "{:?}" , edge. kind) ,
21432141 "derived_direction" : direction,
21442142 "from" : {
2145- "index" : i - 1 ,
2146- "pitch" : tab. pitches[ i - 1 ] . 0 ,
2143+ "index" : edge . from ,
2144+ "pitch" : tab. pitches[ edge . from ] . 0 ,
21472145 "string" : from. string,
21482146 "fret" : from. fret,
2149- "tapped" : tab. tapped[ i - 1 ] ,
2147+ "tapped" : tab. tapped[ edge . from ] ,
21502148 } ,
2151- "immediate_to" : {
2152- "index" : i,
2153- "pitch" : tab. pitches[ i] . 0 ,
2154- "string" : immediate_to. string,
2155- "fret" : immediate_to. fret,
2156- "tapped" : tab. tapped[ i] ,
2149+ "target" : {
2150+ "index" : edge. to,
2151+ "notes_after_origin" : edge. to - edge. from,
2152+ "pitch" : tab. pitches[ edge. to] . 0 ,
2153+ "string" : target. string,
2154+ "fret" : target. fret,
2155+ "tapped" : tab. tapped[ edge. to] ,
21572156 } ,
2158- "next_on_origin_string" : next_on_origin_string,
21592157 } ) )
21602158 } )
21612159 . collect ( )
@@ -2217,7 +2215,7 @@ fn legato_line(line: &Line, weights: &FingeringWeights) -> LegatoLine {
22172215 paths. push ( path) ;
22182216 }
22192217 let tapped = tab. tapped . iter ( ) . filter ( |t| * * t) . count ( ) as u64 ;
2220- let has_legato = tab. edges . iter ( ) . any ( |e| e . is_legato ( ) ) ;
2218+ let has_legato = ! tab. edges . is_empty ( ) ;
22212219 LegatoLine {
22222220 notes : tab. human . len ( ) as u64 ,
22232221 tapped,
@@ -2497,7 +2495,7 @@ fn legato(corpus: Corpus, out: &Path) -> std::io::Result<()> {
24972495 let file = corpus. names . get ( line. file ) ;
24982496 let forensic = serde_json:: json!( {
24992497 "schema" : "griff.constraint-lab-legato-violator" ,
2500- "version" : 1 ,
2498+ "version" : 2 ,
25012499 "id" : line. id,
25022500 "file" : file,
25032501 "song" : file. map( |name| song_key( name) ) ,
@@ -2580,7 +2578,7 @@ fn legato(corpus: Corpus, out: &Path) -> std::io::Result<()> {
25802578 let without_legato = corpus
25812579 . lines
25822580 . iter ( )
2583- . filter ( |l| l. tab . edges . iter ( ) . all ( |e| !e . is_legato ( ) ) )
2581+ . filter ( |l| l. tab . edges . is_empty ( ) )
25842582 . count ( ) ;
25852583 let untapped = corpus
25862584 . lines
0 commit comments