<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>WizusLabs Engineering</title>
    <link>https://wizuslabs.com/blog/</link>
    <description>Build-in-public articles on how an AI-native studio actually builds software — dimensionality, frameworks, data, craft, and the AI-native studio itself.</description>
    <language>en</language>
    <lastBuildDate>Mon, 03 Aug 2026 10:00:00 GMT</lastBuildDate>
    <item>
      <title>Sixteen locales, one grammar: the localization assumptions English hides</title>
      <link>https://wizuslabs.com/blog/sixteen-locales-one-grammar/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/sixteen-locales-one-grammar/</guid>
      <pubDate>Mon, 03 Aug 2026 10:00:00 GMT</pubDate>
      <description>A localization defect is rarely a translation error; it is usually a correct decision made in the wrong grammar. Our games ship in sixteen languages, and not one of the faults in this post is a mistranslation — every string was correct, every gate stayed green, and no test went red at any point. In French and Italian the spoken star rating put a plural noun on a count of one, because those translations moved the counted noun next to the count that varies instead of the total that does not; the commit that fixed it puts the reach plainly, it fired on every one-star clear. The fix was a sentence rather than a selector: eleven other languages already used a frame no plural rule can break. Hindi is where that shortcut runs out, and the selector we then chose on purpose — an explicit =1, which reads like an instruction about the integer one — does not survive flutter gen-l10n, which emits the CLDR one category instead. That costs exactly one integer: Hindi's one category admits zero, so a zero-star clear takes the singular. Nor is Hindi an edge case. Measured against CLDR's own rule table, the group whose one category admits zero has four members among our sixteen locales, and the member nobody predicts is Vietnamese, which shares an identical rule element with Hindi. Then the test oracle, where the fault is not the instrument at all. In English incorrect contains correct, as a plain consequence of building an antonym with a negative prefix, so an assertion meaning "this announced correct" is satisfied by the announcement for incorrect in seven of sixteen locales, and by partial credit in all sixteen. It ran, it reported faithfully, and it fails toward pass on precisely the outcomes it exists to distinguish; the question was only ever well-formed in English. The positional repair does not rescue it either: a prefix test and a suffix test partition the sixteen languages between them, and each is sound over exactly the part the other breaks. Including the rule we wrote to close the hazard, which reproduced it one level up, and the answer that was shipping in a sibling app the whole time.</description>
    </item>
    <item>
      <title>Read back what you published: four ways the two store APIs disagree</title>
      <link>https://wizuslabs.com/blog/read-back-what-you-published/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/read-back-what-you-published/</guid>
      <pubDate>Fri, 31 Jul 2026 10:00:00 GMT</pubDate>
      <description>Two publishing APIs, one listing, and no shared idea of what a write means. A first-hand account of refreshing one game's store copy and screenshots across 18 locales on both stores, where every response we got was a success and not one of them was evidence about the listing: any signal that can be produced without the world changing is not evidence that the world changed. Play mutates one live resource inside a transaction and commits once, with no review; Apple hangs the same copy off an app version, patched field by field and staged for a human. Neither model is wrong, and a mismatched convention surfaces as a 200 rather than as an error. Play's edits.listings.update is spelled like an update and behaves like a replacement: send a body carrying only the long description and that locale's title, short description and video go blank, with a success code. We never let that happen — the hazard was named in the brief before the first write, so the code read the full resource, re-read it inside the writing edit, and PUT it back with one key reassigned. A prevented hazard, not an incident. What we did hit is the one call in Apple's three-step screenshot upload that is not Apple's: an unauthenticated presigned PUT signed over the host header, to which our shared HTTP helper attached a bearer token. It failed 400 Invalid request after six deletes had already committed, and that set was genuinely empty for a window. The strongest thing in the record is a place we were wrong in writing: a no-op probe patching Apple's app name back to its own value returned 409, can not be modified in the current state. That became evidence only later, when a second App Info record appeared alongside an editable version and the identical patches went 17 times 200 OK. Also here: why the order you upload screenshots is not the order Apple keeps, on two live sets already scrambled in production by an upload nobody had read back; why store images need comparing by content hash, after one frame came back same name, same size, different hash; and the single ls that stood between a production promote and a release with a blank What's new in every language, because fastlane resolves release notes by a filename keyed to a build number a re-cut had just invalidated. Play's versionCode 140 of 1.0.6 is live at full rollout with notes in all 18 locales; the App Store's 1.0.6 was submitted on 2026-07-30 and still waiting for review when these records closed — staged, not approved, not serving.</description>
    </item>
    <item>
      <title>Every zero has two explanations: the control your check is missing</title>
      <link>https://wizuslabs.com/blog/every-zero-has-two-explanations/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/every-zero-has-two-explanations/</guid>
      <pubDate>Thu, 30 Jul 2026 14:00:00 GMT</pubDate>
      <description>A failing check tells you something. A check that comes back empty tells you almost nothing, because there is one observable — zero — and at least five states that produce it. Four of them are broken instruments; the fifth is the answer you wanted. This is a first-hand account of four of them out of our own tooling. On NeuralSpark's 1.9.8+231 staging cut, the check that proves a build carries no debug-unlock marker returned empty for all six of its checks — on a correctly-built release artifact whose real marker count was one. The strings output had been captured into a shell variable instead of piped, so the instrument never ran, and zero was the documented legend for "production build." Along the way we correct two of our own explanations. Our commit message said "POSIX performs the command substitution before the redirection"; the standard says the opposite by default and then leaves the order unspecified for exactly this case, which is why bash and zsh disagree and why the same line works at a prompt and fails in a script. And an unescaped parenthesis in a regular expression does not return a silent zero — it is loud. The silence is manufactured by our own defensive idioms. The payload is the control pattern: grep a string you know is present in the same invocation as the claim, and report both counts, because if the control reads zero every other number in that pass is void. Then the part that surprised us — a bundle-identifier control happily accompanied a mistyped marker and cleared a staging binary as clean, so the second control has to share the pattern under test, not just the pipeline. Seven incidents, none of them caught by a test going red.</description>
    </item>
    <item>
      <title>Two homes for one rule: 'extract' is half a verb</title>
      <link>https://wizuslabs.com/blog/two-homes-for-one-rule/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/two-homes-for-one-rule/</guid>
      <pubDate>Thu, 30 Jul 2026 10:00:00 GMT</pubDate>
      <description>A refactor is not finished when the new home works. It is finished when the old site carries no independent copy of the rule — and only the destination half of that verb is satisfying, so only the destination half reliably gets done. A first-hand account of a duplicate we left behind in one of our own codebases. Our decision record promoted an ad-eligibility predicate into a single place, named the original five times with file and line coordinates, asserted in its own summary that the promotion removes a duplicated predicate, and in the same document filed the convergence that would have made that true as a natural follow-up not required by this decision. One page, two tenses. The failure was not forgetting; it was that a deferred retirement reads exactly like a completed one by the time anybody comes back to the file, because the summary is what gets re-read and not the scope note. Both homes stayed live from 2026-07-27 to 2026-07-30, and the next two fixes each patched only the new one — both shipping with green tests, because a test that the new home behaves correctly passes just as happily with a fully stale duplicate still deciding the boot path. No test went red. The duplicate surfaced because the second of those two fixes had the boot path open, read the stale copy, and wrote it up as a finding rather than folding it in: a person caught this, no check did. What came out of it — the guard has to be an oracle that asserts the old site still agrees with the new home under inputs where a rotted copy would visibly disagree, plus one anti-vacuity state where both must refuse, because otherwise the equality is satisfied by the unconditional yes that error paths already return. Ordinary inputs agree between a fresh copy and a rotted one, which is exactly why a single they-match scenario proves nothing. Also: how to bite-prove a private predicate that no test can pin, by relocating the body byte-for-byte into a test-visible seam with its stale formula intact rather than mutating a shared tree; why unpinnable code is the habitat duplicates survive in; why restating a formula in prose next to its single home is how the second copy rots in the first place; and the third reader of the same flag that we found, reported, and deliberately did not fix, because sameness of shape is not sameness of purpose.</description>
    </item>
    <item>
      <title>Pausing is the easy half</title>
      <link>https://wizuslabs.com/blog/pausing-is-the-easy-half/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/pausing-is-the-easy-half/</guid>
      <pubDate>Wed, 29 Jul 2026 10:00:00 GMT</pubDate>
      <description>Freezing the simulation is the easy half: you gate the update loop, the hazards stop, and on your own screen the game looks properly paused — which is exactly why the remaining half goes unnoticed. The hard half is that everything which measures the game has to agree it is paused, and that the pause has to know who is holding it. A first-hand account of rediscovering that shape in five of our own games in one week, from five different directions. A progression gate whose "did you really play this run" test read a wall clock, which kept climbing while the simulation sat correctly frozen — so holding any pause reason for sixty seconds and then dying immediately banked credit for a run that never happened. The gate was right; its input was lying to it. A win overlay that painted over a finished puzzle without absorbing pointer events, so two taps could restart its clock and inflate the solve time on screen — a visible overlay is a statement about painting, not about hit-testing, and that one was reachable by players. An inner overlay closing and un-pausing the game underneath an outer overlay still on screen, because a bare acquire/release pair is not nesting-safe. One pause reason with two independent producers, where the first release cancels the other's intent. And why we rejected the cheaper fix — subtracting accumulated paused time from a wall clock — in favour of a clock that only advances when the world actually steps.</description>
    </item>
    <item>
      <title>A guard nobody has seen fail</title>
      <link>https://wizuslabs.com/blog/a-guard-nobody-has-seen-fail/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/a-guard-nobody-has-seen-fail/</guid>
      <pubDate>Tue, 28 Jul 2026 10:00:00 GMT</pubDate>
      <description>A first-hand account of a localization parity gate that passed green for two days beside a file it could not, in principle, detect: an unreferenced app_ar.arb compiled into every binary and selectable by no player, invisible because the check enumerated the same list it was validating. The file turned out not to be an Arabic translation at all but a machine-generated right-to-left pseudo-locale fixture — and scanning it for Arabic codepoints returns 100% while proving nothing, since the generator appends an Arabic word to every string. Two rules came out of it. First, derive the set you are checking from the authority — the ARB files on disk, the OverlayEntry call sites in source, the supportedLocales bindings — never from the list under test, and make every intentional exemption declared data carrying a reason rather than an absence. Second, and more important: a guard nobody has seen fail is indistinguishable from a guard that cannot fail, so the deliverable of a new guard is not a green test but evidence it can go red — break the condition on purpose, read the real failure message, restore, and verify the restoration by sha256 equality rather than by eye. The same failure shape produced six defects in one work wave, and the worst of them broke nothing at all: a census pattern that silently dropped every hyphenated id and produced a confident, wrong count.</description>
    </item>
    <item>
      <title>Valid does not mean ready</title>
      <link>https://wizuslabs.com/blog/valid-does-not-mean-ready/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/valid-does-not-mean-ready/</guid>
      <pubDate>Mon, 27 Jul 2026 12:00:00 GMT</pubDate>
      <description>A first-hand release-ops account of the trap that catches the first upload to a brand-new App Store Connect record: the build reaches processingState=VALID and still cannot be handed to a tester. VALID is a fact about the binary, not about whether a human can install it — a fresh app stalls at MISSING_EXPORT_COMPLIANCE, invisible to testers, until an internal beta group, export compliance, beta app review detail and localizations, and a first tester are in place. The fast diagnostic is that the new app's beta groups lack the internal group its already-shipping siblings have. Because the structural setup persists, the trap fires exactly once per app — which is why it keeps catching people on every new record.</description>
    </item>
    <item>
      <title>A debug switch that ships in the release build</title>
      <link>https://wizuslabs.com/blog/a-debug-switch-that-ships-in-release/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/a-debug-switch-that-ships-in-release/</guid>
      <pubDate>Mon, 27 Jul 2026 11:00:00 GMT</pubDate>
      <description>A first-hand devlog on compiling a debug/QA switch into the release build itself — the staging artifact is identical in shape to the one that reaches the store, so testers evaluate the thing that will actually ship. The switch is a single compile-time boolean set with --dart-define, and the interesting part is a compiler quirk: ahead-of-time compilation constant-folds the flag and tree-shakes the dead branch, so in a production build the token you would grep for (DEV_UNLOCK) is gone — and it is gone in the QA build too. The fix is a print()-side-effect marker inside the guarded branch that the optimizer cannot prove is dead, so strings | grep returns one in a QA build and zero in production. The discipline — a marker the binary can prove, third-party SDKs forced into test-mode, and a never-promote rule — is what makes a debug switch in a release binary safe rather than reckless.</description>
    </item>
    <item>
      <title>The locale that breaks your layout</title>
      <link>https://wizuslabs.com/blog/the-locale-that-breaks-your-layout/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/the-locale-that-breaks-your-layout/</guid>
      <pubDate>Mon, 27 Jul 2026 10:00:00 GMT</pubDate>
      <description>A first-hand devlog on the localization trap an English-only overflow test cannot catch. We localized our offline-first arcade game to five languages (English, Vietnamese, Spanish, Portuguese, German) and a control laid out to fit English silently overflowed in German — at the default 1.0x text scale, before any accessibility text scaling. Why an English-only overflow suite, even cranked to 2x, is a different failure mode than a genuinely longer locale (German compound words are one long unbreakable token, not scaled English); how the existing suite only ever pumped Locale('en'); and the cheap parameterized guard — 42 cases across the densest surfaces in de/es/pt at default and max scale — that codifies the rule: a longer-than-English locale is not done until its layout-overflow is verified, so bundle a locale-overflow guard into every locale addition and test the longest locale, not just a non-English one.</description>
    </item>
    <item>
      <title>Never name the other store</title>
      <link>https://wizuslabs.com/blog/never-name-the-other-store/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/never-name-the-other-store/</guid>
      <pubDate>Sun, 26 Jul 2026 10:00:00 GMT</pubDate>
      <description>A first-hand studio note on a store rule that surprises almost everyone: an iOS listing may not mention Android or Google Play (App Store Review Guideline 2.3.10), and Google enforces the mirror image against Apple platforms — so the same marketing sentence ("play on iPhone, iPad, and Android") is rejected by both stores at once, for opposite reasons. Why the rule is coherent from the store's side and annoying from ours, where the forbidden words hide (keywords, promotional text, release notes, a Pixel in a screenshot), which files are in scope versus safe (README and CHANGELOG never upload), and the two-line grep audit we run before every upload — expecting zero hits — so a person's memory is not the thing guarding the line.</description>
    </item>
    <item>
      <title>When two agents edit one file, the last write wins</title>
      <link>https://wizuslabs.com/blog/when-two-agents-edit-one-file/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/when-two-agents-edit-one-file/</guid>
      <pubDate>Sat, 25 Jul 2026 10:00:00 GMT</pubDate>
      <description>Run two AI agents at once against the same file and you have not doubled your output — you have started a silent argument about whose edit survives, and the loser is never told. There is no merge conflict and no error: in one shared working tree, concurrent writes resolve as last-write-wins, and hot files (game_bloc.dart, the app root, pubspec.yaml) revert with no trace. A first-hand account of the three rules that fixed it — one owner per hot file per batch, a post-dispatch grep for a unique marker to prove the change landed, and a no-stash recovery policy because git stash and git reset --hard act on the whole shared tree — plus the structural fix of splitting into independent repos so parallel work runs in isolated trees.</description>
    </item>
    <item>
      <title>A version number is a promise; a build number is a receipt</title>
      <link>https://wizuslabs.com/blog/a-version-is-a-promise/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/a-version-is-a-promise/</guid>
      <pubDate>Fri, 24 Jul 2026 10:00:00 GMT</pubDate>
      <description>A version string is really two numbers with two audiences: the marketing version a human reads (a promise about how much changed, per semantic versioning) and the build number the app store orders by (a monotonic receipt). A first-hand account of why we let automation bump the build number on every cut but only a human is allowed to move the marketing version — "ship it" and "go to production" never authorise a version bump — and why the build number must stay the same integer on both stores (iOS CFBundleVersion, Android versionCode) or the two stores disagree about which upload is newest.</description>
    </item>
    <item>
      <title>The permission you don't use is a liability, not a convenience</title>
      <link>https://wizuslabs.com/blog/the-permission-you-dont-use/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/the-permission-you-dont-use/</guid>
      <pubDate>Thu, 23 Jul 2026 10:00:00 GMT</pubDate>
      <description>A first-hand studio postmortem on declaring an iOS capability you don't use. We shipped UIBackgroundModes: [audio] in games that pause all sound the instant they are backgrounded — a promise contradicted by the app's own didChangeAppLifecycleState code. Apple rejected it under App Store Review Guideline 2.5.4 in Sudoku on 2026-05-22, and then again in the tank game on 2026-07-09, because we fixed the file and never wrote down the lesson. The durable fix was not code but an audit script and waiver that turns a forgettable habit into a rule the build refuses to skip.</description>
    </item>
    <item>
      <title>One canonical home: the day we deleted 800 pages of our own site</title>
      <link>https://wizuslabs.com/blog/one-canonical-home/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/one-canonical-home/</guid>
      <pubDate>Wed, 22 Jul 2026 10:00:00 GMT</pubDate>
      <description>A first-hand devlog on the day we consolidated the WizusLabs site onto a single canonical content origin. Two live origins — wizuslabs.com (Cloudflare Pages) and the legacy wizuslabs.github.io — were one site competing with itself for ranking signal, so we picked one canonical home, redirected the other path-for-path, and stripped the losing origin from 803 files to 7 (kept version.json, app-ads.txt, and the Search Console token; removed pages 404→noindex). Including the trade-off we took on purpose: no fallback host, a redirect shell to maintain, and a hard deploy guard so the deleted duplicates can never be re-pushed.</description>
    </item>
    <item>
      <title>Staging has to lie exactly like production</title>
      <link>https://wizuslabs.com/blog/staging-lies-like-production/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/staging-lies-like-production/</guid>
      <pubDate>Tue, 21 Jul 2026 10:00:00 GMT</pubDate>
      <description>Why our test builds carry production third-party SDK keys, not sandbox — keeping the staging binary byte-identical to the one we ship, the "wrong API key" trap that blocks a tester before they play, and the single difference we always flag.</description>
    </item>
    <item>
      <title>Why a well-behaved game goes silent the moment you leave it</title>
      <link>https://wizuslabs.com/blog/going-silent-when-you-leave/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/going-silent-when-you-leave/</guid>
      <pubDate>Mon, 20 Jul 2026 10:00:00 GMT</pubDate>
      <description>A first-hand devlog on why a mobile game must pause its audio the instant it loses focus — via Flutter's WidgetsBindingObserver.didChangeAppLifecycleState — and must never declare the iOS background-audio capability it does not use: a claim that is both illegitimate under App Store review and contradicted by the app's own pause-on-background behaviour. Including the recurring rejection we caused twice (Sudoku, then the tank game) and the local audit script and waiver mechanism that finally made the fix stick.</description>
    </item>
    <item>
      <title>Deterministic by design: seeding randomness so bugs reproduce</title>
      <link>https://wizuslabs.com/blog/deterministic-by-design/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/deterministic-by-design/</guid>
      <pubDate>Sun, 19 Jul 2026 10:00:00 GMT</pubDate>
      <description>A first-hand devlog on how we make game randomness reproducible: one seeded PRNG owned by the engine (seeded from the level, never the clock), a canonicalized state hash that fingerprints a moment of play, and deterministic replay from a seed and a list of inputs — so a run that broke once can be played back bit-for-bit. Includes why we vendored a tiny serializable generator over the built-in Random, the per-concern sub-stream we keep deciding not to build, and the discipline tax determinism costs.</description>
    </item>
    <item>
      <title>Consent without dark patterns: the first screen a free game is judged by</title>
      <link>https://wizuslabs.com/blog/consent-without-dark-patterns/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/consent-without-dark-patterns/</guid>
      <pubDate>Sat, 18 Jul 2026 10:00:00 GMT</pubDate>
      <description>A first-hand account of how we wire GDPR/UMP consent and Apple's App Tracking Transparency across our games — no pre-ticked boxes, no guilt-trip asymmetry, the correct prompt order (GDPR form before ATT), and an honest 'no' that still works: the app keeps running and ads go non-personalised rather than gone. Including the trade-off we accept on purpose — a fair flow measurably lowers the opt-in rate, and that is the point.</description>
    </item>
    <item>
      <title>The hidden cost of renaming a shipped app</title>
      <link>https://wizuslabs.com/blog/the-hidden-cost-of-renaming-a-shipped-app/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/the-hidden-cost-of-renaming-a-shipped-app/</guid>
      <pubDate>Fri, 17 Jul 2026 10:00:00 GMT</pubDate>
      <description>We renamed a shipped game from Iron Blitz to Iron Swarm — and the new name was the easy part. A first-hand devlog on what a rename actually touches: store listings, canonical URLs baked into shipped binaries, redirect stubs you must never delete, a version manifest answering to both names, and the permanent identifiers a rename can never move.</description>
    </item>
    <item>
      <title>Designing an app icon that survives the home screen</title>
      <link>https://wizuslabs.com/blog/designing-an-icon-that-survives-the-home-screen/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/designing-an-icon-that-survives-the-home-screen/</guid>
      <pubDate>Thu, 16 Jul 2026 10:00:00 GMT</pubDate>
      <description>We had a Boardlore icon we were proud of at 1024 pixels — then judged it the way players actually see one: masked to the iOS squircle and shrunk to a home-screen thumbnail. A first-hand devlog on why concept and legibility are two separate tests, why a radiating pinwheel of pieces blurred to a blob while an open book survived, and why we redrew the winner coarse and bold to read at 120 pixels.</description>
    </item>
    <item>
      <title>Write the canonical form: a lenient reader hid a mess</title>
      <link>https://wizuslabs.com/blog/write-the-canonical-form/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/write-the-canonical-form/</guid>
      <pubDate>Wed, 15 Jul 2026 10:00:00 GMT</pubDate>
      <description>A strict form validator rejected an App Store link that works perfectly in every browser — because the browser silently follows a 301 redirect the validator will not. That same forgiveness had been hiding an inconsistency across our own links: some canonical, some bare, none broken, none noticed. A devlog on lenient vs strict readers of the same data, and why you write the canonical form everywhere.</description>
    </item>
    <item>
      <title>The browser tells the truth: a Back-button bug we almost fixed wrong</title>
      <link>https://wizuslabs.com/blog/the-browser-tells-the-truth/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/the-browser-tells-the-truth/</guid>
      <pubDate>Tue, 14 Jul 2026 10:00:00 GMT</pubDate>
      <description>We had a confident, framework-deep theory for why the browser Back button did nothing on our Iron Swarm web build — and a fix designed and QA'd against it. Then we drove a real headless browser through the exact flow, and the actual cause was far simpler: a 'Play on web' link that opened the game in a new tab, where Back has no previous page. A devlog on reproducing a bug before you theorise about it.</description>
    </item>
    <item>
      <title>Designing games that survive interruption</title>
      <link>https://wizuslabs.com/blog/designing-games-to-survive-interruption/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/designing-games-to-survive-interruption/</guid>
      <pubDate>Mon, 13 Jul 2026 10:00:00 GMT</pubDate>
      <description>Mobile games are played in stolen minutes, so the interruption — a call, a screen lock, an app switch, an OS kill — is the normal case, not the edge case. The craft of surviving it: treat the fragment as the unit of play, save state on the way out and never on quit, pause the instant focus is lost, and resume without dropping the player into a live hazard.</description>
    </item>
    <item>
      <title>Designing game audio that players don't mute</title>
      <link>https://wizuslabs.com/blog/game-audio-players-dont-mute/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/game-audio-players-dont-mute/</guid>
      <pubDate>Sun, 12 Jul 2026 10:00:00 GMT</pubDate>
      <description>Most phones live on silent, and the mute toggle is the most honest review a game's audio will ever get. The craft of sound players keep on — design for silence first, never punish with audio, and spend loudness like a budget, with the fanfares saved for moments that deserve them.</description>
    </item>
    <item>
      <title>One codebase, two stores: what actually differs shipping iOS and Android</title>
      <link>https://wizuslabs.com/blog/one-codebase-two-stores/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/one-codebase-two-stores/</guid>
      <pubDate>Sat, 11 Jul 2026 15:00:00 GMT</pubDate>
      <description>A first-hand map of what really forks when a single Flutter codebase ships to both the App Store and Google Play — code signing, store review and tracks, metadata and screenshots, in-app purchases, build-number parity, and privacy declarations.</description>
    </item>
    <item>
      <title>The first 60 seconds: designing a game's first-run experience</title>
      <link>https://wizuslabs.com/blog/the-first-60-seconds-ftue/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/the-first-60-seconds-ftue/</guid>
      <pubDate>Sat, 11 Jul 2026 14:00:00 GMT</pubDate>
      <description>A player decides whether your game is worth their evening in about a minute. Here is how a first-run experience earns that minute: orienting without a wall of text, the first meaningful action, the first win, and a reason to come back.</description>
    </item>
    <item>
      <title>How we generate and grade Sudoku puzzles</title>
      <link>https://wizuslabs.com/blog/how-we-generate-and-grade-sudoku-puzzles/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/how-we-generate-and-grade-sudoku-puzzles/</guid>
      <pubDate>Fri, 10 Jul 2026 14:00:00 GMT</pubDate>
      <description>A first-hand look at how our Sudoku app builds puzzles: fill a complete grid, dig holes while preserving a single solution, verify uniqueness with a solver, and grade difficulty by the techniques a puzzle demands — not by counting clues.</description>
    </item>
    <item>
      <title>The small-game discovery problem: what Steam's data says about getting seen</title>
      <link>https://wizuslabs.com/blog/the-small-game-discovery-problem/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/the-small-game-discovery-problem/</guid>
      <pubDate>Fri, 10 Jul 2026 13:00:00 GMT</pubDate>
      <description>Discovery, not development, is the wall for small games. A sourced read of Steam's 2025 data — ~19,000 releases, roughly half seen by almost no one, and the levers that measurably move visibility.</description>
    </item>
    <item>
      <title>An AI QA gate for games: what it catches, what it misses</title>
      <link>https://wizuslabs.com/blog/an-ai-qa-gate-for-games/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/an-ai-qa-gate-for-games/</guid>
      <pubDate>Fri, 10 Jul 2026 12:00:00 GMT</pubDate>
      <description>A first-hand, honest account of the automated QA gate every change clears before we ship a game — what it reliably catches, and the things it structurally cannot see, which is why a human still signs off.</description>
    </item>
    <item>
      <title>Designing games colorblind players can actually play</title>
      <link>https://wizuslabs.com/blog/designing-games-for-colorblind-players/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/designing-games-for-colorblind-players/</guid>
      <pubDate>Fri, 10 Jul 2026 11:00:00 GMT</pubDate>
      <description>Around 1 in 12 men and 1 in 200 women have some colour vision deficiency, and color-only encoding quietly locks them out. Here is why it fails, and the practical design fixes — shape, label, contrast, safe palettes, colorblind modes, and simulator testing — that let everyone read the game.</description>
    </item>
    <item>
      <title>Difficulty done right: curves, rubber-banding, and respecting the player</title>
      <link>https://wizuslabs.com/blog/difficulty-done-right/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/difficulty-done-right/</guid>
      <pubDate>Fri, 10 Jul 2026 10:00:00 GMT</pubDate>
      <description>A good difficulty curve is a conversation, not a wall. Here is how the ramp, plateau, and recovery work, when dynamic difficulty and rubber-banding feel cheap, and the difference between a game that is hard and one that is simply unfair.</description>
    </item>
    <item>
      <title>What's charting and why — July 2026</title>
      <link>https://wizuslabs.com/blog/whats-charting-2026-07/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/whats-charting-2026-07/</guid>
      <pubDate>Thu, 09 Jul 2026 22:00:00 GMT</pubDate>
      <description>Our first monthly read on what is actually charting and why: Cyberpunk 2077 back above 100k concurrent on Steam, the dated PC peak leaderboard (CS2, Dota 2, PUBG), and June 2026's top mobile games by rank.</description>
    </item>
    <item>
      <title>What 2026's top games have in common: a data teardown</title>
      <link>https://wizuslabs.com/blog/what-top-games-2026-have-in-common/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/what-top-games-2026-have-in-common/</guid>
      <pubDate>Thu, 09 Jul 2026 21:00:00 GMT</pubDate>
      <description>A sourced teardown of the patterns that actually unite 2026's top games — the $197B market and its platform split, free-to-play monetization, and why retention is a percentile band, not an average.</description>
    </item>
    <item>
      <title>Shipping web games in 2026: HTML5, WebGL, WebGPU, and Flutter-to-browser</title>
      <link>https://wizuslabs.com/blog/shipping-web-games-in-2026/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/shipping-web-games-in-2026/</guid>
      <pubDate>Thu, 09 Jul 2026 20:00:00 GMT</pubDate>
      <description>The web-game rendering stack in 2026 — Canvas2D, WebGL, and a WebGPU that now ships in every major desktop browser — plus HTML5 distribution, the mobile support gap, and what it actually takes to ship a Flutter/Flame game to the browser.</description>
    </item>
    <item>
      <title>How 2.5D games fake depth: parallax, billboards, and isometric tricks</title>
      <link>https://wizuslabs.com/blog/how-2-5d-games-fake-depth/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/how-2-5d-games-fake-depth/</guid>
      <pubDate>Thu, 09 Jul 2026 19:00:00 GMT</pubDate>
      <description>2.5D is the art of faking depth without paying for full 3D. Here is how parallax scrolling, billboarded sprites, isometric projection, pre-rendered backgrounds, and layered dioramas each conjure the impression of a third dimension — and when faking it is the right call.</description>
    </item>
    <item>
      <title>Free-to-play, done honestly: ads, IAP, and not being evil</title>
      <link>https://wizuslabs.com/blog/free-to-play-done-honestly/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/free-to-play-done-honestly/</guid>
      <pubDate>Thu, 09 Jul 2026 18:00:00 GMT</pubDate>
      <description>How a small studio funds a free game without dark patterns — honest ad-supported free play, optional Pro to remove ads, and no pay-to-win, timers, or loot-box coercion. Why the honest path also wins long-term.</description>
    </item>
    <item>
      <title>Why we build in Flutter and Flame (and where it breaks down)</title>
      <link>https://wizuslabs.com/blog/why-we-build-in-flutter-and-flame/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/why-we-build-in-flutter-and-flame/</guid>
      <pubDate>Thu, 09 Jul 2026 17:00:00 GMT</pubDate>
      <description>A first-hand account of why our studio builds its games on Flutter + Flame — one codebase for iOS, Android, and web, fast iteration, the widget model and the Flame game loop — and an honest look at where it breaks down.</description>
    </item>
    <item>
      <title>The anatomy of 'juice': why one mechanic feels alive and another dead</title>
      <link>https://wizuslabs.com/blog/anatomy-of-game-juice/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/anatomy-of-game-juice/</guid>
      <pubDate>Thu, 09 Jul 2026 16:00:00 GMT</pubDate>
      <description>Two games can share the exact same mechanic and one feels alive while the other feels dead. The difference is juice — the stacked layers of feedback around an action. Here is the whole stack, why each layer matters, and when it tips into noise.</description>
    </item>
    <item>
      <title>Unity vs the alternatives in 2026</title>
      <link>https://wizuslabs.com/blog/unity-vs-alternatives-2026/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/unity-vs-alternatives-2026/</guid>
      <pubDate>Thu, 09 Jul 2026 12:00:00 GMT</pubDate>
      <description>Unity, Unreal, Godot, GameMaker, Bevy, Flame and the rest in 2026 — with the one caveat most engine comparisons skip: the ranking flips depending on whether you count Steam releases, revenue, or a developer survey.</description>
    </item>
    <item>
      <title>Building games with an AI agent crew: our actual pipeline</title>
      <link>https://wizuslabs.com/blog/building-games-with-an-ai-agent-crew/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/building-games-with-an-ai-agent-crew/</guid>
      <pubDate>Thu, 09 Jul 2026 09:00:00 GMT</pubDate>
      <description>A first-hand, honest account of how an AI-native studio actually ships games: an orchestrator plus specialist agent crews, review gates, and a human who stays accountable — including where the model works and where it breaks.</description>
    </item>
    <item>
      <title>2D vs 2.5D vs 3D vs "4D": what game dimensions actually mean</title>
      <link>https://wizuslabs.com/blog/game-dimensions-2d-2-5d-3d-4d/</link>
      <guid isPermaLink="true">https://wizuslabs.com/blog/game-dimensions-2d-2-5d-3d-4d/</guid>
      <pubDate>Thu, 09 Jul 2026 00:00:00 GMT</pubDate>
      <description>What 2D, 2.5D, and 3D really mean in games — plus an honest, myth-busting look at "4D": theme-park motion seats and buzzwords versus genuine four-dimensional games like Miegakure and 4D Toys.</description>
    </item>
  </channel>
</rss>
