forked from Rockachopa/Timmy-time-dashboard
This commit is contained in:
28
config/matrix.yaml
Normal file
28
config/matrix.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
# Matrix World Configuration
|
||||
# Serves lighting, environment, and feature settings to the Matrix frontend.
|
||||
|
||||
lighting:
|
||||
ambient_color: "#1a1a2e"
|
||||
ambient_intensity: 0.4
|
||||
point_lights:
|
||||
- color: "#FFD700"
|
||||
intensity: 1.2
|
||||
position: { x: 0, y: 5, z: 0 }
|
||||
- color: "#3B82F6"
|
||||
intensity: 0.8
|
||||
position: { x: -5, y: 3, z: -5 }
|
||||
- color: "#A855F7"
|
||||
intensity: 0.6
|
||||
position: { x: 5, y: 3, z: 5 }
|
||||
|
||||
environment:
|
||||
rain_enabled: false
|
||||
starfield_enabled: true
|
||||
fog_color: "#0f0f23"
|
||||
fog_density: 0.02
|
||||
|
||||
features:
|
||||
chat_enabled: true
|
||||
visitor_avatars: true
|
||||
pip_familiar: true
|
||||
workshop_portal: true
|
||||
@@ -22,11 +22,14 @@ import re
|
||||
import time
|
||||
from collections import deque
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
from fastapi import APIRouter, WebSocket
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from config import settings
|
||||
from infrastructure.presence import serialize_presence
|
||||
from timmy.workshop_state import PRESENCE_FILE
|
||||
|
||||
@@ -489,6 +492,102 @@ async def _generate_bark(visitor_text: str) -> str:
|
||||
return "Hmm, my thoughts are a bit tangled right now."
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Matrix Configuration Endpoint
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Default Matrix configuration (fallback when matrix.yaml is missing/corrupt)
|
||||
_DEFAULT_MATRIX_CONFIG: dict[str, Any] = {
|
||||
"lighting": {
|
||||
"ambient_color": "#1a1a2e",
|
||||
"ambient_intensity": 0.4,
|
||||
"point_lights": [
|
||||
{"color": "#FFD700", "intensity": 1.2, "position": {"x": 0, "y": 5, "z": 0}},
|
||||
{"color": "#3B82F6", "intensity": 0.8, "position": {"x": -5, "y": 3, "z": -5}},
|
||||
{"color": "#A855F7", "intensity": 0.6, "position": {"x": 5, "y": 3, "z": 5}},
|
||||
],
|
||||
},
|
||||
"environment": {
|
||||
"rain_enabled": False,
|
||||
"starfield_enabled": True,
|
||||
"fog_color": "#0f0f23",
|
||||
"fog_density": 0.02,
|
||||
},
|
||||
"features": {
|
||||
"chat_enabled": True,
|
||||
"visitor_avatars": True,
|
||||
"pip_familiar": True,
|
||||
"workshop_portal": True,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _load_matrix_config() -> dict[str, Any]:
|
||||
"""Load Matrix world configuration from matrix.yaml with fallback to defaults.
|
||||
|
||||
Returns a dict with sections: lighting, environment, features.
|
||||
If the config file is missing or invalid, returns sensible defaults.
|
||||
"""
|
||||
try:
|
||||
config_path = Path(settings.repo_root) / "config" / "matrix.yaml"
|
||||
if not config_path.exists():
|
||||
logger.debug("matrix.yaml not found, using default config")
|
||||
return _DEFAULT_MATRIX_CONFIG.copy()
|
||||
|
||||
raw = config_path.read_text()
|
||||
config = yaml.safe_load(raw)
|
||||
if not isinstance(config, dict):
|
||||
logger.warning("matrix.yaml invalid format, using defaults")
|
||||
return _DEFAULT_MATRIX_CONFIG.copy()
|
||||
|
||||
# Merge with defaults to ensure all required fields exist
|
||||
result: dict[str, Any] = {
|
||||
"lighting": {
|
||||
**_DEFAULT_MATRIX_CONFIG["lighting"],
|
||||
**config.get("lighting", {}),
|
||||
},
|
||||
"environment": {
|
||||
**_DEFAULT_MATRIX_CONFIG["environment"],
|
||||
**config.get("environment", {}),
|
||||
},
|
||||
"features": {
|
||||
**_DEFAULT_MATRIX_CONFIG["features"],
|
||||
**config.get("features", {}),
|
||||
},
|
||||
}
|
||||
|
||||
# Ensure point_lights is a list
|
||||
if "point_lights" in config.get("lighting", {}):
|
||||
result["lighting"]["point_lights"] = config["lighting"]["point_lights"]
|
||||
else:
|
||||
result["lighting"]["point_lights"] = _DEFAULT_MATRIX_CONFIG["lighting"]["point_lights"]
|
||||
|
||||
return result
|
||||
except Exception as exc:
|
||||
logger.warning("Failed to load matrix config: %s, using defaults", exc)
|
||||
return _DEFAULT_MATRIX_CONFIG.copy()
|
||||
|
||||
|
||||
@matrix_router.get("/config")
|
||||
async def get_matrix_config() -> JSONResponse:
|
||||
"""Return Matrix world configuration.
|
||||
|
||||
Serves lighting presets, environment settings, and feature flags
|
||||
to the Matrix frontend so it can be config-driven rather than
|
||||
hardcoded. Reads from config/matrix.yaml with sensible defaults.
|
||||
|
||||
Response structure:
|
||||
- lighting: ambient_color, ambient_intensity, point_lights[]
|
||||
- environment: rain_enabled, starfield_enabled, fog_color, fog_density
|
||||
- features: chat_enabled, visitor_avatars, pip_familiar, workshop_portal
|
||||
"""
|
||||
config = _load_matrix_config()
|
||||
return JSONResponse(
|
||||
content=config,
|
||||
headers={"Cache-Control": "no-cache, no-store"},
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Matrix Agent Registry Endpoint
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -863,3 +863,197 @@ def test_matrix_agents_endpoint_graceful_degradation(matrix_client):
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert resp.json() == []
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Matrix Configuration Endpoint (/api/matrix/config)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestMatrixConfigEndpoint:
|
||||
"""Tests for the Matrix configuration endpoint."""
|
||||
|
||||
def test_matrix_config_endpoint_returns_json(self, matrix_client):
|
||||
"""GET /api/matrix/config returns JSON config."""
|
||||
resp = matrix_client.get("/api/matrix/config")
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert isinstance(data, dict)
|
||||
assert "lighting" in data
|
||||
assert "environment" in data
|
||||
assert "features" in data
|
||||
assert resp.headers["cache-control"] == "no-cache, no-store"
|
||||
|
||||
def test_matrix_config_lighting_structure(self, matrix_client):
|
||||
"""Config has correct lighting section structure."""
|
||||
resp = matrix_client.get("/api/matrix/config")
|
||||
data = resp.json()
|
||||
|
||||
lighting = data["lighting"]
|
||||
assert "ambient_color" in lighting
|
||||
assert "ambient_intensity" in lighting
|
||||
assert "point_lights" in lighting
|
||||
assert isinstance(lighting["point_lights"], list)
|
||||
|
||||
# Check first point light structure
|
||||
if lighting["point_lights"]:
|
||||
pl = lighting["point_lights"][0]
|
||||
assert "color" in pl
|
||||
assert "intensity" in pl
|
||||
assert "position" in pl
|
||||
assert "x" in pl["position"]
|
||||
assert "y" in pl["position"]
|
||||
assert "z" in pl["position"]
|
||||
|
||||
def test_matrix_config_environment_structure(self, matrix_client):
|
||||
"""Config has correct environment section structure."""
|
||||
resp = matrix_client.get("/api/matrix/config")
|
||||
data = resp.json()
|
||||
|
||||
env = data["environment"]
|
||||
assert "rain_enabled" in env
|
||||
assert "starfield_enabled" in env
|
||||
assert "fog_color" in env
|
||||
assert "fog_density" in env
|
||||
assert isinstance(env["rain_enabled"], bool)
|
||||
assert isinstance(env["starfield_enabled"], bool)
|
||||
|
||||
def test_matrix_config_features_structure(self, matrix_client):
|
||||
"""Config has correct features section structure."""
|
||||
resp = matrix_client.get("/api/matrix/config")
|
||||
data = resp.json()
|
||||
|
||||
features = data["features"]
|
||||
assert "chat_enabled" in features
|
||||
assert "visitor_avatars" in features
|
||||
assert "pip_familiar" in features
|
||||
assert "workshop_portal" in features
|
||||
assert isinstance(features["chat_enabled"], bool)
|
||||
|
||||
|
||||
class TestMatrixConfigLoading:
|
||||
"""Tests for _load_matrix_config function."""
|
||||
|
||||
def test_load_matrix_config_returns_dict(self):
|
||||
"""_load_matrix_config returns a dictionary."""
|
||||
from dashboard.routes.world import _load_matrix_config
|
||||
|
||||
config = _load_matrix_config()
|
||||
assert isinstance(config, dict)
|
||||
assert "lighting" in config
|
||||
assert "environment" in config
|
||||
assert "features" in config
|
||||
|
||||
def test_load_matrix_config_has_all_required_sections(self):
|
||||
"""Config contains all required sections."""
|
||||
from dashboard.routes.world import _load_matrix_config
|
||||
|
||||
config = _load_matrix_config()
|
||||
lighting = config["lighting"]
|
||||
env = config["environment"]
|
||||
features = config["features"]
|
||||
|
||||
# Lighting fields
|
||||
assert "ambient_color" in lighting
|
||||
assert "ambient_intensity" in lighting
|
||||
assert "point_lights" in lighting
|
||||
|
||||
# Environment fields
|
||||
assert "rain_enabled" in env
|
||||
assert "starfield_enabled" in env
|
||||
assert "fog_color" in env
|
||||
assert "fog_density" in env
|
||||
|
||||
# Features fields
|
||||
assert "chat_enabled" in features
|
||||
assert "visitor_avatars" in features
|
||||
assert "pip_familiar" in features
|
||||
assert "workshop_portal" in features
|
||||
|
||||
def test_load_matrix_config_fallback_on_missing_file(self, tmp_path):
|
||||
"""Returns defaults when matrix.yaml is missing."""
|
||||
from dashboard.routes.world import _load_matrix_config
|
||||
|
||||
with patch("dashboard.routes.world.settings") as mock_settings:
|
||||
mock_settings.repo_root = str(tmp_path)
|
||||
config = _load_matrix_config()
|
||||
|
||||
# Should return defaults
|
||||
assert config["lighting"]["ambient_color"] == "#1a1a2e"
|
||||
assert config["environment"]["rain_enabled"] is False
|
||||
assert config["features"]["chat_enabled"] is True
|
||||
|
||||
def test_load_matrix_config_merges_with_defaults(self, tmp_path):
|
||||
"""Partial config file is merged with defaults."""
|
||||
from dashboard.routes.world import _load_matrix_config
|
||||
|
||||
# Create a partial config file
|
||||
config_dir = tmp_path / "config"
|
||||
config_dir.mkdir()
|
||||
config_file = config_dir / "matrix.yaml"
|
||||
config_file.write_text("""
|
||||
lighting:
|
||||
ambient_color: "#ff0000"
|
||||
ambient_intensity: 0.8
|
||||
environment:
|
||||
rain_enabled: true
|
||||
""")
|
||||
|
||||
with patch("dashboard.routes.world.settings") as mock_settings:
|
||||
mock_settings.repo_root = str(tmp_path)
|
||||
config = _load_matrix_config()
|
||||
|
||||
# Custom values
|
||||
assert config["lighting"]["ambient_color"] == "#ff0000"
|
||||
assert config["lighting"]["ambient_intensity"] == 0.8
|
||||
assert config["environment"]["rain_enabled"] is True
|
||||
|
||||
# Defaults preserved
|
||||
assert config["features"]["chat_enabled"] is True
|
||||
assert config["environment"]["starfield_enabled"] is True
|
||||
assert len(config["lighting"]["point_lights"]) == 3
|
||||
|
||||
def test_load_matrix_config_handles_invalid_yaml(self, tmp_path):
|
||||
"""Returns defaults when YAML is invalid."""
|
||||
from dashboard.routes.world import _load_matrix_config
|
||||
|
||||
config_dir = tmp_path / "config"
|
||||
config_dir.mkdir()
|
||||
config_file = config_dir / "matrix.yaml"
|
||||
config_file.write_text("not: valid: yaml: [{")
|
||||
|
||||
with patch("dashboard.routes.world.settings") as mock_settings:
|
||||
mock_settings.repo_root = str(tmp_path)
|
||||
config = _load_matrix_config()
|
||||
|
||||
# Should return defaults despite invalid YAML
|
||||
assert "lighting" in config
|
||||
assert "environment" in config
|
||||
assert "features" in config
|
||||
|
||||
def test_load_matrix_config_custom_point_lights(self, tmp_path):
|
||||
"""Custom point lights override defaults completely."""
|
||||
from dashboard.routes.world import _load_matrix_config
|
||||
|
||||
config_dir = tmp_path / "config"
|
||||
config_dir.mkdir()
|
||||
config_file = config_dir / "matrix.yaml"
|
||||
config_file.write_text("""
|
||||
lighting:
|
||||
point_lights:
|
||||
- color: "#FFFFFF"
|
||||
intensity: 2.0
|
||||
position: { x: 1, y: 2, z: 3 }
|
||||
""")
|
||||
|
||||
with patch("dashboard.routes.world.settings") as mock_settings:
|
||||
mock_settings.repo_root = str(tmp_path)
|
||||
config = _load_matrix_config()
|
||||
|
||||
# Should have custom point lights, not defaults
|
||||
lights = config["lighting"]["point_lights"]
|
||||
assert len(lights) == 1
|
||||
assert lights[0]["color"] == "#FFFFFF"
|
||||
assert lights[0]["intensity"] == 2.0
|
||||
assert lights[0]["position"] == {"x": 1, "y": 2, "z": 3}
|
||||
|
||||
Reference in New Issue
Block a user