// Platform

Two engines. Six shapes. One ingest endpoint.

Traced is intentionally simple at the data layer: every event your SDK fires flows through one HTTPS endpoint and lands in one of two tables. The complexity is on the viewer side, where the same primitives compose into every report you see.

Data flow

Unity SDK
    │ batched POST (gzipped JSON, ~5s flush)
    ▼
Edge Function /v1/ingest
    │ API key auth → project lookup → tier check
    ▼
Postgres
    ├─ events            (one row per Track* call)
    └─ position_samples  (one row per TrackPosition call, Pro+ only)
                │
                │  Supabase Realtime
                ▼
Dashboard (Next.js + Three.js)
    ├─ Spatial Density engine  → DBSCAN cluster heatmap
    └─ Path Flow engine        → Catmull-Rom curves

The two engines

Spatial Density

Input: a stream of (x, y, z) positions from events. The viewer bins them into uniform spatial cells (default 1.6m cube), then renders each occupied cell as a translucent sphere whose color and size encode density. The uniqueness-weighted mode (default) prefers cells touched by many distinct sessions over cells where one session emitted many events — defends against farming or AFK bots distorting the heatmap.

Path Flow

Input: position_samples grouped by (session_id, player_id). Each group is sorted by timestamp, decimated to ≤500 samples, and rendered as a Catmull-Rom curve in a teal→cyan hue range. Different players take adjacent hue slots so they read as distinct.

Reports = templates over engines

Death Heatmap, Kill Heatmap, Combat Hotspots, Player Paths — these aren't separate code. They're JSON configs that parameterize one of the two engines with a filter, an accent color, and an icon. Adding a new template = adding a config entry. Adding a new engine is the only thing that requires renderer work.

Backend stack

LayerTech
Postgres + RLSSupabase (managed)
HTTP ingestDeno Edge Function
Realtime pushSupabase Realtime (Phoenix Channels)
Object storageSupabase Storage (for uploaded maps)
Scheduled jobspg_cron (retention purges)
AuthSupabase Auth (email + password; OAuth roadmap)

Frontend stack

LayerTech
FrameworkNext.js 14 (app router, client-side only)
3D viewerThree.js (custom orbit controls, no addon deps)
StylingTailwind + custom component classes
TypeGeist Sans / Geist Mono
Auth@supabase/supabase-js (RLS does the gating)

Security posture

  • RLS: every public table has Row-Level Security enabled. The dashboard uses the anon key + the user's JWT; RLS policies tie reads to org_members membership. The ingest function uses service-role (RLS bypass) since it authenticates via X-API-Key.
  • Beta gate: new signups land with approved=false. The dashboard shows a waitlist screen until an admin flips the flag.
  • Tier enforcement: position streams are rejected for free-tier projects with HTTP 403 at the ingest function. Other tier caps will land before public launch.
  • API keys: stored plaintext (they're scoped tokens, not user secrets — revocability bounds the blast radius). Test-tier keys committed in public docs are deliberate.

Performance

  • SDK: zero allocations in steady-state TrackEvent calls; ring buffer reuses slots. ~0.1ms per frame upper bound.
  • Ingest: ~10ms p50 latency Unity → Postgres on us-west-1; gzip cuts payload ~5×.
  • Realtime: ~100ms websocket fanout from insert to dashboard render.
  • Viewer: heatmap rebuild is O(N) where N is the bin count; 5000 events → ~120 bins → 16ms.

Roadmap notes

Architecture v1.0 documented an older 2D-first design. The current product is 3D-first per a 2026-05-13 design decision. Position streams shipped in MVP rather than Phase 2. The per-event style map is in production, with icon billboards landing 2026-05-15. See the changelog for the full release timeline.