first GodDaemons group commit

This commit is contained in:
STEINNI
2026-06-12 17:05:35 +00:00
commit 327efdbe9a
21 changed files with 1721 additions and 0 deletions
+274
View File
@@ -0,0 +1,274 @@
import { AccesRights } from '../accesRights.js'
import { AgentStore } from './agentStore.js'
import { CollisionRegistry } from './collisionRegistry.js'
import {
compareWorldlinePair,
positionAt,
needsPrismRefresh,
advanceAgentSegment,
} from './actions/arena/worldline.js'
export class gpsServer {
constructor(configHelper, allRediscnx, debug) {
this.configHelper = configHelper
this.gpsConfig = configHelper.config
this.allRediscnx = allRediscnx
this.debug = debug
this.accessRights = new AccesRights(this.gpsConfig, debug)
this.agents = new Map()
this.registry = new CollisionRegistry()
this.arenaCnx = null
this.systemCnx = null
this.agentStore = null
}
getGpsSettings() {
const gps = this.gpsConfig.gps ?? {}
return({
nearMissDistance: gps.nearMissDistance ?? 1,
prismTimeHeight: gps.prismTimeHeight ?? 60,
collisionTickMs: gps.collisionTickMs ?? 100,
prismRefreshLeadSeconds: gps.prismRefreshLeadSeconds ?? 1,
})
}
now() {
return(Date.now() / 1000)
}
wireSystemConnexion(cnx) {
cnx.gpsSrv = this
cnx.accessRights = this.accessRights
cnx.reloadAccessRights = () => this.reloadAccessRights()
cnx.getAccessRights = () => this.getAccessRights()
if(!this.systemCnx || cnx.redisConfig.role === 'primary') {
this.systemCnx = cnx
this.initAgentStore()
}
}
initAgentStore() {
const storage = this.gpsConfig.gps?.storage
if(storage && this.systemCnx) {
this.agentStore = new AgentStore(this.systemCnx, storage, this.debug)
}
}
wireArenaConnexion(cnx) {
cnx.gpsSrv = this
this.arenaCnx = cnx
}
getAgent(agentId) {
return(this.agents.get(agentId) ?? null)
}
getAllAgents() {
return([...this.agents.values()])
}
buildAgentSnapshot(agent, at) {
return({
id: agent.id,
position: positionAt(agent, at),
vector: { ...agent.vector },
since: agent.since,
generation: agent.generation ?? 0,
at: new Date(at * 1000).toISOString(),
})
}
getAgentPosition(agentId, at = null) {
const agent = this.agents.get(agentId)
if(!agent) return(null)
const t = at ?? this.now()
return(this.buildAgentSnapshot(agent, t))
}
isPositionInPrism(position, prism) {
return(
position.x >= prism.xMin && position.x <= prism.xMax &&
position.y >= prism.yMin && position.y <= prism.yMax &&
position.z >= prism.zMin && position.z <= prism.zMax
)
}
isValidPrism(prism) {
if(!prism || typeof(prism) !== 'object') return(false)
const axes = ['xMin', 'xMax', 'yMin', 'yMax', 'zMin', 'zMax']
for(const key of axes) {
if(typeof(prism[key]) !== 'number' || Number.isNaN(prism[key])) return(false)
}
return(
prism.xMin <= prism.xMax &&
prism.yMin <= prism.yMax &&
prism.zMin <= prism.zMax
)
}
getAgentsInPrism(prism, at = null) {
const t = at ?? this.now()
const agents = []
for(const agent of this.agents.values()) {
const position = positionAt(agent, t)
if(this.isPositionInPrism(position, prism)) {
agents.push(this.buildAgentSnapshot(agent, t))
}
}
return(agents)
}
upsertAgent(agentId, newVector, newPosition = null) {
const now = this.now()
let agent = this.agents.get(agentId)
if(!agent) {
agent = {
id: agentId,
position: newPosition ?? { x: 0, y: 0, z: 0 },
vector: { ...newVector },
since: now,
generation: 1,
}
this.agents.set(agentId, agent)
this.agentStore?.exportSegment(agent, 'change')
return(agent)
}
agent.position = positionAt(agent, now)
agent.vector = { ...newVector }
agent.since = now
agent.generation = (agent.generation ?? 0) + 1
if(newPosition) agent.position = { ...newPosition }
this.agentStore?.exportSegment(agent, 'change')
return(agent)
}
ensurePrismValid(agentId, refreshed = new Set()) {
return(this.refreshAgentPrism(agentId, refreshed))
}
refreshAgentPrism(agentId, refreshed = new Set()) {
if(refreshed.has(agentId)) return(false)
const agent = this.agents.get(agentId)
if(!agent) return(false)
const { prismTimeHeight, prismRefreshLeadSeconds } = this.getGpsSettings()
const now = this.now()
if(!needsPrismRefresh(agent, now, prismTimeHeight, prismRefreshLeadSeconds)) return(false)
refreshed.add(agentId)
this.registry.purge(agentId)
advanceAgentSegment(agent, now)
const hits = this.scanAgentPairs(agentId, refreshed)
for(const hit of hits) this.registry.add(hit)
this.agentStore?.exportSegment(agent, 'refresh')
if(this.debug) console.log(`[GPS] Prism refresh: ${agentId}`)
return(true)
}
scanAgentPairs(changedAgentId, refreshed = new Set()) {
this.ensurePrismValid(changedAgentId, refreshed)
const changed = this.agents.get(changedAgentId)
if(!changed) return([])
const { nearMissDistance, prismTimeHeight } = this.getGpsSettings()
const now = this.now()
const hits = []
for(const [otherId, other] of this.agents) {
if(otherId === changedAgentId) continue
this.ensurePrismValid(otherId, refreshed)
const hit = compareWorldlinePair(
changed,
other,
prismTimeHeight,
nearMissDistance,
now
)
if(hit) hits.push(hit)
}
return(hits)
}
registerHits(hits) {
for(const hit of hits) this.registry.add(hit)
}
onVectorChange(agentId, newVector, newPosition = null) {
this.registry.purge(agentId)
this.upsertAgent(agentId, newVector, newPosition)
const hits = this.scanAgentPairs(agentId)
this.registerHits(hits)
if(this.debug && hits.length) console.log(`[GPS] ${hits.length} proximity pair(s) for ${agentId}`)
return(hits)
}
onAgentRemove(agentId) {
this.agents.delete(agentId)
this.registry.purge(agentId)
this.agentStore?.exportRemove(agentId)
if(this.debug) console.log(`[GPS] Agent removed: ${agentId}`)
}
tickPrismRefresh() {
for(const agentId of this.agents.keys()) {
this.refreshAgentPrism(agentId)
}
}
buildProximityBatches(due) {
const byAgent = new Map()
for(const entry of due) {
for(const agentId of [entry.agentA, entry.agentB]) {
const otherAgent = entry.agentA === agentId ? entry.agentB : entry.agentA
if(!byAgent.has(agentId)) byAgent.set(agentId, [])
byAgent.get(agentId).push({
otherAgent,
distance: entry.distance,
at: new Date(entry.time * 1000).toISOString(),
minDistance: entry.minDistance,
minAt: new Date(entry.minTime * 1000).toISOString(),
})
}
}
return(byAgent)
}
async publishProximityBatch(targetAgentId, pairs) {
if(!this.arenaCnx || !pairs.length) return
const chan = this.arenaCnx.config.gps.collisionsChannel.replace(/\[UID\]/g, targetAgentId)
await this.arenaCnx.redisPublish(chan, {
eventType: 'proximity',
payload: { pairs },
sender: 'gps',
})
}
async tickCollisions() {
const due = this.registry.dueBefore(this.now())
if(!due.length) return
const batches = this.buildProximityBatches(due)
for(const entry of due) this.registry.remove(entry)
for(const [agentId, pairs] of batches) {
await this.publishProximityBatch(agentId, pairs)
}
}
tickArena() {
this.tickPrismRefresh()
this.tickCollisions()
}
async reloadAccessRights() {
await this.configHelper.refreshAccessRights()
this.gpsConfig.accessRights = this.configHelper.config.accessRights
this.accessRights.refreshAccessRights(this.gpsConfig)
}
getAccessRights() {
return(this.gpsConfig.accessRights)
}
}