<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Dynamodb on Worlds of the Next Realm - Dev Blog</title><link>https://ipjohnson-org.github.io/WorldsOfTheNextRealm.Blog/tags/dynamodb/</link><description>Recent content in Dynamodb on Worlds of the Next Realm - Dev Blog</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Tue, 17 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://ipjohnson-org.github.io/WorldsOfTheNextRealm.Blog/tags/dynamodb/index.xml" rel="self" type="application/rss+xml"/><item><title>The Development Journey, Part 1: Nine Days from Zero</title><link>https://ipjohnson-org.github.io/WorldsOfTheNextRealm.Blog/p/the-development-journey-part-1-nine-days-from-zero/</link><pubDate>Tue, 17 Feb 2026 00:00:00 +0000</pubDate><guid>https://ipjohnson-org.github.io/WorldsOfTheNextRealm.Blog/p/the-development-journey-part-1-nine-days-from-zero/</guid><description>&lt;p&gt;This is the first of a three-part series covering the development journey of Worlds of the Next Realm so far. In this post, we cover the foundation: standing up infrastructure, building the first services, and getting something on screen.&lt;/p&gt;
&lt;p&gt;The timeline is aggressive. The first commit landed on February 8th, 2026. Nine days later, we had 11 repositories, a live beta environment, a working authentication system, a Flutter web client with isometric tile maps, and a backend serving real game data from DynamoDB.&lt;/p&gt;
&lt;h2 id="day-1-2-the-foundation-sprint-feb-8-10"&gt;&lt;a href="#day-1-2-the-foundation-sprint-feb-8-10" class="header-anchor"&gt;&lt;/a&gt;Day 1-2: The Foundation Sprint (Feb 8-10)
&lt;/h2&gt;&lt;p&gt;Everything started with the Flutter client and the AWS infrastructure.&lt;/p&gt;
&lt;p&gt;The FrontEndClient repo received 12 PRs in the first two days. We scaffolded the entire application structure — domain models, theming, navigation, mock backend layer, and all the core UI screens: troops, leaders, research, guild, inventory, expeditions, barter, events, settings, chat, shop, and AI companion. Most of these screens were populated with mock data, but the architecture was real — Riverpod state management, GoRouter navigation, Dio HTTP client with interceptors.&lt;/p&gt;
&lt;p&gt;The most significant piece was the isometric 2.5D tile map renderer built on the Flame game engine. This would become the city view and world map — the visual heart of the game.&lt;/p&gt;
&lt;p&gt;Simultaneously, the Infra repo went up with CDK stacks for the VPC, DynamoDB tables, Application Load Balancer, and CloudFront distribution. We deployed the BackendApi on Lambda behind API Gateway, and the NotificationService and WorldSimulation on Fargate.&lt;/p&gt;
&lt;p&gt;All three backend services had working CDK deployments by the end of day 2. The CI/CD pipelines were live — every push to main deployed to beta automatically.&lt;/p&gt;
&lt;h2 id="day-3-4-the-data-layer-feb-11-12"&gt;&lt;a href="#day-3-4-the-data-layer-feb-11-12" class="header-anchor"&gt;&lt;/a&gt;Day 3-4: The Data Layer (Feb 11-12)
&lt;/h2&gt;&lt;p&gt;This is where BackendCommon became the backbone of the project.&lt;/p&gt;
&lt;p&gt;We built a generalized DynamoDB data store abstraction — a single-table design where every game entity (players, cities, buildings, resources, troops, worlds) lives in one table with partition key patterns and GSIs for alternate access patterns. The data store handles serialization, optimistic concurrency via version IDs, and batch operations.&lt;/p&gt;
&lt;p&gt;The DynamoDB schema went through several iterations during these two days:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Started with a partition key + sort key design&lt;/li&gt;
&lt;li&gt;Removed the sort key&lt;/li&gt;
&lt;li&gt;Added GSI1&lt;/li&gt;
&lt;li&gt;Added GSI2&lt;/li&gt;
&lt;li&gt;Restored the sort key and added GSI1&lt;/li&gt;
&lt;li&gt;Recreated the table as PK-only with GSI1&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That&amp;rsquo;s 6 PRs in the Infra repo just iterating on the table schema. Each change required coordinating across BackendCommon (data models), Infra (CDK table definition), and the services that read the data. This is one of the costs of a multi-repo architecture — schema changes ripple across repositories.&lt;/p&gt;
&lt;p&gt;The OperationalTools CLI also came to life during this phase: &lt;code&gt;create-user&lt;/code&gt;, &lt;code&gt;load-game-data&lt;/code&gt;, &lt;code&gt;generate-map&lt;/code&gt;, and &lt;code&gt;bootstrap-world&lt;/code&gt; commands. These tools are essential for populating the game world with data — definition files for buildings, troops, resources, and research loaded from JSON into DynamoDB.&lt;/p&gt;
&lt;h2 id="day-5-authentication-and-api-wiring-feb-13"&gt;&lt;a href="#day-5-authentication-and-api-wiring-feb-13" class="header-anchor"&gt;&lt;/a&gt;Day 5: Authentication and API Wiring (Feb 13)
&lt;/h2&gt;&lt;p&gt;February 13th was one of the busiest days of the project. Across all repos, we merged PRs covering:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Authentication service&lt;/strong&gt;: Full username/password auth with RS256 JWT tokens, Argon2id password hashing, family-based refresh token rotation, and JWKS endpoint for public key distribution. The master encryption key went through its own evolution — first as a CDK context value, then moved to AWS Secrets Manager for proper secret management.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;JWT middleware in BackendApi&lt;/strong&gt;: All API endpoints now verified authentication tokens. We added request/response models for all 22 planned endpoints and created stub handlers for each one.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Frontend auth integration&lt;/strong&gt;: The Flutter client was hooked up to the real authentication service. The mock login button was removed. Real JWT tokens flowed from login through to API calls.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Structured logging&lt;/strong&gt;: Every service got consistent JSON logging — critical for debugging in a distributed system where you need to correlate requests across Lambda, Fargate, and CloudWatch.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security hardening&lt;/strong&gt;: CloudFront origin verify headers and WAF rules on the ALB to prevent direct access bypassing the CDN.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This was also the day of the &amp;ldquo;README wave&amp;rdquo; — every repo got a README, documentation links, and design doc references. Housekeeping, but important for a multi-repo project where anyone (human or AI) needs to find their way around.&lt;/p&gt;
&lt;h2 id="the-first-render"&gt;&lt;a href="#the-first-render" class="header-anchor"&gt;&lt;/a&gt;The First Render
&lt;/h2&gt;&lt;p&gt;Getting the isometric city to render with real data in the browser was the first real milestone. The early versions had&amp;hellip; issues.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://ipjohnson-org.github.io/WorldsOfTheNextRealm.Blog/p/the-development-journey-part-1-nine-days-from-zero/missing-icons.png"
	width="3016"
	height="1556"
	loading="lazy"
	
		alt="Early city view with missing navigation icons"
	
 
 title="The first render of the city view. Buildings are placed, the isometric grid works, but the bottom navigation bar icons are all missing — a Flutter web asset deployment issue."
 data-title-escaped="The first render of the city view. Buildings are placed, the isometric grid works, but the bottom navigation bar icons are all missing — a Flutter web asset deployment issue."
 
	
		class="gallery-image" 
		data-flex-grow="193"
		data-flex-basis="465px"
	
