Docs / Configuration

Configuration reference

All Asobi config lives under the asobi OTP application. Set it via sys.config (releases), config.exs/sys.config.src (env-var templating), or application:set_env/3 at runtime.

Database

{kura, [
    {repo,     asobi_repo},
    {backend,  kura_backend_postgres},
    {host,     "localhost"},
    {port,     5432},
    {database, "asobi"},
    {user,     "postgres"},
    {password, "postgres"},
    {pool_size, 10}
]}

Asobi runs on Kura 2.x with pluggable backends. Add kura_postgres to your rebar.config deps and the backend key tells Kura which one to use. Swap to kura_backend_sqlite for an embedded setup.

Environment variables (consumed via sys.config.src): ASOBI_DB_HOST, ASOBI_DB_NAME, ASOBI_DB_USER, ASOBI_DB_PASSWORD.

HTTP / WebSocket

{nova, [
    {cowboy_configuration, #{port => 8080}},
    {plugins, [
        {pre_request, nova_cors_plugin, #{allow_origins => <<"*">>}}
        %% ... other pre/post request plugins
    ]}
]}

Game modes (matches and worlds)

All per-mode config — whether a mode is a match or a world, which module implements it, match size, tick rate, bots, spatial config — lives under the single game_modes map.

{asobi, [
    {game_modes, #{
        ~"arena" => #{
            module      => arena_game,
            match_size  => 4,
            max_players => 4,
            tick_rate   => 50,          %% ms per tick (default 100)
            strategy    => fill,        %% fill | skill_based | module()
            bots        => #{enabled => true, min_players => 4,
                             script => ~"bots/arena.lua"}
        },
        ~"world1" => #{
            type         => world,
            module       => {lua, ~"world1/match.lua"},
            grid_size    => 10,
            zone_size    => 200,
            view_radius  => 1,
            tick_rate    => 50,
            persistent   => false,
            lazy_zones   => true,
            zone_idle_timeout => 30000,
            max_active_zones  => 10000
        }
    }}
]}

Matchmaker

{asobi, [
    {matchmaker, #{
        tick_interval    => 1000,
        max_wait_seconds => 60
    }}
    %% Strategy is per-mode — see the `strategy` key under game_modes.
]}

Voting

{asobi, [
    {vote_templates, #{
        <<"default">>   => #{method => <<"plurality">>, window_ms => 15000,
                             visibility => <<"live">>},
        <<"boon_pick">> => #{method => <<"plurality">>, window_ms => 15000}
    }}
]}

Authentication

{asobi, [
    {base_url, ~"https://api.example.com"},   %% used for OIDC redirects
    {oidc_providers, #{
        google => #{issuer => ~"https://accounts.google.com",
                    client_id => ~"...", client_secret => ~"..."},
        apple  => #{issuer => ~"https://appleid.apple.com",
                    client_id => ~"...", client_secret => ~"..."}
    }},
    {steam_api_key, ~"..."},
    {steam_app_id,  ~"..."},
    {apple_bundle_id,    ~"com.example.game"},
    {google_package_name, ~"com.example.game"},
    %% Apple StoreKit 2 receipt verification — root CA used for x5c chain validation.
    %% Defaults to priv/apple_root_ca.pem inside the asobi app.
    {apple_root_ca_path, ~"/etc/asobi/apple_root_ca.pem"}
]}

Rate limits

Per-route limits enforced by asobi_rate_limit_plugin. Defaults: 5 req/sec/IP on auth, 10 on IAP, 300 elsewhere. The auth limiter is the brute-force gate — a 5/sec cap plus the bcrypt cost on login makes online password guessing infeasible at internet scale.

{asobi, [
    {rate_limits, #{
        auth => #{limit => 5,   window => 1000},
        iap  => #{limit => 10,  window => 1000},
        api  => #{limit => 300, window => 1000}
    }}
]}

The dev/test sys config bumps all three to 1000 because CT bursts register/login calls against 127.0.0.1.

World capacity

Caps on persistent worlds (world server). When a player tries to create a world beyond the per-player cap, the API returns 429; when the global cap is hit, 503.

{asobi, [
    {world_max_per_player, 5},     %% default 5
    {world_max,            1000}   %% default 1000
]}

Terrain provider allowlist (asobi_lua only)

A Lua script returning { module = "<some_atom>", ... } from terrain_provider/1 must name a module on this allowlist. Defaults to the two built-in providers; widen explicitly if you ship a custom one.

{asobi_lua, [
    {terrain_providers, [asobi_terrain_flat, asobi_terrain_perlin]}
]}

Per-call upper bounds

These limits exist to bound the cost of a single hostile request and are not currently runtime-tunable. See the security guide for the rationale.

 Endpoint / surface          | Limit
-----------------------------|------------------------------------------------
 Cloud save body             | 256 KB
 Cloud save slots / player   | 10
 Inventory consume quantity  | 1 .. 1_000_000
 Leaderboard top ?limit      | 1 .. 100
 Leaderboard around ?range   | 1 .. 50
 Chat history ?limit         | 1 .. 200
 DM content                  | 2000 bytes
 WS chat channels / conn     | 32
 Idle channel timeout        | 60 s
 Lua decode depth            | 64 levels

Leaderboards

Leaderboards are spawned per-board on demand — there is no config map. Start one eagerly with asobi_leaderboard_sup:start_board/1, or just call asobi_leaderboard_server:submit/3 and the first hit will spawn it.

Lua runtime

{asobi, [
    {game_dir, "/app/game"}   %% where asobi_lua_config looks for match.lua / config.lua
]},
{asobi_lua, []}

Clustering

{asobi, [
    {cluster, #{
        strategy       => dns,               %% dns | epmd
        dns_name       => ~"asobi-headless", %% DNS A record (for `dns`)
        hosts          => [],                %% list of hosts (for `epmd`)
        poll_interval  => 10000              %% ms between discovery polls
    }}
]}

Telemetry & logs

{kernel, [
    {logger, [
        {handler, default, logger_std_h, #{
            level => info,
            formatter => {nova_jsonlogger_formatter, #{}}
        }}
    ]}
]}

Common env vars

These are the variables consumed by the published asobi_lua image's sys.config.src:

 ASOBI_PORT           HTTP/WebSocket listen port (required)
 ASOBI_DB_HOST        Postgres host
 ASOBI_DB_NAME        Postgres database name
 ASOBI_DB_USER        Postgres user
 ASOBI_DB_PASSWORD    Postgres password
 ASOBI_CORS_ORIGINS   Comma-separated allowed origins for CORS

Where next?