<?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>DEV Community: Kehinde Owolabi</title>
    <description>The latest articles on DEV Community by Kehinde Owolabi (@kehinde_owolabi_e2e54567a).</description>
    <link>https://dev.to/kehinde_owolabi_e2e54567a</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3480586%2F4dc44601-a864-4783-a450-aababe5d5e70.jpg</url>
      <title>DEV Community: Kehinde Owolabi</title>
      <link>https://dev.to/kehinde_owolabi_e2e54567a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kehinde_owolabi_e2e54567a"/>
    <language>en</language>
    <item>
      <title>🎮 RIFTFIRE Submitted to CrazyGames — Play the Preview Now</title>
      <dc:creator>Kehinde Owolabi</dc:creator>
      <pubDate>Wed, 11 Nov 2026 02:41:42 +0000</pubDate>
      <link>https://dev.to/kehinde_owolabi_e2e54567a/riftfire-submitted-to-crazygames-play-the-preview-now-dm0</link>
      <guid>https://dev.to/kehinde_owolabi_e2e54567a/riftfire-submitted-to-crazygames-play-the-preview-now-dm0</guid>
      <description>&lt;h1&gt;
  
  
  🎮 RIFTFIRE: 360-Level Raycast FPS Built With Limn Engine — Now on CrazyGames
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;A full first-person shooter, a portal submission, and what it means for a lightweight JavaScript engine.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Play the Preview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Try RIFTFIRE on CrazyGames right now:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://www.crazygames.com/preview/153296f7-b2f9-4a24-95fc-d5025b49ea9b?gameBuildId=bdcba4aa-1a24-40ef-978f-e6f419406364&amp;amp;qaTool=true&amp;amp;role=developer" rel="noopener noreferrer"&gt;CrazyGames Preview — RIFTFIRE&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the developer preview link. It's the exact build currently awaiting review — you can play it before it goes live to the public.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&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;&lt;strong&gt;Build ID&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;6cd245c7-52d9-4741-892e-393271eb2fe3&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Status&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Awaiting review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Category&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Shooting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Engine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Limn Engine (&lt;code&gt;epic.js&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk008bgypq0udstlwd5br.png" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk008bgypq0udstlwd5br.png" alt="RIFTFIRE on CrazyGames" width="480" height="960"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📖 Introduction
&lt;/h2&gt;

&lt;p&gt;CrazyGames is one of the largest browser game portals on the web. Being listed there means visibility at a scale no independent site can match.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RIFTFIRE&lt;/strong&gt; is the first Limn Engine game submitted to a portal like this. It's a full raycaster — the same rendering technique that powered &lt;em&gt;Wolfenstein 3D&lt;/em&gt; and &lt;em&gt;Doom&lt;/em&gt; — built entirely in vanilla JavaScript with Limn Engine as the foundation.&lt;/p&gt;

&lt;p&gt;This article covers what RIFTFIRE is, why the CrazyGames submission matters for the engine, and what happens next.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎮 What RIFTFIRE Is
&lt;/h2&gt;

&lt;p&gt;RIFTFIRE is a &lt;strong&gt;raycast first-person shooter&lt;/strong&gt;. Instead of rendering a 3D scene with polygons, it casts a ray from the player's position for every vertical slice of the screen and draws walls based on where those rays hit. It's the same approach that powered &lt;em&gt;Wolfenstein 3D&lt;/em&gt; in 1992 — and it runs remarkably well in a browser today.&lt;/p&gt;

&lt;h3&gt;
  
  
  The scale
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Seasons&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Levels per season&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total levels&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;360&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Weapons&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6 (Pistol, SMG, Shotgun, Rifle, Sniper, Minigun)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enemy types&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5 (Basic, Fast, Tank, Sniper, Boss)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Difficulty curve&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Eased ramp from 0.0 to 1.0 across all 360 levels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Map generation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Procedurally seeded — each level is deterministic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Audio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fully procedural Web Audio API — no audio files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rank system&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10 ranks from Recruit to Commander&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Game modes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Campaign, Tournament, Daily Challenge&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Features that make it more than a demo
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Procedural maps.&lt;/strong&gt; Every level is generated from a seed. The same seed always produces the same map, but no two runs share a layout.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Procedural audio.&lt;/strong&gt; Gunshots, explosions, footsteps, and music are all synthesized at runtime with Web Audio. No audio assets, no loading time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boss fights.&lt;/strong&gt; Each level ends with a boss wave — more HP, higher accuracy, and burst-fire patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistence.&lt;/strong&gt; Wallet, XP, ranks, kills, and level completions are saved to &lt;code&gt;localStorage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tournaments.&lt;/strong&gt; Five tiers (Bronze → Diamond), each a three-round gauntlet. Win three rounds in a row to earn a trophy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daily Challenge.&lt;/strong&gt; A new level and gameplay modifier every day — Glass Cannon, Iron Skin, or Pistol Only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Login rewards.&lt;/strong&gt; A seven-day login streak system with escalating rewards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile support.&lt;/strong&gt; Full touch controls — virtual joystick, aim-drag fire button, grenade, slide, bullet time, and zoom.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🏗️ Why This Matters for Limn Engine
&lt;/h2&gt;

