On Saturday evening, August 29th, I was lying on my sofa after eating way too much watermelon. I was taking a short break from writing the narrative core of my game, NICHLYST, pushing the limits of narrative game design for the RevenueCat Shipaton 2026.
Then the shockwave hit.
It sounded like heavy crushed stone slamming against sheet iron. The protective netting we installed to keep our cat from falling out of the window was blown a meter across the balcony to the door. I threw on my sneakers, grabbed my walking cane, tossed a piece of chewing gum in my mouth, and quickly messaged my family that the blast was so loud I nearly crapped my pants. Then I ran downstairs to see if our building was hit and if anyone needed help.
It turned out to be falling debris from a downed "Shahed" attack drone about 300 meters away. If the drone itself had hit the building, the consequences would have been much worse, and the windows in my apartment would have undoubtedly shattered.
When the initial adrenaline faded, my hands started shaking uncontrollably. You do not design survival mechanics when physical survival is being decided 300 meters from your desk. You just shake. I took a tranquilizer—the kind I keep strictly for situations when I feel a panic attack imminent or during severe stress—went back up to my apartment, sat down at my old AMD A4 PC, and opened VS Code.
On Wednesday, September 2nd, I had my regular online appointment with my psychiatrist. The verdict was recorded clinically: "State without significant changes. Changing Sertraline to Fluoxetine."
But the code has to ship. The 14-day Google Play closed testing rule does not care about my neurochemistry or the craters in my neighborhood.
Here is how the architecture and narrative of NICHLYST evolved this week through the fog of war, community feedback, and medication changes.
Filling the Void: Writing a Novel You Can Fail
In my last devlog, I shared the architectural shift of throwing away the 1-sentence script and refactoring the UI to support 4-phase literary chapters (Awakening → Approach → Encounter → Dilemma).
But building the container is easy. Filling it with prose that justifies a $4.99 premium unlock—the entire indie game monetization strategy for NICHLYST—is brutal.
This entire weekend was dedicated to writing the Prologue and Acts I through IV (Days 1–15). Because this is a game without 3D models or combat mechanics, the pacing has to rely entirely on syntax.
- On Day 12 (The Raid), I wrote the text using fragmented, staccato sentences to simulate a panic-induced adrenaline spike: "Impact. The concrete shudders. Dust. Tactical flashlights slice the darkness like strobes."
- On Day 13 (The Aftermath), the syntax completely changes. The sentences become long, heavy, and meditative, reflecting absolute, deafening silence after the violence has passed.
The RevenueCat Paywall Threshold
The monetization flow was also deeply integrated into this narrative pacing. The $4.99 paywall now appears precisely at the end of Day 3, not as a jarring commercial banner, but as an in-lore narrative threshold: "The Archive demands a key. Without a key, the blast door remains shut."
It’s not an interruption; it’s a pause before the descent.
Diegetic Accessibility: Subtitles That Whisper
A brilliant piece of feedback came from a community discussion on accessibility (a11y). Someone asked: How do deaf players, or people playing on mobile with the sound muted, experience an atmospheric audio-horror game?
My initial thought was to add generic tags like [wind blows] or [silence]. But that breaks the magic realism of the world.
Instead, I built a diegetic subtitle engine in AudioManager.js using the aria-live polite pattern for screen readers. The subtitles do not describe the sound; they describe the psychological weight of the room.
- Instead of
[silence], the subtitle reads: "[The room holds its breath and quietly laughs]" - Instead of
[distant noise], it reads: "[A fragile violin is choked by the distant roar of turbines]"
<!-- AudioManager.js: Diegetic subtitle container -->
<div
id="diegetic-subtitles"
role="status"
aria-live="polite"
aria-atomic="true"
class="whisper-layer">
</div>
If the Archivist's mental state is compromised (e.g., the codeine_faith state where clarity is artificially high but endurance is dead), the JavaScript dynamically stretches the typography of the subtitles, injecting slow ellipses and spaced letters ("...k-n-o-c-k... a-t... t-h-e... d-o-o-r...").
Accessibility became a core narrative mechanic.
Somatic UI: When the Interface Feels Pain
To further separate the game from mere "stat management," I implemented somatic micro-gestures directly into DayScreen.js and the CSS.
If your endurance drops below 10, the choice buttons do not appear. You are physically too weak to make a decision. Instead, a single, violently trembling button renders: [Bandage hands to stop them trembling...]. You have to click it, watch the 1-second CSS animation stabilize, and only then do the real narrative choices reveal themselves.
If your guilt exceeds 80, the morning rendering of the day screen is accompanied by a 1-second overlay of muddy rust-sepia (.ds-pain-sun). The interface itself degrades with your psyche.
Antifragility: Breaking the JSON Monolith
On the engineering side, I realized I had built a massive single point of failure.
NICHLYST runs entirely on a master file: game_data.json. It holds 40 days, 31 endings, all the codex fragments, and 116 persistent flags. It was over 4,000 lines long. One missing comma on Day 39, and JSON.parse would fail on boot, crashing the game on Day 1.
That is fragile.
So, I wrote a Node.js workflow to decouple the data. I split the narrative into modular files: prologue.json, act_1.json, postscript.json.
// A fragment of the bundler script that ensures dual redundancy
const manifest = JSON.parse(fs.readFileSync('www/data/manifest.json', 'utf8'));
// The script compiles the isolated acts back into the master file at build time.
Now, the engine's loader attempts to fetch the modular manifest first. If a network request drops or a module is corrupted, it gracefully falls back to the bundled monolith—a core principle of graceful degradation JavaScript in production environments.
// manifest-loader.js: Promise.allSettled pattern for module resilience
const moduleUrls = [
'www/data/prologue.json',
'www/data/act_1.json',
'www/data/act_2.json',
'www/data/postscript.json'
];
const results = await Promise.allSettled(
moduleUrls.map(url =>
fetch(url).then(r => r.json()).catch(err => ({ error: err.message }))
)
);
const loadedModules = results
.filter(r => r.status === 'fulfilled')
.map(r => r.value);
// Fallback to bundled monolith if critical modules failed
if (loadedModules.length < moduleUrls.length) {
console.warn('Falling back to bundled game_data.json');
return fetch('www/data/game_data.json').then(r => r.json());
}
The game survives.
Google Play Closed Testing: The 14-day Clock is Locked
I finally completed the formal verification of my Google Play Developer account.
The verdict: The 14-day closed testing rule is mandatory. There is no bypass.
To meet the Shipaton deadline on September 28th, I must launch the closed test track no later than Tuesday, September 8th. The timer does not start when you upload the APK. It starts when the testers physically click Opt-In.
Google Play 12 Testers Requirement
The good news? The Shipaton organizers recently confirmed that the threshold to pass isn't 20 testers—it's 12+ active opt-ins. I already have 15 volunteers from the community. My task for the next 72 hours is to finish writing the Postscript chapters, integrate Sentry for error tracking, and hit the "Start Test" button.
My hands still shake sometimes. The explosions haven't stopped. The medication is adjusting. But the engine is stable, the narrative is heavy, and the architecture is antifragile.
I'll see you in the Archive.
Follow this devlog for the final sprint of the #BuildInPublic journey toward the RevenueCat Shipaton 2026.
Top comments (0)