FlyBrain-Lab / src /world3d /service.py
timfromhcs's picture
FlyBrain V9.0.0 Space sync (v9 release, endless world, chunk streaming, FlyAsset compiler)
6343479 verified
Raw History Blame Contribute Delete
6.09 kB
"""Persistent world runtime service (V6): one World3D + hero + friends.
Singleton per process (server owns it). Friends are full EmbodiedAgents
(genome, needs, memory, goals, social state). Backup/restore covers world +
all agents + spatial DB. Loading AI models is event-driven and lazy via
ModelManager; the world loop never waits for them.
"""
import os
from typing import Any, Dict, List, Optional
from src.genome.schema import Genome
from src.organism.organism import Organism
from src.connectome.types import GraphMode
from src.world3d.world import World3D
from src.world3d.agent import EmbodiedAgent
from src.world3d.spatial_memory import SpatialMemory
from src.world3d.social import social_encounter
from src.world.chunks.chunk_manager import WorldManager
_SERVICE = None
def _organism(oid: str, seed: int) -> Organism:
return Organism(Genome.founder(seed, legacy=False), oid, generation=0,
seeds={"organism_seed": seed, "development_seed": seed + 1},
graph_mode=GraphMode.SYNTHETIC_TEST, circuit_size=32,
autonomy_mode=True)
class WorldService:
def __init__(self, seed: int = 7, friends: int = 2,
db_path: str = "diagnostics/world_memory.db"):
self.seed = seed
spawn = [{"name": "hero", "x": 0.0, "y": 4.0}]
for i in range(friends):
spawn.append({"name": f"friend_{i}", "x": -2.0 - i, "y": 2.0})
self.world = World3D(seed=seed, characters=spawn)
self.spatial = SpatialMemory(db_path)
self.world_manager = WorldManager(seed=seed)
self.agents: Dict[str, EmbodiedAgent] = {}
self.agents["hero"] = EmbodiedAgent(
_organism("hero", 101), "hero", self.world, self.spatial, yaw=0.0)
for i in range(friends):
oid = f"friend_{i}"
self.agents[oid] = EmbodiedAgent(
_organism(oid, 200 + i), oid, self.world, self.spatial, yaw=0.0)
def chunks(self) -> Dict[str, Any]:
hero_pos = self.world.physics.char_state("hero")["pos"]
active = self.world_manager.chunks.update_center(hero_pos[0], hero_pos[1], radius_chunks=2)
return {
"active_chunk_count": len(active),
"chunks": [self.world_manager.chunks.loaded_chunks[k].to_dict() for k in active]
}
def step(self, agent_ticks: int = 1) -> List[Dict[str, Any]]:
out = []
for _ in range(max(1, agent_ticks)):
for ag in self.agents.values():
if ag.body3d.alive:
out.append(ag.tick())
# pairwise social encounters (close friends interact for real)
names = list(self.agents)
for i in range(len(names)):
for j in range(i + 1, len(names)):
social_encounter(self.agents[names[i]], self.agents[names[j]])
return out
def state(self) -> Dict[str, Any]:
chars = []
for name, ag in self.agents.items():
st = self.world.physics.char_state(name)
chars.append({"name": name, "pos": st["pos"], "yaw": round(ag.yaw, 3),
"upright": self.world.physics.upright(name),
"grounded": self.world.physics.grounded(name),
"goal": ag.body3d.goal, "action": ag.body3d.action,
"energy": round(ag.body3d.base.energy, 3),
"hunger": round(ag.body3d.hunger, 3),
"alive": ag.body3d.alive,
"sleeping": ag.sleeping})
return {"tick": self.world.tick, "clock_sec": round(self.world.clock_sec, 2),
"day_fraction": round(self.world.day_fraction, 3),
"is_night": self.world.is_night,
"state_hash": self.world.state_hash(),
"objects": self.world.object_states(),
"characters": chars}
def geometry(self) -> Dict[str, Any]:
"""Static render geometry derived from the spec (observer view)."""
spec = self.world.spec
h = spec["home"]
cx, cy, w, d, t, dw, wh = (h["cx"], h["cy"], h["w"], h["d"], h["wall_t"],
h["door_w"], h["wall_h"])
q = (w - dw) / 2
walls = [
{"x": cx, "y": cy + d / 2, "sx": w, "sy": t, "h": wh},
{"x": cx - w / 2, "y": cy, "sx": t, "sy": d, "h": wh},
{"x": cx + w / 2, "y": cy, "sx": t, "sy": d, "h": wh},
{"x": cx - (dw / 2 + q / 2), "y": cy - d / 2, "sx": q, "sy": t, "h": wh},
{"x": cx + (dw / 2 + q / 2), "y": cy - d / 2, "sx": q, "sy": t, "h": wh},
]
return {"size": spec["size"], "home": spec["home"], "walls": walls,
"furniture": spec["furniture"], "trees": spec["trees"],
"rocks": spec["rocks"],
"foods": [f for f in spec["foods"] if f["id"] not in self.world.consumed],
"water": spec["water"], "doors": spec["doors"],
"door_state": dict(self.world.door_state)}
def snapshot(self) -> Dict[str, Any]:
return {"world": self.world.snapshot(),
"agents": {n: a.snapshot() for n, a in self.agents.items()},
"seed": self.seed}
def restore(self, snap: Dict[str, Any]) -> None:
if snap.get("seed") != self.seed:
raise ValueError("world backup belongs to a different seed")
self.world.restore(snap["world"])
for n, a in snap["agents"].items():
if n in self.agents:
self.agents[n].restore(a)
def get_service() -> WorldService:
global _SERVICE
if _SERVICE is None:
_SERVICE = WorldService(seed=int(os.environ.get("FLYBRAIN_WORLD_SEED", "7")))
return _SERVICE
def reset_service(seed: int = 7, friends: int = 2) -> WorldService:
global _SERVICE
if _SERVICE is not None:
try:
_SERVICE.spatial.close()
except Exception:
pass
_SERVICE = WorldService(seed=seed, friends=friends)
return _SERVICE