&lt;p&gt;For years, Limn Engine has been positioned as a beginner-friendly engine for 2D games. RIFTFIRE proves it can do more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. It proves the engine can handle 3D-style rendering.&lt;/strong&gt; Raycasting is a form of 3D rendering. It isn't polygons, but it creates the illusion of depth with comparable complexity. If Limn Engine can drive a raycaster, it can drive a lot of other things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. It proves the engine scales to a real project.&lt;/strong&gt; 360 levels, 6 weapons, 5 enemy types, 3 game modes, procedural audio and maps — this isn't a weekend experiment. It's a complete game with hundreds of hours of content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. It proves the engine can run on portals.&lt;/strong&gt; CrazyGames has strict technical requirements. If RIFTFIRE passes, Limn Engine passes. That's a validation no article or tutorial can provide.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb76kny69jf2xneshz0ic.png" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb76kny69jf2xneshz0ic.png" alt="RIFTFIRE gameplay" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📋 The CrazyGames Submission
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&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;&lt;strong&gt;Game ID&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;165783&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Build ID&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;6cd245c7-52d9-4741-892e-393271eb2fe3&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Submission date&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;October 11, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Category&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Shooting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Status&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Awaiting review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Engine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Limn Engine (&lt;code&gt;epic.js&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~139 KB game code + ~55 KB engine&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What the review covers
&lt;/h3&gt;

&lt;p&gt;CrazyGames reviews games against a published checklist. The review typically covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Technical quality.&lt;/strong&gt; Does the game load reliably? Are there runtime errors? Does it crash on mobile?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance.&lt;/strong&gt; Does it run at a stable frame rate on mid-range devices?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content quality.&lt;/strong&gt; Is the game complete? Does it have enough content to justify a portal listing?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User experience.&lt;/strong&gt; Is it easy to understand? Does the first minute teach the player what to do?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Originality.&lt;/strong&gt; Is it a clone, or does it stand out?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ads and monetization.&lt;/strong&gt; Does the game work with CrazyGames' ad platform?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The review can take anywhere from a few days to a few weeks. During that time, the game sits in &lt;strong&gt;Awaiting Review&lt;/strong&gt; status.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 What We Learn Regardless of the Outcome
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;If it's accepted:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Limn Engine has a validated path to portal distribution.&lt;/li&gt;
&lt;li&gt;Other Limn Engine games can follow the same route.&lt;/li&gt;
&lt;li&gt;We get real traffic, real feedback, and real data on how the engine performs in the wild.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;If it's rejected:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The rejection reason is the most valuable thing we can get. It tells us exactly what to fix — performance, UX, content, or technical requirements.&lt;/li&gt;
&lt;li&gt;Every fix we make in response to a portal review also improves the engine for every game built on it.&lt;/li&gt;
&lt;li&gt;The next submission will be stronger.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Either way, the review is a learning experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔄 What's Changing on the Limn Arcade
&lt;/h2&gt;

&lt;p&gt;The Limn Arcade is being restructured. Some games are being moved off the platform as part of a broader strategy, and the lineup will look different in the coming weeks.&lt;/p&gt;

&lt;p&gt;We're not announcing the full details yet. The short version: &lt;strong&gt;the arcade is being reorganized&lt;/strong&gt;, not closed. Some of the games currently listed will move elsewhere, and the arcade will refocus on the games that fit its direction.&lt;/p&gt;

&lt;p&gt;RIFTFIRE is not affected. It stays available on the arcade until the CrazyGames review is complete.&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 What RIFTFIRE Uses From Limn Engine
&lt;/h2&gt;

&lt;p&gt;RIFTFIRE is built entirely on Limn Engine's core features. Here's what it relies on:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Where It's Used&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;Display&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The canvas, game loop, and input handling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;Component&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The master render layer (&lt;code&gt;masterLayer&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;Camera&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The raycasting FOV calculation and camera transforms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;Sound&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not used — RIFTFIRE uses Web Audio directly for procedural audio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;Particle&lt;/code&gt; / &lt;code&gt;ParticleSystem&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not used — RIFTFIRE has custom blood, damage, and explosion systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;Tctxt&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HUD text, menu labels, buttons&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;move&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not used — RIFTFIRE handles movement directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;scene&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The scene system (menu, game, pause, win, lose, loading, shop, ranks, help, settings, play, tournament, daily, rewards)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What RIFTFIRE adds on top of Limn Engine
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A raycasting renderer&lt;/li&gt;
&lt;li&gt;Procedural map generation (mulberry32 seeded)&lt;/li&gt;
&lt;li&gt;Enemy AI with line-of-sight detection&lt;/li&gt;
&lt;li&gt;Weapon system with 6 weapons and per-weapon stats&lt;/li&gt;
&lt;li&gt;Particle-based blood and damage number systems&lt;/li&gt;
&lt;li&gt;Procedural Web Audio engine&lt;/li&gt;
&lt;li&gt;Persistent state via &lt;code&gt;localStorage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Touch controls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The engine handles the canvas, the game loop, the input layer, and the component system. Everything else is game code.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Try It Yourself
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Play the CrazyGames preview:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://www.crazygames.com/preview/3496eeae-ae2c-4fed-9961-610279212d8a?gameBuildId=6cd245c7-52d9-4741-892e-393271eb2fe3&amp;amp;qaTool=true&amp;amp;role=developer" rel="noopener noreferrer"&gt;CrazyGames Preview — RIFTFIRE&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get Limn Engine:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build your own game:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app/editor/" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/editor&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 What You've Learned
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Why It Matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Portal submission&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A major milestone for any engine or game&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Raycasting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A 1990s rendering technique that still works today&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Procedural generation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Seeded maps give 360 unique levels from one codebase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Procedural audio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No files needed — sound is synthesized at runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Persistent state&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;localStorage&lt;/code&gt; saves progress across sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Touch controls&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The same game works on mobile without a separate build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Engine validation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A portal review validates the engine itself&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  ⚠️ What's Next
&lt;/h2&gt;

&lt;p&gt;The review is pending. Here's what happens either way:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If accepted:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RIFTFIRE goes live on CrazyGames.&lt;/li&gt;
&lt;li&gt;We announce the launch and link to the portal listing.&lt;/li&gt;
&lt;li&gt;We monitor reviews and fix any issues that come up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;If rejected:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We read the feedback carefully.&lt;/li&gt;
&lt;li&gt;We fix the specific issues the reviewer raised.&lt;/li&gt;
&lt;li&gt;We resubmit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Either way, we'll write a follow-up article once the review is complete, sharing the outcome and what we learned from it.&lt;/p&gt;




&lt;h2&gt;
  
  
  🐛 Report Bugs
&lt;/h2&gt;

&lt;p&gt;If you find a bug in RIFTFIRE, report it on GitHub:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc/issues" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc/issues&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Resources
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CrazyGames Preview&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.crazygames.com/preview/153296f7-b2f9-4a24-95fc-d5025b49ea9b?gameBuildId=bdcba4aa-1a24-40ef-978f-e6f419406364&amp;amp;qaTool=true&amp;amp;role=developer" rel="noopener noreferrer"&gt;crazygames.com/preview/3496eeae-ae2c-4fed-9961-610279212d8a&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Arcade&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/arcade" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/arcade&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Engine Docs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Studio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/editor/" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/editor&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Light Editor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/editor/light-editor.html" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/editor/light-editor.html&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub Repository&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Report Bugs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc/issues" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc/issues&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discord&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://discord.gg/kVpSmYWXr" rel="noopener noreferrer"&gt;discord.gg/kVpSmYWXr&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kehinde on DEV.to&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/kehinde_owolabi_e2e54567a"&gt;dev.to/kehinde_owolabi_e2e54567a&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🎯 The One-Line Summary
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"RIFTFIRE — a 360-level raycast FPS built with Limn Engine — is now awaiting review on CrazyGames. The preview build is playable today."&lt;/strong&gt; 🎮🚀&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Draw your game into existence — one submission at a time.&lt;/em&gt; 🎮🚀&lt;/p&gt;

</description>
      <category>programming</category>
      <category>discuss</category>
      <category>showdev</category>
      <category>news</category>
    </item>
    <item>
      <title>📬 How to Reach Me Directly — A Personal Note from Kehinde Owolabi</title>
      <dc:creator>Kehinde Owolabi</dc:creator>
      <pubDate>Fri, 06 Nov 2026 03:48:01 +0000</pubDate>
      <link>https://dev.to/kehinde_owolabi_e2e54567a/how-to-reach-me-directly-a-personal-note-from-kehinde-owolabi-1i1e</link>
      <guid>https://dev.to/kehinde_owolabi_e2e54567a/how-to-reach-me-directly-a-personal-note-from-kehinde-owolabi-1i1e</guid>
      <description>&lt;h1&gt;
  
  
  📬 How to Reach Me Directly — A Personal Note from Kehinde Owolabi
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Beyond DEV.to and GitHub — the accounts where you can actually talk to me.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 The Short Version
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Facebook (personal account):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://www.facebook.com/damilare.ape" rel="noopener noreferrer"&gt;facebook.com/damilare.ape&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Name on Facebook:&lt;/strong&gt; Owolabi Sam&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DEV.to:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://dev.to/kehinde_owolabi_e2e54567a"&gt;dev.to/kehinde_owolabi_e2e54567a&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Discord (Limn Arcade community):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://discord.gg/kVpSmYWXr" rel="noopener noreferrer"&gt;discord.gg/kVpSmYWXr&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📖 Introduction
&lt;/h2&gt;

&lt;p&gt;Most of the interactions I have with readers happen on DEV.to and GitHub. You comment on an article. You open an issue. You fork a game. That's the public layer of the ecosystem — and it's where most questions belong, because the answers help everyone who reads them later.&lt;/p&gt;

&lt;p&gt;But sometimes you don't want to write a public comment. Maybe you have a question that doesn't fit into an article. Maybe you want to send feedback that isn't a bug report. Maybe you just want to say hi, tell me what you built with Limn Engine, or ask me to look at something specific.&lt;/p&gt;

&lt;p&gt;For those cases, the public channels aren't always the right fit. This article is about the direct channel — the Facebook account I use for personal and community contact, and how to use it well.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 A Quick Note on Names
&lt;/h2&gt;

&lt;p&gt;Before I explain how to reach me, I need to explain something. My DEV.to account is under the name &lt;strong&gt;Kehinde Owolabi&lt;/strong&gt;. My Facebook account is under &lt;strong&gt;Owolabi Sam&lt;/strong&gt;. Both names refer to the same person.&lt;/p&gt;

&lt;p&gt;This is normal for personal accounts. I've used Facebook longer than I've used DEV.to, and the Facebook name is what my friends and family know. But it means if you search for "Kehinde Owolabi" on Facebook, you might not find me. If you search for "Owolabi Sam" on DEV.to, you might be confused.&lt;/p&gt;

&lt;p&gt;The link below connects the two:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://www.facebook.com/damilare.ape" rel="noopener noreferrer"&gt;facebook.com/damilare.ape&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the account. Whatever name appears on the profile, that's me.&lt;/p&gt;




&lt;h2&gt;
  
  
  📬 When to Use the Facebook Account
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Good uses:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Saying hi.&lt;/strong&gt; If you built something with Limn Engine and want to show me, this is the channel. I love seeing what people build.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Questions that don't fit DEV.to.&lt;/strong&gt; If you have a question that's too short for an article but too long for a comment, a message is fine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback on articles.&lt;/strong&gt; If a tutorial didn't make sense or an article had an error you spotted, a message is a fast way to tell me.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaboration requests.&lt;/strong&gt; If you want to work on something together — a game, an article, a feature — the Facebook account is a good starting point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community connection.&lt;/strong&gt; If you're building Limn Engine games and want to talk about the ecosystem, I'm happy to chat.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Not-so-good uses:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bug reports.&lt;/strong&gt; Those belong on &lt;a href="https://github.com/terracodes004/limn-engine-doc/issues" rel="noopener noreferrer"&gt;GitHub Issues&lt;/a&gt; so they're trackable and searchable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technical questions with long answers.&lt;/strong&gt; Those belong on DEV.to or Discord so others can find the answers later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anything urgent that isn't actually urgent.&lt;/strong&gt; Facebook messages aren't monitored on a schedule. If it's truly urgent, use the Discord community — there are always people there.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🎯 When to Use the Other Channels
&lt;/h2&gt;

&lt;p&gt;Each channel has a purpose. Here's how I'd recommend using each one:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Channel&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;th&gt;Response Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DEV.to comments&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Questions on specific articles, general Limn Engine discussion&lt;/td&gt;
&lt;td&gt;Usually within a day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub Issues&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bugs, feature requests, documentation gaps&lt;/td&gt;
&lt;td&gt;Within a few days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discord&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Community chat, quick questions, help from other developers&lt;/td&gt;
&lt;td&gt;Usually within hours (community-run)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Facebook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Personal connection, feedback, questions that don't fit the others&lt;/td&gt;
&lt;td&gt;Depends on my schedule&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Facebook account is a bit like a direct message to me. It's not for public interaction. It's not monitored by a community. It's just me.&lt;/p&gt;




&lt;h2&gt;
  
  
  📝 A Note on Time Zones
&lt;/h2&gt;

&lt;p&gt;I'm based in &lt;strong&gt;Lagos, Nigeria&lt;/strong&gt;. That means I'm on West Africa Time (WAT). If you're writing from a different time zone, expect a delay if you message at what's for me an unusual hour.&lt;/p&gt;

&lt;p&gt;This isn't a complaint — it's just a fact of building things from a specific place. If your message arrives at 3am my time, I'll answer it when I'm awake. That's usually the next day.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 What You Can Expect
&lt;/h2&gt;

&lt;p&gt;If you message me on Facebook:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I'll read it. I try to read every message that isn't spam.&lt;/li&gt;
&lt;li&gt;I'll respond when I have time. That might be the same day, it might be a week later.&lt;/li&gt;
&lt;li&gt;I'll answer honestly. If I don't know something, I'll say so rather than making something up.&lt;/li&gt;
&lt;li&gt;I'll keep it brief. If your message turns into a long technical discussion, I'll probably suggest we move to Discord or GitHub so the answer can help others.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📊 What You've Learned
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Channel&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;&lt;strong&gt;DEV.to&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Articles, public discussion, technical questions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub Issues&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bugs, feature requests, structured feedback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discord&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Community chat, quick help, community-run support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Facebook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Personal connection, direct messages to me&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  ⚠️ A Note on the Public Record
&lt;/h2&gt;

&lt;p&gt;Facebook messages are private. They're not indexed by search engines. Nobody else can read them. That's part of why they're useful — some things don't belong on a public comment thread.&lt;/p&gt;

&lt;p&gt;But there's a trade-off. When you ask me something privately, only you get the answer. When you ask it publicly on DEV.to or Discord, everyone who comes after you gets the answer too.&lt;/p&gt;

&lt;p&gt;Both are valid. Use whichever makes sense for the question you're asking.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 What's Next?
&lt;/h2&gt;

&lt;p&gt;The Limn Engine ecosystem is still growing. Here are some ways to stay connected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Follow the DEV.to series&lt;/strong&gt; for new articles on the engine and the arcade.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Star the GitHub repo&lt;/strong&gt; to keep up with issues and updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Join the Discord&lt;/strong&gt; for community conversation and quick help.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add me on Facebook&lt;/strong&gt; if you want a direct line.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm always happy to hear from people building with Limn Engine. If you have a project you're working on, a question I haven't answered in an article, or just want to say hello — all of those are welcome.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Resources
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Facebook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.facebook.com/damilare.ape" rel="noopener noreferrer"&gt;facebook.com/damilare.ape&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DEV.to&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/kehinde_owolabi_e2e54567a"&gt;dev.to/kehinde_owolabi_e2e54567a&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discord&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://discord.gg/kVpSmYWXr" rel="noopener noreferrer"&gt;discord.gg/kVpSmYWXr&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Engine Docs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Arcade&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/arcade" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/arcade&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Studio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/editor/" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/editor&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Light Editor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/editor/light-editor.html" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/editor/light-editor.html&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🎯 The One-Line Summary
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Facebook is the direct channel — Owolabi Sam on &lt;a href="https://www.facebook.com/damilare.ape" rel="noopener noreferrer"&gt;facebook.com/damilare.ape&lt;/a&gt; is Kehinde Owolabi on DEV.to, and it's where you can message me one-on-one."&lt;/strong&gt; 📬🚀&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Draw your game into existence — one connection at a time.&lt;/em&gt; 🎮🚀&lt;/p&gt;

</description>
      <category>career</category>
      <category>discuss</category>
      <category>startup</category>
      <category>marketing</category>
    </item>
    <item>
      <title>🎄 Limn League Christmas 2026 — Festive Arcade Football</title>
      <dc:creator>Kehinde Owolabi</dc:creator>
      <pubDate>Tue, 03 Nov 2026 02:47:13 +0000</pubDate>
      <link>https://dev.to/kehinde_owolabi_e2e54567a/limn-league-christmas-2026-festive-arcade-football-6pj</link>
      <guid>https://dev.to/kehinde_owolabi_e2e54567a/limn-league-christmas-2026-festive-arcade-football-6pj</guid>
      <description>&lt;h1&gt;
  
  
  🎄 Limn League Christmas 2026 — Festive Arcade Football
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;A career-mode football sim wrapped in snow, santa hats, and Jingle Bells — now live on the Limn Arcade.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Play the Game
&lt;/h2&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app/arcade/game.html?slug=limn-league-christmas-2026-hgz8" rel="noopener noreferrer"&gt;Limn League Christmas 2026&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The game is live on the Limn Arcade and ready to play. It was &lt;strong&gt;Last Week's Winner&lt;/strong&gt; at the time of writing, and it's a strong showcase of what's possible with Limn Engine in the browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  📖 Introduction
&lt;/h2&gt;

&lt;p&gt;Limn League Christmas 2026 is a browser-based football career simulator built with Limn Engine. It's the festive entry in the Limn League series — a family of football sims on the arcade that each take a different angle on the same sport.&lt;/p&gt;

&lt;p&gt;This one wraps the season in snow, santa hats, and Jingle Bells. It has divisions to climb, cups to win, and a 30-match championship to grind through. It's the kind of game that starts as a five-minute test and turns into an hour of career management without you noticing.&lt;/p&gt;

&lt;p&gt;Here's what makes it worth playing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;12 divisions&lt;/strong&gt; to climb, from Street all the way to Immortal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cup tiers&lt;/strong&gt; to win alongside the league.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A full 30-match championship&lt;/strong&gt; as the endgame.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upgrade systems&lt;/strong&gt; so your squad improves over time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Christmas visual theme&lt;/strong&gt; — snow, santa hats, seasonal audio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No installs&lt;/strong&gt; — it runs in the browser, on phone or desktop.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🎮 What the Game Is
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The core loop.&lt;/strong&gt; You start in a lower division, play matches, win games, earn rewards, upgrade your squad, and climb. The higher you climb, the tougher the opponents. The progression is the game — you're not trying to reach a single win condition, you're trying to build a career.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The divisions.&lt;/strong&gt; Twelve of them, from Street to Immortal. Each one has its own difficulty curve. Advancing requires winning matches, and winning matches requires a squad that's good enough. That's where the upgrade system comes in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The cups.&lt;/strong&gt; Alongside the league, there are cup tiers. Cups are shorter, higher-stakes competitions that give you a chance at bigger rewards. Winning a cup accelerates your climb.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The championship.&lt;/strong&gt; The endgame is a 30-match championship. If you can get through all 30 matches, you've proven your squad is competitive at the top. That's the long-term goal the divisions feed into.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Christmas theme.&lt;/strong&gt; Snow on the pitch, santa hats on the players, Jingle Bells as the soundtrack. It's the same football sim underneath, but the theme makes it feel like a holiday season — which is exactly what makes it a distinct entry in the Limn League series.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 How It Plays
&lt;/h2&gt;

&lt;p&gt;The game runs entirely in the browser. The arcade loads it, and the game's own logic takes over. From the arcade logs, we can see the game is a single file of about &lt;strong&gt;138 kilobytes&lt;/strong&gt; running on the &lt;code&gt;epic.js&lt;/code&gt; engine — which is another &lt;strong&gt;55 kilobytes&lt;/strong&gt;. That's the entire game, engine included, at under 200KB.&lt;/p&gt;

&lt;p&gt;For comparison: a single hero image on a modern website is often larger than that. The entire game is smaller than most social media avatars.&lt;/p&gt;

&lt;p&gt;That's the point of Limn Engine. It's not trying to be Unity. It's trying to be small enough to load instantly and fast enough to feel good.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On mobile.&lt;/strong&gt; The game is designed to work on phones. The preview image from the arcade shows it running at a phone resolution with readable UI and a play button that works. Tapping Play starts a game session with the same logic the desktop version uses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On desktop.&lt;/strong&gt; Same game, bigger screen. The arcade scales the canvas to fit.&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 Behind the Scenes
&lt;/h2&gt;

&lt;p&gt;Here's what happens when you open the game's URL, from the arcade logs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;What Happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1. Boot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The arcade calls &lt;code&gt;boot()&lt;/code&gt; with the game's slug&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2. Fetch&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;It fetches the game's code from Supabase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3. Load&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The game's code (138KB) and the engine (55KB) are loaded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;4. Render&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An iframe is created and the game is rendered inside it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;5. Session&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The user's session is checked — if you're signed in, likes and comments are wired up&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's the entire load process. It takes about two seconds from clicking a game to seeing it on screen. That's fast enough that you can browse the arcade, tap a game, and be playing before your finger has left the screen.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏆 Why It Was Last Week's Winner
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Last Week's Winner&lt;/strong&gt; badge is the arcade's way of highlighting the best game of the week. When a game wins, it gets the top spot on the arcade home page for seven days, then moves to the &lt;strong&gt;Last Week's Winner&lt;/strong&gt; slot for the following week before being archived in the &lt;strong&gt;Hall of Fame&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Limn League Christmas 2026 earned that badge. The reason is visible in the game itself — it's a complete, polished experience that feels like a real football career sim, not a demo. The Christmas theme makes it stand out, the divisions give it depth, and the cup and championship systems give it a long-term goal.&lt;/p&gt;

&lt;p&gt;For a game built with a lightweight 2D engine, that's a lot of substance.&lt;/p&gt;




&lt;h2&gt;
  
  
  📝 What You've Learned
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Why It Matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn League series&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A family of football sims, each with its own angle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Career-mode design&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Divisions, cups, upgrades, and a championship give the game long-term depth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The Christmas theme&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A seasonal wrap makes a familiar game feel fresh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Single-file games&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The whole game is 138KB, engine included — under 200KB total&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fast load&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Under two seconds from tap to play&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Arcade infrastructure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Supabase fetch, iframe render, session check&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  ⚠️ Known Limitations
&lt;/h2&gt;

&lt;p&gt;The game is single-player only. There's no multiplayer or online leaderboard.&lt;/p&gt;

&lt;p&gt;The Christmas theme is cosmetic — the underlying football mechanics are the same as other Limn League entries. That's deliberate: the theme is a skin, not a different game.&lt;/p&gt;

&lt;p&gt;The career mode is long. Thirty championship matches is a lot of games. If you're looking for a quick session, this might not be the right entry point — but if you want something to sink time into, it is.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 What's Next?
&lt;/h2&gt;

&lt;p&gt;The Limn League series is still growing. Here are some directions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;More themes&lt;/strong&gt; — the Christmas entry proves the series can wrap itself in different aesthetics. A summer theme, a Halloween theme, or a retro 8-bit theme would all work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-game progression&lt;/strong&gt; — imagine carrying your squad from one Limn League entry to another.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leaderboards&lt;/strong&gt; — a public ranking of the best careers would make the championship feel more competitive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seasonal events&lt;/strong&gt; — timed events tied to real-world seasons would keep the game feeling alive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build any of these, send a pull request.&lt;/p&gt;




&lt;h2&gt;
  
  
  🐛 Report Bugs
&lt;/h2&gt;

&lt;p&gt;If you find a bug in Limn League Christmas 2026, report it on GitHub:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc/issues" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc/issues&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Please include your device, your browser, and a description of what happened.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Resources
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Play Limn League Christmas 2026&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/arcade/game.html?slug=limn-league-christmas-2026-hgz8" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/arcade/game.html?slug=limn-league-christmas-2026-hgz8&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Arcade&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/arcade" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/arcade&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Engine Docs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Studio (Editor)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/editor/" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/editor&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Light Editor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/editor/light-editor.html" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/editor/light-editor.html&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub Repository&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Report Bugs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc/issues" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc/issues&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discord&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://discord.gg/kVpSmYWXr" rel="noopener noreferrer"&gt;discord.gg/kVpSmYWXr&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kehinde on DEV.to&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/kehinde_owolabi_e2e54567a"&gt;dev.to/kehinde_owolabi_e2e54567a&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🎯 The One-Line Summary
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Limn League Christmas 2026 is a festive career-mode football sim — twelve divisions, cup tiers, a 30-match championship, and a full Christmas theme, all in under 200KB of browser code."&lt;/strong&gt; 🎄⚽🚀&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Draw your game into existence — one division at a time.&lt;/em&gt; 🎮🚀&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>discuss</category>
      <category>showdev</category>
    </item>
    <item>
      <title>⚡ Why I Built a Lighter Version of Limn Studio</title>
      <dc:creator>Kehinde Owolabi</dc:creator>
      <pubDate>Sat, 31 Oct 2026 14:55:46 +0000</pubDate>
      <link>https://dev.to/kehinde_owolabi_e2e54567a/why-i-built-a-lighter-version-of-limn-studio-3a35</link>
      <guid>https://dev.to/kehinde_owolabi_e2e54567a/why-i-built-a-lighter-version-of-limn-studio-3a35</guid>
      <description>&lt;h1&gt;
  
  
  ⚡ Why I Built a Lighter Version of Limn Studio
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;The full IDE was too heavy for mobile and low-end devices. Here's the version that isn't.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Try It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Light Editor:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app/editor/light-editor.html" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/editor/light-editor.html&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full Studio (still available):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app/editor" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/editor&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Arcade (play games made by others):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app/arcade" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/arcade&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📖 Introduction
&lt;/h2&gt;

&lt;p&gt;Limn Studio is the online IDE for Limn Engine — a place where you can write, run, and test games directly in the browser. It has templates, a file tree, a build tab, asset injection, a live preview, and a code editor with syntax highlighting. It's a full IDE, and for serious development, it's exactly what you want.&lt;/p&gt;

&lt;p&gt;But it's heavy.&lt;/p&gt;

&lt;p&gt;On a desktop with a decent connection, that's fine. On a phone, or a Chromebook with limited RAM, or a connection that's slower than you'd like, it can take long enough to load that you give up before you've written a line of code.&lt;/p&gt;

&lt;p&gt;I built a lighter version to fix that. It's called the &lt;strong&gt;Light Editor&lt;/strong&gt;, and it does the same core job — pick a template, run a game, edit the code — without the weight.&lt;/p&gt;

&lt;p&gt;Here's what the light editor is, why it matters, and where it fits alongside the full studio.&lt;/p&gt;




&lt;h2&gt;
  
  
  🐛 The Problem: The Studio Is Heavy
&lt;/h2&gt;

&lt;p&gt;The full Limn Studio is a proper IDE. When you open it, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;Build tab&lt;/strong&gt; with template selection, color tuning, and asset injection&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Code tab&lt;/strong&gt; with a file tree, a main file editor, and a language selector&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;live preview&lt;/strong&gt; pane that runs your game&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;console&lt;/strong&gt; that shows errors and logs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud sync&lt;/strong&gt; so your projects are saved&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Share links&lt;/strong&gt; so you can send your game to someone else&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of that adds up. The build tab alone has multiple form fields, a preview, and a template picker. The code tab includes a full syntax-highlighting editor. Every one of those pieces has to load before the page is usable.&lt;/p&gt;

&lt;p&gt;On a fast machine, that's a one-second load. On a phone with a slow connection, it can be ten seconds or more. And during that time, the user is looking at a blank screen wondering whether it worked.&lt;/p&gt;

&lt;p&gt;That's a bad first impression for a platform whose entire pitch is "you can build a game in minutes."&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ What We're Going to Build
&lt;/h2&gt;

&lt;p&gt;The light editor is a stripped-down version of the same idea. It keeps the parts that matter for getting started, and drops the ones that make the page heavy.&lt;/p&gt;

&lt;p&gt;Here's what stays:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Templates.&lt;/strong&gt; All fifteen templates are still available. Pick one, get a working game.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build / Code toggle.&lt;/strong&gt; Same idea as the full studio — use the form, or edit the JavaScript directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A live preview.&lt;/strong&gt; Hit Run, see your game.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A console.&lt;/strong&gt; Errors and logs appear at the bottom.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud sync and share links.&lt;/strong&gt; Your projects are saved and shareable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's what goes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The full file tree. The light editor works on a single file. That's enough for a beginner.&lt;/li&gt;
&lt;li&gt;The asset injection tab. If you need to inject HTML or CSS, use the full studio.&lt;/li&gt;
&lt;li&gt;The file-based language tools. The light editor is JavaScript-only, which is all Limn Engine uses anyway.&lt;/li&gt;
&lt;li&gt;The complex layout. The light editor is one column on mobile, two columns on desktop. It's readable on any screen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is a page that loads in under a second on almost any device.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔧 Inside the Light Editor
&lt;/h2&gt;

&lt;p&gt;Let me walk through what you see when you open it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The landing screen.&lt;/strong&gt; When you first load the light editor, you see "Ready to build" with three instructions:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Pick a template, customize the colors, then hit ▶ Run to launch your game."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Below that, a second line:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Switch to ⌨️ Code to edit or export the generated JavaScript."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And a small badge row: "15 templates • Live preview • Cloud sync • Share links."&lt;/p&gt;

&lt;p&gt;That's the entire onboarding. There's no tour, no tutorial, no modal. You're told what to do, and you can do it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The template picker.&lt;/strong&gt; Choose from fifteen templates — Platformer, Shooter, Runner, Dress-Up, Clicker, and the rest. Each one generates working game code when you select it. You don't have to write anything to see a game on screen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Build / Code toggle.&lt;/strong&gt; The Build tab is the form-driven experience — pick a template, set colors, hit Run. The Code tab is the direct editing experience — the generated JavaScript appears in a text editor, ready to modify. You can switch between them at any time; the code you see in Code mode is exactly what Build mode generated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The preview.&lt;/strong&gt; When you hit Run, the game launches in the preview pane. It uses the same &lt;code&gt;epic.js&lt;/code&gt; engine as the full studio, the arcade, and a local download. It's not a mock-up or a simulation. It's the real thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The console.&lt;/strong&gt; At the bottom of the screen, a small console shows one line on load:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"22:48:07 • Console ready. Press ▶ Run, then type expressions below."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a hint. You can type expressions into the console and see the results. It's a small touch, but it's the difference between "I don't know what to do" and "I know exactly what to do."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloud sync and share links.&lt;/strong&gt; Everything you create is stored under your account and shareable. That means you can start a game on your phone and continue it on your desktop, or send a link to a friend who can fork your game and remix it.&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 Full Studio vs. Light Editor
&lt;/h2&gt;

&lt;p&gt;Here's the comparison in a table.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Full Studio&lt;/th&gt;
&lt;th&gt;Light Editor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Templates&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ 15&lt;/td&gt;
&lt;td&gt;✅ 15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Build tab (form-driven)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Code tab (direct edit)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Live preview&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Console&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloud sync&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Share links&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;File tree&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌ Single file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Asset injection (HTML/CSS)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-file projects&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Load time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~1s on desktop, longer on mobile&lt;/td&gt;
&lt;td&gt;&amp;lt;1s on most devices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mobile-friendly layout&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Somewhat&lt;/td&gt;
&lt;td&gt;Fully&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Use case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Serious development&lt;/td&gt;
&lt;td&gt;Quick experiments, first-time users&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The short version: the full studio is for when you know what you're doing. The light editor is for when you don't yet.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 When to Use Each
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use the light editor when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're on a phone or a low-end device.&lt;/li&gt;
&lt;li&gt;You're trying Limn Engine for the first time.&lt;/li&gt;
&lt;li&gt;You want to quickly test a template.&lt;/li&gt;
&lt;li&gt;You're teaching a workshop and want everyone to start at the same place.&lt;/li&gt;
&lt;li&gt;You want to send someone a link that "just works."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use the full studio when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're working on a real project with multiple files.&lt;/li&gt;
&lt;li&gt;You need to inject custom HTML or CSS.&lt;/li&gt;
&lt;li&gt;You're editing with a full IDE-style file tree.&lt;/li&gt;
&lt;li&gt;You want the full toolchain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both tools are bookmarked. Both are useful. The light editor doesn't replace the full studio — it's a lower-friction entry point to the same engine.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Try It Yourself
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Open the light editor:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app/editor/light-editor.html" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/editor/light-editor.html&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pick a template. Hit Run. See a game. That's it.&lt;/p&gt;

&lt;p&gt;If you've never used Limn Engine before, start here. If you've used it and you want more, the full studio is one link away.&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 What You've Learned
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Why It Matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Light editor vs. full studio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Two entry points for different needs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Templates&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pick one, get working code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Build / Code toggle&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Form-driven or direct editing, your choice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Live preview&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Real engine, real game, no mocks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Console&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Type expressions, see results&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloud sync&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Your projects travel with you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mobile-first design&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Works on phones, not just desktops&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  ⚠️ Known Limitations
&lt;/h2&gt;

&lt;p&gt;The light editor doesn't have a file tree. If you're building a project with multiple files, you'll need the full studio.&lt;/p&gt;

&lt;p&gt;It doesn't support asset injection. If you need to inject HTML, CSS, or external scripts into the game's page, that's a full-studio feature.&lt;/p&gt;

&lt;p&gt;It's JavaScript-only. That's fine for Limn Engine, but if you're mixing languages, the full studio is more flexible.&lt;/p&gt;

&lt;p&gt;These are deliberate trade-offs. The light editor is lighter because it does less.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 What's Next?
&lt;/h2&gt;

&lt;p&gt;The light editor is still growing. Here are the next steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auto-save&lt;/strong&gt; so you never lose your work between sessions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL-based project state&lt;/strong&gt; so you can share a game that starts with the code pre-loaded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Template customization&lt;/strong&gt; in the light editor — currently you pick a template and get it as-is; in the next version you'll be able to tune colors from the Build tab without switching to the full studio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A "fork this game" button&lt;/strong&gt; in the light editor that loads any arcade game's code directly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build any of these, send a pull request.&lt;/p&gt;




&lt;h2&gt;
  
  
  🐛 Report Bugs
&lt;/h2&gt;

&lt;p&gt;If you find a bug in the light editor, report it on GitHub:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc/issues" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc/issues&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Please include your device, your browser, and a description of what happened.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Resources
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Light Editor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/editor/light-editor.html" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/editor/light-editor.html&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Full Studio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/editor" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/editor&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Engine Docs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Arcade&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/arcade" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/arcade&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub Repository&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Report Bugs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc/issues" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc/issues&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discord&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://discord.gg/kVpSmYWXr" rel="noopener noreferrer"&gt;discord.gg/kVpSmYWXr&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kehinde on DEV.to&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/kehinde_owolabi_e2e54567a"&gt;dev.to/kehinde_owolabi_e2e54567a&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Desire on DEV.to&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/desire_george_434_ai"&gt;dev.to/desire_george_434_ai&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🎯 The One-Line Summary
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"The light editor is Limn Studio's fast-start path — pick a template, hit Run, see a game in seconds. It's the version you send to someone who's never touched Limn Engine before."&lt;/strong&gt; ⚡🚀&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Draw your game into existence — one template at a time.&lt;/em&gt; 🎮🚀&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>programming</category>
      <category>discuss</category>
    </item>
    <item>
      <title>🎥 Video: The Display Class in Limn Engine</title>
      <dc:creator>Kehinde Owolabi</dc:creator>
      <pubDate>Fri, 23 Oct 2026 02:45:19 +0000</pubDate>
      <link>https://dev.to/kehinde_owolabi_e2e54567a/video-introduction-to-limn-engine-build-your-first-game-with-javascript-27ib</link>
      <guid>https://dev.to/kehinde_owolabi_e2e54567a/video-introduction-to-limn-engine-build-your-first-game-with-javascript-27ib</guid>
      <description>&lt;h1&gt;
  
  
  🎥 Video: The Display Class in Limn Engine
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;A full walkthrough of the Display class — creating the game window, setting the background, and controlling the canvas.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Watch the Video
&lt;/h2&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://youtu.be/uNa9wgj7I-0?feature=shared" rel="noopener noreferrer"&gt;Learn about the Display class in Limn Engine — YouTube&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the next video in the Limn Engine tutorial series. It focuses on the &lt;code&gt;Display&lt;/code&gt; class — the foundation of every Limn Engine game. It walks through creating the game window, setting the background color, and controlling the canvas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Official documentation:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app/" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📖 Introduction
&lt;/h2&gt;

&lt;p&gt;Every Limn Engine game starts with one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Display&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single line creates the canvas, sets up the game loop, and prepares the engine to draw. Everything else — every component, every particle, every score, every pixel — is drawn through the Display.&lt;/p&gt;

&lt;p&gt;The Display class is easy to use but easy to misuse. There are a few things you need to get right — the order of &lt;code&gt;perform()&lt;/code&gt; and &lt;code&gt;start()&lt;/code&gt;, the variable name &lt;code&gt;display&lt;/code&gt;, and the difference between the canvas and the world. Getting them wrong doesn't crash the engine, but it does make your game behave in ways that are hard to debug.&lt;/p&gt;

&lt;p&gt;This video walks through the Display class from start to finish.&lt;/p&gt;

&lt;p&gt;Here's what the video covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating a Display and starting the canvas&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;perform()&lt;/code&gt; method — why it comes first, and what breaks if you forget&lt;/li&gt;
&lt;li&gt;Setting the background color&lt;/li&gt;
&lt;li&gt;Sizing the canvas&lt;/li&gt;
&lt;li&gt;The camera and how it relates to the Display&lt;/li&gt;
&lt;li&gt;Common mistakes and how to avoid them&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🎬 What the Video Covers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Creating a Display.&lt;/strong&gt; The video starts with the one-line setup — &lt;code&gt;new Display()&lt;/code&gt; — and explains what happens under the hood when you create it. The engine creates a canvas element, sets up a 2D drawing context, and prepares the game loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;perform()&lt;/code&gt; method.&lt;/strong&gt; Most of the video focuses on this. &lt;code&gt;display.perform()&lt;/code&gt; is what switches the engine from the default &lt;code&gt;setInterval&lt;/code&gt; loop to &lt;code&gt;requestAnimationFrame&lt;/code&gt;. Without it, the game runs at about 50fps with inconsistent pacing. With it, the game runs at a smooth 60fps (or whatever the monitor's refresh rate is).&lt;/p&gt;

&lt;p&gt;The order matters: &lt;code&gt;perform()&lt;/code&gt; must come before &lt;code&gt;start()&lt;/code&gt;. If you call &lt;code&gt;start()&lt;/code&gt; first, the patch doesn't take effect, and the game runs on the slow loop. This is one of the most common mistakes beginners make.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setting the background color.&lt;/strong&gt; The video shows &lt;code&gt;display.backgroundColor()&lt;/code&gt;, which sets the canvas background via CSS. It also shows the gradient options — &lt;code&gt;lgradient()&lt;/code&gt; for linear gradients and &lt;code&gt;rgradient()&lt;/code&gt; for radial ones — for more polished backgrounds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sizing the canvas.&lt;/strong&gt; &lt;code&gt;display.start(width, height)&lt;/code&gt; sets the canvas dimensions. The video covers how to choose a size, how the size relates to mobile screens, and how to scale the canvas with CSS without changing its internal resolution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The camera.&lt;/strong&gt; The Display has a camera attached to it. The video explains how the camera moves independently of the canvas, how world coordinates differ from screen coordinates, and how to set the camera's world size so it doesn't scroll past the edges of the level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common mistakes.&lt;/strong&gt; The video finishes with the most common Display-related bugs: forgetting &lt;code&gt;perform()&lt;/code&gt;, using a different variable name than &lt;code&gt;display&lt;/code&gt;, and calling &lt;code&gt;start()&lt;/code&gt; more than once.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Try It Yourself
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The engine:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app/" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The online editor (no download required):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app/editor/" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/editor&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The arcade (play games made by others):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app/arcade" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/arcade&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📺 Full Video Series
&lt;/h2&gt;

&lt;p&gt;This video is part of the ongoing Limn Engine tutorial series. Here's the current lineup:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Video&lt;/th&gt;
&lt;th&gt;What It Covers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/watch?v=xBUtyqKQcXc&amp;amp;list=PLS665-mFu8gkwQkUorwO2325UW_OZe2F5" rel="noopener noreferrer"&gt;Series Start&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Introduction to the series&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Setup and first Display&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Components and movement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/watch?v=xBUtyqKQcXc&amp;amp;list=PLS665-mFu8gkwQkUorwO2325UW_OZe2F5&amp;amp;index=4" rel="noopener noreferrer"&gt;Introduction to Limn Engine&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;What Limn is, and how to get started&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://youtu.be/uNa9wgj7I-0?feature=shared" rel="noopener noreferrer"&gt;The Display Class&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Creating the game window, background colors, and canvas control&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  📝 Quick Reference: The Display Class
&lt;/h2&gt;

&lt;p&gt;Here's the minimal Display setup from the video, in case you want to follow along without typing every line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Display Demo&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"epic.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;// Create the engine&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Display&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// MUST come before start()&lt;/span&gt;
    &lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Create the canvas&lt;/span&gt;
    &lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Set the background&lt;/span&gt;
    &lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#0a0a0a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Game loop&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Game logic goes here&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's the gradient version, for a more polished background:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lgradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bottom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#0f3460&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#16213e&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// or&lt;/span&gt;
&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rgradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#1a1a2e&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#0a0a0f&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  📊 What You've Learned
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Why It Matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;new Display()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Creates the canvas, the game loop, and the camera&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;display.perform()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Switches to &lt;code&gt;requestAnimationFrame&lt;/code&gt; for smooth 60fps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Order matters&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;perform()&lt;/code&gt; must come before &lt;code&gt;start()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;display.start(w, h)&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sets the canvas size and begins the game loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;display.backgroundColor()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sets the canvas background via CSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;display.lgradient()&lt;/code&gt; / &lt;code&gt;rgradient()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gradient backgrounds for a more polished look&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;display.camera&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Controls what part of the world is visible&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  ⚠️ Common Mistakes to Avoid
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Forgetting &lt;code&gt;perform()&lt;/code&gt;.&lt;/strong&gt; Without it, your game runs at about 50fps on an unreliable &lt;code&gt;setInterval&lt;/code&gt; loop. Movement feels inconsistent, and &lt;code&gt;dt&lt;/code&gt; doesn't behave the way you expect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Calling &lt;code&gt;start()&lt;/code&gt; before &lt;code&gt;perform()&lt;/code&gt;.&lt;/strong&gt; The patch doesn't take effect if &lt;code&gt;start()&lt;/code&gt; has already run. Always call &lt;code&gt;perform()&lt;/code&gt; first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Naming the Display something else.&lt;/strong&gt; The engine references &lt;code&gt;display&lt;/code&gt; internally. Calling your instance &lt;code&gt;game&lt;/code&gt; or &lt;code&gt;app&lt;/code&gt; will throw errors in places you don't expect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Calling &lt;code&gt;start()&lt;/code&gt; more than once.&lt;/strong&gt; The engine creates a canvas on every &lt;code&gt;start()&lt;/code&gt; call. Multiple calls stack multiple canvases on the page.&lt;/p&gt;




&lt;h2&gt;
  
  
  🐛 Found a Bug?
&lt;/h2&gt;

&lt;p&gt;If you find something in the video that doesn't work, report it on GitHub:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc/issues" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc/issues&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Please include your browser, your device, and the timestamp in the video where the issue appears.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Resources
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;YouTube Video&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://youtu.be/uNa9wgj7I-0?feature=shared" rel="noopener noreferrer"&gt;The Display Class&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Previous Video&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/watch?v=xBUtyqKQcXc&amp;amp;list=PLS665-mFu8gkwQkUorwO2325UW_OZe2F5&amp;amp;index=4" rel="noopener noreferrer"&gt;Introduction to Limn Engine&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Engine Docs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Studio (Editor)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/editor/" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/editor&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Arcade&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/arcade" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/arcade&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub Repository&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Report Bugs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc/issues" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc/issues&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discord&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://discord.gg/kVpSmYWXr" rel="noopener noreferrer"&gt;discord.gg/kVpSmYWXr&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kehinde on DEV.to&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/kehinde_owolabi_e2e54567a"&gt;dev.to/kehinde_owolabi_e2e54567a&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🎯 The One-Line Summary
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"A full walkthrough of the Display class — creating the game window, setting the background, sizing the canvas, and controlling the camera in Limn Engine."&lt;/strong&gt; 🎮🚀&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Draw your game into existence — one Display at a time.&lt;/em&gt; 🎮🚀&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>programming</category>
      <category>javascript</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>🎥 Video: Introduction to Limn Engine — Build Your First Game with JavaScript</title>
      <dc:creator>Kehinde Owolabi</dc:creator>
      <pubDate>Mon, 19 Oct 2026 04:38:20 +0000</pubDate>
      <link>https://dev.to/kehinde_owolabi_e2e54567a/video-introduction-to-limn-engine-build-your-first-game-with-javascript-2aak</link>
      <guid>https://dev.to/kehinde_owolabi_e2e54567a/video-introduction-to-limn-engine-build-your-first-game-with-javascript-2aak</guid>
      <description>&lt;h1&gt;
  
  
  🎥 Video: Introduction to Limn Engine — Build Your First Game with JavaScript
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;A full video walkthrough of Limn Engine: what it is, why it exists, and how to get started.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Watch the Video
&lt;/h2&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://www.youtube.com/watch?v=xBUtyqKQcXc&amp;amp;list=PLS665-mFu8gkwQkUorwO2325UW_OZe2F5&amp;amp;index=4" rel="noopener noreferrer"&gt;Introduction to Limn Engine — YouTube&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the fourth video in the Limn Engine tutorial series. It walks through what Limn Engine is, what problem it solves, and how to go from zero to your first running game.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Official documentation:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app/" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📖 Introduction
&lt;/h2&gt;

&lt;p&gt;Most game development tutorials start with an installation step. You download a package manager, set up a build tool, configure a bundler, install dependencies, and wait ten minutes for npm to do its thing. By the time you write your first line of game code, you've already given up twice.&lt;/p&gt;

&lt;p&gt;Limn Engine takes the opposite approach. There is no installation. There is no build step. There is no package manager. You download one file, include it in a script tag, and you have a game running in the browser.&lt;/p&gt;

&lt;p&gt;That's the promise. This video walks through whether the promise holds up — and shows you how to actually use the engine once you've got it.&lt;/p&gt;

&lt;p&gt;Here's what the video covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What Limn Engine is, and what it isn't&lt;/li&gt;
&lt;li&gt;The zero-configuration philosophy&lt;/li&gt;
&lt;li&gt;How to download and include the engine&lt;/li&gt;
&lt;li&gt;Your first Display, Component, and game loop&lt;/li&gt;
&lt;li&gt;Where to go next&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🎬 What the Video Covers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What Limn Engine is.&lt;/strong&gt; Limn is a lightweight 2D game engine for the browser, built on HTML5 Canvas. It's designed for beginners — people who know a little JavaScript and want to make a game without fighting tooling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Limn Engine isn't.&lt;/strong&gt; It's not Unity. It's not Phaser. It doesn't try to be. It's not a visual editor, it's not a physics engine, and it's not a full-featured game framework. It's a small, focused set of tools that cover the most common tasks in 2D game development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The zero-configuration philosophy.&lt;/strong&gt; Most game engines require setup. Limn doesn't. The video walks through the one-file download process and shows just how small the setup actually is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your first game.&lt;/strong&gt; The video builds a working game from scratch — a moving player, collision detection, and a score — in about fifty lines of code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where to go next.&lt;/strong&gt; Once you have a game running, the video points to the official documentation for the next steps: physics, tilemaps, particles, camera controls, and the arcade where you can publish your game.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Try It Yourself
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The engine:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app/" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The online editor (no download required):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app/editor/" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/editor&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The arcade (play games made by others):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app/arcade" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/arcade&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The video uses the download-and-include approach, but if you don't want to download anything, the online editor runs the engine directly in the browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  📺 Full Video Series
&lt;/h2&gt;

&lt;p&gt;This is the fourth video in the series. If you're starting from the beginning, work through them in order:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Video&lt;/th&gt;
&lt;th&gt;What It Covers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/watch?v=xBUtyqKQcXc&amp;amp;list=PLS665-mFu8gkwQkUorwO2325UW_OZe2F5" rel="noopener noreferrer"&gt;Series Start&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Introduction to the series&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Setup and first Display&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Components and movement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://www.youtube.com/watch?v=xBUtyqKQcXc&amp;amp;list=PLS665-mFu8gkwQkUorwO2325UW_OZe2F5&amp;amp;index=4" rel="noopener noreferrer"&gt;Introduction to Limn Engine&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What Limn is, and how to get started&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Building your first complete game&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  📝 Quick Reference: The Minimal Game
&lt;/h2&gt;

&lt;p&gt;Here's the smallest Limn Engine game you can build, in case you want to follow along in the video without typing every line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;My First Limn Game&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"epic.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Display&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#0a0a0a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#7fffb2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;375&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;275&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rect&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;39&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;37&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;move&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save that as an HTML file, put &lt;code&gt;epic.js&lt;/code&gt; next to it, open it in a browser, and you have a working game. That's the whole setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 What You've Learned
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Why It Matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Zero-config engine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One script tag, no build tools, no package manager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Display&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The canvas and the game loop — the foundation of every game&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Component&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Every game object — player, enemy, coin, wall&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;update(dt)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The game loop that runs every frame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;deltaTime&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multiplies movement so the game runs at the same speed on all devices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;move.bound()&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Keeps objects on screen without writing your own edge detection&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🐛 Found a Bug?
&lt;/h2&gt;

&lt;p&gt;If you find something in the video that doesn't work, report it on GitHub:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc/issues" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc/issues&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Please include your browser, your device, and the timestamp in the video where the issue appears.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Resources
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;YouTube Video&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/watch?v=xBUtyqKQcXc&amp;amp;list=PLS665-mFu8gkwQkUorwO2325UW_OZe2F5&amp;amp;index=4" rel="noopener noreferrer"&gt;Introduction to Limn Engine&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Engine Docs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Studio (Editor)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/editor/" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/editor&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Arcade&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/arcade" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/arcade&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub Repository&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Report Bugs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc/issues" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc/issues&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discord&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://discord.gg/kVpSmYWXr" rel="noopener noreferrer"&gt;discord.gg/kVpSmYWXr&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kehinde on DEV.to&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/kehinde_owolabi_e2e54567a"&gt;dev.to/kehinde_owolabi_e2e54567a&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🎯 The One-Line Summary
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"A full video walkthrough of Limn Engine — what it is, why it exists, and how to build your first game with JavaScript and zero configuration."&lt;/strong&gt; 🎮🚀&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Draw your game into existence — one video at a time.&lt;/em&gt; 🎮🚀&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Announcing the first limn engine extension called Bust Beta</title>
      <dc:creator>Kehinde Owolabi</dc:creator>
      <pubDate>Thu, 15 Oct 2026 22:19:22 +0000</pubDate>
      <link>https://dev.to/kehinde_owolabi_e2e54567a/announcing-the-first-limn-engine-extension-called-bust-beta-1iog</link>
      <guid>https://dev.to/kehinde_owolabi_e2e54567a/announcing-the-first-limn-engine-extension-called-bust-beta-1iog</guid>
      <description>&lt;h1&gt;
  
  
  Bust Beta
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/terracodes004/bust-beta/" rel="noopener noreferrer"&gt;Bust Bets&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Limn Engine's dual-thread rendering plugin — in beta.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Bust Beta is an experimental performance layer for &lt;a href="https://github.com/terracodes004/limn-engine-doc" rel="noopener noreferrer"&gt;Limn Engine&lt;/a&gt;. It moves rendering work off the main thread and onto a Web Worker, then hands the finished frame back to the visible canvas for display.&lt;/p&gt;

&lt;p&gt;On devices that can keep up, the result is a higher and more stable frame rate than a single-threaded Canvas 2D loop can deliver.&lt;/p&gt;

&lt;p&gt;It is beta. It works. It is not finished.&lt;/p&gt;


&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What It Does&lt;/li&gt;
&lt;li&gt;Why It Matters&lt;/li&gt;
&lt;li&gt;How It Works&lt;/li&gt;
&lt;li&gt;Status&lt;/li&gt;
&lt;li&gt;Known Limitations&lt;/li&gt;
&lt;li&gt;How to Try It&lt;/li&gt;
&lt;li&gt;What's Next&lt;/li&gt;
&lt;li&gt;Philosophy&lt;/li&gt;
&lt;li&gt;Feedback&lt;/li&gt;
&lt;li&gt;License&lt;/li&gt;
&lt;li&gt;Links&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;p&gt;Limn already ships with a dual-canvas architecture — a hidden "fake" canvas caches the static layer, and the visible canvas draws the dynamic layer on top every frame. Bust Beta extends that idea across threads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;main thread&lt;/strong&gt; runs game logic, input, and the final composite.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;worker thread&lt;/strong&gt; renders the dynamic layer into an &lt;code&gt;OffscreenCanvas&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The worker returns the finished frame as an &lt;code&gt;ImageBitmap&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The main thread blits it onto the visible canvas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both threads work at the same time. The display shows whichever frame is freshest.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why It Matters
&lt;/h2&gt;

&lt;p&gt;Most Canvas 2D engines are limited by what a single thread can do between two &lt;code&gt;requestAnimationFrame&lt;/code&gt; ticks. On a 120Hz or 144Hz display, the panel is ready to show 120 or 144 frames per second — but the engine often can't produce them, because rendering and logic are fighting for the same thread.&lt;/p&gt;

&lt;p&gt;Bust Beta decouples the two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Higher refresh-rate utilization&lt;/strong&gt; — on high-refresh panels, the worker can produce frames faster than a single-threaded loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More consistent pacing&lt;/strong&gt; — when the main thread has a heavy frame, the worker keeps producing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same API surface&lt;/strong&gt; — you don't rewrite your game. Bust Beta hooks &lt;code&gt;ani()&lt;/code&gt; from outside.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a claim that Canvas 2D beats WebGL. It isn't trying to. It's a claim that a Canvas 2D engine, properly threaded, can match the frame-rate &lt;em&gt;consistency&lt;/em&gt; of a GPU engine on the class of 2D games Limn targets.&lt;/p&gt;


&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;The pipeline has four stages, repeated every animation frame.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 1 — Main thread prepares the frame.&lt;/strong&gt; The main thread runs &lt;code&gt;update(dt)&lt;/code&gt; so game logic stays in sync with input. It also renders the static layer (the fake canvas) as it always did, and sends a message to the worker containing the current game state: the component list, the camera, the current scene, and the delta time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 2 — Worker receives and renders.&lt;/strong&gt; The worker listens on &lt;code&gt;onmessage&lt;/code&gt;. On first message, it stores the &lt;code&gt;OffscreenCanvas&lt;/code&gt; and grabs its 2D context. On every subsequent message, it clears the offscreen canvas, applies the camera transform, loops through the component list, moves and draws each component, then calls &lt;code&gt;transferToImageBitmap()&lt;/code&gt; to freeze the frame and hand it back to the main thread.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 3 — Main thread receives the bitmap.&lt;/strong&gt; The main thread's &lt;code&gt;worker.onmessage&lt;/code&gt; handler stores the returned &lt;code&gt;ImageBitmap&lt;/code&gt; in a variable called &lt;code&gt;nextTCJSgameFrame&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 4 — Main thread composites.&lt;/strong&gt; On the next animation frame, the main thread blits the worker's bitmap onto the visible canvas via &lt;code&gt;transferFromImageBitmap()&lt;/code&gt;, then draws the static layer on top, then draws any dynamic components that the worker didn't cover.&lt;/p&gt;

&lt;p&gt;The net effect: while the main thread is running game logic, the worker is producing the next frame. Neither thread blocks the other.&lt;/p&gt;


&lt;h2&gt;
  
  
  Status
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Worker pipeline runs&lt;/td&gt;
&lt;td&gt;✅ Working&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:1 send/receive in steady state&lt;/td&gt;
&lt;td&gt;✅ Confirmed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;120fps on desktop (high-refresh panel)&lt;/td&gt;
&lt;td&gt;✅ Observed on dev machine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fallback main-thread render&lt;/td&gt;
&lt;td&gt;⚠️ Still active — pipeline is additive, not replacing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Low-end device testing&lt;/td&gt;
&lt;td&gt;❌ Not done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delta timing across refresh rates&lt;/td&gt;
&lt;td&gt;⚠️ Uses &lt;code&gt;1/fps&lt;/code&gt;, drift possible between devices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Published benchmarks&lt;/td&gt;
&lt;td&gt;❌ Pending&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Beta means: the mechanism works, but the numbers aren't proven across devices yet. Treat it as a promising experiment, not a solved problem.&lt;/p&gt;


&lt;h2&gt;
  
  
  Known Limitations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;upgradePerform()&lt;/code&gt; must be called manually.&lt;/strong&gt; In this beta, you call it before &lt;code&gt;display.start()&lt;/code&gt;. A future version will auto-activate on script load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delta timing uses &lt;code&gt;1/fps&lt;/code&gt;.&lt;/strong&gt; On a 60Hz device vs. a 120Hz device, physics speed can differ. A fixed-timestep update loop is the eventual fix. Until then, tune physics constants for your target refresh rate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The main thread still draws a fallback layer.&lt;/strong&gt; Until that's disabled, both threads do some work. The intended final architecture is worker-only rendering for the dynamic layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No published benchmark suite yet.&lt;/strong&gt; The claim of GPU-comparable consistency is based on local testing, not a public, reproducible benchmark.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Same-origin only.&lt;/strong&gt; The worker cannot be loaded cross-origin. If you serve &lt;code&gt;bust.js&lt;/code&gt; from a CDN, you'll need to fetch and Blob-wrap &lt;code&gt;bust-worker.js&lt;/code&gt; yourself, or self-host both files.&lt;/p&gt;


&lt;h2&gt;
  
  
  How to Try It
&lt;/h2&gt;

&lt;p&gt;Bust Beta is three files. Drop them next to your game:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"epic.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"bust.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"test.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in your game file, call &lt;code&gt;upgradePerform()&lt;/code&gt; before you create your Display:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Display&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;upgradePerform&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bust.js&lt;/code&gt; loads &lt;code&gt;bust-worker.js&lt;/code&gt; automatically. Both files must be served from the same origin — the worker cannot be loaded cross-origin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Beta note:&lt;/strong&gt; &lt;code&gt;upgradePerform()&lt;/code&gt; must be called before &lt;code&gt;display.start()&lt;/code&gt; runs. In this beta it is a manual step. A future version will auto-activate on script load.&lt;/p&gt;

&lt;p&gt;That's it. Your existing Limn game runs otherwise unchanged.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Auto-activate on script load, so &lt;code&gt;upgradePerform()&lt;/code&gt; becomes optional.&lt;/li&gt;
&lt;li&gt;Disable the fallback render path so the worker truly owns the dynamic layer.&lt;/li&gt;
&lt;li&gt;Add a worker-side fps counter to prove the frame count is worker-produced, not RAF-tick-produced.&lt;/li&gt;
&lt;li&gt;Test on low-end Android — the device class the plugin is for.&lt;/li&gt;
&lt;li&gt;Publish a benchmark scene (open source) so anyone can reproduce the numbers.&lt;/li&gt;
&lt;li&gt;Move to a fixed-timestep update loop so physics is refresh-rate independent.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Philosophy
&lt;/h2&gt;

&lt;p&gt;Limn's rule is "common outcomes should be functions." Bust Beta's rule is "the engine shouldn't be the reason a frame is missed."&lt;/p&gt;

&lt;p&gt;Add the script. Get a frame. If your hardware can show it, Bust Beta tries to produce it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Feedback
&lt;/h2&gt;

&lt;p&gt;Bust Beta is a beta. If it works on your device — or if it doesn't — open an issue with your device model, browser version, and the fps counter reading. Every report is a data point.&lt;/p&gt;




&lt;h2&gt;
  
  
  License
&lt;/h2&gt;

&lt;p&gt;MIT&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/terracodes004/bust-beta/" rel="noopener noreferrer"&gt;Bust Bets&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Limn Engine:&lt;/strong&gt; &lt;a href="https://github.com/terracodes004/limn-engine-doc" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discord:&lt;/strong&gt; &lt;a href="https://discord.gg/kVpSmYWXr" rel="noopener noreferrer"&gt;discord.gg/kVpSmYWXr&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issues:&lt;/strong&gt; &lt;a href="https://github.com/terracodes004/limn-engine-doc/issues" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc/issues&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation:&lt;/strong&gt; &lt;a href="https://limn-engine-doc.vercel.app" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arcade:&lt;/strong&gt; &lt;a href="https://limn-engine-doc.vercel.app/arcade" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/arcade&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Add the script. Get a frame.&lt;/em&gt; 🚀&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>testing</category>
      <category>showdev</category>
    </item>
    <item>
      <title>From Buggy Prototype to Polished Arcade: How I Upgraded Three Browser Games</title>
      <dc:creator>Kehinde Owolabi</dc:creator>
      <pubDate>Wed, 14 Oct 2026 12:58:25 +0000</pubDate>
      <link>https://dev.to/kehinde_owolabi_e2e54567a/from-buggy-prototype-to-polished-arcade-how-i-upgraded-three-browser-games-4jd9</link>
      <guid>https://dev.to/kehinde_owolabi_e2e54567a/from-buggy-prototype-to-polished-arcade-how-i-upgraded-three-browser-games-4jd9</guid>
      <description>&lt;h1&gt;
  
  
  From Buggy Prototype to Polished Arcade: How I Upgraded Three Browser Games
&lt;/h1&gt;

&lt;p&gt;Over the past few days I've been deep in the trenches refactoring and rebuilding three browser games built on a lightweight 2D/3D canvas engine. What started as "fix a few bugs" turned into a full-blown feature overhaul across a platformer, a racer, and an FPS.&lt;/p&gt;

&lt;p&gt;Here's what actually changed, why, and what I learned.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Starting Point
&lt;/h2&gt;

&lt;p&gt;Three games, all built on the same homegrown engine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Neon Caverns: Seasons&lt;/strong&gt; — a platformer with 20 levels and a coin economy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Death Boost: Seasons&lt;/strong&gt; — a pseudo-3D arcade racer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Riftfire&lt;/strong&gt; (formerly Raycast FPS) — a raycast shooter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The initial request was simple: "there are bugs." But as I dug in, the real issues were architectural — not just a few typos.&lt;/p&gt;




&lt;h2&gt;
  
  
  Neon Caverns: Seasons
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Bugs
&lt;/h3&gt;

&lt;p&gt;Two silent killers were ruining the game:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Tile mutations across restarts.&lt;/strong&gt; The engine's tilemap constructor mutated a shared array every time a level loaded. After one restart, every tile lookup broke. Players saw invisible walls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The pause button drifted off screen.&lt;/strong&gt; It was added as a world-space entity, so it scrolled away with the camera instead of staying pinned to the HUD.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Overhaul
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;4 seasons × 12 levels = 48 unique stages&lt;/strong&gt;, each procedurally generated but deterministic per run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ramping difficulty curve&lt;/strong&gt; — enemy HP, speed, accuracy, and map density all scale with a single normalized value from 0 to 1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full procedural audio&lt;/strong&gt; — every sound effect and music track is synthesized live via the Web Audio API. No external files, no downloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent coins and upgrades&lt;/strong&gt; — health, speed, jump, magnet range, shield tiers, all saved to localStorage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Star ratings&lt;/strong&gt; per level (1–3 stars based on health, time, and coin collection).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Season unlock chaining&lt;/strong&gt; — clear all 12 levels of a season to open the next one.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Death Boost: Seasons
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Bugs
&lt;/h3&gt;

&lt;p&gt;This game had a list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Invisible hazards were killing players&lt;/li&gt;
&lt;li&gt;Cars were colliding from &lt;em&gt;behind&lt;/em&gt; the player&lt;/li&gt;
&lt;li&gt;Traffic recycled to a moving target, causing pile-ups&lt;/li&gt;
&lt;li&gt;Menu clicks silently failed when audio init threw&lt;/li&gt;
&lt;li&gt;Sprites rendered ~1000× too small to be visible&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Overhaul
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;4× wider road&lt;/strong&gt; — from 2000 to 3400 units wide, 5 lanes instead of 3.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;60% fewer obstacles&lt;/strong&gt;, 50% less traffic, longer spacing between hazards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;12 tracks total&lt;/strong&gt; with lap counts from 1 to 3.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bug fixes&lt;/strong&gt;: signed z-window for hazard collisions, cooldowns on repeated hits, sprite scale corrected to match world geometry, audio failures no longer swallow input.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always-visible pause button&lt;/strong&gt; — now pinned to screen coordinates instead of world space.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-level difficulty display&lt;/strong&gt; — cars show their speed, health, and boost stats as animated bars.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Riftfire
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Overhaul
&lt;/h3&gt;

&lt;p&gt;What was originally a 5-map raycast FPS became:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;12 seasons × 30 levels = 360 stages&lt;/strong&gt;, all procedurally generated with seeded randomness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Difficulty ramp&lt;/strong&gt; — enemy HP, damage, accuracy, speed, and fire rate all scale from easy (level 1) to brutal (level 360).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Procedural audio&lt;/strong&gt; — every gunshot has its own synthesized waveform. Rifle, pistol, shotgun, sniper, minigun — all distinct, all generated at runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adaptive music&lt;/strong&gt; — four different musical scales that switch based on the current level's difficulty.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weapons shop, ranks, and coin economy&lt;/strong&gt; — buy new weapons, earn XP, climb 10 ranks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile touch support&lt;/strong&gt; — analog joystick for movement, drag-to-aim for firing.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Common Threads
&lt;/h2&gt;

&lt;p&gt;Across all three games, the same principles showed up again and again:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Separate World Space from Screen Space
&lt;/h3&gt;

&lt;p&gt;The pause button bug and the sprite scale bug both came from mixing world coordinates with screen coordinates. Anything that should stay fixed on the HUD must be drawn &lt;em&gt;after&lt;/em&gt; the camera transform is popped.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Never Trust Global Mutable State
&lt;/h3&gt;

&lt;p&gt;The tilemap bug was caused by an engine-level &lt;code&gt;array.unshift()&lt;/code&gt; that silently mutated data shared between level loads. Whenever possible, rebuild state fresh at scene boundaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Procedural Content Is Cheap and Endless
&lt;/h3&gt;

&lt;p&gt;All three games now generate their content at runtime. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No asset loading delays&lt;/li&gt;
&lt;li&gt;No missing-file errors&lt;/li&gt;
&lt;li&gt;No copyright issues&lt;/li&gt;
&lt;li&gt;No download size&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Web Audio can synthesize a gunshot, a jump sound, or a full musical theme in a few lines of code.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Input Handling Must Be Defensive
&lt;/h3&gt;

&lt;p&gt;The click-swallowing bug in Death Boost happened because &lt;code&gt;initAudio()&lt;/code&gt; threw an exception and aborted the event listener before &lt;code&gt;pendingClick&lt;/code&gt; was ever set. &lt;strong&gt;Always set state before calling anything that might throw.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Difficulty Should Be a Single Scalar
&lt;/h3&gt;

&lt;p&gt;Rather than tweaking 20 numbers per level, each game derives all difficulty factors from one normalized value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;difficulty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;globalLevel&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;totalLevels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one number then scales enemy HP, count, speed, accuracy, and map density. It's clean, tunable, and easy to reason about.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;If I started over:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Version control from day one.&lt;/strong&gt; This was all done in single monolithic files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write tests for input flow.&lt;/strong&gt; The click bug was caught by a human, not a test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate rendering from state.&lt;/strong&gt; Several bugs came from render functions that also mutated game state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build a small dev console&lt;/strong&gt; — a way to jump to any level, tweak difficulty, and inspect state.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Final Numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Game&lt;/th&gt;
&lt;th&gt;Levels&lt;/th&gt;
&lt;th&gt;Audio&lt;/th&gt;
&lt;th&gt;Notable Feature&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Neon Caverns: Seasons&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;100% procedural&lt;/td&gt;
&lt;td&gt;Season unlock chains&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Death Boost: Seasons&lt;/td&gt;
&lt;td&gt;12 tracks&lt;/td&gt;
&lt;td&gt;100% procedural&lt;/td&gt;
&lt;td&gt;Multi-lap enduros&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Riftfire&lt;/td&gt;
&lt;td&gt;360&lt;/td&gt;
&lt;td&gt;100% procedural&lt;/td&gt;
&lt;td&gt;Adaptive music scales&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All three ship as &lt;strong&gt;single HTML files&lt;/strong&gt;, &lt;strong&gt;no external assets&lt;/strong&gt;, and run in any modern browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;The best thing about browser games is how fast the iteration loop can be. Change a number, refresh the page, feel the difference. The worst thing is how quickly small architectural mistakes compound into invisible bugs.&lt;/p&gt;

&lt;p&gt;The three games above went from "playable but broken" to "actually fun" by fixing a handful of fundamental issues and then building outward. Every new feature is now cheap to add because the foundation is solid.&lt;/p&gt;

&lt;p&gt;If you're building a browser game and something feels off, it's almost never "just one bug." It's usually a small design decision that ignored a boundary between two systems. Find the boundary. Fix the boundary. The rest follows.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>devops</category>
    </item>
    <item>
      <title>Limn Engine Is Getting a Performance Plugin That Might Change How You Think About Browser Games</title>
      <dc:creator>Kehinde Owolabi</dc:creator>
      <pubDate>Wed, 07 Oct 2026 20:58:37 +0000</pubDate>
      <link>https://dev.to/kehinde_owolabi_e2e54567a/limn-engine-is-getting-a-performance-plugin-that-might-change-how-you-think-about-browser-games-324n</link>
      <guid>https://dev.to/kehinde_owolabi_e2e54567a/limn-engine-is-getting-a-performance-plugin-that-might-change-how-you-think-about-browser-games-324n</guid>
      <description>&lt;h1&gt;
  
  
  Limn Engine Is Getting a Performance Plugin That Might Change How You Think About Browser Games
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;The engine used to cap at 60. Now the display is the only limit.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you've ever built a browser game, you know the feeling. It runs at a smooth 60fps on your laptop. You send it to a friend. Their phone chokes. Their older Android tablet chokes harder. The frame counter drops from 60 to 45, then 30, then stutters at 18 while particles flicker and input feels like it's arriving by mail.&lt;/p&gt;

&lt;p&gt;This is the universal tax of browser game development. Weak hardware. Inconsistent frame pacing. Frustration that has nothing to do with your code and everything to do with how the browser schedules rendering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limn Engine has a fix coming.&lt;/strong&gt; And it's not a rewrite. It's not a new engine. It's a plugin — one extra &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag, no configuration, no code changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Limn Already Gets Right
&lt;/h2&gt;

&lt;p&gt;Limn Engine was built on a simple philosophy: &lt;strong&gt;common outcomes should be functions.&lt;/strong&gt; No build tools. No &lt;code&gt;npm install&lt;/code&gt;. No config files. One script tag, one &lt;code&gt;Display&lt;/code&gt; instance, and you're making a game.&lt;/p&gt;

&lt;p&gt;That philosophy has already produced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A dual-canvas architecture that caches static scenes&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;move&lt;/code&gt; utility that turns boilerplate into one-liners&lt;/li&gt;
&lt;li&gt;A built-in audio system with volume buses&lt;/li&gt;
&lt;li&gt;Particles, sprites, tilemaps, and camera shake — all in one file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But even with all of that, Limn is still bound by the same browser limits that every other Canvas 2D engine is bound by. Weak devices drop frames. High-refresh panels don't always get used. Frame pacing wobbles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The upcoming plugin changes that.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  "60fps Is the Ceiling"
&lt;/h2&gt;

&lt;p&gt;That's what we were told. On low-end devices, 60 frames per second is the natural limit of what the browser can deliver. The display refreshes at 60Hz. RAF fires 60 times a second. The math closes. There's nothing above it.&lt;/p&gt;

&lt;p&gt;It's a fair objection. On a 60Hz panel, 60 visible frames per second really is the physical limit — no engine can show more than the screen refreshes. But "the display caps at 60" and "the engine caps at 60" are two different claims. One is hardware. The other is a design choice.&lt;/p&gt;

&lt;p&gt;For most engines, on most hardware, the ceiling holds — but only because nobody has bothered to push past it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Beyond the ceiling there is the sky. Beyond the sky there is infinite space."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We're not claiming we've broken physics. We're claiming that the ceiling people keep pointing to is lower than it needs to be — and that once you stop assuming it's fixed, there's a lot of room above it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Coming
&lt;/h2&gt;

&lt;p&gt;A drop-in performance plugin for Limn Engine that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Raises the engine's internal tick rate&lt;/strong&gt; so the display is no longer waiting on the engine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Matches GPU engine frame-rate consistency&lt;/strong&gt; on small to medium 2D scenes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works across device classes&lt;/strong&gt; — desktop, mid-range Android, low-end Android&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Requires zero configuration&lt;/strong&gt; — one script tag, auto-activates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Falls back gracefully&lt;/strong&gt; if the device can't support it&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Doesn't change a single line of your game code&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your Limn game runs today, it'll run with the plugin. Faster. Smoother. On hardware you might have given up on.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;h3&gt;
  
  
  For solo developers
&lt;/h3&gt;

&lt;p&gt;You don't have a QA team. You don't have a device lab. You have your laptop and a vague hope that your game runs on whatever your players own. The plugin closes that gap. What you see on your machine is closer to what your players will see on theirs.&lt;/p&gt;

&lt;h3&gt;
  
  
  For game jams
&lt;/h3&gt;

&lt;p&gt;Game jams are won and lost on first impressions. A game that stutters on a judge's phone doesn't get a second look. A game that hits a stable frame rate on a budget Android gets remembered. The plugin turns "technically works" into "feels great."&lt;/p&gt;

&lt;h3&gt;
  
  
  For web games as a medium
&lt;/h3&gt;

&lt;p&gt;Browser games have always lived in the shadow of native. Not because browsers can't do great games — they can — but because the &lt;em&gt;feel&lt;/em&gt; has often been second-rate on low-end hardware. If this plugin does what it's designed to do, that gap narrows. Not on flagship phones. On &lt;em&gt;everyone's&lt;/em&gt; phones.&lt;/p&gt;

&lt;h3&gt;
  
  
  For Limn's philosophy
&lt;/h3&gt;

&lt;p&gt;The plugin preserves what makes Limn worth using. It doesn't ask you to restructure your code. It doesn't require a build step. It doesn't add a dependency. It's an &lt;em&gt;enhancement&lt;/em&gt; — one script tag that makes existing games smoother without changing how they're written.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We're Not Claiming
&lt;/h2&gt;

&lt;p&gt;We're not saying this replaces Unity, Godot, or a "real" game engine. Different tools, different problems.&lt;/p&gt;

&lt;p&gt;We're not saying it's free performance. The plugin uses real resources. It's engineered, not magic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we are saying is this:&lt;/strong&gt; for the class of 2D games Limn is built for — platformers, puzzle games, RPGs, retro-style action games, game jam entries, learning projects — the plugin brings frame-rate consistency that previously required a much heavier engine.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Actually Changes
&lt;/h2&gt;

&lt;p&gt;The frame rate you &lt;em&gt;see&lt;/em&gt; is bounded by your display — a 60Hz panel shows 60, a 120Hz panel shows up to 120. That's hardware, not software. No engine can change it.&lt;/p&gt;

&lt;p&gt;What the plugin changes is the &lt;em&gt;engine's&lt;/em&gt; internal rate. Instead of ticking once per display refresh, the engine produces state at twice that rate and hands the freshest frame to the display at the moment it refreshes. On a 60Hz panel, the frame you see was computed half a frame ago instead of a full frame ago. On a 120Hz panel, the display actually receives 120 distinct frames per second.&lt;/p&gt;

&lt;p&gt;The result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lower input latency&lt;/strong&gt; — your input is sampled at twice the panel rate, even on a 60Hz screen&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smoother motion&lt;/strong&gt; — physics and animation update at twice the tick rate, so nothing is skipped&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Higher visible frame rate on capable displays&lt;/strong&gt; — up to the panel's native maximum&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To be clear: the plugin does not produce more frames than your display can present. A 60Hz panel receives 60 refreshes per second, and that's what gets shown. A 120Hz panel receives up to 120. The plugin does not duplicate, interpolate, or fake frames — the number on your frame counter corresponds to real displayed refreshes.&lt;/p&gt;

&lt;p&gt;This isn't double rendering. It isn't frame interpolation. It isn't an upscaling illusion. And it doesn't cost more CPU — the plugin is designed to use &lt;em&gt;fewer&lt;/em&gt; resources than a naive render loop, which is exactly why it helps on low-end devices. It raises the floor. The display is the ceiling, and only the display.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Numbers We're Targeting
&lt;/h2&gt;

&lt;p&gt;We'll publish real benchmarks when the plugin ships. For now, here's what we're aiming for across device classes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Device&lt;/th&gt;
&lt;th&gt;Current typical Canvas 2D&lt;/th&gt;
&lt;th&gt;Target with plugin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Desktop&lt;/td&gt;
&lt;td&gt;60fps stable&lt;/td&gt;
&lt;td&gt;60fps stable (parity)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flagship phone&lt;/td&gt;
&lt;td&gt;60fps, occasional jitter&lt;/td&gt;
&lt;td&gt;60–120fps stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mid-range Android&lt;/td&gt;
&lt;td&gt;30–45fps with stutter&lt;/td&gt;
&lt;td&gt;60fps stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Low-end Android&lt;/td&gt;
&lt;td&gt;10–20fps unplayable&lt;/td&gt;
&lt;td&gt;60fps stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High-refresh panels&lt;/td&gt;
&lt;td&gt;Limited by main loop&lt;/td&gt;
&lt;td&gt;Full refresh rate unlocked&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We'll measure 1% lows and 99th-percentile frame times, not just averages — because averages lie, and stutter is what players actually feel.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Try It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Soon.&lt;/strong&gt; The plugin is in development. When it's ready:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add one &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag to your page after &lt;code&gt;epic.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;That's it&lt;/li&gt;
&lt;li&gt;Your existing Limn game gets faster automatically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If it doesn't work on your device, the plugin detects that and quietly steps aside. Your game still runs exactly as it did before. Zero risk.&lt;/p&gt;

&lt;p&gt;Follow the Limn project for updates — the release will be announced the day it's ready.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;We'll publish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The benchmark scene&lt;/strong&gt; — open source, so anyone can reproduce our numbers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Before/after comparisons&lt;/strong&gt; — on real devices, not throttled DevTools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A technical writeup&lt;/strong&gt; — explaining the approach at a level that's useful without giving away the implementation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The plugin itself&lt;/strong&gt; — one file, one script tag, zero config&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The web deserves better 2D games. Limn has always believed that. This plugin is the next step in proving it.&lt;/p&gt;

&lt;p&gt;So when someone tells you 60fps is the ceiling, nod, and remember: ceilings are just the point where someone stopped looking up. Above them there's still sky. And above that, still space.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Limn Engine is a zero-configuration 2D game engine for the browser. One script tag, one line of code, and you're making a game. Follow the project for release announcements.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The performance plugin is under development. No release date announced yet.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>productivity</category>
      <category>javascript</category>
      <category>discuss</category>
    </item>
    <item>
      <title>What Happened After I Got a Zero for "Too Good" Code</title>
      <dc:creator>Kehinde Owolabi</dc:creator>
      <pubDate>Mon, 05 Oct 2026 02:56:26 +0000</pubDate>
      <link>https://dev.to/kehinde_owolabi_e2e54567a/what-happened-after-i-got-a-zero-for-too-good-code-1gij</link>
      <guid>https://dev.to/kehinde_owolabi_e2e54567a/what-happened-after-i-got-a-zero-for-too-good-code-1gij</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 2 of 2. If you haven't read Part 1 yet, start here: &lt;a href="https://dev.to/kehinde_owolabi_e2e54567a/i-got-a-zero-on-a-beginner-python-assignment-because-my-code-was-too-good-192n"&gt;I Got a Zero on a Beginner Python Assignment Because My Code Was "Too Good"&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Two weeks ago, I wrote about getting a temporary zero on a beginner Python assignment because my code was &lt;em&gt;too&lt;/em&gt; feature-rich. The grader thought an AI must have written it. I argued that the rough edges — the &lt;code&gt;abcdefghijklmnopqrstuvwxyz&lt;/code&gt; placeholder I forgot to delete, the inconsistent variable names, the Windows-only &lt;code&gt;winsound&lt;/code&gt; — were proof it was mine.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4jz5vbickr1rgwy214i4.png" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4jz5vbickr1rgwy214i4.png" alt="Image descriptio" width="799" height="374"&gt;&lt;/a&gt;&lt;br&gt;
I ended that article on a cliffhanger: the system had pushed back, and I'd just sent my appeal.&lt;/p&gt;

&lt;p&gt;Here's what happened next.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Instructor Replied First
&lt;/h2&gt;

&lt;p&gt;Before my appeal even landed, my instructor sent me a message. And it wasn't what I expected.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Kehinde, I didn't flag this assignment, it was our grader. As I look at this code, it was immediately obvious to me that you wrote it. I will tell the graders to grade this normally. The use of &lt;code&gt;winsound&lt;/code&gt; is great, but I don't know what the graders will think. They won't want to install the library to test your program. It wasn't working for me so I just disabled it and the program ran fine without the sound. The graders may dock you — we'll see. If they do, you can resubmit without the sound component and I'm sure you'll be fine. It may take a couple days before it's graded, but I don't have concerns with this assignment. It looks really good — good work!"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read that again.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqmr5gw9gdj4bna0oiv5d.png" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqmr5gw9gdj4bna0oiv5d.png" alt=" " width="733" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The instructor — the person I'd assumed was suspicious of me — didn't flag the assignment at all. &lt;strong&gt;The grader did.&lt;/strong&gt; And when the instructor actually looked at my code, his response wasn't "prove it's yours." It was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"It was immediately obvious to me that you wrote it."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sentence is the entire story.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Revealed
&lt;/h2&gt;

&lt;p&gt;The system has two layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The grader&lt;/strong&gt; — often a teaching assistant or automated reviewer, working from a checklist, with no context about who you are. They see code that looks "too advanced" for Week 4 and flag it. Fair enough.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The instructor&lt;/strong&gt; — the person who knows the students, has context, and can override. They looked at my code for thirty seconds and saw what I'd been trying to explain: &lt;strong&gt;the quirks are the signature.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The grader followed the process. The instructor corrected it. That's how it's supposed to work.&lt;/p&gt;

&lt;p&gt;But here's the part that matters: &lt;strong&gt;if I hadn't written that appeal, if I hadn't pointed at the rough edges, if I hadn't stayed calm and factual&lt;/strong&gt; — would the instructor have looked at my code as carefully? Maybe. Maybe not. The system worked &lt;em&gt;because I pushed back the right way&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The &lt;code&gt;winsound&lt;/code&gt; Thing
&lt;/h2&gt;

&lt;p&gt;The instructor's feedback also included a practical note I hadn't considered:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"They won't want to install the library to test your program. It wasn't working for me so I just disabled it and the program ran fine without the sound."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the kind of thing you only learn by shipping code to real environments. &lt;code&gt;winsound&lt;/code&gt; is Windows-only. On Mac and Linux, the &lt;code&gt;import winsound&lt;/code&gt; fails. My &lt;code&gt;try/except&lt;/code&gt; caught it — so the program &lt;em&gt;ran&lt;/em&gt; — but the sound didn't play. For a grader on a Mac, my "creative sound effects" were just dead code.&lt;/p&gt;

&lt;p&gt;The instructor's advice was practical, not punitive: &lt;strong&gt;remove the sound, resubmit, you'll be fine.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I did. Ten minutes of cleanup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Commented out &lt;code&gt;import winsound&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Removed all &lt;code&gt;winsound.PlaySound(...)&lt;/code&gt; and &lt;code&gt;winsound.Beep(...)&lt;/code&gt; calls&lt;/li&gt;
&lt;li&gt;Updated the creativity comment on line 1 to remove "sound effects"&lt;/li&gt;
&lt;li&gt;Un-commented a &lt;code&gt;print("Invalid input!")&lt;/code&gt; that had accidentally been disabled along with the winsound lines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Resubmitted.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Grade Came Back
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;100/100.&lt;/strong&gt;&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkep0bvcvhrwmcm9xi3bl.png" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkep0bvcvhrwmcm9xi3bl.png" alt=" " width="800" height="425"&gt;&lt;/a&gt;&lt;br&gt;
Every criterion. Every point.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Repeated guessing loop — 40/40&lt;/li&gt;
&lt;li&gt;✅ Guess counter — 13/13&lt;/li&gt;
&lt;li&gt;✅ Initial hint with spaces — 10/10&lt;/li&gt;
&lt;li&gt;✅ Hint logic (uppercase / lowercase / underscore) — 20/20&lt;/li&gt;
&lt;li&gt;✅ Length validation with warning — 10/10&lt;/li&gt;
&lt;li&gt;✅ Creativity (menu, help screen, random words, loading animations) — 7/7&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The System Has Layers — Use Them
&lt;/h3&gt;

&lt;p&gt;A grader's flag is not the end. It's a signal for a human to look closer. If you get flagged:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't argue with the grader (they're following the process)&lt;/li&gt;
&lt;li&gt;Appeal to the instructor with &lt;strong&gt;evidence&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Offer concrete ways to demonstrate authorship&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Not Every Instructor Is the Adversary
&lt;/h3&gt;

&lt;p&gt;I spent a week assuming my instructor thought I cheated. He didn't. He thought my code was good. He was &lt;em&gt;defending&lt;/em&gt; me to the graders while I was drafting careful-but-pissed messages to him.&lt;/p&gt;

&lt;p&gt;The lesson: &lt;strong&gt;assume good faith until proven otherwise.&lt;/strong&gt; The person on the other end is usually just trying to do their job.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Cross-Platform Matters
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;winsound&lt;/code&gt; is Windows-only. If you're building anything anyone else will ever run — assignments, portfolio pieces, shared tools — use cross-platform libraries or gracefully degrade. My &lt;code&gt;try/except&lt;/code&gt; was the right idea; the execution just wasn't complete.&lt;/p&gt;

&lt;p&gt;Better options for future projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pygame.mixer&lt;/code&gt; (cross-platform, needs install)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;simpleaudio&lt;/code&gt; (lightweight, cross-platform)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;playsound&lt;/code&gt; (simple wrapper, cross-platform)&lt;/li&gt;
&lt;li&gt;Or just print ASCII/emoji feedback instead of sound&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Creativity Should Match the Environment
&lt;/h3&gt;

&lt;p&gt;My sound effects were genuinely creative — but they weren't testable on the grader's machine. That's a real problem. Creativity that only runs on your own laptop is not fully shipped.&lt;/p&gt;

&lt;p&gt;The fix: &lt;strong&gt;creativity that runs everywhere.&lt;/strong&gt; Loading animations (&lt;code&gt;time.sleep&lt;/code&gt;) work everywhere. Random word selection works everywhere. A formatted help screen works everywhere. Sound is the one feature that broke portability — and it was the one feature I lost points for considering.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Unexpected Part
&lt;/h2&gt;

&lt;p&gt;After Part 1 went up, I got messages from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A 30-year-old career changer flagged for a "too clean" Java project&lt;/li&gt;
&lt;li&gt;A high schooler accused of AI on an essay he wrote himself&lt;/li&gt;
&lt;li&gt;A teacher who said the flagging system is "broken but the only tool we have"&lt;/li&gt;
&lt;li&gt;Two other self-taught devs in Nigeria who'd been through similar things&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wrote the first article out of frustration. But the response told me something bigger: &lt;strong&gt;this isn't a me problem. It's a system problem.&lt;/strong&gt; And the way through it — for now — is to keep your receipts, stay calm, and appeal to the human on the other side.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;W05 (Shopping Cart) was already at 100/100. W06 and W07 are coming. And after that: &lt;strong&gt;more Limn Engine, more arcade, more shipping.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The class was never the goal. But getting through W04 taught me something no lecture could:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rough edges are the story. Keep them. Ship them. Defend them when you have to.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And when the system works — when a teacher says &lt;em&gt;"it was immediately obvious to me that you wrote it"&lt;/em&gt; — that's worth writing about too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;If you're a self-taught dev in a beginner class and you get flagged:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You're not crazy.&lt;/strong&gt; The system is awkward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stay calm.&lt;/strong&gt; Bring receipts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Appeal to the human.&lt;/strong&gt; The grader isn't the final word.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix what's asked.&lt;/strong&gt; Even if it's small. Even if it feels unfair.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resubmit.&lt;/strong&gt; Usually you get the points.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write about it.&lt;/strong&gt; Because the next person will Google this, and your story will be the answer they find.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system worked for me. Not smoothly, not instantly, but it worked. And that's worth saying out loud.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Kehinde Owolabi is the creator of &lt;a href="https://limn-engine-doc.vercel.app/" rel="noopener noreferrer"&gt;Limn Engine&lt;/a&gt; and founder of Evolved Tech. He writes about self-taught development, browser game engines, and the awkward intersection of code and education.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>career</category>
      <category>python</category>
      <category>careerdevelopment</category>
    </item>
    <item>
      <title>🧹 How I Cleaned Up Limn Engine's Global Variables (and Fixed Two Bugs Along the Way)</title>
      <dc:creator>Kehinde Owolabi</dc:creator>
      <pubDate>Sat, 03 Oct 2026 04:10:17 +0000</pubDate>
      <link>https://dev.to/kehinde_owolabi_e2e54567a/how-i-cleaned-up-limn-engines-global-variables-and-fixed-two-bugs-along-the-way-5hh5</link>
      <guid>https://dev.to/kehinde_owolabi_e2e54567a/how-i-cleaned-up-limn-engines-global-variables-and-fixed-two-bugs-along-the-way-5hh5</guid>
      <description>&lt;h1&gt;
  
  
  🧹 How I Cleaned Up Limn Engine's Global Variables (and Fixed Two Bugs Along the Way)
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;A refactor that made the engine safer to use, fixed a tile corruption bug, and corrected a particle system issue that had been hiding in plain sight.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Try the New Version
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Play a game built on the updated engine:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://limn-engine-doc.vercel.app/arcade" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/arcade&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every game on the arcade runs on this version of the engine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grab the source:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📖 Introduction
&lt;/h2&gt;

&lt;p&gt;Limn Engine started as a single file with a handful of global variables. That worked fine when the engine and the games were written by the same person, on the same machine, in the same session. But once games started being published to the &lt;strong&gt;Limn Arcade&lt;/strong&gt; — a community platform where anyone can upload a game — the globals became a liability.&lt;/p&gt;

&lt;p&gt;This article walks through the cleanup that fixed that. Along the way, two real bugs surfaced: a tile corruption bug that broke levels after restart, and a particle system bug that made every particle invisible on any scene other than the default.&lt;/p&gt;

&lt;p&gt;Here's what we're going to cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why global variables were a problem in the first place&lt;/li&gt;
&lt;li&gt;How the refactor moved everything into a single namespace&lt;/li&gt;
&lt;li&gt;The tile bug — what caused it, and how &lt;code&gt;concat&lt;/code&gt; fixed it&lt;/li&gt;
&lt;li&gt;The particle bug — what caused it, and how a scene parameter fixed it&lt;/li&gt;
&lt;li&gt;A sound system simplification&lt;/li&gt;
&lt;li&gt;What this means for existing games&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🐛 The Problem with Globals
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we're going to look at:&lt;/strong&gt; The original engine's top-level variables.&lt;/p&gt;

&lt;p&gt;Before the cleanup, the top of &lt;code&gt;epic.js&lt;/code&gt; looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;refresh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;TCJSgameGameArea&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;commp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;tileComm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;comm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six globals, all sitting directly in the window's scope. Any script loaded on the same page could read them, overwrite them, or shadow them by accident. If a game defined its own &lt;code&gt;let comm = ...&lt;/code&gt;, the engine's internal component list would silently be replaced — and the game would stop drawing anything.&lt;/p&gt;

&lt;p&gt;The risk isn't theoretical. Two files on the same page — one being the engine, one being a game — share exactly one global scope. Every top-level declaration is a potential collision.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ What We're Going to Build
&lt;/h2&gt;

&lt;p&gt;We're going to move every internal engine variable into a single object called &lt;code&gt;TCJSgameVariable&lt;/code&gt;. That object becomes the engine's private state, and anything that wants to reach into the engine has exactly one name to remember.&lt;/p&gt;

&lt;p&gt;While we're in there, we're going to fix two bugs that surfaced during the move.&lt;/p&gt;

&lt;p&gt;The first is a &lt;code&gt;TileMap&lt;/code&gt; bug that mutated the caller's tile array in place, breaking levels after a restart. The second is a &lt;code&gt;ParticleSystem&lt;/code&gt; bug that always added particles to scene 0, making them invisible on any other scene.&lt;/p&gt;

&lt;p&gt;And we're going to simplify the sound system by removing a broken clone-on-overlap code path.&lt;/p&gt;

&lt;p&gt;Let me walk through each change.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔧 Step 1: The Namespace
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we're going to do:&lt;/strong&gt; Replace the six globals with properties on a single object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;TCJSgameGameArea&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;TCJSgameVariable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;fileLoaded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;TCJSgameVariable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;TCJSgameVariable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refresh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;TCJSgameVariable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TCJSgameGameArea&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;TCJSgameVariable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What we just did:&lt;/strong&gt; We created &lt;code&gt;TCJSgameVariable&lt;/code&gt; as the home for every internal engine value. &lt;code&gt;com&lt;/code&gt; (the last-added component wrapper), &lt;code&gt;refresh&lt;/code&gt; (the FPS flag), &lt;code&gt;commp&lt;/code&gt; (the fake canvas component list), and &lt;code&gt;TCJSgameGameArea&lt;/code&gt; (the visibility check component) are now all namespaced.&lt;/p&gt;

&lt;p&gt;We kept &lt;code&gt;TCJSgameGameArea&lt;/code&gt; as a &lt;strong&gt;global&lt;/strong&gt; too, because existing games reference it directly. The global is set at the same time the namespace property is set, so both stay in sync.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔧 Step 2: Updating References
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we're going to do:&lt;/strong&gt; Change every internal use of the old globals to use the namespaced versions.&lt;/p&gt;

&lt;p&gt;Inside &lt;code&gt;Display.add()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;scene&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;TCJSgameVariable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;scene&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nx"&gt;TCJSgameVariable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TCJSgameVariable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside &lt;code&gt;Display.updat()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;TCJSgameVariable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside &lt;code&gt;Component.destroy()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TCJSgameVariable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findIndex&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;TCJSgameVariable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TCJSgameVariable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findIndex&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;TCJSgameVariable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What we just did:&lt;/strong&gt; Every read and write of the old globals now goes through &lt;code&gt;TCJSgameVariable&lt;/code&gt;. Same logic, same behaviour — but the engine's state is now contained.&lt;/p&gt;

&lt;p&gt;One thing to note: &lt;code&gt;TCJSgameVariable.comm&lt;/code&gt; is declared as a plain array at the top of the file, but the actual &lt;code&gt;let comm = []&lt;/code&gt; line was renamed. If you're looking for where the component list lives now, it's &lt;code&gt;TCJSgameVariable.comm&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🐛 Step 3: The Tile Bug
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we're going to do:&lt;/strong&gt; Fix a bug in &lt;code&gt;TileMap&lt;/code&gt; that corrupted the level data after restart.&lt;/p&gt;

&lt;p&gt;Here was the original code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TileMap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;scene&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tile&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unshift&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// ← This is the bug&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tileHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What was wrong:&lt;/strong&gt; &lt;code&gt;unshift(0)&lt;/code&gt; &lt;strong&gt;mutates the caller's array in place&lt;/strong&gt;. Every time a &lt;code&gt;TileMap&lt;/code&gt; was constructed, another &lt;code&gt;0&lt;/code&gt; was inserted at the front of the user's tile array.&lt;/p&gt;

&lt;p&gt;The first level load looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// User's array&lt;/span&gt;
&lt;span class="nx"&gt;tile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;grass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;spike&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;brick&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// After TileMap construction&lt;/span&gt;
&lt;span class="nx"&gt;tile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;grass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;spike&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;brick&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second level load — say, after a restart — looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Same user array&lt;/span&gt;
&lt;span class="nx"&gt;tile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;grass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;spike&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;brick&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// After TileMap construction&lt;/span&gt;
&lt;span class="nx"&gt;tile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;grass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;spike&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;brick&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;tile[1]&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt; instead of &lt;code&gt;grass&lt;/code&gt;. Every tile lookup is shifted by one. The entire map renders with the wrong tile IDs.&lt;/p&gt;

&lt;p&gt;Here's the fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What we just did:&lt;/strong&gt; We create a new array with &lt;code&gt;0&lt;/code&gt; prepended, leaving the caller's array untouched. The &lt;code&gt;0&lt;/code&gt; at index 0 still serves as the "empty tile" placeholder, but the user's data is never mutated. Reloading the level any number of times produces identical results.&lt;/p&gt;

&lt;p&gt;This bug was probably invisible for a long time because many games only loaded a level once. It only surfaced when a game restarted — or when a scene changed back to the game scene after visiting a menu.&lt;/p&gt;




&lt;h2&gt;
  
  
  🐛 Step 4: The Particle Bug
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we're going to do:&lt;/strong&gt; Fix &lt;code&gt;ParticleSystem&lt;/code&gt; so particles are added to the scene the game is actually using.&lt;/p&gt;

&lt;p&gt;Here was the original code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParticleSystem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;particles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emitters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;particle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Particle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;particles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;particle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;particle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// ← No scene argument, defaults to 0&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;particle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What was wrong:&lt;/strong&gt; &lt;code&gt;display.add(particle)&lt;/code&gt; defaults to scene 0. If the game runs on scene 1 — which is where every game that uses menus lives — the particle is added to a scene that isn't being rendered. It exists. It updates. But it's never drawn.&lt;/p&gt;

&lt;p&gt;This meant particles were silently invisible in any game that used the scene system. Explosions, sparkles, blood, smoke — none of it showed up. The code all ran without errors. The particles just never appeared.&lt;/p&gt;

&lt;p&gt;Here's the fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParticleSystem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;scene&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;particles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emitters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;particle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Particle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;particles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;particle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;particle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;particle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What we just did:&lt;/strong&gt; The &lt;code&gt;ParticleSystem&lt;/code&gt; now stores the scene number it should render to, and passes it to &lt;code&gt;display.add()&lt;/code&gt; for every particle. A game that runs on scene 1 can now create particles that actually appear.&lt;/p&gt;

&lt;p&gt;The default value of &lt;code&gt;scene = 0&lt;/code&gt; preserves backwards compatibility with any existing code that used particles on the default scene.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔊 Step 5: The Sound System Simplification
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What we're going to do:&lt;/strong&gt; Remove a broken clone-on-overlap code path from &lt;code&gt;Sound.play()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here was the original:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;volume&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loaded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Sound not loaded yet: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Clone for overlapping sounds (effects)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loop&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Sound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;volume&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;volume&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What was wrong:&lt;/strong&gt; The clone-on-overlap block looked clever, but it never worked. When you create &lt;code&gt;new Sound(...)&lt;/code&gt;, the constructor starts loading the file asynchronously. The &lt;code&gt;loaded&lt;/code&gt; flag doesn't become &lt;code&gt;true&lt;/code&gt; until the browser fires &lt;code&gt;canplaythrough&lt;/code&gt; — which happens on a later microtask, not synchronously.&lt;/p&gt;

&lt;p&gt;So when &lt;code&gt;clone.play()&lt;/code&gt; is called on the very next line, &lt;code&gt;clone.loaded&lt;/code&gt; is still &lt;code&gt;false&lt;/code&gt;. The guard at the top of &lt;code&gt;play()&lt;/code&gt; returns immediately, and the clone never plays. Every "overlap" was silently dropped.&lt;/p&gt;

&lt;p&gt;The fix is simply to remove the block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;volume&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loaded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Sound not loaded yet: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;volume&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Audio play prevented:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What we just did:&lt;/strong&gt; Now every call to &lt;code&gt;play()&lt;/code&gt; produces audible sound. If the sound is already playing, &lt;code&gt;currentTime = 0&lt;/code&gt; rewinds it and &lt;code&gt;play()&lt;/code&gt; restarts it. That's a much better default than a code path that silently fails.&lt;/p&gt;

&lt;p&gt;If layering is ever needed — for example, a shotgun spread playing two distinct shots in the same frame — the right fix is to pre-build a small pool of &lt;code&gt;Audio&lt;/code&gt; elements at load time and cycle through them. Cloning on demand, which is what the old code tried to do, will always fail for the same reason.&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 Before and After
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Engine state&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6+ globals in the window scope&lt;/td&gt;
&lt;td&gt;Contained in &lt;code&gt;TCJSgameVariable&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Level restart&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tile IDs corrupted by repeated &lt;code&gt;unshift&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Levels reload correctly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Particles on non-zero scenes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Silently invisible&lt;/td&gt;
&lt;td&gt;Correctly added to the game's scene&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Overlapping sounds&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Broken clone-on-overlap path&lt;/td&gt;
&lt;td&gt;Restarts the sound cleanly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Backwards compatibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;TCJSgameGameArea&lt;/code&gt; still global&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  📊 What You've Learned
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Why It Matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Namespacing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Keeps engine internals from colliding with user code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;concat&lt;/code&gt; vs &lt;code&gt;unshift&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;unshift&lt;/code&gt; mutates the caller's array — &lt;code&gt;concat&lt;/code&gt; returns a new one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scene-aware object creation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Objects added to the wrong scene are invisible, not broken&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Async loading pitfalls&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Anything that depends on &lt;code&gt;.loaded&lt;/code&gt; must wait for the load event&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Clone-on-demand&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A clone that hasn't loaded can't play&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Backwards compatibility shims&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Keeping a global alive while also namespacing it costs nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  ⚠️ A Note on Backwards Compatibility
&lt;/h2&gt;

&lt;p&gt;If any existing game on the arcade references &lt;code&gt;comm&lt;/code&gt;, &lt;code&gt;commp&lt;/code&gt;, &lt;code&gt;tileComm&lt;/code&gt;, or &lt;code&gt;refresh&lt;/code&gt; directly, those references will now throw &lt;code&gt;ReferenceError&lt;/code&gt;. In practice, none of the published games do — those were never part of the documented API — but it's worth knowing.&lt;/p&gt;

&lt;p&gt;The engine itself has been updated to use &lt;code&gt;TCJSgameVariable.*&lt;/code&gt; throughout, so the internal reference path is complete. Only external code that reached into engine internals would be affected.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;TCJSgameGameArea&lt;/code&gt; global is preserved because it &lt;strong&gt;was&lt;/strong&gt; used externally. Both the global and the namespace property are set in &lt;code&gt;Display.start()&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 What's Next?
&lt;/h2&gt;

&lt;p&gt;Now that the engine's state is contained, the next cleanup targets are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wrapping the engine in an IIFE or ES module so even &lt;code&gt;TCJSgameVariable&lt;/code&gt; isn't on the window&lt;/li&gt;
&lt;li&gt;Adding JSDoc comments to every public method&lt;/li&gt;
&lt;li&gt;Renaming confusing variables like &lt;code&gt;comm&lt;/code&gt; and &lt;code&gt;commp&lt;/code&gt; to &lt;code&gt;componentList&lt;/code&gt; and &lt;code&gt;fakeComponentList&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Adding a &lt;code&gt;timeScale&lt;/code&gt; property to &lt;code&gt;Display&lt;/code&gt; so games can pause cleanly without setting &lt;code&gt;dt&lt;/code&gt; to 0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build any of these, send a pull request.&lt;/p&gt;




&lt;h2&gt;
  
  
  🐛 Report Bugs
&lt;/h2&gt;

&lt;p&gt;If you find a bug introduced by this refactor, report it on GitHub:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc/issues" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc/issues&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Resources
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Engine Docs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Studio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/editor/" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/editor&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limn Arcade&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://limn-engine-doc.vercel.app/arcade" rel="noopener noreferrer"&gt;limn-engine-doc.vercel.app/arcade&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub Repository&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Report Bugs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/terracodes004/limn-engine-doc/issues" rel="noopener noreferrer"&gt;github.com/terracodes004/limn-engine-doc/issues&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kehinde on DEV.to&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/kehinde_owolabi_e2e54567a"&gt;dev.to/kehinde_owolabi_e2e54567a&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Desire on DEV.to&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/desire_george_434_ai"&gt;dev.to/desire_george_434_ai&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🎯 The One-Line Summary
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Moving Limn Engine's globals into a namespace also surfaced a tile corruption bug and a particle scene bug — both fixed in the same pass."&lt;/strong&gt; 🧹🚀&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Draw your game into existence — one refactor at a time.&lt;/em&gt; 🎮🚀&lt;/p&gt;

</description>
      <category>debugging</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>discuss</category>
    </item>
    <item>
      <title>I Got a Zero on a Beginner Python Assignment Because My Code Was "Too Good"</title>
      <dc:creator>Kehinde Owolabi</dc:creator>
      <pubDate>Wed, 30 Sep 2026 12:02:32 +0000</pubDate>
      <link>https://dev.to/kehinde_owolabi_e2e54567a/i-got-a-zero-on-a-beginner-python-assignment-because-my-code-was-too-good-192n</link>
      <guid>https://dev.to/kehinde_owolabi_e2e54567a/i-got-a-zero-on-a-beginner-python-assignment-because-my-code-was-too-good-192n</guid>
      <description>&lt;h1&gt;
  
  
  I Got a Zero on a Beginner Python Assignment Because My Code Was "Too Good"
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;How AI paranoia in education is punishing self-taught developers — and why my sloppy code is the proof I wrote it.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;I'm 18. I've been coding since I was 15. I built a 2D HTML5 game engine called &lt;a href="https://limn-engine-doc.vercel.app/" rel="noopener noreferrer"&gt;Limn Engine&lt;/a&gt; that runs on a single JavaScript file. I co-founded a studio called Evolved Tech. I ship code every week.&lt;/p&gt;

&lt;p&gt;Last week, I submitted a Wordle-style word puzzle for my intro Python class (CSE 110 at BYU-Pathway).&lt;/p&gt;

&lt;p&gt;I got a zero.&lt;/p&gt;

&lt;p&gt;The comment: &lt;em&gt;"It looks like you may have gotten too much outside help on this assignment."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let me tell you why that stings — and why it's a problem the entire industry needs to think about.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Submission
&lt;/h2&gt;

&lt;p&gt;The assignment: build a word guessing game. Secret word, hints (uppercase = correct spot, lowercase = present but wrong spot, &lt;code&gt;_&lt;/code&gt; = absent), guess counter, length validation. Standard Week 4 stuff.&lt;/p&gt;

&lt;p&gt;I submitted that. Plus:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A main menu (Play / Help / Quit)&lt;/li&gt;
&lt;li&gt;Random word selection from a list&lt;/li&gt;
&lt;li&gt;A formatted help screen with examples and a rules table&lt;/li&gt;
&lt;li&gt;Sound effects (&lt;code&gt;winsound&lt;/code&gt;) with cross-platform fallback&lt;/li&gt;
&lt;li&gt;Loading animations (&lt;code&gt;time.sleep&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;In-game navigation commands (&lt;code&gt;1&lt;/code&gt; = menu, &lt;code&gt;2&lt;/code&gt; = quit)&lt;/li&gt;
&lt;li&gt;Emoji everywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every single feature works. Every feature is mine.&lt;br&gt;
&lt;a href="https://github.com/terracodes004/wordie/blob/main/word.py" rel="noopener noreferrer"&gt;See python code here&lt;/a&gt;&lt;br&gt;
The instructor flagged it anyway.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqmlf15parzi7qkbpuadh.png" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqmlf15parzi7qkbpuadh.png" alt="Screenshot of the rubric" width="799" height="374"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Irony: My Code Isn't Even That Clean
&lt;/h2&gt;

&lt;p&gt;Here's the thing that gets me. If an AI &lt;em&gt;had&lt;/em&gt; written my submission, it would have been &lt;strong&gt;cleaner&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;My code has rough edges everywhere — the kind you only get from a human typing fast and shipping without a refactor pass. Here's the actual evidence:&lt;/p&gt;
&lt;h3&gt;
  
  
  1. The word list was a string, not a list
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temple church food nephi guess moroni mosiah video correct...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;word_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&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 pro (or an LLM) writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temple&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;church&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;food&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nephi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I didn't. I used a string and &lt;code&gt;.split(" ")&lt;/code&gt; because it was faster to type.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. I left a placeholder in the word list
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;... abcdefghijklmnopqrstuvwxyz helaman&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a 26-letter "word" sitting in my list of guessable words. It's impossible to guess. I forgot to remove it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No AI would leave that in.&lt;/strong&gt; It would break the game on a theoretical level. But I typed the list by hand and never cleaned it up. That oversight is my signature.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Bare &lt;code&gt;except:&lt;/code&gt; instead of &lt;code&gt;except ImportError:&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;winsound&lt;/span&gt;
    &lt;span class="n"&gt;SOUND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;SOUND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A pro writes &lt;code&gt;except ImportError:&lt;/code&gt;. I wrote &lt;code&gt;except:&lt;/code&gt; because I was moving fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Inconsistent variable names
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;no_of_children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How many children are there? &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;no_of_adult&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How many adults are there? &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;children&lt;/code&gt; (plural) vs &lt;code&gt;adult&lt;/code&gt; (singular). An LLM would never do that. I did.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Duplicate entries in the word list
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;temple&lt;/code&gt;, &lt;code&gt;altar&lt;/code&gt;, &lt;code&gt;hymn&lt;/code&gt;, &lt;code&gt;prayer&lt;/code&gt;, &lt;code&gt;mercy&lt;/code&gt;, &lt;code&gt;kindness&lt;/code&gt; — each appears &lt;strong&gt;twice&lt;/strong&gt;. Manual typing, no dedup pass.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Windows-only &lt;code&gt;winsound&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;I used &lt;code&gt;winsound&lt;/code&gt; for sound effects — which crashes on Mac and Linux. A pro would reach for a cross-platform lib. I built for my own machine and didn't care.&lt;/p&gt;

&lt;p&gt;Every one of those is a &lt;strong&gt;mark of authorship&lt;/strong&gt;. They're not signs that I copied something — they're signs that I &lt;em&gt;didn't&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Problem: Competence Looks Like Cheating Now
&lt;/h2&gt;

&lt;p&gt;Here's the uncomfortable truth about AI detection in 2026:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The more competent you are, the more suspicious you look.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AI writes clean, feature-rich, well-structured code. So when an instructor sees a submission that's &lt;em&gt;too&lt;/em&gt; feature-rich — too far ahead of the rubric — the default assumption has flipped from "this student is talented" to "this student cheated."&lt;/p&gt;

&lt;p&gt;That assumption punishes exactly the wrong people:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Self-taught devs&lt;/strong&gt; who learned outside the classroom&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Career changers&lt;/strong&gt; with professional experience&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Students with real portfolios&lt;/strong&gt; taking intro classes for credit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anyone&lt;/strong&gt; whose side projects are years ahead of the curriculum&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The paradox: &lt;strong&gt;the more evidence you have that you can code, the more suspicious your classwork looks.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Handled It
&lt;/h2&gt;

&lt;p&gt;I didn't get defensive. I wrote my instructor a message that did four things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Owned the situation calmly&lt;/strong&gt; — acknowledged the concern was reasonable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explained my background&lt;/strong&gt; — Limn Engine, Evolved Tech, years of shipping&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pointed to the rough edges&lt;/strong&gt; — the exact quirks above, as proof of authorship&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offered verification&lt;/strong&gt; — line-by-line walkthrough, oral quiz, live coding, or a simpler version that strictly meets the rubric&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's the playbook. Not "you should have known better." Not accusations. Evidence + willingness + respect.&lt;/p&gt;

&lt;p&gt;The instructor isn't the villain. Their job is to make sure students actually understand the material, and AI has made that job exponentially harder. If I were in their seat, I'd be paranoid too.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Educators Should Do Differently
&lt;/h2&gt;

&lt;p&gt;If you're an instructor reading this, here's what I'd ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Look at style, not just features.&lt;/strong&gt; Feature-rich ≠ AI-generated. Rough edges matter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask before accusing.&lt;/strong&gt; "Can you walk me through this?" is a better first move than a temporary zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shift toward process-based assessment.&lt;/strong&gt; Oral quizzes, live coding, commit history — these tell you far more than a final product ever could.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assume good faith first.&lt;/strong&gt; Some of your students are building game engines at 2 AM on 4GB laptops. They exist.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And if you're a self-taught dev taking formal classes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don't dumb down your work.&lt;/strong&gt; That's a losing game.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep your drafts and commit history.&lt;/strong&gt; Evidence matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be able to explain every line.&lt;/strong&gt; If you can't explain it, you don't own it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When you get flagged, don't panic.&lt;/strong&gt; Offer to demonstrate. Make it easy for the instructor to verify.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rough edges are your friend.&lt;/strong&gt; Don't polish so much that the work stops looking like yours.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;I'm not mad at my instructor. I'm frustrated at the system that made this inevitable — one where years of self-taught effort looks indistinguishable from a chatbot's output, and where the only way to prove yourself is to stand in front of someone and explain what you wrote.&lt;/p&gt;

&lt;p&gt;Which, honestly, is exactly what should have happened from the start.&lt;/p&gt;

&lt;p&gt;The rough edges in my code aren't bugs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They're the receipts.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://github.com/terracodes004/wordie/blob/main/word.py" rel="noopener noreferrer"&gt;See python code here&lt;/a&gt;&lt;br&gt;
&lt;em&gt;What's your experience? Have you been flagged for "too good" work? Or are you an educator trying to navigate this mess? Drop it in the comments — I'd love to hear both sides.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I'm Kehinde Owolabi, creator of &lt;a href="https://limn-engine-doc.vercel.app/" rel="noopener noreferrer"&gt;Limn Engine&lt;/a&gt; and founder of Evolved Tech. I build browser games and game tooling. Follow me if you want more posts about self-taught development, tooling, and the messy intersection of code and education.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>career</category>
      <category>discuss</category>
      <category>python</category>
    </item>
  </channel>
</rss>
