<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Adil Bouchnita</title>
    <description>The latest articles on Forem by Adil Bouchnita (@adilbouchnita).</description>
    <link>https://forem.com/adilbouchnita</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3863448%2F55b28800-4a4c-4f47-87e3-a969e2a180c1.jpg</url>
      <title>Forem: Adil Bouchnita</title>
      <link>https://forem.com/adilbouchnita</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/adilbouchnita"/>
    <language>en</language>
    <item>
      <title>Supabase as a Game Backend: A Practical Guide</title>
      <dc:creator>Adil Bouchnita</dc:creator>
      <pubDate>Wed, 08 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://forem.com/adilbouchnita/supabase-as-a-game-backend-a-practical-guide-p2h</link>
      <guid>https://forem.com/adilbouchnita/supabase-as-a-game-backend-a-practical-guide-p2h</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiz1clk3avnbc5kk259ju.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiz1clk3avnbc5kk259ju.jpg" alt="Supabase game backend illustration showing Supabase logo powering multiple game genres including fantasy RPG, sci-fi shooter, and adventure games, representing scalable real-time backend for Unity and WebGL games" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Supabase is an open-source Firebase alternative built on PostgreSQL that provides auth, storage, Edge Functions, Row Level Security, and a REST API via PostgREST. It's not designed as a game backend, but with the right architecture it can serve as one.&lt;/p&gt;

&lt;p&gt;The first instinct when you need a game backend is to reach for something that calls itself a game backend. That sounds logical, but it means inheriting an opinionated architecture before you've even defined your own.&lt;/p&gt;

&lt;p&gt;I've shipped a game on Supabase. The specifics are under NDA, but the patterns are not. What I found is that Supabase handles auth, storage, server logic, and structured game data surprisingly well, as long as you architect around what it actually is: a Postgres-backed platform with a clean API layer. Not a game server. Not a networking stack. A persistence and logic layer that happens to be very good at the things most game backends need.&lt;/p&gt;

&lt;p&gt;This guide is engine-agnostic. Whether you're working in Unity, Godot, Unreal, or something custom, the architecture applies. If your engine can make HTTP calls and parse JSON, you can use Supabase as your backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Backend Landscape
&lt;/h2&gt;

&lt;p&gt;Before talking about Supabase specifically, it helps to understand what else is out there and what tradeoffs each option brings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PlayFab&lt;/strong&gt; is full-featured and Microsoft-backed, but its proprietary entity model means you're building around PlayFab's data assumptions. Migration is painful. You're locked in the moment you commit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nakama&lt;/strong&gt; is open-source and Go-based, with strong real-time capabilities. You can self-host or use Heroic Cloud (a managed offering), but either way you're managing more game-specific infrastructure than a fully managed BaaS gives you out of the box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firebase&lt;/strong&gt; is the easiest starting point. Google auth, Firestore, Cloud Functions. But Firestore is NoSQL, and once your game data gets relational (and it will), you're fighting the data model instead of building features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom servers&lt;/strong&gt; give you maximum flexibility and maximum operational burden. You own every line of code, every deployment, every scaling decision. For a solo dev or small team, that's often more infrastructure than game development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supabase&lt;/strong&gt; sits in an interesting middle ground. You get a real Postgres database, a REST API via PostgREST, Edge Functions for server logic, Storage for binary assets, built-in auth, and Row Level Security. Low vendor lock because everything underneath is open-source Postgres. If you outgrow the hosted service, you can self-host or migrate to raw Postgres and keep your schema intact.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;PlayFab&lt;/th&gt;
&lt;th&gt;Nakama&lt;/th&gt;
&lt;th&gt;Firebase&lt;/th&gt;
&lt;th&gt;Supabase&lt;/th&gt;
&lt;th&gt;Custom&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vendor Lock&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Getting Started&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Hard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engine SDKs&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Fair&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data Model&lt;/td&gt;
&lt;td&gt;Proprietary&lt;/td&gt;
&lt;td&gt;SQL/Lua&lt;/td&gt;
&lt;td&gt;NoSQL&lt;/td&gt;
&lt;td&gt;SQL (Postgres)&lt;/td&gt;
&lt;td&gt;Your choice&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I reached for Supabase because I wanted a real database with real SQL, without managing my own infrastructure. The fact that it came with auth, storage, and serverless functions out of the box meant I could focus on building the game instead of building the backend for the backend.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm2kbgtvn0fgqc21f3jaf.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm2kbgtvn0fgqc21f3jaf.webp" alt="Drake meme: rejecting Firebase NoSQL, approving Supabase Postgres" width="600" height="919"&gt;&lt;/a&gt; &lt;em&gt;When someone suggests using a document database for relational game data.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Relational Database Makes Sense for Games
&lt;/h2&gt;

