@@ -23,6 +23,11 @@ export class MineflayerPerceptionCollector {
2323 private readonly lastSneak = new Map < string , boolean > ( )
2424 private lastSelfHealth : number | null = null
2525
26+ private lastStatsAt = 0
27+ private stats : Record < string , number > = { }
28+ private losSamples = 0
29+ private losTotalMs = 0
30+
2631 constructor (
2732 private readonly deps : {
2833 logger : Logg
@@ -35,6 +40,13 @@ export class MineflayerPerceptionCollector {
3540 this . bot = bot
3641 this . lastSelfHealth = bot . bot . health
3742
43+ this . lastStatsAt = Date . now ( )
44+ this . stats = { }
45+ this . losSamples = 0
46+ this . losTotalMs = 0
47+
48+ this . deps . logger . withFields ( { maxDistance : this . deps . maxDistance } ) . log ( 'MineflayerPerceptionCollector: init' )
49+
3850 this . onBot ( 'entityMoved' , ( entity : any ) => {
3951 const now = Date . now ( )
4052 const dist = this . distanceTo ( entity )
@@ -61,6 +73,8 @@ export class MineflayerPerceptionCollector {
6173 }
6274
6375 this . deps . emitRaw ( event )
76+ this . bumpStat ( 'sighted.entity_moved' )
77+ this . maybeLogStats ( )
6478 } )
6579
6680 this . onBot ( 'entitySwingArm' , ( entity : any ) => {
@@ -83,6 +97,8 @@ export class MineflayerPerceptionCollector {
8397 }
8498
8599 this . deps . emitRaw ( event )
100+ this . bumpStat ( 'sighted.arm_swing' )
101+ this . maybeLogStats ( )
86102 } )
87103
88104 this . onBot ( 'entityUpdate' , ( entity : any ) => {
@@ -119,6 +135,8 @@ export class MineflayerPerceptionCollector {
119135 }
120136
121137 this . deps . emitRaw ( event )
138+ this . bumpStat ( 'sighted.sneak_toggle' )
139+ this . maybeLogStats ( )
122140 } )
123141
124142 this . onBot ( 'soundEffectHeard' , ( soundId : string , pos : Vec3 ) => {
@@ -167,6 +185,8 @@ export class MineflayerPerceptionCollector {
167185 }
168186
169187 this . deps . emitRaw ( event )
188+ this . bumpStat ( 'felt.damage_taken' )
189+ this . maybeLogStats ( )
170190 } )
171191
172192 // Felt: item collected (best-effort; depends on mineflayer version/events)
@@ -190,6 +210,8 @@ export class MineflayerPerceptionCollector {
190210 }
191211
192212 this . deps . emitRaw ( event )
213+ this . bumpStat ( 'felt.item_collected' )
214+ this . maybeLogStats ( )
193215 } )
194216
195217 this . onBot ( 'entityCollect' , ( collector : any , collected : any ) => {
@@ -212,17 +234,21 @@ export class MineflayerPerceptionCollector {
212234 }
213235
214236 this . deps . emitRaw ( event )
237+ this . bumpStat ( 'felt.item_collected' )
238+ this . maybeLogStats ( )
215239 } )
216240 }
217241
218242 public destroy ( ) : void {
219243 if ( ! this . bot )
220244 return
221245
246+ this . deps . logger . withFields ( { listeners : this . listeners . length } ) . log ( 'MineflayerPerceptionCollector: destroy' )
247+
222248 for ( const { event, handler } of this . listeners ) {
223249 try {
224- ( this . bot . bot as any ) . off ?.( event , handler ) ;
225- ( this . bot . bot as any ) . removeListener ?.( event , handler )
250+ ( this . bot . bot as any ) . off ?.( event , handler )
251+ ( this . bot . bot as any ) . removeListener ?.( event , handler )
226252 }
227253 catch ( err ) {
228254 this . deps . logger . withError ( err as Error ) . error ( 'MineflayerPerceptionCollector: failed to remove listener' )
@@ -236,6 +262,29 @@ export class MineflayerPerceptionCollector {
236262 this . bot = null
237263 }
238264
265+ private bumpStat ( key : string ) : void {
266+ this . stats [ key ] = ( this . stats [ key ] ?? 0 ) + 1
267+ }
268+
269+ private maybeLogStats ( ) : void {
270+ const now = Date . now ( )
271+ if ( now - this . lastStatsAt < 2000 )
272+ return
273+
274+ const losAvgMs = this . losSamples > 0 ? this . losTotalMs / this . losSamples : 0
275+
276+ this . deps . logger . withFields ( {
277+ ...this . stats ,
278+ losSamples : this . losSamples ,
279+ losAvgMs,
280+ } ) . log ( 'MineflayerPerceptionCollector: stats' )
281+
282+ this . lastStatsAt = now
283+ this . stats = { }
284+ this . losSamples = 0
285+ this . losTotalMs = 0
286+ }
287+
239288 private onBot ( event : string , handler : ( ...args : any [ ] ) => void ) : void {
240289 if ( ! this . bot )
241290 return
@@ -273,41 +322,52 @@ export class MineflayerPerceptionCollector {
273322 if ( ! this . bot )
274323 return false
275324
276- try {
277- const canSee = ( this . bot . bot as any ) . canSeeEntity
278- if ( typeof canSee === 'function' )
279- return ! ! canSee . call ( this . bot . bot , entity )
280- }
281- catch { }
325+ const startedAt = Date . now ( )
282326
283- // Fallback ray-march; intentionally simple (we'll optimize later)
284327 try {
285- const from = this . bot . bot . entity . position . offset ( 0 , this . bot . bot . entity . height * 0.9 , 0 )
286- const to = entity . position . offset ( 0 , entity . height * 0.9 , 0 )
287- const dir = to . minus ( from )
288- const total = dir . norm ( )
289- if ( total <= 0 )
290- return true
291-
292- const stepSize = 0.25
293- const steps = Math . ceil ( total / stepSize )
294- const step = dir . normalize ( ) . scaled ( stepSize )
328+ try {
329+ const canSee = ( this . bot . bot as any ) . canSeeEntity
330+ if ( typeof canSee === 'function' )
331+ return ! ! canSee . call ( this . bot . bot , entity )
332+ }
333+ catch { }
295334
296- let cur = from . clone ( )
297- for ( let i = 0 ; i < steps ; i ++ ) {
298- cur = cur . plus ( step )
299- const block = this . bot . bot . blockAt ( cur )
300- if ( ! block )
301- continue
335+ // Fallback ray-march; intentionally simple (we'll optimize later)
336+ try {
337+ const from = this . bot . bot . entity . position . offset ( 0 , this . bot . bot . entity . height * 0.9 , 0 )
338+ const to = entity . position . offset ( 0 , entity . height * 0.9 , 0 )
339+ const dir = to . minus ( from )
340+ const total = dir . norm ( )
341+ if ( total <= 0 )
342+ return true
343+
344+ const stepSize = 0.25
345+ const steps = Math . ceil ( total / stepSize )
346+ const step = dir . normalize ( ) . scaled ( stepSize )
347+
348+ let cur = from . clone ( )
349+ for ( let i = 0 ; i < steps ; i ++ ) {
350+ cur = cur . plus ( step )
351+ const block = this . bot . bot . blockAt ( cur )
352+ if ( ! block )
353+ continue
354+
355+ if ( block . boundingBox === 'block' && ! block . transparent )
356+ return false
357+ }
302358
303- if ( block . boundingBox === 'block' && ! block . transparent )
304- return false
359+ return true
360+ }
361+ catch {
362+ return false
305363 }
306-
307- return true
308364 }
309- catch {
310- return false
365+ finally {
366+ const costMs = Date . now ( ) - startedAt
367+ if ( costMs > 0 ) {
368+ this . losSamples ++
369+ this . losTotalMs += costMs
370+ }
311371 }
312372 }
313373}
0 commit comments