@@ -1999,7 +1999,18 @@ export class ClipsService {
19991999 const tail = Math . round ( tickRate * 3 ) ;
20002000 const CLUSTER_GAP_SECS = 10 ;
20012001 const clusterGapTicks = Math . round ( tickRate * CLUSTER_GAP_SECS ) ;
2002- const clamp = ( t : number ) => Math . max ( 0 , Math . min ( t , totalTicks || t ) ) ;
2002+ // Demo's total_ticks is the literal last observed tick — i.e. gameover.
2003+ // Playing a segment up to that tick triggers the engine's gameover
2004+ // transition and auto-closes the demo, breaking every subsequent job
2005+ // in the same batch. Hold segments a few seconds short of demo end.
2006+ const SAFETY_SECS = 3 ;
2007+ const safetyTicks = Math . round ( tickRate * SAFETY_SECS ) ;
2008+ const maxClipEnd = totalTicks > 0 ? totalTicks - safetyTicks : 0 ;
2009+ const clamp = ( t : number ) =>
2010+ Math . max ( 0 , maxClipEnd > 0 ? Math . min ( t , maxClipEnd ) : t ) ;
2011+ const MIN_SEGMENT_TICKS = Math . round ( tickRate * 1.5 ) ;
2012+ const segmentIsViable = ( s : { start_tick : number ; end_tick : number } ) =>
2013+ s . end_tick - s . start_tick >= MIN_SEGMENT_TICKS ;
20032014
20042015 const clusterKills = (
20052016 ks : Array < { tick : number } > ,
@@ -2059,40 +2070,30 @@ export class ClipsService {
20592070 segments . push ( ...clusterKills ( inRound ) ) ;
20602071 }
20612072 } else if ( preset === "best_round" ) {
2062- let best : {
2063- round : number ;
2073+ // Build all viable candidates so that if the kill-leader round
2074+ // happens to be the final round (kills land past the safety
2075+ // margin), we naturally fall through to the next-best round
2076+ // instead of producing an empty/unrenderable clip.
2077+ const candidates : Array < {
20642078 count : number ;
20652079 span : number ;
2066- start : number ;
2067- end : number ;
2068- } | null = null ;
2080+ segs : Array < { start_tick : number ; end_tick : number } > ;
2081+ } > = [ ] ;
20692082 for ( const r of rounds ) {
20702083 const inRound = myKills
20712084 . filter ( ( k ) => k . tick >= r . start_tick && k . tick <= r . end_tick )
20722085 . sort ( ( a , b ) => a . tick - b . tick ) ;
20732086 const count = inRound . length ;
20742087 if ( count === 0 ) continue ;
2088+ const segs = clusterKills ( inRound ) . filter ( segmentIsViable ) ;
2089+ if ( segs . length === 0 ) continue ;
20752090 const span = count >= 2 ? inRound [ count - 1 ] . tick - inRound [ 0 ] . tick : 0 ;
2076- if (
2077- ! best ||
2078- count > best . count ||
2079- ( count === best . count && span < best . span )
2080- ) {
2081- best = {
2082- round : r . round ,
2083- count,
2084- span,
2085- start : r . start_tick ,
2086- end : r . end_tick ,
2087- } ;
2088- }
2091+ candidates . push ( { count, span, segs } ) ;
20892092 }
2090- if ( best && best . count > 0 ) {
2091- stats . bestRoundKills = best . count ;
2092- const inRound = myKills
2093- . filter ( ( k ) => k . tick >= best ! . start && k . tick <= best ! . end )
2094- . sort ( ( a , b ) => a . tick - b . tick ) ;
2095- segments = clusterKills ( inRound ) ;
2093+ candidates . sort ( ( a , b ) => b . count - a . count || a . span - b . span ) ;
2094+ if ( candidates . length > 0 ) {
2095+ stats . bestRoundKills = candidates [ 0 ] . count ;
2096+ segments = candidates [ 0 ] . segs ;
20962097 }
20972098 } else if ( preset === "recap" ) {
20982099 segments = clusterKills ( myKills ) ;
@@ -2110,6 +2111,8 @@ export class ClipsService {
21102111 stats . usedFallback = true ;
21112112 }
21122113
2114+ segments = segments . filter ( segmentIsViable ) ;
2115+
21132116 if ( segments . length === 0 ) {
21142117 throw new Error (
21152118 `no clip-worthy moments for ${ targetSteamId } in this match — preset "${ preset } " produced zero segments and the player has no kills to fall back on` ,
0 commit comments