&gt;&lt;/p&gt;
&lt;p&gt;The bottom navigation bar icons were missing because of how Flutter web packages and deploys JSON-declared assets. It took a dedicated fix to properly deploy subdirectory JSON assets to S3.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://ipjohnson-org.github.io/WorldsOfTheNextRealm.Blog/p/the-development-journey-part-1-nine-days-from-zero/clouds.png"
	width="3020"
	height="1566"
	loading="lazy"
	
		alt="Cloud rendering dominating the viewport"
	
 
 title="An early attempt at the city exterior. The cloud/fog-of-war effect was supposed to mark unexplored territory, but it completely dominates the view, obscuring everything underneath."
 data-title-escaped="An early attempt at the city exterior. The cloud/fog-of-war effect was supposed to mark unexplored territory, but it completely dominates the view, obscuring everything underneath."
 
	
		class="gallery-image" 
		data-flex-grow="192"
		data-flex-basis="462px"
	
&gt;&lt;/p&gt;
&lt;p&gt;The cloud overlay — intended to obscure the city exterior — was rendering far too aggressively. The terrain and buildings underneath were completely invisible. This would take several more days and multiple PRs to get right.&lt;/p&gt;
&lt;h2 id="what-we-learned"&gt;&lt;a href="#what-we-learned" class="header-anchor"&gt;&lt;/a&gt;What We Learned
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Multi-repo coordination is expensive.&lt;/strong&gt; A schema change in DynamoDB touches Infra (CDK), BackendCommon (models), BackendApi (endpoints), OperationalTools (data loading), and sometimes the FrontEndClient (API contracts). The NuGet package pipeline adds latency — you merge a BackendCommon PR, wait for CI to publish the package, then update dependent repos. We ended up with explicit rules: create the BackendCommon PR first, wait for CI, then create dependent PRs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mock-first works.&lt;/strong&gt; Building the entire Flutter client with mock backends first meant we could iterate on UI and navigation without waiting for the real APIs. When the APIs were ready, we swapped in real repositories one at a time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CDK schema iteration is painful.&lt;/strong&gt; Six PRs to get the DynamoDB table right. Each one required a CDK deploy, which means CloudFormation stack updates, which means waiting for DynamoDB table operations to complete. Some of these were destructive — dropping and recreating the table. In a production environment, this would require careful migration planning.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="stats-days-1-5-feb-8-13"&gt;&lt;a href="#stats-days-1-5-feb-8-13" class="header-anchor"&gt;&lt;/a&gt;Stats: Days 1-5 (Feb 8-13)
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Metric&lt;/th&gt;
 &lt;th&gt;Value&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Repositories created&lt;/td&gt;
 &lt;td&gt;11&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;PRs merged&lt;/td&gt;
 &lt;td&gt;143&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Commits&lt;/td&gt;
 &lt;td&gt;~310&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Services deployed&lt;/td&gt;
 &lt;td&gt;5 (BackendApi, AuthService, NotificationService, WorldSimulation, FrontEndClient)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;CDK stacks&lt;/td&gt;
 &lt;td&gt;5 (VPC, DataStore, Web/ALB, CloudFront, PipelineOIDC)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;DynamoDB schema iterations&lt;/td&gt;
 &lt;td&gt;6&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Backend endpoints stubbed&lt;/td&gt;
 &lt;td&gt;22&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Flutter screens built&lt;/td&gt;
 &lt;td&gt;17&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;NuGet packages published&lt;/td&gt;
 &lt;td&gt;3 (BackendCommon, BackendCommon.Cdk, BackendCommon.Testing)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;CI/CD pipelines&lt;/td&gt;
 &lt;td&gt;8 (one per deployable repo)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="code-written-as-of-feb-13"&gt;&lt;a href="#code-written-as-of-feb-13" class="header-anchor"&gt;&lt;/a&gt;Code Written (as of Feb 13)
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Language&lt;/th&gt;
 &lt;th&gt;Lines&lt;/th&gt;
 &lt;th&gt;Purpose&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Dart&lt;/td&gt;
 &lt;td&gt;~14,000&lt;/td&gt;
 &lt;td&gt;Flutter client&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;C#&lt;/td&gt;
 &lt;td&gt;~6,500&lt;/td&gt;
 &lt;td&gt;Backend services + shared libraries&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;TypeScript&lt;/td&gt;
 &lt;td&gt;~520&lt;/td&gt;
 &lt;td&gt;CDK infrastructure&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Markdown&lt;/td&gt;
 &lt;td&gt;~30,000&lt;/td&gt;
 &lt;td&gt;Design docs, game data, READMEs&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;JSON&lt;/td&gt;
 &lt;td&gt;~18,000&lt;/td&gt;
 &lt;td&gt;Game definitions, config&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;</description></item><item><title>The Development Journey, Part 2: Making It Real</title><link>https://ipjohnson-org.github.io/WorldsOfTheNextRealm.Blog/p/the-development-journey-part-2-making-it-real/</link><pubDate>Tue, 17 Feb 2026 00:00:00 +0000</pubDate><guid>https://ipjohnson-org.github.io/WorldsOfTheNextRealm.Blog/p/the-development-journey-part-2-making-it-real/</guid><description>&lt;p&gt;This is part two of our three-part development journey series. &lt;a class="link" href="https://ipjohnson-org.github.io/WorldsOfTheNextRealm.Blog/p/dev-journey-part-1/" &gt;Part 1&lt;/a&gt; covered the foundation — standing up infrastructure and getting the first render on screen. This post covers the transition from &amp;ldquo;something renders&amp;rdquo; to &amp;ldquo;something plays.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The period from February 14th to 17th was where the game came alive — and where most of the visual bugs lived.&lt;/p&gt;
