Skip to content

Commit 8beac18

Browse files
shinohara-rinnekomeowww
authored andcommitted
feat(minecraft): semantic percetion, belief and state pulling
1 parent 0eca160 commit 8beac18

File tree

16 files changed

+658
-29
lines changed

16 files changed

+658
-29
lines changed

services/minecraft/src/cognitive/conscious/brain.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,16 @@ export class Brain {
5656
public init(bot: MineflayerWithAgents): void {
5757
this.log('INFO', 'Brain: Initializing...')
5858

59-
// Unified Perception Signal Handler
60-
// this.deps.eventManager.on<PerceptionSignal>('perception', async (event) => {
61-
// this.log('INFO', `Brain: Received perception signal: ${event.payload.type} - ${event.payload.description}`)
62-
// await this.enqueueEvent(bot, event)
63-
// })
59+
// Perception Signal Handler - Only process chat messages for now
60+
this.deps.eventManager.on<PerceptionSignal>('perception', async (event) => {
61+
const signal = event.payload
62+
// Only handle chat messages in the deliberative layer
63+
if (signal.type !== 'chat_message')
64+
return
65+
66+
this.log('INFO', `Brain: Received chat: ${signal.description}`)
67+
await this.enqueueEvent(bot, event)
68+
})
6469

6570
// Listen to Task Execution Events (Action Feedback)
6671
this.deps.taskExecutor.on('action:completed', async ({ action, result }) => {

services/minecraft/src/cognitive/container.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,12 @@ export function createAgentContainer(options: {
117117
}),
118118

119119
// Reflex Manager (Reactive Layer)
120-
reflexManager: asFunction(({ eventBus, logger }) =>
121-
new ReflexManager({ eventBus, logger }),
120+
reflexManager: asFunction(({ eventBus, perceptionPipeline, logger }) =>
121+
new ReflexManager({
122+
eventBus,
123+
perception: perceptionPipeline.getPerceptionAPI(),
124+
logger,
125+
}),
122126
).singleton(),
123127
})
124128

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import type { Logg } from '@guiiai/logg'
2+
3+
import type { Belief, EntityState, EntityView } from '../world/types'
4+
5+
import { BeliefEngine } from '../semantics/belief-engine'
6+
import { teabagPattern } from '../semantics/patterns/teabag'
7+
import { EntityStore } from '../world/entity-store'
8+
import { TemporalBuffer } from '../world/temporal-buffer'
9+
10+
/**
11+
* Unified perception API for upper layers
12+
* Provides entity queries and belief computations
13+
*/
14+
export class PerceptionAPI {
15+
private store: EntityStore
16+
private buffer: TemporalBuffer
17+
private engine: BeliefEngine
18+
19+
constructor(
20+
private readonly deps: {
21+
logger: Logg
22+
},
23+
) {
24+
this.deps.logger.log('PerceptionAPI: initialized')
25+
this.store = new EntityStore()
26+
this.buffer = new TemporalBuffer(5000) // 5s history
27+
this.engine = new BeliefEngine()
28+
29+
// Register default patterns
30+
this.engine.register(teabagPattern)
31+
}
32+
33+
// ============ Entity Updates (from Mineflayer) ============
34+
35+
/**
36+
* Update an entity's state (called by perception collector)
37+
*/
38+
updateEntity(id: string, partial: Partial<EntityState>): void {
39+
const changes = this.store.update(id, partial)
40+
this.buffer.recordAll(changes)
41+
}
42+
43+
/**
44+
* Remove an entity
45+
*/
46+
removeEntity(id: string): void {
47+
this.store.remove(id)
48+
this.buffer.clearEntity(id)
49+
}
50+
51+
/**
52+
* Update self position (for distance calculations)
53+
*/
54+
updateSelfPosition(x: number, y: number, z: number): void {
55+
this.store.updateSelfPosition({ x, y, z } as any)
56+
}
57+
58+
// ============ Entity Queries ============
59+
60+
/**
61+
* Get all player entities with computed beliefs
62+
*/
63+
getPlayers(): EntityView[] {
64+
return this.store.getPlayers().map(e => this.buildEntityView(e))
65+
}
66+
67+
/**
68+
* Get a specific entity by ID
69+
*/
70+
getEntity(id: string): EntityView | null {
71+
const state = this.store.get(id)
72+
if (!state)
73+
return null
74+
return this.buildEntityView(state)
75+
}
76+
77+
// ============ Belief Queries ============
78+
79+
/**
80+
* Find entities with high confidence of a pattern
81+
*/
82+
entitiesWithBelief(pattern: string, minConfidence: number = 0.5): EntityView[] {
83+
return this.getPlayers().filter(e => (e.beliefs[pattern]?.confidence ?? 0) >= minConfidence)
84+
}
85+
86+
/**
87+
* Get the top belief for an entity
88+
*/
89+
getTopBelief(entityId: string): { pattern: string, belief: Belief } | null {
90+
const entity = this.getEntity(entityId)
91+
if (!entity)
92+
return null
93+
94+
let top: { pattern: string, belief: Belief } | null = null
95+
for (const [pattern, belief] of Object.entries(entity.beliefs)) {
96+
if (!top || belief.confidence > top.belief.confidence) {
97+
top = { pattern, belief }
98+
}
99+
}
100+
return top
101+
}
102+
103+
// ============ Pattern Management ============
104+
105+
/**
106+
* Register a custom pattern
107+
*/
108+
registerPattern(pattern: Parameters<BeliefEngine['register']>[0]): void {
109+
this.engine.register(pattern)
110+
}
111+
112+
// ============ Maintenance ============
113+
114+
/**
115+
* Prune old history entries
116+
*/
117+
prune(): void {
118+
this.buffer.prune()
119+
}
120+
121+
/**
122+
* Clear all state
123+
*/
124+
clear(): void {
125+
this.store.clear()
126+
this.buffer.clear()
127+
}
128+
129+
// ============ Internal ============
130+
131+
private buildEntityView(state: EntityState): EntityView {
132+
const beliefs = this.engine.computeBeliefs(
133+
state.id,
134+
id => this.store.get(id),
135+
(id, since) => this.buffer.query(id, since),
136+
this.store.getSelfPosition(),
137+
)
138+
139+
return {
140+
id: state.id,
141+
name: state.name ?? state.id,
142+
type: state.type,
143+
state,
144+
beliefs,
145+
distanceToSelf: this.store.distanceToSelf(state.id) ?? Infinity,
146+
}
147+
}
148+
}

services/minecraft/src/cognitive/perception/pipeline.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import type { PerceptionStage } from './types/stage'
1111
import { DebugService } from '../../debug'
1212
import { createPerceptionFrameFromRawEvent } from './frame'
1313
import { MineflayerPerceptionCollector } from './mineflayer-perception-collector'
14+
import { PerceptionAPI } from './perception-api'
1415
import { SaliencyDetector } from './saliency-detector'
1516

1617
export class PerceptionPipeline {
1718
private readonly detector: SaliencyDetector
19+
private readonly perception: PerceptionAPI
1820
private collector: MineflayerPerceptionCollector | null = null
1921
private initialized = false
2022

@@ -31,6 +33,8 @@ export class PerceptionPipeline {
3133
logger: Logg
3234
},
3335
) {
36+
this.perception = new PerceptionAPI({ logger: this.deps.logger })
37+
3438
this.detector = new SaliencyDetector({
3539
logger: this.deps.logger,
3640
onAttention: (signal) => {
@@ -44,6 +48,31 @@ export class PerceptionPipeline {
4448
})
4549

4650
this.stages = [
51+
{
52+
name: 'entity_update',
53+
handle: (frame) => {
54+
if (frame.kind !== 'world_raw')
55+
return frame
56+
57+
const raw = frame.raw as RawPerceptionEvent
58+
59+
// Feed entity updates to PerceptionAPI
60+
if ('entityId' in raw && 'entityType' in raw) {
61+
const entityRaw = raw as RawPerceptionEvent & { entityId: string, entityType: string, displayName?: string, pos?: { x: number, y: number, z: number } }
62+
if (entityRaw.entityType === 'player') {
63+
this.perception.updateEntity(entityRaw.entityId, {
64+
id: entityRaw.entityId,
65+
type: 'player',
66+
name: entityRaw.displayName,
67+
position: entityRaw.pos as any,
68+
isSneaking: 'sneaking' in entityRaw ? (entityRaw as any).sneaking : undefined,
69+
})
70+
}
71+
}
72+
73+
return frame
74+
},
75+
},
4776
{
4877
name: 'attention',
4978
handle: (frame) => {
@@ -152,6 +181,13 @@ export class PerceptionPipeline {
152181
this.initialized = false
153182
}
154183

184+
/**
185+
* Get the PerceptionAPI for querying entity beliefs
186+
*/
187+
public getPerceptionAPI(): PerceptionAPI {
188+
return this.perception
189+
}
190+
155191
public ingest(frame: PerceptionFrame): void {
156192
if (!this.initialized)
157193
return

services/minecraft/src/cognitive/reflex/behaviors/teabag.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,24 @@ export const teabagBehavior: ReflexBehavior = {
55
modes: ['social', 'idle'],
66
cooldownMs: 5000,
77

8-
when: (ctx) => {
9-
// Check if we recently received a teabag signal
10-
// Check if we recently received a teabag signal
11-
if (ctx.social.lastGesture === 'teabag') {
12-
const now = Date.now()
13-
const signalAge = now - (ctx.social.lastGestureAt || 0)
14-
15-
// Only respond if signal is fresh (< 2s)
16-
return signalAge < 2000
17-
}
18-
return false
8+
when: (_ctx, api) => {
9+
// Check if any player is teabagging with high confidence
10+
if (!api?.perception)
11+
return false
12+
const teabaggers = api.perception.entitiesWithBelief('teabag', 0.6)
13+
return teabaggers.length > 0
1914
},
2015

21-
score: () => {
22-
// Higher priority than LookAt (50)
23-
return 60
16+
score: (_ctx, api) => {
17+
// Higher priority than LookAt (50), scaled by confidence
18+
if (!api?.perception)
19+
return 0
20+
const teabaggers = api.perception.entitiesWithBelief('teabag', 0.6)
21+
if (teabaggers.length === 0)
22+
return 0
23+
// Use highest confidence as score boost
24+
const maxConfidence = Math.max(...teabaggers.map(e => e.beliefs.teabag?.confidence ?? 0))
25+
return 60 + (maxConfidence * 20)
2426
},
2527

2628
run: async ({ bot }) => {

services/minecraft/src/cognitive/reflex/reflex-manager.test.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,14 @@ describe('reflexManager', () => {
4343
} as any
4444

4545
const logger = makeLogger()
46-
const reflex = new ReflexManager({ eventBus, logger }) // Now accepts eventBus
46+
const perception = {
47+
getPlayers: vi.fn(() => []),
48+
getEntity: vi.fn(() => null),
49+
entitiesWithBelief: vi.fn(() => []),
50+
updateEntity: vi.fn(),
51+
updateSelfPosition: vi.fn(),
52+
} as any
53+
const reflex = new ReflexManager({ eventBus, perception, logger })
4754

4855
const bot = makeBot()
4956
reflex.init(bot)

services/minecraft/src/cognitive/reflex/reflex-manager.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Logg } from '@guiiai/logg'
22

33
import type { EventBus, TracedEvent } from '../os'
4+
import type { PerceptionAPI } from '../perception/perception-api'
45
import type { PerceptionSignal } from '../perception/types/signals'
56
import type { MineflayerWithAgents } from '../types'
67
import type { ReflexContextState } from './context'
@@ -19,6 +20,7 @@ export class ReflexManager {
1920
constructor(
2021
private readonly deps: {
2122
eventBus: EventBus
23+
perception: PerceptionAPI
2224
logger: Logg
2325
},
2426
) {
@@ -86,7 +88,7 @@ export class ReflexManager {
8688
// For greeting behavior compatibility, we might need to map specific signals to social state.
8789

8890
// Trigger behavior selection
89-
this.runtime.tick(bot, 0)
91+
this.runtime.tick(bot, 0, this.deps.perception)
9092

9193
// Emit reflex state for observability
9294
DebugService.getInstance().emitReflexState({

services/minecraft/src/cognitive/reflex/runtime.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Logg } from '@guiiai/logg'
22

3+
import type { PerceptionAPI } from '../perception/perception-api'
34
import type { MineflayerWithAgents } from '../types'
45
import type { ReflexModeId } from './modes'
56
import type { ReflexBehavior } from './types/behavior'
@@ -38,7 +39,7 @@ export class ReflexRuntime {
3839
this.behaviors.push(behavior)
3940
}
4041

41-
public tick(bot: MineflayerWithAgents, deltaMs: number): string | null {
42+
public tick(bot: MineflayerWithAgents, deltaMs: number, perception: PerceptionAPI): string | null {
4243
const now = Date.now()
4344

4445
this.context.updateNow(now)
@@ -73,16 +74,17 @@ export class ReflexRuntime {
7374
this.activeBehaviorUntil = null
7475

7576
const ctx = this.context.getSnapshot()
77+
const api = { bot, context: this.context, perception }
7678

7779
let best: { behavior: ReflexBehavior, score: number } | null = null
7880
for (const behavior of this.behaviors) {
7981
if (!behavior.modes.includes(this.mode))
8082
continue
8183

82-
if (!behavior.when(ctx))
84+
if (!behavior.when(ctx, api))
8385
continue
8486

85-
const score = behavior.score(ctx)
87+
const score = behavior.score(ctx, api)
8688
if (score <= 0)
8789
continue
8890

@@ -102,7 +104,7 @@ export class ReflexRuntime {
102104
this.runHistory.set(best.behavior.id, { lastRunAt: now })
103105

104106
try {
105-
const maybePromise = best.behavior.run({ bot, context: this.context })
107+
const maybePromise = best.behavior.run(api)
106108
if (maybePromise && typeof (maybePromise as any).then === 'function') {
107109
this.activeBehaviorUntil = now + Math.max(deltaMs, 50)
108110
void (maybePromise as Promise<void>).finally(() => {

0 commit comments

Comments
 (0)