Skyland

πŸš€ mission codename: skylands survival β€” funday plugin blueprint for a coding agent

  • 🎯 objective

    • deliver a web-first, 1–4 player co-op craft-defense game as a funday plugin
    • integration types: iframe-themeable client and dedicated-server (nakama) via agones
    • target pilot: 10k dau, 12–20 min sessions, p95 ws rtt ≀ 120 ms in eu-central
    • monetization: cosmetics + season pass (no pay-to-win)
    • art: low-poly stylized; device coverage: desktop first, mobile capable via pwa
  • πŸ—ΊοΈ scope of work

    • build plugin-compliant client with theme messaging, pwa shell, and realtime netcode
    • run nakama authoritative servers as agones game servers with autoscaling
    • provide data schema, liveops hooks, analytics taxonomy, and infra automation
    • ship developer tooling, ci/cd, observability, load & e2e tests, security hardening
  • πŸ—οΈ funday plugin architecture alignment

    • dynamic serving via /games/[…path]/+server.ts

    • plugin types used

      • iframe-themeable for the web client ui and game loop
      • dedicated-server for the nakama authoritative match runtime under agones
    • single source of truth: funday-plugin.json manifest (client) and server manifest (server)

  • πŸ“ repo & directory layout (monorepo)

    games/
    β”œβ”€ sky-islands-survival/
    β”‚  β”œβ”€ funday-plugin.json                 # client plugin manifest
    β”‚  β”œβ”€ index.html                         # iframe entry (bootstraps sveltekit app)
    β”‚  β”œβ”€ assets/                            # images, audio, fonts, hdr, glb/ktx2
    β”‚  β”‚  β”œβ”€ images/
    β”‚  β”‚  β”œβ”€ models/
    β”‚  β”‚  β”œβ”€ textures/
    β”‚  β”‚  └─ audio/
    β”‚  β”œβ”€ client/                            # sveltekit + threlte
    β”‚  β”‚  β”œβ”€ src/
    β”‚  β”‚  β”‚  β”œβ”€ app.html
    β”‚  β”‚  β”‚  β”œβ”€ routes/+layout.svelte
    β”‚  β”‚  β”‚  β”œβ”€ lib/game/
    β”‚  β”‚  β”‚  β”‚  β”œβ”€ core/                     # ecs, scheduler, rng, fixed-step
    β”‚  β”‚  β”‚  β”‚  β”œβ”€ net/                      # nakama client, clock sync, interp
    β”‚  β”‚  β”‚  β”‚  β”œβ”€ scenes/                   # loading, hub, match
    β”‚  β”‚  β”‚  β”‚  β”œβ”€ systems/                  # input, camera, building, vfx
    β”‚  β”‚  β”‚  β”‚  β”œβ”€ ui/                       # hud, chat, shop, settings
    β”‚  β”‚  β”‚  β”‚  β”œβ”€ content/                  # recipes, drops, tech-tree (data)
    β”‚  β”‚  β”‚  β”‚  └─ assets/loader.ts          # ktx2/draco pipelines
    β”‚  β”‚  β”œβ”€ static/                         # pwa icons, manifest.webmanifest
    β”‚  β”‚  β”œβ”€ vite.config.ts
    β”‚  β”‚  └─ package.json
    β”‚  β”œβ”€ server/                            # nakama authoritative server
    β”‚  β”‚  β”œβ”€ funday-server.json              # dedicated-server manifest
    β”‚  β”‚  β”œβ”€ modules/                        # go modules for match/economy
    β”‚  β”‚  β”‚  β”œβ”€ match/
    β”‚  β”‚  β”‚  β”œβ”€ economy/
    β”‚  β”‚  β”‚  └─ rpc/
    β”‚  β”‚  β”œβ”€ sql/                            # migrations
    β”‚  β”‚  β”œβ”€ Dockerfile
    β”‚  β”‚  └─ Makefile
    β”‚  β”œβ”€ ops/
    β”‚  β”‚  β”œβ”€ docker-compose.dev.yml          # local: nakama, cockroach, redis, satori
    β”‚  β”‚  β”œβ”€ k8s/                             # agones GameServer, fleet, cm, svc, hpa
    β”‚  β”‚  β”œβ”€ terraform/                       # cluster, crdb, redis, namespaces
    β”‚  β”‚  └─ grafana-dashboards/
    β”‚  β”œβ”€ tests/
    β”‚  β”‚  β”œβ”€ e2e/                             # playwright
    β”‚  β”‚  β”œβ”€ load/                            # k6 websocket scenarios
    β”‚  β”‚  └─ bots/                            # headless match bots
    β”‚  β”œβ”€ scripts/                            # ci helpers, asset pipeline
    β”‚  └─ README.md
    
  • 🧾 funday client manifest (funday-plugin.json)

    {
      "name": "sky-islands-survival",
      "version": "1.0.0",
      "integrationType": "iframe-themeable",
      "theme": "funday-dark",
      "metadata": {
        "title": "Sky Islands Survival",
        "description": "Co-op craft-defense on floating islands.",
        "genre": ["Co-op", "Survival", "Crafting"],
        "maxPlayers": 4,
        "minPlayers": 1,
        "thumbnail": "/images/games/sky-islands.svg",
        "screenshots": ["/images/games/sis-1.png", "/images/games/sis-2.png"]
      },
      "deployment": {
        "resources": { "cpu": "100m", "memory": "128Mi" }
      },
      "api": {
        "endpoints": ["/rpc/season/progress", "/rpc/shop/roll", "/rpc/cosmetics/equip"],
        "webhooks": []
      }
    }
  • 🧾 funday dedicated-server manifest (server/funday-server.json)

    {
      "name": "sky-islands-survival-server",
      "version": "1.0.0",
      "integrationType": "dedicated-server",
      "image": "registry.example.com/funday/sis-nakama:1.0.0",
      "agones": {
        "fleet": { "replicas": 2, "maxReplicas": 20 },
        "ports": [{ "name": "nakama", "containerPort": 7350, "protocol": "UDP" }]
      },
      "deployment": {
        "resources": { "cpu": "500m", "memory": "512Mi" },
        "env": [
          { "name": "DB_URL", "valueFrom": "secret:crdb_url" },
          { "name": "REDIS_ADDR", "valueFrom": "secret:redis_addr" },
          { "name": "SATORI_API_KEY", "valueFrom": "secret:satori_key" }
        ]
      }
    }
  • 🎨 theme integration in iframe

    <!doctype html>
    <html lang="en" data-theme="funday-dark">
      <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width,initial-scale=1" />
        <script src="https://cdn.tailwindcss.com"></script>
        <link href="https://cdn.jsdelivr.net/npm/daisyui@5.1.12/dist/full.css" rel="stylesheet" />
      </head>
      <body class="bg-base-100 text-base-content">
        <div id="app"></div>
        <script type="module" src="/client/src/main.ts"></script>
        <script>
          window.addEventListener("message", (event) => {
            if (event?.data?.type === "funday:theme-inject") {
              const { theme, colors } = event.data
              document.documentElement.setAttribute("data-theme", theme)
              window.dispatchEvent(new CustomEvent("funday-theme", { detail: { theme, colors } }))
            }
          })
          if (window.parent !== window) {
            window.parent.postMessage(
              { type: "funday:game-ready", game: "sky-islands-survival" },
              "*",
            )
          }
        </script>
      </body>
    </html>
  • 🧠 gameplay brief

    • loop: gather β†’ craft/fortify β†’ night raid defend β†’ extract/checkpoint β†’ repeat
    • coop: drop-in/out up to 4; private party or public matchmaking
    • persistence: base blueprint, tech tree, cosmetics; seasonal island seeds
  • πŸ”Œ realtime netcode

    • transport: websocket (json mvp β†’ protobuf later), 20 hz server tick, client fixed-step 60 fps with interpolation
    • authority: server authoritative for resources, build placement, enemy ai, damage; client predicts movement and building ghosting
    • time sync: rtt/clock skew smoothing, input seq with ack, 120–200 ms buffer on mobile
    • interest management: per-client entity culling by island cell and distance
  • πŸ›°οΈ nakama server modules (go)

    • match loop responsibilities

      • integrate inputs β†’ validate β†’ simulate ai β†’ produce state deltas
      • craft/build validators (grid snaps, resource consumption with versioned writes)
      • raid director scaler (team power, wave cadence, biome modifiers)
      • checkpoint to db each wave end and on extract
    • rpc endpoints

      • claim daily, cosmetics equip, shop roll, season progress
    • storage

      • users, profiles, inventories, cosmetics, islands, recipes, seasons
    • caching

      • redis for presence, cooldowns, ephemeral match snapshots
  • πŸ”€ websocket opcodes

    1 join_match          { partyId, loadout }
    2 input               { seq, axes:{x,y}, actions:{build:bool, attack:int, use:int}, ts }
    3 state_delta         { authoredFrame, seqAck, ents:[{id,t,p,r,hp,...}], events:[{type,...}] }
    4 craft_request       { recipeId, inputsVer }
    5 build_place         { blueprintId, grid:{x,y,z}, rot, ghostId }
    6 chat                { channel:'team'|'party', text }
    7 extract_request     {}
    8 emote               { id }
    9 ping                { t }
    10 pong               { t }
    
  • πŸ—„οΈ data model (sql sketch)

    create table users (
      id uuid primary key,
      created_at timestamptz not null default now(),
      auth_provider text not null,
      region text
    );
     
    create table cosmetics (
      user_id uuid references users(id),
      slot text,           -- 'head','body','tool'
      item_id text,
      equipped boolean default false,
      primary key (user_id, slot, item_id)
    );
     
    create table inventories (
      user_id uuid references users(id) primary key,
      version int not null default 1,
      items jsonb not null default '[]'
    );
     
    create table islands (
      id uuid primary key,
      owner_user_id uuid references users(id),
      mode text,           -- 'private','shared'
      seed bigint not null,
      biome text,
      tech_level int not null default 0,
      blueprint jsonb not null default '{}'
    );
     
    create table seasons (
      id text primary key,
      config jsonb not null,
      starts_at timestamptz,
      ends_at timestamptz
    );
     
    create table leaderboards (
      season_id text references seasons(id),
      category text,
      user_id uuid references users(id),
      score int,
      updated_at timestamptz default now(),
      primary key (season_id, category, user_id)
    );
  • 🧰 client architecture (sveltekit + threlte)

    • core

      • fixed-step scheduler (dt buckets), rng seed per match, deterministic input packing
      • ecs-lite pattern: components, systems, queries; render separated from sim
    • rendering

      • threlte/three scene graph, lod tiers, pooled vfx, post chain minimal
      • ktx2 textures, draco gltf; lazy stream content by island cell
    • physics

      • rapier-wasm for local feedback only; server collision is primitive validation
    • ui/ux

      • daisyui components; hud with health, wave timer, inventory, quick craft bar
      • chat with quick pings; settings panel; responsive with safe-area insets
    • storage

      • local slots for options; resume checkpoint id
    • pwa

      • workbox; offline shell; background asset warm-up; install prompt gating
  • πŸ§ͺ testing strategy

    • unit

      • go: match rules, economy math
      • ts: input packing, clock sync, reconciler
    • integration

      • headless nakama with bots; 100 simulated matches in ci nightly
    • e2e

      • playwright: join/leave, build, defend, extract on chromium, firefox, webkit
    • load

      • k6 websocket: soak 2k concurrent per node; scenarios for spikes, churn
    • desync detection

      • snapshot/restore harness; divergence alerts if checksum mismatch
  • 🧰 developer commands

    # bootstrap
    make deps
     
    # run local stack
    docker compose -f ops/docker-compose.dev.yml up --build
     
    # run client dev server
    pnpm --filter @sis/client dev
     
    # build client
    pnpm --filter @sis/client build
     
    # run nakama tests
    make -C games/sky-islands-survival/server test
     
    # e2e
    pnpm --filter @sis/tests e2e
     
    # load test
    k6 run tests/load/ws_soak.js
  • πŸ§ͺ sample k6 websocket snippet

    import ws from "k6/ws"
    import { check, sleep } from "k6"
     
    export const options = { vus: 500, duration: "5m" }
     
    export default function () {
      const url = "wss://server.example.com/ws"
      ws.connect(url, {}, (socket) => {
        socket.on("open", () => {
          socket.send(JSON.stringify({ op: 1, partyId: null }))
        })
        socket.on("message", (d) => {
          /* track pings/state */
        })
        socket.setInterval(
          () => socket.send(JSON.stringify({ op: 2, seq: Date.now(), axes: { x: 0, y: 1 } })),
          50,
        )
        socket.setTimeout(() => socket.close(), 60000)
      })
    }
  • 🧩 nakama go match loop skeleton

    func MatchLoop(ctx context.Context, logger runtime.Logger, nk runtime.NakamaModule, initialState *State, tick int64) (*State, []runtime.MatchDataSend, string) {
      s := *initialState
      applyInputs(&s, tick)
      simulateAI(&s, tick)
      deltas := buildDeltas(&s, tick)
      msgs := []runtime.MatchDataSend{{
        OpCode: 3,  // state_delta
        Data:   deltas,
        Presences: s.Presences,
      }}
      if shouldCheckpoint(tick) { checkpoint(ctx, nk, &s) }
      return &s, msgs, ""
    }
  • πŸ” security & privacy

    • iframe sandbox: allow-scripts, allow-pointer-lock, disallow top nav; csp strict with asset whitelists
    • xss protection: sanitize chat; never inject raw html from network
    • storage: no third-party cookies; localStorage limited to client settings; gameplay data via server only
    • api hardening: rate limits per ip/user; signed inputs are not requiredβ€”use server authority instead
    • auth: guest β†’ email link/oauth; rotate tokens; logout invalidates refresh
    • gdpr: data export and delete rpc; pii minimization
    • cheat resistance: server validates damage, resource spends, build placements; replayable inputs; anomaly detection via telemetry
  • πŸ“Š analytics & liveops (satori)

    • event taxonomy

      • session_start, session_end, match_join, match_end, craft, build_place, damage_dealt, damage_taken, raid_wave, extract, shop_open, purchase, cosmetic_equip
    • properties

      • user_id, party_size, biome, tech_level, wave, build_id, recipe_id, ping_ms, fps, region
    • flags

      • drop_rates_vX, raid_scaler_vY, shop_rotation_vZ
    • dashboards

      • retention d1/d7; funnel to wave 3; economy sinks/sources; latency heatmaps
  • πŸ’Έ monetization flows

    • cosmetics store

      • soft currency earn; hard currency purchase (platform-compliant)
      • preview in scene; equip persists in cosmetics table
    • season pass

      • free/premium tracks; xp from raids and dailies; weekly challenges feed
    • compliance

      • parental gate for purchases; receipt verify server-side; refunds revoke entitlements
  • 🎯 performance budgets

    • initial load ≀ 3.0 mb gz main + ≀ 10 mb assets streamed; first interactive ≀ 6 s on mid-tier mobile over 4g
    • frame: ≀ 8 ms cpu main thread budget at 60 fps on desktop; 30 fps fallback mobile
    • draw calls ≀ 120; triangles ≀ 400k in view; texture atlas usage; gpu timers tracked
    • network: ≀ 18 kb/s avg per client during combat; delta comp; interest culling
  • πŸ”Ž observability

    • metrics

      • server: tick duration, backlog, ws rtt p50/p95, match count, errors
      • client: fps, cpu time, gc pauses, ws rtt, reconnects
    • logging

      • structured json; correlate by match_id, user_id, seq
    • tracing

      • opentelemetry spans: join β†’ match loop β†’ checkpoint β†’ extract
    • alerts

      • p95 rtt > 180 ms 10 min, match errors > 1%, auth failures surge, memory > 80%
  • 🏭 ci/cd pipeline (github actions sketch)

    name: sis-ci
    on: [push, pull_request]
    jobs:
      client:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: pnpm/action-setup@v2
          - run: pnpm i --frozen-lockfile
          - run: pnpm -r build
          - run: pnpm -r test
          - uses: actions/upload-artifact@v4
            with: { name: client-dist, path: games/sky-islands-survival/client/build }
      server:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-go@v5
            with: { go-version: "1.22.x" }
          - run: make -C games/sky-islands-survival/server test
          - run: docker build -t registry/sis-nakama:${{ github.sha }} games/sky-islands-survival/server
          - run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login -u token --password-stdin registry
          - run: docker push registry/sis-nakama:${{ github.sha }}
      e2e:
        needs: [client, server]
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - run: pnpm -r e2e
  • ☁️ agones/k8s excerpts

    apiVersion: "agones.dev/v1"
    kind: Fleet
    metadata: { name: sis-nakama }
    spec:
      replicas: 2
      template:
        spec:
          ports: [{ name: nakama, containerPort: 7350 }]
          template:
            spec:
              containers:
                - name: nakama
                  image: registry.example.com/funday/sis-nakama:1.0.0
                  env:
                    - name: DB_URL
                      valueFrom: { secretKeyRef: { name: crdb, key: url } }
                    - name: REDIS_ADDR
                      valueFrom: { secretKeyRef: { name: redis, key: addr } }
                    - name: SATORI_API_KEY
                      valueFrom: { secretKeyRef: { name: satori, key: api_key } }
  • 🧩 theme communication examples

    // client/src/lib/theme.ts
    export function bindFundayTheme() {
      window.addEventListener("funday-theme", (e: any) => {
        const { theme, colors } = e.detail
        document.documentElement.setAttribute("data-theme", theme)
        // propagate to shaders/materials if needed
      })
    }
  • πŸ•ΈοΈ nakama js client bootstrap

    import { Client, Session } from "@heroiclabs/nakama-js"
    const client = new Client("defaultkey", location.hostname, "7350", location.protocol === "https:")
    let session: Session
    export async function connect() {
      session = await client.authenticateDevice(`device-${crypto.randomUUID()}`, true)
      const socket = client.createSocket(false)
      await socket.connect(session)
      return socket
    }
  • 🧱 build placement validation (go)

    func ValidateBuildPlace(s *State, u UserID, bp BuildPlace) error {
      if !HasResources(s, u, bp.BlueprintId) { return errNoResources }
      if !GridAligned(bp.Grid) { return errGrid }
      if OverlapsRestricted(s, bp) { return errOverlap }
      return nil
    }
  • 🧡 asset pipeline commands

    # convert textures to ktx2
    npx ktx2 --uastc 4 assets/textures/src/**/*.png -o assets/textures/ktx2/
     
    # draco compress models
    npx gltf-pipeline -i assets/models/src/ -o assets/models/ -d
     
    # generate asset manifest with hashes
    node scripts/mk-manifest.mjs
  • 🧰 local testing quickstart

    # 1) start infra
    docker compose -f ops/docker-compose.dev.yml up -d
     
    # 2) run nakama with modules and db migrations
    make -C games/sky-islands-survival/server migrate run
     
    # 3) run client dev
    pnpm --filter @sis/client dev --open
     
    # 4) open http://localhost:5173/games/sky-islands-survival/
  • πŸ§ͺ platform route smoke test

    curl http://localhost:5173/games/sky-islands-survival/index.html | head
  • πŸ›‘οΈ security checklist

    • csp headers set; only self cdn and allowed font domains
    • iframe sandbox without top navigation; no storage access to parent
    • input sanitation for chat and names; profanity filter
    • rate-limit rpc; captchas on abusive create-party bursts
    • db roles least privilege; rotate creds; daily backups
  • 🧭 accessibility & localization

    • ui contrast meets wcag aa in dark and light themes
    • scalable text, rem-based sizing; focus states; keyboard navigation in menus
    • i18n json catalogs; rtl safe layouts; number/date regionalization
  • 🧯 runbooks

    • incident triage

      • elevated ws rtt: check regional saturation, fleet replicas, redis latency
      • desync spike: roll back latest match module; inspect checksums
      • crash loops: fetch pod logs, inspect ooms, reduce wave size flag
    • rollback

      • blue/green modules; toggle feature flags; drain agones fleet
  • 🧰 publishing steps

    • validate funday-plugin.json with schema
    • verify theme messaging and responsiveness
    • optimize assets and regenerate manifest
    • run e2e across chromium/firefox/webkit
    • tag client and server releases; push images; apply agones fleet update
    • register game in platform catalog and provide screenshots/readme
  • βœ… production readiness checklist

    • plugin manifests present and valid
    • perf budgets met on mid-tier mobile and desktop
    • p95 rtt < 120 ms eu; error rate < 1%
    • observability dashboards populated; alerts green for 24h burn-in
    • load test at 2k ccu per node passes; no memory leaks
    • security review complete; backups verified; dr run tested
  • 🧩 funday modal integration notes

    • set integrationType to iframe-themeable in client manifest
    • respond to funday:theme-inject events
    • post funday:game-ready when boot complete
    • emit status events optionally: { type:β€˜funday:game-status’, status:β€˜loading|ready|error’ }
  • πŸ—‚οΈ example ui snippet (daisyui)

    <div class="p-3">
      <div class="stats shadow">
        <div class="stat">
          <div class="stat-title">Wave</div>
          <div class="stat-value">{wave}</div>
        </div>
        <div class="stat">
          <div class="stat-title">Resources</div>
          <div class="stat-value">{wood}</div>
        </div>
      </div>
      <button class="btn btn-primary mt-3" onclick={startMatch}>Start</button>
    </div>
  • 🧠 coding standards

    • typescript strict, eslint + prettier; go vet + staticcheck
    • commit style conventional commits; trunk-based development
    • zero any types; explicit json schemas; pb later for binary
    • deterministic sim inputs; never trust client side collisions
  • πŸŽ‰ fun day inspirations

    • daily island seed remix hour for boosted drops
    • photo mode with postcard export
    • glider races between waves for mini-events
    • cozy campfire lobby with emotes before matchmaking
  • 🏁 acceptance criteria

    • player can host/join match, build defenses, survive wave 3, extract with rewards
    • theme integration responds instantly; pwa installable; reconnect after network blip
    • analytics events fire and appear in dashboards
    • ci passes unit/integration/e2e; load test within budgets
    • publishing checklist fully green
  • 🧩 appendices

    • client bootstrap main.ts

      import App from "./app/App.svelte"
      import { bindFundayTheme } from "$lib/theme"
      bindFundayTheme()
      const app = new App({ target: document.getElementById("app")! })
      export default app
    • docker-compose.dev.yml excerpt

      services:
        cockroach:
          image: cockroachdb/cockroach:latest
          command: start-single-node --insecure
          ports: ["26257:26257", "8080:8080"]
        redis:
          image: redis:7
          ports: ["6379:6379"]
        nakama:
          image: heroiclabs/nakama:3
          depends_on: [cockroach, redis]
          ports: ["7350:7350", "7351:7351"]
          volumes: ["../server/modules:/nakama/data/modules", "../server/sql:/nakama/data/sql"]
    • service worker register

      if ("serviceWorker" in navigator) {
        window.addEventListener("load", () => navigator.serviceWorker.register("/service-worker.js"))
      }
    • json schema example for input

      {
        "$id": "sis.input.v1",
        "type": "object",
        "properties": {
          "seq": { "type": "integer", "minimum": 0 },
          "axes": {
            "type": "object",
            "properties": { "x": { "type": "number" }, "y": { "type": "number" } },
            "required": ["x", "y"]
          },
          "actions": { "type": "object" },
          "ts": { "type": "integer" }
        },
        "required": ["seq", "axes", "ts"],
        "additionalProperties": false
      }
  • 🧨 go/no-go rule

    • only merge to main when production readiness checklist is met and burn-in can start within 24 hours with oncall scheduled
  • 🧭 end of briefing

    • start with local stack, bring up the client, verify theme handshake, connect to nakama, run a 4-player bot match, then proceed to load and e2e gates
    • keep the core principles: web-first performance, server authority, small delightful loops, and liveops-friendly content

Ask Docs

AI assistant to help answer questions about the documentation. Answers are read-only and cite docs/source.

Hi! How can I help you with the documentation today? Answers are read-only and cite docs/source.

Ctrl+Enter to send