&lt;p&gt;Most game data is inherently relational. Player profiles reference inventories. Inventories reference item definitions. Tournaments reference both players and scores. Leaderboards join players, scores, and time windows. Progression systems track relationships between achievements, quests, and player state.&lt;/p&gt;

&lt;p&gt;NoSQL databases handle each of these individually just fine. The pain starts when you need to query across them. "Show me the top 10 players in this tournament who own a specific item" is a single SQL query with two joins. In Firestore, it's multiple reads, client-side filtering, and a data model you'll restructure three times before it works.&lt;/p&gt;

&lt;p&gt;Postgres handles relational game data naturally. Foreign keys enforce referential integrity (no orphaned inventory items pointing to deleted players). Indexes make leaderboard queries fast. Transactions keep concurrent score submissions consistent. And when you do need flexible, schemaless data (player settings, custom loadouts, event configurations), JSONB columns let you store structured JSON inside your relational tables without sacrificing the ability to query it.&lt;/p&gt;

&lt;p&gt;The relational model isn't just a preference. For most game backends, it's the right tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structuring Game Data in Postgres
&lt;/h2&gt;

&lt;p&gt;Game concepts map cleanly to database tables. Here's a minimal schema that covers the core of most indie games: player identity, inventory, and live events.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Player profiles linked to auth&lt;/span&gt;
&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;profiles&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;references&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;display_name&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;unique&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="n"&gt;jsonb&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Game economy&lt;/span&gt;
&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;inventories&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;player_id&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;references&lt;/span&gt; &lt;span class="n"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;item_type&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="n"&gt;jsonb&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Live events&lt;/span&gt;
&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;tournaments&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;starts_at&lt;/span&gt; &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ends_at&lt;/span&gt; &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;jsonb&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth noting about this structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Foreign keys do the heavy lifting.&lt;/strong&gt; The &lt;code&gt;profiles.id&lt;/code&gt; column references &lt;code&gt;auth.users&lt;/code&gt;, so every profile is automatically tied to an authenticated user. The &lt;code&gt;inventories.player_id&lt;/code&gt; references &lt;code&gt;profiles(id)&lt;/code&gt;, so you can never have inventory items floating around without an owner. The database enforces these relationships. Your application code doesn't have to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSONB handles the flexible parts.&lt;/strong&gt; Not everything in a game fits neatly into typed columns. Player settings (control preferences, UI layout, notification toggles) vary per player and change shape over time. Item metadata (enchantments, custom colors, upgrade paths) differs per item type. Tournament config (scoring rules, entry fees, prize pools) changes per event. JSONB columns let you store this kind of semi-structured data inside your relational schema, queryable and indexable, without needing a separate document store.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timestamps with time zones matter.&lt;/strong&gt; Tournaments that start at "8pm" need to mean the same thing regardless of where the server or player is located. Using &lt;code&gt;timestamptz&lt;/code&gt; and storing everything in UTC saves you from an entire category of bugs that only surface when your players span multiple time zones.&lt;/p&gt;

&lt;p&gt;If you're building something with AI-powered NPCs or semantic search (item descriptions, quest logs), Postgres also supports vector columns via the &lt;a href="https://supabase.com/docs/guides/database/extensions/pgvector" rel="noopener noreferrer"&gt;&lt;code&gt;pgvector&lt;/code&gt; extension&lt;/a&gt;. That's a topic for another post, but it's worth knowing the database can grow with you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Supabase Brings to the Table
&lt;/h2&gt;

&lt;p&gt;Supabase wraps Postgres with a set of services that cover most of what a game backend needs. Here's how each one maps to game development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auth
&lt;/h3&gt;