&lt;h2 id="the-city-grid-problem-feb-14-15"&gt;&lt;a href="#the-city-grid-problem-feb-14-15" class="header-anchor"&gt;&lt;/a&gt;The City Grid Problem (Feb 14-15)
&lt;/h2&gt;&lt;p&gt;The initial city grid was 50x50 tiles. On paper, that sounded reasonable. In the isometric renderer, it was too large — buildings were tiny, navigation was tedious, and the render performance suffered. But the bigger problem was visual.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://ipjohnson-org.github.io/WorldsOfTheNextRealm.Blog/p/the-development-journey-part-2-making-it-real/city-clipping.png"
	width="3018"
	height="1560"
	loading="lazy"
	
		alt="City view with buildings clipping at the edges"
	
 
 title="The 50x50 city grid. Buildings along the edges clip into the void. The isometric diamond shape means the corners of the rectangular viewport show empty space."
 data-title-escaped="The 50x50 city grid. Buildings along the edges clip into the void. The isometric diamond shape means the corners of the rectangular viewport show empty space."
 
	
		class="gallery-image" 
		data-flex-grow="193"
		data-flex-basis="464px"
	
&gt;&lt;/p&gt;
&lt;p&gt;Buildings at the grid edges clipped into empty space. The isometric diamond layout meant the rectangular browser viewport showed the diamond&amp;rsquo;s corners — ugly gaps of nothingness. And the cloud overlay we&amp;rsquo;d been struggling with was still dominating the view.&lt;/p&gt;
&lt;p&gt;The fix came in stages across multiple PRs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Resize to 20x20&lt;/strong&gt; — a dramatically smaller grid that kept buildings visible and navigation manageable&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add grass border tiles&lt;/strong&gt; — filled the area beyond the grid diamond so the viewport always shows terrain&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clamp the camera&lt;/strong&gt; — prevented scrolling beyond the rendered area&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storm cloud rework&lt;/strong&gt; — the exterior overlay got completely reworked&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="the-storm-cloud-saga"&gt;&lt;a href="#the-storm-cloud-saga" class="header-anchor"&gt;&lt;/a&gt;The Storm Cloud Saga
&lt;/h2&gt;&lt;p&gt;The cloud overlay deserves its own section because it consumed 6 PRs over two days. The idea was simple: the area outside your built city should have ominous storm clouds, creating a &amp;ldquo;fog of war&amp;rdquo; effect that makes the playable area feel like a clearing in the darkness.&lt;/p&gt;
&lt;p&gt;The first implementation rendered clouds over the &lt;em&gt;entire&lt;/em&gt; map and then punched a hole for the city. It looked like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://ipjohnson-org.github.io/WorldsOfTheNextRealm.Blog/p/the-development-journey-part-2-making-it-real/more-clouds.png"
	width="3004"
	height="1560"
	loading="lazy"
	
		alt="Clouds completely obscuring the game view"
	
 
 title="When the clouds win. The storm effect was rendering too aggressively, with the city barely visible underneath. The blur effect made everything look smudged."
 data-title-escaped="When the clouds win. The storm effect was rendering too aggressively, with the city barely visible underneath. The blur effect made everything look smudged."
 
	
		class="gallery-image" 
		data-flex-grow="192"
		data-flex-basis="462px"
	
