# GridNode – Features (Rust + Rapier vs. OpenSim + ubODE)

Dieses Dokument beschreibt die **Fähigkeiten** der neuen Grid-Software `gridnode`
gegenüber dem klassischen OpenSim/Robust (Mono/C# + ubODE-Physik). GridNode ist
eine **neu entwickelte Grid-Server-Software** – kein Fork, keine Herauslösung
einzelner Robust-Handler, sondern eine eigenständige Software in **Rust** (native
Binary, kein GC/JIT, planbarer RAM, speichersicher, Cross-Plattform Linux +
Windows ohne Runtime beim Nutzer; Ausnahme: reine CPU-Hotspots wie Kompression
dürfen in C eingebunden werden).

Nach außen bleibt GridNode **strikt LLUDP + CAPs + SL-Asset-Typen**, damit
**Firestorm, Alchemy, Cool VL** ohne einen einzigen Patch verbinden. Nach innen
ist alles neu, modern, parallel und schnell. Dieses Dokument geht ausschließlich
um die Fähigkeiten selbst – nicht um Installation (siehe installation.md) und
nicht um die Bauwellen-Reihenfolge/Roadmap (siehe roadmap.md).

**Produktziel in einem Satz:** Betritt ein User mit unverändertem Firestorm ein
gridnode-Grid, soll er innerhalb der ersten Minute merken, dass alles **rezt in
Sekunden statt Minuten**, **flüssig läuft**, **PBR-fotorealistisch** aussieht,
**saubere Physik** hat, **Sub-100-ms-Chat/Teleport** reagiert und **spatial
WebRTC-Voice** funktioniert – ohne dass er irgendetwas umgestellt hat.

---

## 1. Physik: Rapier statt ubODE

- **Rapier-Physik** (nativ Rust, Multi-Core) als **ubODE-Ersatz**. Deterministisch,
  saubere Fahrzeuge/Boote/Fluggeräte.
- **Vehicle-Constraints** (`llSetVehicleType`-kompatibel): Ein Test-Fahrzeug fährt
  sauber und fällt nicht durch den Boden – dort, wo ubODE bei Fahrzeugen bekannte
  Schwächen hat, muss Rapier **messbar sauberer** sein.
- **Determinismus:** Rapier läuft mit `enhanced-determinism` – dieselbe fixierte
  Feature-Matrix bildet zusammen mit dem WASM-Fuel-Determinismus die Basis für
  späteren Rollback-Netcode.
- **Physik-Schutz / Anti-Griefing (§17a.4):**
  - **Cap auf dynamische Bodies pro Avatar** (`MaxDynamicBodiesPerAgent`, Default
    256) – verhindert das Fluten des `IslandManager` durch „1000 Trümmer am Körper".
  - **Auto-Sleep** für ruhende Bodies (Standard) – ruhende Masse kostet keine CPU.
  - **CCD** bleibt an (schnelle Objekte tunneln nicht), ist aber pro Region budgetiert.
  - Absurd hohe Kollisions-Shapes werden auf Convex-Hull/Box degradiert (Rapier
    `SharedShape`), statt die Broad-Phase zu fluten.

Der Kernunterschied zum Alten: Klassisches OpenSim fährt die Region im
**Single-Thread + .NET-GC** – eine überkomplexe Physiklast reißt die ganze Sim mit.
Rapier läuft parallel (siehe §2) und ist pro Region konfigurierbar budgetiert.

---

## 2. Multi-Core / Nebenläufigkeit (die VM darf, was sie kann)

**Grundregel (nicht verhandelbar):** Was die VM/der LXC an **CPU-Kernen und RAM**
hat, darf `gridnode` **auch benutzen**. Kein künstliches Single-Thread-Limit wie
im klassischen Mono-OpenSim.

- **`tokio` Multi-Thread-Runtime** überall (`worker_threads = num_cpus`). Kein
  `current_thread`-Runtime für Server-Prozesse.
- **CPU-heavy Arbeit** (Physik-Steps, KTX2-Transcode, JPEG2000-Decode, Hashing,
  LSL→WASM-Kompilierung, Interest-Management-Rechnungen) läuft im **`rayon`-Pool**
  über alle Kerne, **nicht** im Async-Runtime.
- **LLUDP-Paketverarbeitung** über einen **Worker-Pool** (mehrere Tasks pro Circuit),
  damit ein zappelnder Client keinen anderen ausbremst.
- **WASM-Scripting** (`wasmtime`): Instance-Pool + eigener Thread-Pool, jedes Skript
  mit eigenem Zeit-Budget, keine globale Sperre.
- **HTTP/2-CAPs** (`hyper`): multiplexed, laufen auf allen Tokio-Workern.
- **DB-Zugriffe**: `sqlx`-Pool mit `max_connections = 2 × num_cpus` als Startwert.
- **Ressourcen-Freigabe:** Die `[Runtime]`-Section darf Limits **hoch**setzen, nicht
  künstlich runter (`WorkerThreads`/`RayonThreads`/`WasmThreads = auto`).
  `gridnode-region`/`-login`/`-caps` bekommen **keine** `CPUQuota` (dürfen 100 % aller
  Kerne nutzen); nur Worker (IAR/OAR/Backup/HG/Cleanup) sind gequotet.
- **Verifikation:** Unter Last muss `htop`/`top -H` zeigen, dass **alle** Cores der
  VM ausgelastet werden, nicht nur einer; Prometheus-Metriken exportieren per-Thread-
  CPU-Zeit, damit Single-Thread-Regressionen sofort auffallen.

**Ökosystem:** async I/O über `tokio`, HTTP über `hyper`/`axum`.

---

## 3. Prozess-Architektur (jeder Handler ein eigener Prozess)

**Problem beim Alten:** Klassisches OpenSim/Robust läuft im Wesentlichen in **einem**
Sim- und **einem** Robust-Prozess. Sobald jemand eine große **IAR/OAR importiert oder
exportiert**, ein Backup läuft oder ein HG-Transfer viele Assets zieht, geht der ganze
Prozess (und damit die Region bzw. das Grid) in die Knie – Avatare ruckeln, Login geht
nicht mehr, Physik steht.

**Regel bei GridNode (nicht verhandelbar):**

- **Jeder Handler = eigener OS-Prozess**, verwaltet über eine eigene systemd-Unit
  (`Restart=on-failure`, eigene Ressourcen-Limits via `CPUQuota=`, `MemoryMax=`,
  `IOWeight=`).
- **Kein Handler darf die Simulation blockieren.** Lange laufende Arbeit
  (Import/Export/Backup/HG-Bulk) läuft **immer** in einem Worker-Prozess mit eigener
  CPU-/IO-/Memory-Quote.
- **Kommunikation ausschließlich über IPC** (Unix-Domain-Sockets + gRPC oder
  `tokio`-Message-Bus). Kein Shared-State im Speicher zwischen Handlern.
- **Backpressure ist Pflicht:** Jede IPC-Queue hat eine feste Kapazität; ist sie voll,
  wird der Producer gebremst – **niemals** der Sim.
- **Priorität:** `gridnode-region` und `gridnode-login` höchste Priorität (`Nice=-5`);
  Worker (`gridnode-iar/-oar/-backup/-hg`) niedrige (`Nice=+10`, `IOWeight=50`).
- **Crash-Isolation:** Stürzt ein Worker ab, startet ihn systemd neu; Region und Login
  laufen ungestört weiter.

**Prozess-Landkarte (systemd-Units):** `gridnode-region` (LLUDP, Physik,
Interest-Management, EventQueue), `-login`, `-caps` (HTTP/2-CAPs), `-assets` (CAS-Storage,
KTX2-Transcode-Queue, Cache), `-peer` (Peer-Hub, Gossip/PEX, Heartbeats), `-voice`
(WebRTC-SFU-Bridge), `-scripts` (WASM-Runtime), `-jobs` (persistente Job-Queue), `-iar`,
`-oar`, `-backup`, `-hg`, `-metrics`, `-search`, `-webviewer`.

**Effekt („Ei-aus-der-Hose"):** Region-TPS bleibt selbst während 5-GB-OAR-Exports
stabil; Login und Teleport funktionieren, während ein IAR importiert wird; ein
defekter/hängender Worker reißt nichts anderes mit; Ressourcen sind planbar.

**Kill-Switch:** Owner/Admin kann jeden Job per Admin-CAP abbrechen – das killt nur den
Worker-Prozess, nicht die Region.

### 3.1 Admin-Konsole (`gridnode console`)

Ersatz für die interaktive OpenSim/Robust-Konsole: ein **REPL** mit kurzen, sprechenden
Befehlen, der Config + `gridnode.env` selbst lädt.

- **Lese-Befehle:** `show regions`, `show users`, `show estates`, `show services`
  (`gridnode-*`-Units + Status), `help`, `quit`/`exit`.
- **Schreib-Befehle (Anlegen):** `create user`, `create inventory` (Backfill des
  Standard-Ordner-Skeletts), `create estate`, `create region` (mit Grundflächen-
  Kollisionsprüfung über die volle Region-Fläche, `size` = positives Vielfaches von 256).
- **Schreib-Befehle (Ändern):** `change region estate`, `change estate owner`.
- **Als Dienst attachbar (wie Robust):** `gridnode-console.service` startet die Konsole
  in einer `screen`-Session (`screen -r gridnode-console`, lösen mit `Ctrl-a d`);
  `quit`/`exit` schließt nur den Prompt und öffnet sofort einen frischen. PATH-Symlink
  `/usr/local/bin/gridnode`.

---

## 4. Asset-System: CAS/SHA-256 statt BLOB-Chaos

**Problem beim Alten:** In klassischem OpenSim liegen Assets als **BLOBs** in der DB
(`assets`-Tabelle), verknüpft über zig Tabellen (`inventoryitems`, `inventoryfolders`,
`prims`, `primitems`, `terrain`, `regionsettings`, `avatarappearance`, `bakedtextures`,
…). Ein „welche Assets braucht niemand mehr?" ist praktisch nicht beantwortbar → DBs
wachsen ins Unendliche, Backups werden riesig, Migration ist Hölle.

**Lösung bei GridNode:** Assets werden **strikt getrennt** von Metadaten gespeichert.
Die DB kennt nur **Referenzen**, nie den Inhalt.

### 4.1 Content-Addressable Storage (CAS)

- **Pfad = Hash:** `cas/<ab>/<cd>/<sha256>` (zweistufige Fanout-Struktur gegen
  Riesen-Verzeichnisse; ab ~5 Mio. Dateien/Node auf dreistufig migrierbar,
  `[Assets] FanoutDepth`). Datei = Roh-Asset (Textur/Mesh/Sound/Notecard/…).
- **Deduplication automatisch:** Zwei identische Texturen = 1 Datei.
- **Immutable nach Landung (nicht verhandelbar):** Sobald eine Datei atomar in `cas/`
  liegt, wird sie `chmod 0444` gesetzt. Der Hash bleibt damit für immer gültig;
  versehentliches Überschreiben ausgeschlossen. Änderungen = neues Asset mit neuem Hash.
- **Filesystem-Backups** (rsync/zfs-snapshot) reichen – keine 200-GB-MySQL-Dumps.
- **Schlanke DB-Tabellen:** `asset_meta` (`sha256` PK, `asset_type`, `mime`, `size`,
  `created_at`, `origin`, `origin_sha256`), `asset_ref` (eine verbindliche
  Referenz-Tabelle für Cleanup), `inventory_item`, `inventory_folder`, `prim`,
  `prim_asset`. Jede Stelle, die früher eine `asset_id` (UUID) hielt, hält jetzt einen
  **`asset_sha256`**.

### 4.2 Cleanup endlich möglich (Mark-and-Sweep)

Ein trivialer Mark-and-Sweep über die Referenz-Tabelle:

1. **Mark:** `gridnode-jobs` durchläuft `asset_ref` + referenzierende Tabellen und
   markiert alle genutzten `sha256`.
2. **Sweep:** Alles Unmarkierte, das älter als `X` Tage ist (Standard 30), wandert nach
   `quarantine/`.
3. **Purge:** Nach weiteren `Y` Tagen (Standard 30) endgültige Löschung.

Läuft als eigener Worker `gridnode-cleanup` (systemd-Timer, nachts), belastet die Sim
nicht; Owner kann per Admin-CAP „Dry-Run" fahren.

### 4.3 Asset-Performance & Sicherheit (rein serverseitig, Viewer merkt nichts)

- **Kanonisierung vor dem Hashen** (Voraussetzung für echte Dedup): Notecards (UTF-8
  NFC, LF), LSL-Text (UTF-8 NFC, LF), Meshes (kanonische LLSD-Binary-Serialisierung mit
  fester Feld-Reihenfolge), Texturen (J2K→KTX2 falls `Ktx2OnImport=true`). Hash **immer**
  über die ausgelieferte Form. Original-Bytes werden separat als `origin_sha256` gehasht
  und – falls abweichend – zusätzlich abgelegt (für bit-identischen HG-Export).
- **Immutable-Auslieferung mit ewigem Cache:** `GetTexture`/`GetMesh`/`GetMesh2` tragen
  `Cache-Control: public, immutable, max-age=31536000` und einen `ETag` = SHA-256.
  Firestorm/libcurl cached damit **cross-region und cross-grid** unbegrenzt → beim
  zweiten Besuch Rez-Zeit ~0.
- **Zero-Copy-Auslieferung** (`hyper` + Linux): CAS-Dateien per `sendfile(2)` /
  `tokio-uring` `splice` direkt aus dem Filesystem-Cache in den TCP-Socket – kein
  Userspace-Puffer, keine Kopie. Ein `gridnode-caps`-Prozess sättigt trivial 1 Gbit/s
  bei zweistelliger CPU-Auslastung. HTTP/2 multiplexed hunderte parallele Asset-Requests
  eines Viewers über eine Verbindung.
- **Prefetch entlang der Interest-Liste:** `gridnode-region` weiß per Interest-Management,
  welche Prims als nächstes sichtbar werden; `gridnode-assets` warmed die zugehörigen
  Texturen/Meshes in den OS-Filesystem-Cache vor, **bevor** Firestorm sie anfragt → keine
  grauen Kartons mehr bei Bewegung/Draw-Distance-Erhöhung.
- **Sichere Uploads / Peer-Fetches (Hash ist der Beweis):** Jeder eingehende Byte-Strom
  wird streaming gehasht; kein Byte landet in `cas/`, bevor der SHA-256 stimmt. Atomarer
  Schreibvorgang `tmp/<sha>.partial` → `fsync` → `rename()` → `chmod 0444`. Fehlgeschlagene
  Hash-Prüfung → `quarantine/`. **Peer-Antworten müssen nicht signiert sein** – der Hash
  beweist den Inhalt, egal wer die Bytes liefert.
- **Fsync-Modus** (`[Assets] FsyncMode = per_file | per_batch`, Default `per_file`):
  `per_batch` schont SSD-IOPS bei Bulk-Uploads (nur bei journal-safen FS zulässig).

### 4.4 Asset-Resilienz im Peer-Verbund (kein toter Link, kein grauer Karton)

**Regel (nicht verhandelbar):** Wenn ein Grid ein Asset **einmal** aus dem Peer-Verbund
geladen hat, liegt es dauerhaft im lokalen CAS. Fällt das Ursprungs-Grid aus, bleibt das
Asset für die eigenen Nutzer verfügbar. Der **Peer-Verbund ist Cache/CDN, keine
Live-Referenz** nach außen.

- Sobald ein Grid ein Asset per SHA-256 zieht, wird es **atomar** in das lokale `cas/`
  geschrieben und ist ab dem Moment **eigener Besitz**. Keine URL-/Grid-Bindung mehr –
  kein „Asset lebt auf Grid X". `asset_meta.origin` hält nur zur Statistik fest, wo es
  herkam, **nicht** als Auflösungsquelle.
- **Optionale Replikation:** `[Assets] Replicas = N` (Default 0, empfohlen 2–3 für
  Community-Grids) pusht neue eigene Assets aktiv an N weitere Peers.
- **Retention:** Lokal gezogene Peer-Assets zählen wie eigene und werden erst vom
  Mark-and-Sweep angefasst, wenn sie lokal von keinem Prim/Inventar mehr referenziert
  werden. „Ursprungs-Grid offline" ist **kein** Löschgrund.
- **HG-Sonderfall:** Assets aus klassischen HG-Fremdgrids werden beim ersten Zugriff
  **immer** kopiert und lokal persistiert.
- **Ablauf beim Fetch:** Fehlt lokal, fragt `gridnode-assets` den Peer-Verbund
  (SHA-basiert, Anycast auf bekannte Peers, nach Latenz sortiert); Antwort wird
  **gleichzeitig** an den Viewer gestreamt und in `tmp/` geschrieben, nach Verifikation
  atomar nach `cas/` verschoben. Ein temporärer Miss löst **retry/„lädt"** aus, **nie**
  sofort `Unable to find asset`.

### 4.5 Asset-Redundanz (`[Assets] Replication`)

Asset-Verlust (grauer Avatar, fehlende Kleidung) ist der schlimmste Fehler für den User –
Redundanz ist **Pflicht**, nicht Option. Ein einziger Regler:

- `Replication = k1` (nur Kapazität, kein Failover) · `k2` (Standard) · `k3` (maximal).
- **`k` ist ein Ziel-Zustand:** Ein Reconciliation-Loop (Ist→Soll, im `gridnode-jobs`,
  gedrosselt/resumierbar) zieht bei Ist < Soll fehlende Kopien nach (Self-Healing auch
  bei Knoten-Ausfall) und entfernt bei Ist > Soll überzählige.
- **Downgrade-Safety-Gate:** Eine Kopie wird erst gelöscht, wenn nachweislich `k`
  verifizierte Kopien auf gesunden Knoten existieren; nie löschen, solange ein Knoten
  offline ist; Grace-Period statt Sofort-Löschung.
- **Hot-Reload ohne Neustart:** `gridnode assets reload` liest die INI neu ein und stößt
  den Reconciliation-Job an – kein Prozess-Neustart.
- **Erasure Coding** (Reed-Solomon `m`+`p`) ist als gleichrangiges Speicher-Profil für
  große Pools geplant (gleiche Sicherheit bei deutlich weniger Platz).
- **Content-Adressierung, `k`-Replikation und EC sind rein intern** (Data-Plane). Nach
  außen (Viewer, HG) sprechen wir klassisch OpenSim (UUID); die Brücke ist die
  `legacy_asset_map`.

### 4.6 Migration von klassischem OpenSim (kein Neubau, kein Vendor-Lock)

Umsteigende Owner exportieren ihre Alt-Welt als **IAR** (Inventar) und **OAR** (Region)
und importieren sie – immer über die Worker-Prozesse `gridnode-iar`/`gridnode-oar`, damit
die Region weiterläuft.

- **Import-Pipeline pro Asset:** Kanonisierung vor Hash → SHA-256 wird `cas/`-Name;
  Original-Erhalt als `origin_sha256` (bit-identische HG-Rückgabe); Dedup automatisch;
  Alt-UUIDs bleiben in `legacy_asset_map (uuid → sha256)` erhalten (HG-Referenzen weiter
  auflösbar); JPEG2000 optional zusätzlich als KTX2 abgelegt (`ktx2_on_import=true`),
  Original bleibt.
- **Kein Passwort für IAR/OAR-Befehle:** Anders als klassisches OpenSim
  (`load iar <first> <last> <path> <password>`) verlangen gridnode-Befehle **kein**
  Nutzer-Passwort; die Autorisierung ergibt sich aus dem Owner-Kontext (Konsole/Hub).
- **Export-Pipeline** ist der symmetrische Weg zurück: Standard-konformes IAR/OAR, das
  jedes klassische OpenSim wieder einliest; pro Asset wird bevorzugt `origin_sha256`
  verwendet → Alt-Grid erhält die **bit-identische** Original-Datei zurück.

**Warum das alles löst:** DB bleibt schlank (nur Metadaten), Cleanup funktioniert (eine
Referenz-Tabelle, ein Mark-and-Sweep), Backups trivial, echte Deduplication gratis,
Zero-Copy + ewig cachebar, Integrität bewiesen (Hash + `chmod 0444`), Migration ohne
Neubau, kein Vendor-Lock.

---

## 5. Scripting: LSL/OSSL kompatibel, aber massiv schneller

**Zwei-Stufen-Engine (eine Engine nach außen):**

- **Stufe A – AST/Bytecode-Interpreter (zuerst):** LSL-Parser (`pest`/`nom`) →
  typisierter AST/eigener Bytecode → schneller Rust-Interpreter in `gridnode-scripts`.
  Der **gesamte Skript-Zustand** (Variablen, aktueller `state`, Event-Queue, Timer-Rest,
  Listener) ist eine eigene, **`serde`-fähige** Struktur → trivial serialisierbar für
  Region-Crossing.
- **Stufe B – WASM-Beschleuniger (danach):** heiße Skripte laufen als WASM in `wasmtime`
  (Sandbox, Multi-Core, Instance-Pool). Weg dorthin: LSL-AST → Rust → `wasm32`
  (LLVM-Optimierung), **nicht** eigener WASM-Codegen. Kompilat wird als serialisiertes
  `wasmtime`-Modul auf Platte gecacht (gegen Cold-Start beim Region-Start).
- **State-Ownership bleibt bei der AST-Ebene**, nie im WASM-Linearspeicher – so bleibt
  der Zustand über Regionsgrenzen sauber transportierbar.
- **Determinismus:** `wasmtime` mit `consume_fuel` (abgezählte Instruktionen =
  Skript-Zeitbudget) + `nan_canonicalization = true` + fixierter Cranelift-/Feature-Matrix
  (analog Rapier `enhanced-determinism`) → Basis für späteren Rollback-Netcode.
- **OSSL-Kompat-Layer** für die gängigen `os*`-Funktionen. Reales OpenSim-Script aus einem
  Alt-Grid läuft unverändert.
- **Script-Debug-CAP** (später) – ohne Region-Restart-Crash.

**Verifikations-Ziel:** 100 parallele Scripts ohne Region-TPS-Einbrüche; Cold-Start via
Cache <100 ms je Script.

### 5.1 Skript-Zustand, Region-Crossing & Hypergrid-Attachments

**Problem beim Alten (OpenSim):** Bei Regionswechsel/HG-Teleport werden Skripte im
schlimmsten Fall **hart neu gestartet** – States gehen verloren, AO/HUDs „vergessen"
ihren Zustand, Skripte hängen/crashen, der Avatar steht als „Wolke" da.

**Bei GridNode** ist der Skript-Zustand dank AST-State-Ownership transportierbar. Ein
**Skript-Snapshot** (`bincode`/`serde`) enthält: `script_asset_sha256`, `run_state`
(`running`/`stopped`, explizit), aktueller `state`-Name, alle globalen Variablen
(typisiert), Event-Queue-Rest + laufende Timer (verbleibende Zeit), Listener-
Registrierungen, Permissions (`llRequestPermissions`-Grants, z. B.
`PERMISSION_TRIGGER_ANIMATION` für AO).

- **Regionswechsel (verbindlich):** Freeze am Frame-Ende → Snapshot → Handover im selben
  Agent-/Attachment-Transfer wie Physik (Rapier-Snapshot) und Appearance → Rehydrate auf
  der Ziel-Region (Code aus CAS/Peer-CDN, Variablen/State/Timer/Listener/Permissions
  wiederhergestellt) → Resume **exakt dort**, **kein** `state_entry` bei `running`-Skripten,
  damit AO/HUDs nicht „von vorn" starten. Ziel: kein grauer Screen, <200 ms Handover.
- **Hypergrid-Eintritt:** Body-/HUD-Skripte eines HG-Besuchers laufen in gridnode weiter,
  obwohl das Fremd-Grid keinen Snapshot liefert. Jedes **aktive** Attachment-Skript wird
  wie ein frisches Rez behandelt (`state_entry` + `attach(id)`, Permissions aus der
  Attachment-Beziehung automatisch neu erteilt) → AO greift wieder, HUDs initialisieren
  sich neu, ohne dass der User sie ab-/wieder anlegen muss. **Deaktivierte Skripte bleiben
  deaktiviert** (`scriptRunning = false`).
- **`run_state` ist heilig:** an bleibt an, aus bleibt aus – Region-Crossing ändert
  `run_state` grundsätzlich nie.
- **Robustheit:** Idempotent & atomar (bei Fehlschlag bleibt der Avatar in der
  Quell-Region); Backpressure über dieselbe kapazitätsbegrenzte IPC; Fuel-Reset sauber
  (State bleibt, nur das Instruktions-Budget des Slices wird neu vergeben).

---

## 6. Resilienz & Anti-Griefing (Schutz vor Region-Crash durch Masse)

**Problem beim Alten (OpenSim):** Ein einzelner böswilliger Avatar kann eine Region zum
Absturz bringen, indem er Unmengen an Skripten und absurde Komplexität (Prims,
Physik-Shapes, Partikel, Event-Fluten) am Körper trägt. Weil klassisches OpenSim die
Region im **Single-Thread + .NET-GC** fährt, reißt eine solche Last die ganze Sim mit –
**alle** Nutzer laggen oder fliegen raus.

**Kernprinzip in GridNode:** Ein einzelner Avatar darf niemals mehr als sein zugeteiltes
Budget verbrauchen. Alles darüber wird gedrosselt, abgewiesen oder gar nicht erst gerezt –
spürbar nur für den Angreifer selbst, nie für die Region. Möglich durch `wasmtime`-Sandbox
pro Skript, Fuel-/Zeitbudget, Backpressure-IPC und `rayon`/`tokio`-Isolation.

- **Budget-Baum (dreistufig, pro Frame):** Pro Skript (Fuel-Slice, Endlosschleife → Skript
  pausiert ohne Thread-Blockade), pro Avatar (Summe aller Attachment-Skripte gedeckelt,
  round-robin fair gedrosselt), pro Region (harte Obergrenze, globaler Throttle schützt
  Sim-TPS).
- **Admission Control** (Grenzen greifen beim Anlegen): Max. Skripte pro Avatar (überzählige
  starten nicht), Attachment-/Prim-/Land-Impact-Caps, Avatar Rendering Complexity (ARC) →
  zu komplexe Avatare werden als „Jelly-Doll"/Wolke behandelt und physikalisch vereinfacht.
- **Raten-Drosselung** (Event-Fluten sind der häufigste Crash-Vektor): Token-Bucket pro
  Avatar je API-Klasse (Chat/Listen, `llHTTPRequest`, `llEmail`, Partikel, `llTargetOmega`);
  Rez-Rate begrenzt (grey-goo-Schutz); Event-Queue-Cap pro Skript.
- **Abgestufte Reaktion** (nicht binär): 1. Soft-Throttle → 2. Attachment-Detach → 3.
  Freeze/Eject → 4. Ban. Alles wird geloggt (kein stiller Verwurf) und als Per-Avatar-Metrik
  über Prometheus exportiert; jede Auslösung landet als `griefing`-Ereignis im
  owner-lesbaren DB-Log.
- **Hypergrid-Härtung:** Die Budgets greifen auch bei der Reinit fremder Attachments – ein
  HG-Besucher kann nicht an den Quotas vorbei tausende Skripte reinitialisieren. HG-Besucher
  bekommen **strengere Defaults** (`HypergridStrictDefaults = true`, `HypergridBudgetFactor`).
- **Owner-konfigurierbar** über die `[Resilience]`-Section in `gridnode.ini` (pro Region/Grid):
  u. a. `ScriptFuelPerFrame` (200000), `AgentFuelPerFrame` (5000000), `RegionFuelPerFrame`
  (120000000), `MaxScriptsPerAgent` (256), `MaxScriptMemoryMB` (16), `MaxAttachmentsPerAgent`
  (38), `MaxAgentRenderComplexity` (350000), `MaxAgentLandImpact` (5000), `ChatRatePerAgent`
  (40), `MaxListensPerAgent` (512), `HttpRequestRatePerAgent` (25), `RezRatePerAgent` (10),
  `MaxEventQueuePerScript` (4096), `MaxDynamicBodiesPerAgent` (256), `OnAgentLimitExceeded`
  (`throttle`/`detach`/`eject`/`ban`).

**Ehrliche Einordnung:** 100 % „unkillbar" gibt es nicht – aber das erreichte Ziel ist: ein
einzelner böswilliger Avatar kann nur sich selbst lahmlegen, nicht die Region. Genau das ist
der Bruch mit klassischem OpenSim.

---

## 7. Ereignis-Log in der Datenbank (Owner-lesbar, gefiltert)

**Problem:** Ein Grid-Owner kann Linux-fern sein – `journalctl`/Logdateien sind für ihn keine
Option. Er braucht die **wichtigen** Ereignisse in der Datenbank, damit der Hub sie als
filterbare Liste anzeigt (nach Modul, Schweregrad, Region/Avatar).

- **Kein Info-Firehose:** Das DB-Log ist ein kuratierter, gefilterter Auszug nur für
  owner-relevante Ereignisse (`tracing`/journald bleibt die technische Vollspur).
- **„Interessant":** Region-Start/-Stopp/-Crash+Neustart, Owner-/Admin-Login,
  fehlgeschlagene Logins in Serie, Peer verbunden/verloren, HG-Ankunft/-Abreise,
  Backup/IAR/OAR-Import fertig/fehlgeschlagen, Asset-/DB-Fehler, Voice-Backend-Fallback,
  Anti-Griefing-Auslösung (jede Eskalationsstufe). Nicht: jeder `AgentUpdate`, LLUDP-Zähler,
  erfolgreicher `GetTexture`, Physik-Steps.
- **Login-Protokollierung (Modul `login`):** `login_ok` (notice), `login_failed` (warning),
  `login_db_unavailable` (error), `login_malformed` (warning), `login_error` (error); das
  `details`-JSON enthält u. a. `ip`, `mac`, `id0`, `viewer`, `grid` – ohne
  Authentifizierungsgewicht, nur Nachvollziehbarkeit.
- **Tabelle `event_log`** (schlank, InnoDB/`utf8mb4`): `module`, `severity`
  (`info…critical` + eigene, immer sichtbare Klasse `griefing`), `event_type`, `message`,
  `region_name`, `agent_id/agent_name`, `details JSON`, `occurrences`,
  `first_seen_at`/`last_seen_at`.
- **Anti-Spam (vier Bremsen vor dem Insert):** Mindest-Schweregrad (`MinSeverity`),
  Dedup/Coalescing (`DedupWindowSeconds` – aus „5000 Login-Fehler in 60 s" wird **eine**
  Zeile mit `occurrences = 5000`), globale Rate-Obergrenze (`MaxRowsPerMinute` – das Log
  kann selbst nie zum Last-Vektor werden), Modul-Filter (`Modules`).
- **Retention automatisch** (`gridnode-jobs`-Prune-Job): zeitbasiert (`RetentionDays`,
  `CriticalRetentionDays`) + größenbasiert (`MaxRows`, Ring-Puffer) → Tabelle kann
  konstruktiv nicht unbegrenzt wachsen.
- **Anzeige (Hub):** filterbare Liste, Live-Aktualisierung via Mercure (kein Polling),
  Zugriff nur für Grid-/Region-Owner bzw. Admin.
- **Owner-konfigurierbar** über die `[EventLog]`-Section (`Enabled`, `MinSeverity` Default
  notice, `AlwaysLogGriefing`, `DedupWindowSeconds` 60, `MaxRowsPerMinute` 120,
  `RetentionDays` 30, `CriticalRetentionDays` 90, `MaxRows` 100000, `Modules`).

---

## 8. Display Names (live, ohne Relog)

**Problem beim Alten:** OpenSim liefert keinen echten Display-Name-Dienst. Selbst mit
Community-Patches muss man sich meist neu einloggen, um den eigenen neuen Namen zu sehen –
und andere Avatare sehen ihn oft **nie**. In Second Life ist das seit 2010 gelöst.

Bei GridNode wirken Anzeigenamen für sich selbst **und** alle anderen Avatare in der Region
**sofort**, ohne Neuanmeldung.

- **Anzeigename ≠ Login-Name:** Der klassische Legacy-Username (`firstname.lastname`) bleibt
  für Login, HG-Adressen und interne IDs unverändert bestehen (Abwärtskompat).
- **Ratenlimit:** max. 1× pro Woche (SL-Standard), Admin/Owner darf überschreiben.
- **Zeichensatz:** UTF-8 NFC, max. 31 Codepoints, Sperrliste gegen Impersonation.
- **CAPs (Firestorm-standardkonform):** `GetDisplayNames` (Batch-Auflösung),
  `SetDisplayName`; Antworten als LLSD-XML exakt nach SL-Schema.
- **Live-Push (der eigentliche Trick):** Nach `SetDisplayName` sendet `gridnode-region` an
  **alle** Avatare, für die der User im Interest-Set liegt, `AgentDataUpdate` (LLUDP) und
  `AgentDisplayNameReply` (über `EventQueueGet`); der Namens-Cache in `gridnode-caps` wird
  invalidiert. Kein Viewer-Cache-Trick nötig: Firestorm reagiert korrekt, wenn der Server
  tatsächlich pusht – **genau das machen die bekannten OpenSim-Patches nicht** (sie senden
  den Update nur an den User selbst, nicht an alle).
- **Feature-Flag** `[Features] DisplayNames = true`; HG-Fremdgrid ohne Support → Rückfall auf
  `firstname.lastname`.

---

## 9. Voice: Dual-Backend (Vivox + WebRTC parallel)

**Ausgangslage:** Bestehende Grids nutzen **Vivox** (`www.osp.vivox.com`, `[VivoxVoice]` in
`OpenSim.ini`). Firestorm unterstützt zusätzlich seit 2024 nativ **WebRTC**. GridNode muss
**beides** können: Vivox für bestehende Grids ohne Umstellung, WebRTC für den modernen,
Vendor-lock-freien Pfad. Der Owner entscheidet pro Grid (oder pro Parcel).

- **Backend-Auswahl** `[Voice] Backend = vivox | webrtc | dual`:
  - `vivox` – reiner Vivox-Betrieb (1:1-Ersatz, identische Config-Semantik wie
    `[VivoxVoice]`). Bestehende Grids können **ohne Voice-Ausfall** migrieren.
  - `webrtc` – reiner WebRTC-Betrieb über Janus-SFU.
  - `dual` – parallel: WebRTC bevorzugt, Vivox als Fallback. Der Voice-CAP prüft anhand des
    Viewer-Handshakes, welche Antwort er ausliefert.
  - Pro Parcel überschreibbar (`[ParcelVoice] Backend = …`).
- **Vivox-Pfad (Kompatibilität):** `[Vivox]`-Section 1:1 kompatibel zu OpenSim
  `[VivoxVoice]`; Zugangsdaten **niemals** im Klartext (`%env(...)%` aus `gridnode.env`).
  `ProvisionVoiceAccount`/`ParcelVoiceInfoRequest` liefern die klassischen Vivox-URLs –
  Firestorm merkt keinen Unterschied.
- **WebRTC-Pfad (SFU):** primär **Janus** (nativer C-SFU, AudioBridge + VideoRoom),
  alternativ LiveKit/mediasoup (`[WebRTC] Sfu = janus | livekit | mediasoup`). Voice-CAP
  liefert WebRTC-Parameter (Signaling-URL, TURN-Server, Room-ID); Firestorm nutzt seinen
  eingebauten `llwebrtc`-Stack automatisch.
- **`dual`-Automatik:** Server prüft den Viewer-Fingerprint (WebRTC-fähig → WebRTC, ältere
  Viewer → Vivox). Fällt der WebRTC-SFU aus (Janus down), fällt der CAP automatisch auf Vivox
  zurück – **kein Voice-Ausfall** für den Nutzer.
- **Rooms (beide Backends, identische Semantik):**
  - **Region-Voice (Spatial Audio):** Room-ID = Region-UUID; Lautstärke fällt mit Entfernung,
    Panning nach relativer Kopfausrichtung; berechnet im `gridnode-voice`-Prozess, nicht im
    Client.
  - **Group-Voice:** Room-ID = Group-UUID; unabhängig von der Region.
  - **Party-Voice / Ad-hoc-Conference:** Room-ID = generierte UUID, Invite per
    `ChatSessionRequest`; SL-Verhalten 1:1.
- **Persistente Group-Chat-Queue:** Group-Chat läuft nicht mehr „fire and forget";
  Nachrichten werden pro `(group_id, user_id)` persistent gequeued und beim nächsten
  `EventQueueGet` bzw. Login zugestellt (Retention Default 7 Tage); Group-Notices bekommen
  dieselbe Garantie – **kein „message lost"-Effekt mehr**.

---

## 10. Grafik: PBR-Qualitätsstufen serverseitig

**Problem beim Alten:** OpenSim/SL liefern PBR-Materialien und Reflection Probes **immer
voll**, unabhängig von der Viewer-Einstellung. Mittelklasse-Rechner brechen ein.

Bei GridNode meldet der Viewer seine Grafik-Stufe (Low/Mid/High/Ultra) an den Server;
`gridnode` liefert Materialien, Reflection-Probe-Dichte und Textur-LOD **passend** aus –
statt immer „volle PBR-Power". Kein „mein Alt-PC brennt"-Effekt mehr.

- **Erkennung:** Firestorm meldet seine Stufe beim Login (u. a. `X-SecondLife-Shard`,
  `Viewer-Version`, `AgentPreferences`); zusätzlich ein expliziter `X-Gridnode-Gfx-Tier`-Hint.
- **Server-seitig** wird die passende Asset-Variante ausgewählt – **nicht** durch
  Protokoll-Änderung.

| Stufe | Material | Reflection Probes | Textur-LOD (KTX2) | EEP |
|-------|----------|-------------------|-------------------|-----|
| Low   | Legacy-Blinn-Phong-Fallback | keine dynamischen Probes | max. 512 px | einfacher Skybox-Set |
| Mid   | PBR ohne Clearcoat/Iridescence | wenige, statische Probes | max. 1024 px | Standard-EEP |
| High  | Volles PBR (glTF 2.0) | alle statischen Probes | max. 2048 px | Volles EEP + Wetter |
| Ultra | Volles PBR + optionale Extensions | inkl. dynamischer Probes | max. 4096 px | Volles EEP + Reflections + Wolken |

- **Zwei Material-Varianten** werden beim Upload/Import erzeugt und beide im CAS abgelegt
  (unterschiedlicher SHA-256, Cross-Referenz in `asset_meta`); Reflection-Probes-Manifest pro
  Parcel/Region wird stufen-abhängig gefiltert ausgeliefert.
- **Feature-Flags:** `[Features] PbrTiering = true`, `[Features] PbrLowendProfile = true`
  (zwingt alle Nutzer auf max. `Mid` – hilft in öffentlichen Sandboxen mit vielen schwachen
  Clients).
- **Bricht Firestorm nicht:** Der Viewer bekommt weiter genau eine gültige Material-/Probe-
  Antwort per CAP – nur die zur eigenen Grafik-Stufe passende.

Ergänzend zum Grafik-Stack: **PBR / glTF-2.0-Materialien** sauber im Asset-/Material-CAP;
**EEP / Reflection Probes** pro Parcel/Region (Environment-Settings-CAP); **KTX2 /
Basis-Universal-Transcoder** serverseitig (JPEG2000 nur noch Fallback für Alt-Assets;
KTX2 ist zudem mobil-GPU-nativ, siehe Web-Viewer).

---

## 11. Avatare & Appearance

- **AppearanceService** inkl. **Bakes-on-Mesh**, **Bento (133 Bones)**, **Animesh**.
- **Server-side Baking** (Composited Textures) → Avatare in <5 s statt „Wolke für 30 s".
- **Display Names mit Live-Push** (siehe §8).

---

## 12. Interest-Management

- **Frustum + Distanz + Priorität** → 512 m Draw-Distance ohne Lag; **keine
  Broadcast-Fluten** wie in OpenSim.
- Speist zugleich den Asset-Prefetch (siehe §4.3): Der Server weiß, welche Prims als
  nächstes sichtbar werden, und warmed deren Assets vor.

---

## 13. Mesh-Delivery & Transport

- **CAPs-Grundgerüst über HTTP/2 + Keep-Alive:** `SeedCapability`, `GetTexture`, `GetMesh`,
  `GetMesh2`, `EventQueueGet`.
- **EventQueueGet** mit echtem Long-Poll (**Sub-100-ms** Reaktionszeit; Chat-Nachricht in
  Firestorm in <100 ms).
- **Mesh-Delivery** über HTTP/2 (Firestorm/libcurl) und optional **QUIC** (`quinn`) für
  kommende Viewer-Versionen.

---

## 14. Directory, Traffic & Thumbnails (den Viewer endlich voll bedienen)

**Problem beim Alten:** Firestorm hat UI für Places-Suche, Events, Groups, Classifieds,
Picks, Profile-Bilder, Inventar-Thumbnails, Region-Traffic-Anzeige – aber OpenSim liefert
dafür schlicht nichts (leere Listen, `0 traffic`, keine Thumbnails). GridNode bedient den
Viewer voll (eigener Prozess `gridnode-search`, Volltextindex über **Tantivy** nativ Rust
oder **Meilisearch**).

- **Directory / Suche:** `DirectoryPlaces` / `DirectoryEvents` / `DirectoryGroups` /
  `DirectoryPeople` / `DirectoryClassifieds`, `AvatarPickerSearch`, `MapItemRequest`.
- **Region-Traffic-Anzeige:** `gridnode-region` sendet pro Minute
  `(region_id, avatars_last_hour, avatars_last_24h)` an `gridnode-search`;
  `MapItemRequest`/`DirectoryPlaces` gibt die Zahl aus → die Firestorm-Weltkarte zeigt sie
  automatisch.
- **Inventar- UND Outfit-Ordner-Thumbnails** (in OpenSim kaputt, obwohl Firestorm sie kann):
  Inventar-Items **und** Inventar-Ordner (insbesondere Outfit-Ordner) haben ein optionales
  Thumbnail-Asset (Textur, 256²). Server erweitert `InventoryItem` und `InventoryFolder` um
  `thumbnail_asset_sha256`; `FetchInventory2`/`FetchInventoryDescendents2` liefert die
  Thumbnail-UUID für Items **und** Folder (LLSD-Feld `thumbnail`);
  `UpdateInventoryItem`/`UpdateInventoryFolder` akzeptiert „Set Thumbnail" aus Firestorm.
  Beim IAR-Import werden vorhandene Thumbnails übernommen; fehlende können optional automatisch
  erzeugt werden (z. B. Outfit-Ordner aus dem Baked-Torso-Snapshot).
- **Profile / Picks / Classifieds:** `AvatarPropertiesRequest`, `PicksRequest`,
  `ClassifiedInfoRequest` werden sauber aus den DB-Tabellen bedient; Bilder sind ganz normale
  CAS-Assets.
- **Feature-Flags:** `[Features] Directory = true`, `InventoryThumbnails = true`,
  `MapTraffic = true`.

---

## 15. Web-Mobile-Viewer (optional)

**Ziel:** Ein **webbasierter** Viewer für Handy/Tablet (und Desktop-Browser), der über
dieselben CAPs wie Firestorm auf `gridnode` zugreift. Keine App-Store-Hölle, kein
Zweitprotokoll, kein Zweitzustand.

- **Rendering im Browser:** WebGL2 (Basis) oder WebGPU (fortgeschritten), z. B. `three.js`
  oder eigener WGSL-Renderer.
- **Netzwerk:** LLUDP kann der Browser nicht direkt → `gridnode-webviewer` ist ein
  **WebSocket-Gateway**, das intern LLUDP zum Region-Prozess spricht (dünner Übersetzer, kein
  Zweitprotokoll auf Sim-Ebene). CAPs (HTTP/2) laufen direkt vom Browser zu `gridnode-caps`.
  Voice: **WebRTC** identisch zum Firestorm-Weg (Janus), Browser-nativ.
- **Warum trivial(er) mit dieser Architektur:** Assets liegen als immutable Files mit ewigem
  Cache → der Browser cached sie über den normalen HTTP-Cache; HTTP/2-CAPs und WebRTC-Voice
  sind Browser-nativ; KTX2/Basis-Universal ist mobil-GPU-nativ. Der einzige Custom-Bau ist die
  LLUDP↔WebSocket-Bridge.
- **Nicht-Ziele (bewusst):** Kein Ersatz für Firestorm auf dem Desktop (bleibt Referenz-
  Viewer); keine proprietäre Client-Sim-Logik (reiner Anzeige-Client).
- **Feature-Flag:** `[Features] WebViewer = true`, plus `[WebViewer] Bind = 0.0.0.0:8443`.

---

## 16. Peer-to-Peer-Verbund & Teleports (ohne Zentrale)

Das Netz funktioniert wie ein **Filesharing-Netz (Torrent/eMule)**: kein zentraler Server,
der alle kennt. Jede laufende Instanz ist ein **Peer** und zugleich **Client UND Server**;
je mehr Server starten, desto größer und robuster wird das Netz. Es gibt **keinen Single
Point of Failure**.

- **Discovery:** Start-Announce im LAN per UDP; über das Internet holt eine neue Instanz die
  Startliste bekannter Peers vom **Peer-Hub** (Verzeichnis-API, **kein** Datenpfad) und meldet
  sich per **signierter** Nachricht an.
- **Peer-Austausch (Gossip/PEX):** Ist eine Instanz mit einem Peer verbunden, lernt sie von
  diesem alle anderen; die Peer-Liste verbreitet sich von selbst.
- **Heartbeat:** Jeder Peer prüft periodisch Latenz/erreichbare Regionen; fällt ein Heartbeat
  aus, gilt der Peer als offline → Teleport dorthin wird sauber abgewiesen statt Timeout.
- **Identität & Vertrauen:** Grid-Identifikator ist die Adresse `host:port` (kein zentrales
  ID-Register). Jede Meldung ist **signiert** (Ed25519 über `[Security] KeyFile`).
  Grundsatz: Server haben keine Rechte zueinander – nur signierte Anfragen. UDP = schneller
  Hinweis, TCP = verbindliche Wahrheit (Vertrauens-/Teleport-Entscheidung fällt nie allein
  wegen eines UDP-Pakets).
- **Nahtlose Teleports** im Verbund: State-Handover asynchron, **<200 ms, kein grauer
  Screen**. Zwischen Verbund-Grids (gleiche Software, gleiches Protokoll) ist A→B technisch
  wie A→A, obwohl die Grids getrennt bleiben (eigener Name, eigene DB, eigene Assets).
- **Isolation auf Grid-Ebene:** Zugang ist eine binäre Ja/Nein-Entscheidung pro Grid
  (Allowlist/Policy) – es gibt keine isolierten Assets, nur isolierte Grids.
- **Dienste im Netz verteilen (`[ServiceMap]`):** Jeder Dienst kann `local` (UDS, schnellster
  Pfad, Default) oder remote (`grpc://host:port`, verschlüsselt per mTLS) laufen; eine
  Komma-Liste bildet einen Cluster/Pool. Cluster-Typen je Dienst: Robust zustandslos →
  Load-Balancer; Region partitioniert nach `region_id`; Assets content-adressiert
  (Rendezvous-Hash-Sharding + `k`-Replikation, self-completing); Voice/Scripts Round-Robin +
  sticky; Jobs eine aktive Queue + Worker. Der Peer ist Control-Plane/Health-Registry
  (Dispatcher, nicht Proxy) und selbst clusterbar.

---

## 17. Kompatibilitätsversprechen (nicht verhandelbar)

GridNode ist ein **vollständig neues OpenSim-kompatibles Grid-Produkt**. Alles Neue ist
Firestorm-kompatibel ohne Viewer-Patch.

- **Abwärtskompatibel zu klassischem OpenSim** über **Hypergrid (HG)** als Außengrenze – kein
  Owner verliert Zugang zu Bestandsgrids; die HG-Bridge bleibt jederzeit funktionsfähig, auch
  während eine Welle im Bau ist.
- **Keine Protokoll-Erweiterungen, die Firestorm nicht kennt.** Neues geht ausschließlich über
  offiziell dokumentierte CAPs / SL-Message-Templates. Kein Custom-Message ohne dokumentierten
  Firestorm-Pfad.
- **Keine Änderung am Prim-/Skeleton-/Shader-Modell.** Alle Verbesserungen sind serverseitig
  und für den Viewer transparent; wir liefern SL-konforme Objekte, egal wie wir sie intern
  speichern.
- **Feature-Flags pro Grid** in `gridnode.ini` (`pbr=true`, `ktx2=true`, `webrtc_voice=true`,
  `wasm_scripts=true`, …). Zickt ein Viewer irgendwo, wird das Feature gezielt abgeschaltet,
  statt das Grid zu brechen.
- **Rollback-fähig:** Jede Welle wird mit systemd-Unit-Snapshot und DB-Snapshot begleitet
  (Rückkehr in <5 min).
- **Getestete Viewer:** Firestorm (aktuell + LTS, Primärziel), Alchemy, Cool VL. Jeder Schritt
  wird direkt gegen ein klassisches OpenSim/Robust/ubODE-Alt-Grid verglichen (neu↔neu und
  neu↔alt/HG).
- **Ein Handler = ein eigener Prozess** (siehe §3): Import/Export von IAR/OAR, Asset-Uploads,
  Backups oder HG-Transfers dürfen **niemals** die Simulation oder den Login-/Peer-Dienst
  ausbremsen.

---

## 18. Datenhaltung als Fähigkeit (kein `Regions.ini`, keine BLOBs)

Regionen, Nutzer, Inventar und Asset-Metadaten liegen **in der Datenbank**, nicht in
`Regions.ini`/Dateien auf dem Server. Asset-Bytes liegen im CAS. Das Schema ist bewusst neu
und **nicht** OpenSim-kompatibel (die klassischen BLOB-/verstreuten-Referenz-Tabellen sind zu
unsauber); Bestandsdaten kommen über den Standard-IAR/OAR-Import.

- **`regions`** ersetzt `Regions.ini` (Owner kommt ausschließlich aus dem Estate, nicht auf
  der Region dupliziert); **`users`** mit OpenSim-kompatiblem Passwort-Schema
  (`md5(md5(pw)+":"+salt)`, viewer-/HG-kompatibel); **`estates`** (Owner direkt hier, kein
  separates estate-map); **`assets_meta`** (Metadaten, Bytes im CAS); **`legacy_asset_map`**
  (UUID → sha256, HG-Auflösung); **`inventory_folders`/`inventory_items`** (inkl.
  `thumbnail_asset_sha`); **`event_log`** (§7).
- **Grid-Info aus der INI, nicht der DB:** Der Server bedient `get_grid_info` direkt aus der
  `[GridInfoService]`-Section (im Speicher gehalten, **keine** DB-Abfrage je Aufruf). Damit
  sieht der Owner beim Hinzufügen des Grids in Firestorm sofort Grid-Name, Login-URI,
  Welcome/About/Register/Help/Password-URLs, Gatekeeper-URI und Message-of-the-Day.

**Warum kein OpenSim-Schema übernommen wurde:** OpenSim speichert Assets als `LONGBLOB` (killt
Buffer-Pool und I/O); Referenzen sind über viele Tabellen verstreut (Cleanup unmöglich); UUIDs
statt Content-Hash (viel duplizierter Content, keine Integritäts-Prüfung); Regions-Dateien auf
der Platte statt in der DB (keine zentrale Sicht, kein sauberer HA-Failover). GridNode löst das
strukturell: **DB nur für Metadaten + Referenzen, Bytes im CAS, Regionen ausschließlich aus
`regions`.**

---

## 19. Ops & Monitoring

- **Prometheus-Endpoint** pro `gridnode` (Region-TPS, LLUDP-Paketrate, CAPs-Latenz,
  Physik-Steps, aktive Avatare, WASM-Script-Zeiten, per-Thread-CPU-Zeit,
  Per-Avatar-Anti-Griefing-Metriken).
- **Hub-Visualisierung** via **Mercure** – nur Anzeige, **kein** Eingriff in den Datenpfad.
- **Admin-CAP** für Region-Owner: Neustart, Backup, Snapshot, Feature-Flags, Job-Kill-Switch,
  Cleanup-Dry-Run.