&lt;p&gt;Supabase Auth handles email/password registration, OAuth providers (Google, Discord, Apple, Steam requires a custom Edge Function to bridge its OpenID 2.0 flow to Supabase's OAuth 2.0-based auth), and JWT issuance. You get a complete authentication system without building or maintaining an auth server.&lt;/p&gt;

&lt;p&gt;Every authenticated request carries a JWT. Your game client stores the token after login and includes it in every subsequent request. The token contains the user's ID, which Row Level Security policies use to determine what data that user can access. No middleware. No session management on your end.&lt;/p&gt;

&lt;p&gt;For most indie games, email/password plus one or two OAuth providers covers the entire auth surface. That's maybe twenty minutes of setup in the Supabase dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storage
&lt;/h3&gt;

&lt;p&gt;Supabase Storage is S3-compatible object storage. For games, this means avatars, user-generated content (levels, skins, replays), and downloadable asset bundles.&lt;/p&gt;

&lt;p&gt;Upload is a PUT request with the JWT for authorization. Download URLs are predictable if the bucket is public, or signed if private. The client can construct download paths without extra API calls if you follow a consistent naming convention.&lt;/p&gt;

&lt;p&gt;One caveat: at scale, Supabase Storage pricing can add up for high-bandwidth use cases like large asset bundles downloaded by thousands of players. If you're serving multi-megabyte files to a large player base, consider offloading to a dedicated S3 bucket or CDN. For most indie-scale projects, Supabase Storage is fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Realtime
&lt;/h3&gt;

&lt;p&gt;Supabase Realtime broadcasts Postgres changes over WebSockets. When a row in your database changes, connected clients can receive the update instantly. This is useful for lobby presence (who's online), live leaderboards (scores updating in real time), and notification systems (tournament starting, friend request received).&lt;/p&gt;

&lt;p&gt;But let's be honest about the limits. Supabase Realtime caps at around 10,000 concurrent connections on Pro and Team plans (lower on Free, higher on Enterprise with custom quotas), and performance degrades well before those numbers depending on message volume. This is not a replacement for real-time multiplayer networking. If you need frame-rate state sync (player positions updating 30+ times per second), you need a dedicated networking solution: Photon, Netcode for GameObjects, or custom UDP servers.&lt;/p&gt;

&lt;p&gt;Realtime is a notification channel, not a game networking layer. Use it for infrequent, event-driven updates. Keep your subscription count low and your message payloads small.&lt;/p&gt;

&lt;h3&gt;
  
  
  Edge Functions
&lt;/h3&gt;

&lt;p&gt;Edge Functions are Supabase's serverless compute layer (Deno-based, deployed globally on the edge). Because they run close to your players geographically, request latency is naturally lower than routing everything through a single central server. For games, this is where server-authoritative logic lives. Score validation, tournament lifecycle management, reward distribution, integration with external APIs (payment providers, analytics, third-party services).&lt;/p&gt;

&lt;p&gt;The key principle: Edge Functions should orchestrate, not compute. A function that validates a score submission, writes it to the database, and returns a result is a good use case. A function that runs heavy game simulation logic is not. Keep them single-purpose, lightweight, and fast.&lt;/p&gt;

&lt;p&gt;Cold starts are worth planning for. Supabase has improved them on paper, but real-world results vary and some developers still report longer cold starts than advertised. On top of that, execution time itself can add up if you're doing heavy work inside a function: chaining multiple database queries, calling external APIs, or processing data. Design your game flow around both. Use polished transition animations to mask latency, pre-warm critical functions on login, and keep the functions themselves lightweight so execution stays fast.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Furknnhk8g4ahx1hdnzum.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Furknnhk8g4ahx1hdnzum.webp" alt="Look at me, I'm the middleware now" width="800" height="567"&gt;&lt;/a&gt; &lt;em&gt;Row Level Security replacing your entire auth middleware layer.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Row Level Security
&lt;/h3&gt;

&lt;p&gt;Row Level Security (RLS) is one of the most underappreciated features for game backends. It moves access control from your application code into the database itself.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://supabase.com/docs/guides/database/postgres/row-level-security" rel="noopener noreferrer"&gt;RLS enabled&lt;/a&gt;, you write policies like "players can only read and write their own profile" or "players can read tournament data but only admins can modify it." These policies are enforced at the database level. Even if a malicious client crafts a custom request, the database will reject it. No middleware layer. No authorization checks scattered across your API endpoints.&lt;/p&gt;

&lt;p&gt;For a solo dev or small team, this is a significant reduction in surface area for security bugs. The database handles it. You define the rules once. They apply everywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  JSONB and Vectors
&lt;/h3&gt;

&lt;p&gt;I mentioned JSONB columns earlier in the schema section. At the Supabase level, the important thing is that PostgREST (the REST API layer) can query into JSONB fields. You can filter, sort, and select nested JSON properties via query parameters. This means your flexible data (player settings, item metadata, event config) is still queryable through the API without custom server logic.&lt;/p&gt;

&lt;p&gt;Vector columns via &lt;code&gt;pgvector&lt;/code&gt; enable similarity search directly in your database. If you're building AI-powered features (NPC dialogue, semantic item search, content recommendations), the vector support means you don't need a separate vector database. It all lives in the same Postgres instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making It Work in Production
&lt;/h2&gt;

&lt;p&gt;Knowing what Supabase offers is one thing. Making it perform well in a production game is another. Here are the practical lessons that made the difference.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Filter queries aggressively.&lt;/strong&gt; PostgREST supports column selection and row filtering via query parameters. Every request should specify exactly which columns it needs. A leaderboard query that only needs rank, display name, and score should not pull back the entire player profile with settings, inventory metadata, and timestamps. This matters especially with JSONB columns, where a naive select pulls back large nested objects the client doesn't need.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Index tables properly.&lt;/strong&gt; Leaderboard queries on a scores table with 100,000 rows are fast with the right index. Without one, they're slow enough to notice. Add indexes on columns you filter and sort by. For leaderboards, that's typically the score column plus a tournament ID. For inventories, it's the player ID. This is standard database practice, but it's easy to skip during prototyping and painful to debug later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Realtime is a tool, not a default.&lt;/strong&gt; Don't subscribe to Realtime channels for everything. Subscribe to the leaderboard channel when the player opens the leaderboard screen. Unsubscribe when they leave. Subscribe to lobby presence when they're in the lobby. Don't maintain persistent subscriptions to tables the player isn't actively viewing. Stay well below the concurrent connection ceiling and keep message volume low.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reduce client chattiness.&lt;/strong&gt; This is the single most important architectural decision for a Supabase game backend. Batch reads on login: pull the player's profile, inventory, and active tournaments in a single burst, not across three separate screens. Submit scores once at the end of a round, not incrementally during gameplay. Sync user-generated content metadata in bulk when the player opens the browser, not on every scroll. Fewer round trips means lower latency, fewer rate limit concerns, and fewer failure points.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep Edge Functions lightweight.&lt;/strong&gt; Each function should do one thing. Validate a score. Create a tournament entry. Process a webhook. Don't chain multiple database operations inside a single function call. If you need multi-step pipelines (create a job, wait for external processing, update on callback), use a job table pattern: write the job row, return immediately, and handle the next step via webhook or scheduled check.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plan for Edge Function latency.&lt;/strong&gt; Cold starts can still surprise you despite improvements, and execution time adds up if functions do too much. Pre-warm your most-used functions during the loading screen. Use transition animations between screens to mask round-trip time. Keep function bodies lean so execution stays fast.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consider S3 for storage at scale.&lt;/strong&gt; Supabase Storage works great for development and moderate traffic. If you're serving large asset bundles to thousands of concurrent players, a dedicated S3 bucket with CloudFront or another CDN in front of it will be cheaper and faster.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzjxhytby9dqm25amqwss.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzjxhytby9dqm25amqwss.webp" alt="Not sure if game client or DDoS attack" width="800" height="580"&gt;&lt;/a&gt; &lt;em&gt;What your Supabase dashboard looks like when your game client polls every frame.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  No SDK, No Problem
&lt;/h2&gt;

&lt;p&gt;Supabase has official SDKs for JavaScript, Python, Dart, and Swift. It does not have an official SDK for C#, GDScript, or C++. For game engines, that might sound like a dealbreaker. It's not.&lt;/p&gt;

&lt;p&gt;Supabase's API is just HTTP. Every operation, from auth to database queries to storage uploads, is a REST call. If your engine can make HTTP requests and parse JSON responses, you can talk to Supabase directly.&lt;/p&gt;

&lt;p&gt;The auth pattern is straightforward. Every request includes two headers: the &lt;code&gt;apikey&lt;/code&gt; header with the project's public anon key, and an &lt;code&gt;Authorization&lt;/code&gt; header with a &lt;code&gt;Bearer &amp;lt;jwt&amp;gt;&lt;/code&gt; after login. Registration hits &lt;code&gt;/auth/v1/signup&lt;/code&gt; with an email and password in the body. Login hits &lt;code&gt;/auth/v1/token?grant_type=password&lt;/code&gt;. Both return a JWT that the client stores in memory and attaches to every subsequent request.&lt;/p&gt;

&lt;p&gt;Database queries go through &lt;a href="https://postgrest.org/en/stable/references/api/tables_views.html" rel="noopener noreferrer"&gt;PostgREST&lt;/a&gt; endpoints. Reading a player's profile is a GET request to &lt;code&gt;/rest/v1/profiles?id=eq.{user_id}&amp;amp;select=display_name,level&lt;/code&gt;. Inserting a score is a POST to &lt;code&gt;/rest/v1/scores&lt;/code&gt; with the data in the body. Filtering, sorting, pagination, and column selection are all query parameters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faj0s7hqdeo32zwkubn5n.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faj0s7hqdeo32zwkubn5n.webp" alt="Can't have SDK issues if you don't use an SDK" width="800" height="433"&gt;&lt;/a&gt; &lt;em&gt;No C# SDK? No problem. Raw HTTP works across every engine.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I've done this in production. The HTTP-based approach is clean, debuggable, and completely engine-agnostic. No SDK dependency means no version conflicts, no waiting for SDK updates when Supabase ships new features, and no black-box abstractions hiding the actual API calls. You see every request, every response, every error. For a solo dev debugging at 2am, that transparency is worth more than any SDK convenience.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Supabase Fits (and When It Doesn't)
&lt;/h2&gt;

&lt;p&gt;After building with Supabase in production, here's a clear-eyed assessment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Supabase when
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Your game client isn't chatty. Backend interactions are infrequent, batched, and event-driven (login, score submission, content browsing), not continuous.&lt;/li&gt;
&lt;li&gt;You want to build fast without managing infrastructure. Auth, database, storage, and serverless functions in one platform, no DevOps required.&lt;/li&gt;
&lt;li&gt;You don't want vendor lock-in. Everything runs on Postgres. You can self-host Supabase or migrate to raw Postgres and keep your schema, your queries, and your RLS policies.&lt;/li&gt;
&lt;li&gt;Your backend is mostly CRUD with server-side validation. Profiles, inventories, leaderboards, tournaments, user-generated content. The bread and butter of game persistence.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Don't use Supabase when
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Your game clients are chatty. If players are sending and receiving messages to each other multiple times per second (real-time chat, cooperative gameplay, live trading), you need a dedicated messaging or networking layer.&lt;/li&gt;
&lt;li&gt;You need frame-rate state sync. Real-time multiplayer where player positions, physics, or game state update 30+ times per second is not what Supabase Realtime is built for. Use Photon, Netcode for GameObjects, or custom UDP servers.&lt;/li&gt;
&lt;li&gt;You're planning for massive concurrent scale. Hundreds of thousands of concurrent users hitting your backend simultaneously is beyond what Supabase's hosted plans are designed for. At that scale, you need custom infrastructure regardless of which platform you started with.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest take: Supabase handles more than you'd expect from a platform that doesn't market itself as a game backend. Auth, storage, structured data, server logic, access control. That covers a lot of ground. But it only works because the architecture respects what Supabase is good at and doesn't ask it to be a real-time networking stack. The moment you need sub-100ms server authority over game state at high frequency, you need a different tool for that specific job.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmfonw6epajpoy1vg8jjx.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmfonw6epajpoy1vg8jjx.webp" alt="Game backend pointing at CRUD app: they're the same" width="800" height="554"&gt;&lt;/a&gt; &lt;em&gt;Most game backends are just CRUD apps with extra steps.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you're evaluating backend options for a game project, the question isn't "can Supabase do everything?" It's "can I architect my game so that the real-time parts and the persistent parts live in separate layers?" If yes, Supabase is a strong candidate for the persistent side.&lt;/p&gt;

&lt;p&gt;Take a look at &lt;a href="https://dev.to/projects"&gt;my projects&lt;/a&gt; to see how different architectures play out across different types of applications. If you're also making decisions about how to deliver a real-time interactive experience in the browser, my &lt;a href="https://dev.to/blog/unity-webgl-vs-threejs-vs-pixel-streaming"&gt;comparison of Unity WebGL, Three.js, and Pixel Streaming&lt;/a&gt; covers the client-side half of the equation. And if you're weighing options for your own game backend or need help with the architecture, &lt;a href="https://dev.to/contact"&gt;hire me&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>postgres</category>
      <category>gamebackend</category>
      <category>edgefunctions</category>
    </item>
    <item>
      <title>From Azgaar's Fantasy Map Generator to Playable Kingdom: Building a World Pipeline in Unity</title>
      <dc:creator>Adil Bouchnita</dc:creator>
      <pubDate>Mon, 06 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://forem.com/adilbouchnita/from-azgaars-fantasy-map-generator-to-playable-kingdom-building-a-world-pipeline-in-unity-48j6</link>
      <guid>https://forem.com/adilbouchnita/from-azgaars-fantasy-map-generator-to-playable-kingdom-building-a-world-pipeline-in-unity-48j6</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ba7983ry5uilme5qlcz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ba7983ry5uilme5qlcz.jpg" alt="Unity visualization of an Azgaar Fantasy Map Generator export, featuring terrain heightmap, river networks, and an inset of political regions used for simulation." width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm building Kingdom Sim, a medieval political simulation game in Unity, targeting Steam on PC. You play as a king ruling over a procedurally generated island of competing kingdoms. Your goal is to survive, hold power, and expand your realm through politics, marriages, intrigue, wars, and scheming.&lt;/p&gt;

&lt;p&gt;The world is populated with characters: dukes, barons, courtiers, rivals, each with their own traits, families, and ambitions. Your council advises you. Your dukes may or may not stay loyal. Rival kings plot against you. Succession is never guaranteed. Think Crusader Kings III but scoped way down: a focused experience built around political maneuvering and difficult choices rather than sprawling simulation.&lt;/p&gt;

&lt;p&gt;Before any of that could work, I needed a world worth fighting over. Provinces with towns and castles. Terrain with mountains and rivers that form natural borders. Forests and farmland that affect economy and warfare. A map that players could look at and immediately understand as a place, not a dataset. Hand-crafting all of that as a solo dev was not an option. So I built a pipeline instead.&lt;/p&gt;

&lt;p&gt;Here's how I'm turning a single export from Azgaar's Fantasy Map Generator into a playable Unity world with terrain, dynamic kingdoms, and a feudal hierarchy on top, and where the pipeline stands right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Didn't Work: Voronoi-Based Procedural Maps
&lt;/h2&gt;

&lt;p&gt;My first attempt was a fully procedural Voronoi-based map generator built directly in Unity. Seed random points, compute Voronoi cells, assign each cell a province, build an adjacency graph, run the simulation on top.&lt;/p&gt;

&lt;p&gt;It worked for the simulation. Provinces had adjacency. Kingdoms could expand. The political systems ran fine.&lt;/p&gt;

&lt;p&gt;But it didn't look like a world. Voronoi cells produce clean, geometric regions with straight edges and uniform shapes. No mountain ranges forming natural borders. No rivers carving through valleys that force armies into chokepoints. No coastlines with bays and peninsulas. Every map looked like a stained glass window, not a place anyone would want to conquer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Finwvrmt1a6ffjz6e5540.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Finwvrmt1a6ffjz6e5540.webp" alt="Voronoi-based procedural map in Unity showing geometric province cells with uniform shapes and no geographic features" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That matters more than it sounds. In a political sim, mountain ranges should separate kingdoms. Rivers should define province boundaries. A coastal duchy with a natural harbor should feel different from a landlocked barony surrounded by farmland. Without geography baked into the map, the political layer has nothing meaningful to sit on top of.&lt;/p&gt;

&lt;p&gt;The simulation didn't care, but players would.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Azgaar's Fantasy Map Generator
&lt;/h2&gt;

&lt;p&gt;I needed something that could generate believable fantasy worlds, geography that looks hand-drawn but is fully data-rich under the hood. &lt;a href="https://azgaar.github.io/Fantasy-Map-Generator/" rel="noopener noreferrer"&gt;Azgaar's Fantasy Map Generator&lt;/a&gt; is exactly that.&lt;/p&gt;

&lt;p&gt;It's a free, open-source browser tool that produces detailed fantasy worlds: provinces, burgs (towns), population data, adjacency graphs, heightmaps, biome classifications. Under the hood, it simulates tectonic plates for realistic continent shapes, precipitation models that determine where forests and deserts form, and river systems that flow naturally from highlands to coastlines.&lt;/p&gt;

&lt;p&gt;I export one JSON file and a set of image maps. That single export gives me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Province boundaries with full adjacency data&lt;/li&gt;
&lt;li&gt;Burgs with names, populations, and geographic coordinates&lt;/li&gt;
&lt;li&gt;A heightmap as a grayscale image&lt;/li&gt;
&lt;li&gt;Biome masks: forest, farmland, snow, desert, and more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The JSON becomes my simulation data. The images become my terrain.&lt;/p&gt;

&lt;p&gt;The difference from Voronoi is immediate. Mountain ranges run in realistic chains. Rivers flow downhill from highlands to the sea. Coastlines have natural irregularity with harbors and peninsulas. Provinces follow geographic features, a river boundary, a mountain pass, rather than arbitrary cell edges.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxl9qgm6bz9fclqztvnbb.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxl9qgm6bz9fclqztvnbb.webp" alt="Azgaar's Fantasy Map Generator showing a generated world with realistic coastlines, provinces, and kingdom borders" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It looks like a world that could exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terrain: Heightmaps and World Creator
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn7xsldj4whnjiug8vucf.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn7xsldj4whnjiug8vucf.webp" alt="Azgaar's color-coded elevation map converted to a grayscale heightmap for Unity terrain generation" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Azgaar gives me a heightmap, but raw heightmaps are flat and lifeless. Mountains look like smooth bumps, rivers are invisible, everything feels like a plastic relief map.&lt;/p&gt;

&lt;p&gt;The plan is to bring that heightmap into World Creator, a terrain generation asset for Unity. It takes elevation data and adds what's missing: erosion that carves realistic ridges and river valleys, proper slope distribution, sediment accumulation at the base of hills. The terrain starts looking like it was shaped by weather, not by an algorithm.&lt;/p&gt;

&lt;p&gt;Right now, the terrain is still rough. No erosion passes, no visible rivers or mountain detail. It's a functional heightmap that the simulation runs on, but it doesn't look like a fantasy world yet. That's the next visual pass.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgt21shtn8cnke9uxgje2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgt21shtn8cnke9uxgje2.webp" alt="First terrain pass in Unity showing the imported Azgaar heightmap with basic elevation and texture but no erosion or river detail yet" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Biome-Driven Spawning (Planned)
&lt;/h3&gt;

&lt;p&gt;Azgaar classifies every region by biome: temperate forest, grassland, tundra, and so on. The plan is to use those masks in World Creator to drive procedural placement. Forests on forested regions. Farmland textures on grasslands. Snow and rock on high-altitude tundra. The map paints itself based on the underlying data.&lt;/p&gt;

&lt;p&gt;This isn't wired up yet. But the biome data is already in the export, so the path from data to visuals is clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Map Data to Game Entities
&lt;/h2&gt;

&lt;p&gt;The JSON import pipeline runs once at startup. Azgaar's data maps to my entity model like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Azgaar burgs become Lordships.&lt;/strong&gt; Each burg is a lordship, the smallest ownable unit. The largest burg in a province becomes the ducal seat. Population data feeds into the economy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azgaar provinces become Provinces.&lt;/strong&gt; Direct mapping. They keep their adjacency graph, which drives border rendering, war declarations, and trade routes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azgaar states become nothing.&lt;/strong&gt; I throw these away entirely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point surprised me. Azgaar generates full political states with capitals and diplomacy. But they're fixed per export, and I wanted different kingdom configurations on each playthrough. Fixed geography, dynamic politics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Region-Growing Kingdoms
&lt;/h2&gt;

&lt;p&gt;Instead of using Azgaar's states, I generate kingdoms at runtime using a region-growing algorithm on the province adjacency graph.&lt;/p&gt;

&lt;p&gt;The algorithm picks seed provinces, then expands each kingdom outward one province at a time, respecting adjacency. Each step claims a random unclaimed neighbor, so growth patterns vary between runs. The result is always contiguous territories: no disconnected exclaves, no border gore.&lt;/p&gt;

&lt;p&gt;The number of kingdoms is configurable, and because growth order is randomized, the same map produces different political landscapes every run. One game might have a sprawling northern empire and fragmented southern duchies. The next might split the same continent into evenly matched rivals.&lt;/p&gt;

&lt;p&gt;The player learns the geography over multiple playthroughs while facing new political challenges each time. Same valleys and mountain ranges, different kingdoms claiming them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layering It in Unity
&lt;/h2&gt;

&lt;p&gt;With terrain generated and entities built, the last step is making it all visible.&lt;/p&gt;

&lt;p&gt;Lordships spawn at their Azgaar coordinates as placeholders for now, simple markers positioned on the terrain. The visual hierarchy (towers for small burgs, walled towns for medium, full castles for capitals) is planned but not built yet. What matters at this stage is that every lordship is in the right place, sitting on real terrain, and wired into the simulation.&lt;/p&gt;

&lt;p&gt;Borders took more work than expected. Azgaar exports border data as edge segments between cells, but those segments don't form continuous lines out of the box. I had to chain them into continuous polylines, handling T-junctions where three provinces meet and resolving gaps at corners. The border meshes are subdivided to follow terrain elevation, so they drape over hills and along valleys instead of cutting through in straight lines. Small detail, but it makes the map feel grounded rather than painted on.&lt;/p&gt;

&lt;p&gt;The map uses zoom-based visibility layers. Zoomed out: kingdom borders, names, color-coded territories. Zoom in: province boundaries and labels. Get close: individual lordships, burg labels, settlement icons. Same data at every level, just progressively disclosed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Full Pipeline
&lt;/h2&gt;

&lt;p&gt;Azgaar exports a believable world. Unity imports the JSON, grows kingdoms, and renders the base map. World Creator will eventually turn the flat heightmap into real terrain with erosion and biome-driven vegetation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft3xmy40jvger17dwizyn.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft3xmy40jvger17dwizyn.webp" alt="Kingdom Sim map in Unity showing terrain with kingdom borders, province boundaries, lordship markers, and the feudal hierarchy panel" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What's working right now is the simulation layer. Every noble in the game, from your trusted chancellor to the ambitious duke eyeing your throne, is tied to a specific lordship or province. Their income comes from the land they hold. Marriage alliances connect families across provincial borders. War declarations follow adjacency graphs. When a duke dies without an heir and three barons each claim his seat, that crisis plays out on real geography.&lt;/p&gt;

&lt;p&gt;The visual side, terrain detail, biome spawning, settlement art, is still placeholder. But the data pipeline is complete. Every province has real adjacency. Every lordship has a position and an owner. Every kingdom has borders that follow geographic features. The simulation doesn't need pretty terrain to run, it needs correct data, and that's what the pipeline delivers.&lt;/p&gt;

&lt;p&gt;Because biome data is already in the Azgaar export, there's a clear path to tying gameplay to geography later: farmland provinces producing more tax income, forested regions slowing army movement, mountain passes becoming chokepoints. The data is there, waiting to be wired up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where It Stands
&lt;/h2&gt;

&lt;p&gt;The data pipeline works. The political simulation runs on real geography. The map is functional, not pretty yet, but it's a foundation that everything else can build on.&lt;/p&gt;

&lt;p&gt;What's built: Azgaar import, province and lordship entity mapping, region-growing kingdom generation, border rendering, zoom-based map layers, and the feudal hierarchy that ties characters to land.&lt;/p&gt;

&lt;p&gt;What's next: World Creator terrain passes (erosion, rivers, mountains), biome-driven vegetation spawning, settlement art replacing placeholders, and then the player-facing systems: decisions, rival AI, war, succession.&lt;/p&gt;

&lt;p&gt;The world generation pipeline is done. Now I get to build the part that makes it dangerous.&lt;/p&gt;

</description>
      <category>unity3d</category>
      <category>proceduralgeneration</category>
      <category>gamedev</category>
      <category>worldbuilding</category>
    </item>
  </channel>
</rss>