&gt;&lt;/p&gt;
&lt;p&gt;The iteration went:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Add storm cloud overlay&lt;/strong&gt; — initial implementation, way too thick&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Thicken storm clouds and increase lightning&lt;/strong&gt; — made it worse, but established the aesthetic direction&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rebalance for 20x20 grid&lt;/strong&gt; — scaling adjustment after the grid resize&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fix city bleed&lt;/strong&gt; — storm effects were bleeding into the city area&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Replace clipPath with saveLayer+dstOut&lt;/strong&gt; — the rendering approach itself was wrong. &lt;code&gt;clipPath&lt;/code&gt; in Flutter&amp;rsquo;s canvas wasn&amp;rsquo;t compositing correctly with the tile renderer. Switching to &lt;code&gt;saveLayer&lt;/code&gt; with Porter-Duff &lt;code&gt;dstOut&lt;/code&gt; compositing mode fixed the bleed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fix hard edge at map corners&lt;/strong&gt; — the final cleanup&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The progression from &amp;ldquo;cloud soup&amp;rdquo; to &amp;ldquo;atmospheric border&amp;rdquo; was a real lesson in iterative rendering. Each PR fixed one thing and revealed the next problem.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://ipjohnson-org.github.io/WorldsOfTheNextRealm.Blog/p/the-development-journey-part-2-making-it-real/next.png"
	width="3024"
	height="1602"
	loading="lazy"
	
		alt="The city view after cloud fixes"
	
 
 title="After multiple iterations, the city view shows buildings clearly with atmospheric clouds around the edges. Still rough, but playable."
 data-title-escaped="After multiple iterations, the city view shows buildings clearly with atmospheric clouds around the edges. Still rough, but playable."
 
	
		class="gallery-image" 
		data-flex-grow="188"
		data-flex-basis="453px"
	
