@@ -239,13 +239,18 @@ export class LikeGraph implements ILikeGraph {
239239 )
240240 const scoreAcc = new Map < number , number > ( )
241241 const raters = new Map < number , number > ( )
242+ // most-recent co-liker-like time (tsMin) per candidate — drives the recency
243+ // weight applied at selection so fresh posts survive the top-N cut.
244+ const lastTs = new Map < number , number > ( )
242245 const cand : number [ ] = [ ] // postInt
243246 const candRn : number [ ] = [ ] // 1-based recency rank
247+ const candTs : number [ ] = [ ] // tsMin of the co-liker like
244248 for ( const [ c , wc ] of curators ) {
245249 const arr = this . fwd [ c ]
246250 if ( ! arr ) continue
247251 cand . length = 0
248252 candRn . length = 0
253+ candTs . length = 0
249254 const seenPosts = new Set < number > ( )
250255 let rn = 0
251256 // walk recent-first (from the end); arr = [postInt, tsMin, …]
@@ -262,6 +267,7 @@ export class LikeGraph implements ILikeGraph {
262267 rn ++
263268 cand . push ( post )
264269 candRn . push ( rn )
270+ candTs . push ( ts )
265271 if ( ++ visits > budget ) break
266272 }
267273 const deg = cand . length
@@ -274,15 +280,27 @@ export class LikeGraph implements ILikeGraph {
274280 r . coraterDecay > 0 ? Math . pow ( 1 - r . coraterDecay , candRn [ t ] - 1 ) : 1
275281 scoreAcc . set ( post , ( scoreAcc . get ( post ) ?? 0 ) + norm * factor )
276282 raters . set ( post , ( raters . get ( post ) ?? 0 ) + 1 )
283+ const prev = lastTs . get ( post )
284+ if ( prev === undefined || candTs [ t ] > prev ) lastTs . set ( post , candTs [ t ] )
277285 }
278286 if ( visits > budget ) break
279287 }
280288
281- // 5. eligibility + num_paths^smoothing, take top maxCandidates
289+ // 5. eligibility + num_paths^smoothing, weighted by co-liker-like recency so
290+ // fresh (recently-liked) posts survive the top-maxCandidates cut instead of
291+ // being dropped before finalize's post-age decay can rank them.
292+ const recencyOn = r . candidateRecencyHalfLifeHours > 0
293+ const nowMin = toTsMin ( Date . now ( ) )
294+ const halfLifeMin = r . candidateRecencyHalfLifeHours * 60
282295 const scored : { post : number ; raw : number } [ ] = [ ]
283296 for ( const [ post , acc ] of scoreAcc ) {
284297 if ( ( raters . get ( post ) ?? 0 ) < r . minEligibleRaters ) continue
285- scored . push ( { post, raw : Math . pow ( acc , r . smoothing ) } )
298+ let raw = Math . pow ( acc , r . smoothing )
299+ if ( recencyOn ) {
300+ const ageMin = Math . max ( 0 , nowMin - ( lastTs . get ( post ) ?? nowMin ) )
301+ raw *= Math . pow ( 0.5 , ageMin / halfLifeMin )
302+ }
303+ scored . push ( { post, raw } )
286304 }
287305 scored . sort ( ( a , b ) => b . raw - a . raw )
288306 const top = scored . slice ( 0 , candidateLimit )
0 commit comments