@@ -37,7 +37,7 @@ public void C2sRoundTrip(string c2sPath)
3737 AssertNotesEqual ( chart . Notes , reparsed . Notes ) ;
3838 }
3939
40- private static void AssertNotesEqual ( IReadOnlyList < ChuNote > expected_ , IReadOnlyList < ChuNote > actual_ )
40+ private static void AssertNotesEqual ( IReadOnlyList < ChuNote > expected_ , IReadOnlyList < ChuNote > actual_ , bool allowExDiff = false )
4141 {
4242 const string EOF = "<EOF>" ;
4343 List < ChuNote > expected = expected_ . ToList ( ) ;
@@ -49,14 +49,14 @@ private static void AssertNotesEqual(IReadOnlyList<ChuNote> expected_, IReadOnly
4949 if ( i >= expected . Count || i >= actual . Count ) result = false ;
5050 else
5151 {
52- result = CompareNote ( expected [ i ] , actual [ i ] ) ;
52+ result = CompareNote ( expected [ i ] , actual [ i ] , allowExDiff ) ;
5353 if ( ! result )
5454 {
5555 // 尝试同一时刻的其他行有无相同的,如果有,交换之
5656 var j = i + 1 ;
5757 while ( j < expected . Count && expected [ j ] . Time == actual [ i ] . Time )
5858 {
59- if ( CompareNote ( expected [ j ] , actual [ i ] ) )
59+ if ( CompareNote ( expected [ j ] , actual [ i ] , allowExDiff ) )
6060 {
6161 ( expected [ j ] , expected [ i ] ) = ( expected [ i ] , expected [ j ] ) ;
6262 result = true ;
@@ -79,23 +79,43 @@ private static void AssertNotesEqual(IReadOnlyList<ChuNote> expected_, IReadOnly
7979 /// <summary>
8080 /// 比较两个音符是否实质等同;时间与时长等字段可命中宽容规则(见测试类内常量与分支注释)。
8181 /// </summary>
82- public static bool CompareNote ( ChuNote expected , ChuNote actual )
82+ public static bool CompareNote ( ChuNote expected , ChuNote actual , bool allowExDiff = false )
8383 {
84- if ( expected . Type != actual . Type ) return false ;
84+ if ( ! TypesEquivalent ( expected . Type , actual . Type , allowExDiff ) ) return false ;
8585 if ( ! TimesEquivalent ( expected . Time , actual . Time ) ) return false ;
8686 if ( ! DurationsEquivalent ( expected , actual ) ) return false ;
8787 if ( expected . Cell != actual . Cell || expected . Width != actual . Width ) return false ;
8888 if ( expected . EndCell != actual . EndCell || expected . EndWidth != actual . EndWidth ) return false ;
8989 if ( Math . Abs ( expected . Height - actual . Height ) > 0.05m || Math . Abs ( expected . EndHeight - actual . EndHeight ) > 0.05m ) return false ;
9090 if ( expected . CrushInterval != actual . CrushInterval ) return false ;
9191 if ( ! TagsEquivalent ( expected , actual ) ) return false ;
92- if ( expected . TargetNote != actual . TargetNote ) return false ;
92+ if ( ! TypesEquivalent ( expected . TargetNote , actual . TargetNote , allowExDiff ) ) return false ;
9393 return true ;
9494 }
9595
9696 /// <summary>规则 (a):time 相差 ≤ 1/768 视为相等。</summary>
9797 private static bool TimesEquivalent ( Rational a , Rational b ) => ( a - b ) . Abs ( ) <= Tol768 ;
9898
99+ /// <summary>
100+ /// 类型比较。<paramref name="allowExDiff"/> 为 true 时,HLD/HXD、SLD/SXD、SLC/SXC 之间允许互相匹配
101+ /// (即忽略 Ex 标志位差异);否则要求严格相等。
102+ /// </summary>
103+ private static bool TypesEquivalent ( string e , string a , bool allowExDiff )
104+ {
105+ if ( e == a ) return true ;
106+ if ( ! allowExDiff ) return false ;
107+ return StripExFlag ( e ) == StripExFlag ( a ) ;
108+
109+ static string StripExFlag ( string t ) => t switch
110+ {
111+ "HXD" => "HLD" ,
112+ "SXD" => "SLD" ,
113+ "SXC" => "SLC" ,
114+ "AHX" => "AHD" ,
115+ _ => t ,
116+ } ;
117+ }
118+
99119 /// <summary>
100120 /// 规则 (b):|Δduration| ≤ 1/768,或(|Δduration| ≤ 1/384 且 |ΔendTime| ≤ 1/768)时视为 duration 语义相等。
101121 /// </summary>
@@ -158,6 +178,6 @@ public void C2sToUgcViaGenerator(string c2sPath)
158178 // 再把转出来的ugc,parse回去,比较是否和一开始的c2s等价
159179 var ( ugcReparsed , _) = new UgcParser ( ) . Parse ( ugcText ) ;
160180 Assert . NotEmpty ( ugcReparsed . Notes ) ;
161- AssertNotesEqual ( c2s . Notes , ugcReparsed . Notes . Where ( n => n . Type != "CLICK" ) . ToList ( ) ) ;
181+ AssertNotesEqual ( c2s . Notes , ugcReparsed . Notes . Where ( n => n . Type != "CLICK" ) . ToList ( ) , allowExDiff : true ) ;
162182 }
163183}
0 commit comments