&gt;&lt;/p&gt;
&lt;h2 id="building-placement-and-the-real-backend"&gt;&lt;a href="#building-placement-and-the-real-backend" class="header-anchor"&gt;&lt;/a&gt;Building Placement and the Real Backend
&lt;/h2&gt;&lt;p&gt;With the grid sorted, we wired up actual building placement. This required changes across four repos in sequence:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;BackendCommon&lt;/strong&gt;: Add building data models and road segment data&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BackendApi&lt;/strong&gt;: Add building placement and upgrade endpoints with DynamoDB storage&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OperationalTools&lt;/strong&gt;: Add city-buildings ops command for debugging&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FrontEndClient&lt;/strong&gt;: Wire up placement and upgrade API calls&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The cross-repo dependency chain meant this feature took a full day even though the individual changes were straightforward. The NuGet pipeline adds latency — merge BackendCommon, wait for CI to publish, then update the other repos.&lt;/p&gt;
&lt;p&gt;Hardcoded IDs were another pattern that needed systematic removal. The city view initially used &lt;code&gt;city-001&lt;/code&gt; everywhere. The troop screens hardcoded &lt;code&gt;city-001&lt;/code&gt;. World endpoints hardcoded &lt;code&gt;world-001&lt;/code&gt;. Each hardcoded value was a separate PR to replace with the real ID from the player&amp;rsquo;s session.&lt;/p&gt;
&lt;h2 id="the-ui-overhaul-feb-16"&gt;&lt;a href="#the-ui-overhaul-feb-16" class="header-anchor"&gt;&lt;/a&gt;The UI Overhaul (Feb 16)
&lt;/h2&gt;&lt;p&gt;February 16th was almost entirely a frontend day — 25 PRs merged in the FrontEndClient alone. This was the day the game started looking like a game.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Management screens&lt;/strong&gt;: Resources, Buildings, and Research got dedicated tabs in a new Management screen. The bottom navigation grew from City/Map/Troops/Guild/More to City/Map/Manage/Guild/More.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The carousel battles&lt;/strong&gt;: The build panel originally used a dropdown to select buildings. We replaced it with an image carousel showing building previews. This seemingly simple change spawned 5 PRs:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://ipjohnson-org.github.io/WorldsOfTheNextRealm.Blog/p/the-development-journey-part-2-making-it-real/carrolsell-problems.png"
	width="1664"
	height="1042"
	loading="lazy"
	
		alt="Build carousel UI issues"
	
 
 title="The build carousel showing a Lumber Mill. The carousel navigation arrows are barely visible and the panel clips awkwardly at the bottom of the screen."
 data-title-escaped="The build carousel showing a Lumber Mill. The carousel navigation arrows are barely visible and the panel clips awkwardly at the bottom of the screen."
 
	
		class="gallery-image" 
		data-flex-grow="159"
		data-flex-basis="383px"
	
