<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Lambda on Worlds of the Next Realm - Dev Blog</title><link>https://ipjohnson-org.github.io/WorldsOfTheNextRealm.Blog/tags/lambda/</link><description>Recent content in Lambda 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/lambda/index.xml" rel="self" type="application/rss+xml"/><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>