
This engineering journal documents the real architecture decisions, production failures and durable rules learned while building Taht ve Kan.
1. Architecture: the client requests, the server decides
React client → Server Actions → pure game logic → Supabase/PostgreSQL
The client is never trusted. It can request an attack, but turn ownership, action points, adjacency and authorization are verified on the server. Combat, economy, Elo and league thresholds live as database-independent pure functions. RLS is enabled on every table and anonymous access cannot read rows.
2. Identity: guest and account players share one resolver
Guests use an httpOnly per-game token; signed-in players use a 30-day session backed by a scrypt hash. resolvePlayer() accepts both. The secret game token is never added to a broad select list, and fields returned to the client are explicitly enumerated.
3. The real performance bottleneck was geography
The database ran in Frankfurt while server functions initially ran in a distant default region. Dozens of sequential calls repeatedly paid roughly 90 ms of network latency. Pinning functions to fra1 and consolidating account loading from seven round trips to two fixed the actual source of the perceived slowness.

4. Migration resilience
Schema changes and code deployments are not guaranteed to happen together. A query reading a new column is isolated and guarded; when the column does not exist, the default value is used. Breaking this rule with daily_streak caused the entire main select to fail and made cosmetics and the admin flag appear to reset. The permanent rule is simple: never put a guarded column in the main select.
5. Three defenses against stalled matches
- A turn deadline advances play automatically.
- Players, timeouts and AI all pass through the same
advanceTurnCore()function. - Long-abandoned matches can be taken over by AI.
Unused action points also pay a small gold and food bonus, creating an economic nudge to finish a turn instead of holding it.
6. Three generations of the map
The first version used procedural SVG hexes: clean engineering, generic presentation. The second used a painted tile atlas but still looked like a board. The current map is one painted world divided into 22 asymmetric regions by terrain-aware Voronoi segmentation. Artificial white borders were removed; ownership is communicated through soft color influence. Legacy 40-region matches still open with their original renderer so old games remain valid.

7. Android is not a WebView
The Android client began as a TWA wrapper and was rebuilt as a native Kotlin and Jetpack Compose application. The game engine was rewritten in Kotlin, supports offline play and uses the same backend through dedicated mobile endpoints when online. Defaults for new fields and tolerance for unknown fields protect older local saves and APK versions.
8. Decoupling server cost from registered users
- Completed matches skip battle, espionage and event logs.
- Polling stops while
document.hiddenis true. - Chat polls every eight seconds and turn refresh every eighteen seconds.
- The expensive map is memoized and the one-second timer lives in a separate component.
As a result, cost tracks active turns more closely than total accounts.
9. Platform-specific traps
"use server"modules may export only async functions.- Server-only modules cannot be imported into client components.
- Windows case-insensitivity created a real collision between
recentGames.tsandRecentGames.tsx. - Path invalidation cannot be called from a server action during page rendering, so the email verification flow had to be reorganized.
10. Current state
The game is in closed beta with registration, approval, sign-in, email verification, invitations and feedback collection active. Thirty database migrations have been applied. The native Android client is at version 1.8.1. Real payment integration remains intentionally deferred while debugging, balance and retention take priority.

Three product screens, three player decisions
The screenshots in this journal are not decorative renders. They are production interfaces used to test whether an asynchronous strategy game can make a meaningful decision legible before the player spends an action point. Each screen also exposes a constraint that shaped the engineering work described above.
Map: uncertainty must remain actionable
The region map brings ownership, terrain and army presence into one decision surface. Its job is not to show every stored value. It gives the player enough context to decide whether to defend, recruit, scout or wait for the next turn. That is why the map renderer and the server-authoritative rules need to agree on what is current, what is estimated and what belongs to a previous state. The same principle guided the move away from hard visual borders toward softer ownership influence.
Technology: cost, dependency and consequence
The technology screen presents military, economy and espionage choices as separate branches. A player can see the action-point cost, the resource cost, the benefit and the prerequisite before committing. This makes the dependency graph a product decision rather than a hidden data structure. In implementation terms, it also requires versioned rules, guarded configuration and a safe response when an older client does not yet know about a new field.
Army: composition is a readable trade-off
The army screen combines a production region, unit costs, combat roles and the current distribution of troops. It makes composition visible as a trade-off between attack, defense, food and gold, instead of a single strength number. The active combination bonuses give the player a reason to consider several unit types. These screens are the practical test for the architecture: if an action, its cost and its result cannot be explained here, the underlying system is not ready to ship.