&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Replace dropdown with carousel&lt;/li&gt;
&lt;li&gt;Fix navigation buttons and image sizing&lt;/li&gt;
&lt;li&gt;Fix clipping on mobile viewports&lt;/li&gt;
&lt;li&gt;Fix the build button being hidden behind the panel&lt;/li&gt;
&lt;li&gt;Final height and layout adjustments&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Gesture handling&lt;/strong&gt;: Adding pinch-to-zoom to the isometric maps created a cascade of input conflicts. The tile tap handler consumed touch events that the zoom handler needed. The zoom gesture interfered with panning. Pan conflicted with tap detection. This produced 5 bug-fix PRs in sequence — each fixing one gesture interaction and breaking another, until the gesture recognizer configuration was right.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The mobile experience&lt;/strong&gt;: The game runs in Safari on iOS as a progressive web app.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://ipjohnson-org.github.io/WorldsOfTheNextRealm.Blog/p/the-development-journey-part-2-making-it-real/mobile.PNG"
	width="1170"
	height="2532"
	loading="lazy"
	
		alt="Mobile view of the game"
	
 
 title="The game running on iOS Safari. The Build panel shows a Mint building, costs are visible, but the &amp;lsquo;Share&amp;hellip;&amp;rsquo; browser action sheet is leaking into the UI."
 data-title-escaped="The game running on iOS Safari. The Build panel shows a Mint building, costs are visible, but the &amp;amp;amp;lsquo;Share&amp;amp;amp;hellip;&amp;amp;amp;rsquo; browser action sheet is leaking into the UI."
 
	
		class="gallery-image" 
		data-flex-grow="46"
		data-flex-basis="110px"
	
