@@ -1495,7 +1495,13 @@ pub fn phase_judge_need(state: &RuntimeState) -> PhaseJudgeDue {
14951495 let open = phase_judge_open ( state) ;
14961496 if spoke && !open. is_empty ( ) {
14971497 PhaseJudgeDue :: Spoken
1498- } else if typed && open. contains ( & "coding" ) {
1498+ } else if typed
1499+ && ( open. contains ( & "coding" )
1500+ || ( !state. behavioral_round_started
1501+ && !open. is_empty ( )
1502+ && typed_comments ( & state. language , & state. code )
1503+ != typed_comments ( & state. judged_language , & state. judged_code ) ) )
1504+ {
14991505 PhaseJudgeDue :: EditorOnly
15001506 } else {
15011507 PhaseJudgeDue :: No
@@ -1570,19 +1576,34 @@ fn quote_words(text: &str) -> String {
15701576 . join ( " " )
15711577}
15721578
1573- /// The comments in `code`: what follows a line's leading comment marker. Read
1574- /// apart from the code, so a step is quoted from an explanation, not from the
1575- /// code that is Coding's.
1576- fn typed_comments ( code : & str ) -> String {
1577- code. lines ( )
1578- . filter_map ( |line| {
1579- let line = line. trim_start ( ) ;
1580- [ "//" , "#" , "/*" , "*" , "--" ]
1581- . into_iter ( )
1582- . find_map ( |marker| line. strip_prefix ( marker) )
1583- } )
1584- . collect :: < Vec < _ > > ( )
1585- . join ( "\n " )
1579+ /// The comments in `code`, as `language` writes them: whole-line `#` comments
1580+ /// in Python, and `//` lines and `/* */` blocks in the others. Read apart from
1581+ /// the code, so a step is quoted from an explanation, not from the code that
1582+ /// is Coding's, and only from lines that cannot be code: a C line beginning
1583+ /// with `*` is a dereference outside a block. A block's leading `*` needs no
1584+ /// stripping, since a quote is matched on its words.
1585+ fn typed_comments ( language : & str , code : & str ) -> String {
1586+ let mut comments = Vec :: new ( ) ;
1587+ let mut in_block = false ;
1588+ for line in code. lines ( ) . map ( str:: trim_start) {
1589+ let text = if language == "python" {
1590+ line. strip_prefix ( '#' )
1591+ } else if in_block {
1592+ Some ( line)
1593+ } else if let Some ( text) = line. strip_prefix ( "/*" ) {
1594+ in_block = true ;
1595+ Some ( text)
1596+ } else {
1597+ line. strip_prefix ( "//" )
1598+ } ;
1599+ let Some ( mut text) = text else { continue } ;
1600+ if in_block && let Some ( ( head, _) ) = text. split_once ( "*/" ) {
1601+ in_block = false ;
1602+ text = head;
1603+ }
1604+ comments. push ( text) ;
1605+ }
1606+ comments. join ( "\n " )
15861607}
15871608
15881609/// One speaker's turn, as `speaker_turns` reads the transcript.
@@ -1634,8 +1655,9 @@ fn speaker_turns(state: &RuntimeState) -> Vec<Turn> {
16341655 turns
16351656}
16361657
1637- /// Whether `quote` is the candidate's own: words from one recognized candidate
1638- /// turn, or for Coding, from what they typed into the editor.
1658+ /// Where `quote` is the candidate's own, if it is: words from one recognized
1659+ /// candidate turn, or what they typed into the editor, its code for Coding and
1660+ /// a comment for another coding-round step.
16391661///
16401662/// Proof of who said it, not that the step was done; that judgment stays with
16411663/// the model and its prompt. What can be checked mechanically is checked: the
@@ -1648,7 +1670,7 @@ fn quote_is_grounded(
16481670 turns : & [ Turn ] ,
16491671 phase : FrameworkPhase ,
16501672 quote : & str ,
1651- ) -> bool {
1673+ ) -> Option < EvidenceSource > {
16521674 let words = quote_words ( quote) ;
16531675
16541676 // A spoken trace is a sentence like any other answer, though Test is about
@@ -1662,32 +1684,30 @@ fn quote_is_grounded(
16621684 MIN_SPOKEN_QUOTE_WORDS
16631685 } ;
16641686 if words. split ( ' ' ) . count ( ) < least {
1665- return false ;
1687+ return None ;
16661688 }
16671689 let needle = format ! ( " {words} " ) ;
1690+ let typed = |text : & str | format ! ( " {} " , quote_words( text) ) . contains ( & needle) ;
1691+ let editor = Some ( EvidenceSource :: EditorSnapshot ) ;
16681692 if phase == FrameworkPhase :: Coding {
1669- let typed = |code : & str | format ! ( " {} " , quote_words( code) ) . contains ( & needle) ;
1670- return typed ( & state. code ) && !typed ( starter ( state, & state. language ) ) ;
1693+ return editor. filter ( |_| typed ( & state. code ) && !typed ( starter ( state, & state. language ) ) ) ;
16711694 }
16721695
16731696 // A candidate whose speech was not recognized is asked to type their
16741697 // explanation as a comment, and at an editor without the evidence tool
16751698 // nobody else can record it from there.
1676- let commented =
1677- |code : & str | format ! ( " {} " , quote_words( & typed_comments( code) ) ) . contains ( & needle) ;
1699+ let commented = |code : & str | typed ( & typed_comments ( & state. language , code) ) ;
16781700 if !phase. is_star ( )
16791701 && !state. interview_mode . is_whiteboard ( )
16801702 && commented ( & state. code )
16811703 && !commented ( starter ( state, & state. language ) )
16821704 {
1683- return true ;
1705+ return editor ;
16841706 }
16851707 let mut said = turns. iter ( ) . filter ( |turn| turn. words . contains ( & needle) ) ;
1686- let Some ( first) = said. clone ( ) . next ( ) else {
1687- return false ;
1688- } ;
1708+ let first = said. clone ( ) . next ( ) ?;
16891709 if !said. any ( |turn| turn. speaker == CANDIDATE_SPEAKER ) {
1690- return false ;
1710+ return None ;
16911711 }
16921712
16931713 // A trace the interviewer walked first, or a story they told back before
@@ -1698,7 +1718,7 @@ fn quote_is_grounded(
16981718 FrameworkPhase :: Algorithm | FrameworkPhase :: Optimizations | FrameworkPhase :: Test
16991719 ) )
17001720 && first. speaker == INTERVIEWER_SPEAKER ;
1701- !echo
1721+ ( !echo) . then_some ( EvidenceSource :: CandidateSpeech )
17021722}
17031723
17041724/// Records what the judge found, through the same gate the interviewer's calls
@@ -1778,9 +1798,9 @@ pub fn apply_phase_judgment(state: &mut RuntimeState, text: &str) -> Vec<Framewo
17781798 continue ;
17791799 }
17801800 let turns = turns. get_or_insert_with ( || speaker_turns ( state) ) ;
1781- if ! quote_is_grounded ( state, turns, phase, quote) {
1801+ let Some ( source ) = quote_is_grounded ( state, turns, phase, quote) else {
17821802 continue ;
1783- }
1803+ } ;
17841804 let summary = step
17851805 . get ( "summary" )
17861806 . and_then ( serde_json:: Value :: as_str)
@@ -1797,16 +1817,11 @@ pub fn apply_phase_judgment(state: &mut RuntimeState, text: &str) -> Vec<Framewo
17971817 ) ,
17981818 None => format ! ( "Quote: {quoted}" ) ,
17991819 } ;
1800- let source = if phase == FrameworkPhase :: Coding {
1801- "editor_snapshot"
1802- } else {
1803- "candidate_speech"
1804- } ;
18051820 if let Ok ( ( evidence, RecordOutcome :: Added ) ) = record_evidence (
18061821 state,
18071822 & serde_json:: json!( {
18081823 "phase" : id,
1809- "source" : source,
1824+ "source" : evidence_source_id ( source) ,
18101825 "kind" : "observed" ,
18111826 "confidence" : 70 ,
18121827 "summary" : summary,
0 commit comments