Wiring AdSense Into the Boot Loader Without Breaking It
Why the ad slot lives inside the shadow root, not slotted content — and the specific DOM-collapse bug that distinction exists to prevent.
AdSense is wired into the game boot loader today, and getting there meant solving one specific, genuinely tricky DOM problem before a single ad could actually render safely: where exactly does the ad slot live in the page, and what happens to everything around it the moment an ad request comes back empty.
Why the ad slot lives inside the shadow root, not slotted content
The game frame is a custom element with a shadow root, and I built the ad unit directly inside that shadow root rather than passing it in as slotted light-DOM content from the surrounding page. That distinction matters more than it sounds like it should. Slotting only changes where an element visually renders — its real DOM parent is still wherever it was originally declared in the page's actual markup, not inside the shadow tree it appears to render into. When an ad request comes back unfilled, Google's AdSense script walks up from the ad element looking for a "responsive" ancestor it can collapse to avoid leaving empty space. If the ad were slotted content, that walk would traverse the light DOM straight up toward the host custom element, find nothing else it recognizes as safely collapsible in between, and potentially set inline height and min-height styles directly on the host element itself — which would visibly break the entire game frame's layout, not just hide an empty ad slot the way it's supposed to.
An unfilled ad had to fail gracefully, not visibly
Ad fill rates are never 100%, which means the boot loader had to look completely intentional whether or not an ad actually shows up. I built a placeholder — a "sponsored space" badge with the ad icon and dimensions already reserved — that displays by default, then gets replaced by the actual ad content only once a real ad genuinely fills the slot. Checking ad status after mounting means the placeholder never visibly flashes or collapses awkwardly; it simply gets swapped for real content when content exists, or stays exactly as it was designed to look when it doesn't. That's the difference between an unfilled ad slot looking like a broken empty box and looking like a deliberate piece of the boot loader's actual design.
Timing the AdSense push mattered more than I expected
Triggering AdSense's own initialization too early, before the ad slot is actually mounted, laid out, and visible in the DOM, is a well-known way to get ad requests silently rejected or mis-sized. I delayed the actual initialization push until after the boot loader's layout has genuinely settled, rather than firing it the instant the element exists in markup. That's a small, easy-to-overlook timing detail that has an outsized effect on fill rate and correctness — an ad request fired against a slot that hasn't finished laying out yet is a request AdSense can't evaluate correctly, and a wasted request is worse than a slightly delayed one.
The medium rectangle size was a deliberate constraint, not a default
Locking the ad unit to a fixed 300x250 medium rectangle, rather than using a responsive ad unit that resizes to fill available space, was a deliberate choice about how much layout unpredictability I was willing to accept in the boot loader. A responsive unit can render at wildly different aspect ratios depending on what an ad network decides to serve, which would have meant designing the entire preloader layout to gracefully handle an unknown, variable ad shape. A fixed size trades some potential revenue optimization for a boot loader that lays out identically every single time, which felt like the right trade for a first integration where getting the surrounding experience right mattered more than maximizing fill flexibility on day one.
The watermark and brand elements had to make the ad feel native, not bolted on
Beyond the technical DOM concerns, the ad column needed to feel like a genuine part of the boot loader's design rather than a foreign rectangle someone else's script dropped into the middle of it. Adding a large transparent site-wordmark watermark behind the whole preloader and applying the site's own arcade logo font to the surrounding chrome was about making the ad slot read as one panel within a designed layout, not an obviously separate advertising insert breaking up an otherwise cohesive screen. That distinction matters for how trustworthy the whole boot sequence feels — an ad that looks integrated reads as the site monetizing itself deliberately; one that looks bolted on reads as the site being invaded by something external.
Suppressing broken iframe error pages was its own small fight
Ad iframes occasionally render a visible browser error page inside themselves when a request genuinely fails rather than just returning empty, and that error page is far more jarring than a simple unfilled slot — it looks like the site itself is broken, not like an ad network hiccup. Catching and suppressing that specific failure mode, rather than the more common empty-response case my placeholder already handled, needed its own separate handling. It's a small, unglamorous piece of defensive work, and it's exactly the kind of edge case that would otherwise make the very first thing a visitor sees before a game loads look like something had gone wrong on the site itself.
This is the first time revenue infrastructure has actually shaped a core piece of site architecture rather than sitting quietly off to the side. Every previous mention of AdSense on this site was really just a script tag with nothing depending on it. Today it's load-bearing inside the one screen every single game session passes through, which raised the bar for how carefully it needed to be built far above what a simple script include would have demanded, and I expect that bar to keep rising as monetization becomes a more permanent part of how the site actually gets built from here on, not a script tag added once and quietly forgotten about afterward the way it effectively was until today.
More from the Devlog