&gt;&lt;/p&gt;
&lt;p&gt;Mobile brought its own issues — the PWA standalone mode needed fixes to hide browser chrome, and touch gesture handling behaved differently than mouse events.&lt;/p&gt;
&lt;h2 id="token-management"&gt;&lt;a href="#token-management" class="header-anchor"&gt;&lt;/a&gt;Token Management
&lt;/h2&gt;&lt;p&gt;One practical problem: JWT access tokens expire after 6 hours. When a player&amp;rsquo;s session runs long enough, API calls start returning 401s. We added automatic token refresh — when a 401 comes back, the Dio HTTP interceptor transparently refreshes the access token using the stored refresh token and retries the original request. The user never sees the expiration.&lt;/p&gt;
&lt;h2 id="observability"&gt;&lt;a href="#observability" class="header-anchor"&gt;&lt;/a&gt;Observability
&lt;/h2&gt;&lt;p&gt;On the backend side, we added Embedded Metric Format (EMF) logging to the BackendApi. Every API request now emits structured metrics to CloudWatch — request count, latency, and status code breakdown (2xx, 4xx, 5xx). Getting EMF right took 4 PRs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add EMF request metrics middleware&lt;/li&gt;
&lt;li&gt;Fix to emit single structured object (not multiple)&lt;/li&gt;
&lt;li&gt;Fix Dimensions array format per CloudWatch spec&lt;/li&gt;
&lt;li&gt;Align metric names with property names&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The CloudWatch EMF specification is finicky about formatting. Small deviations cause metrics to silently not appear in CloudWatch — no error, just missing data. Each fix was discovered by checking CloudWatch and finding empty graphs.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="stats-days-6-9-feb-14-17"&gt;&lt;a href="#stats-days-6-9-feb-14-17" class="header-anchor"&gt;&lt;/a&gt;Stats: Days 6-9 (Feb 14-17)
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Metric&lt;/th&gt;
 &lt;th&gt;Value&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;PRs merged&lt;/td&gt;
 &lt;td&gt;94&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Frontend PRs&lt;/td&gt;
 &lt;td&gt;62&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Backend PRs&lt;/td&gt;
 &lt;td&gt;22&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;City grid iterations&lt;/td&gt;
 &lt;td&gt;3 (50x50 → 20x20 → final layout)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Storm cloud PRs&lt;/td&gt;
 &lt;td&gt;6&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Gesture handling bug fixes&lt;/td&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Carousel UI iterations&lt;/td&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;EMF metrics iterations&lt;/td&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Hardcoded ID replacements&lt;/td&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;New API endpoints (real, not stubs)&lt;/td&gt;
 &lt;td&gt;18&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="code-growth-feb-14-17"&gt;&lt;a href="#code-growth-feb-14-17" class="header-anchor"&gt;&lt;/a&gt;Code Growth (Feb 14-17)
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Language&lt;/th&gt;
 &lt;th&gt;Lines Added&lt;/th&gt;
 &lt;th&gt;Total&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Dart&lt;/td&gt;
 &lt;td&gt;~12,500&lt;/td&gt;
 &lt;td&gt;~26,500&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;C#&lt;/td&gt;
 &lt;td&gt;~9,200&lt;/td&gt;
 &lt;td&gt;~15,700&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;TypeScript&lt;/td&gt;
 &lt;td&gt;~100&lt;/td&gt;
 &lt;td&gt;~625&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Markdown&lt;/td&gt;
 &lt;td&gt;~8,000&lt;/td&gt;
 &lt;td&gt;~38,000&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;JSON&lt;/td&gt;
 &lt;td&gt;~2,000&lt;/td&gt;
 &lt;td&gt;~20,000&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="feature-velocity"&gt;&lt;a href="#feature-velocity" class="header-anchor"&gt;&lt;/a&gt;Feature Velocity
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Day&lt;/th&gt;
 &lt;th&gt;PRs Merged&lt;/th&gt;
 &lt;th&gt;Key Features&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Feb 14&lt;/td&gt;
 &lt;td&gt;16&lt;/td&gt;
 &lt;td&gt;Server-backed maps, city resize, iOS PWA, post-login flow&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Feb 15&lt;/td&gt;
 &lt;td&gt;28&lt;/td&gt;
 &lt;td&gt;Storm clouds, road rendering, building placement, world simulation phase 1, mock removal&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Feb 16&lt;/td&gt;
 &lt;td&gt;32&lt;/td&gt;
 &lt;td&gt;Management screens, carousel, pinch-to-zoom, token refresh, EMF metrics, troop training UI&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Feb 17&lt;/td&gt;
 &lt;td&gt;18&lt;/td&gt;
 &lt;td&gt;Research trees, building definitions, worker staffing, tile bonuses, blog launch&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;</description></item></channel></rss>