The last NICHLYST post ended in a place I like: 704 failing assertions, a missing data contract, and a repair backed by Git history rather than guesswork.

This one begins with a quieter question:

What does a green test run actually prove?

After that repair came a run of small, deliberately narrow checks. A timezone change should not skip a day. A declared flag should have a writer, or a reason to exist. An ending should have a path through choices. English and Ukrainian fields should arrive in pairs. A palette gate should look beyond the game directory.

Then came the phase regression:

npm run verify ALL GATES PASSED (7/7) npm test PASS: 32/32 

That looks like a clean ending. It wasn't one.

The phase gate stayed open because the latest device-side console audit still contained a known Capacitor startup error, and I had no device run in this session to prove it had gone away. That caveat is the story.

This is Part 10 of Building NICHLYST. It follows the 704-assertion data-contract repair and returns to the constraint that made the earlier article work: evidence has to say exactly what it proves.

The small checks were not small

The next phase was not a new feature wave. It was an audit of assumptions at the edges of the game: time, flags, endings, language data, and visual tokens.

Check What it asks
Timezone shift Does changing the local timezone alter the countdown?
Flag ledger Which declared flags have no choice writer?
Ending paths Can a sequence of choices satisfy each ending's flag requirements?
Language parity Does each localized field have its counterpart?
Color gate Does the scan include the policy page outside www/?

The checks matter because each boundary can fail while the screen still looks normal. A day timer can jump. A choice can write a flag no later scene reads. An ending can have a plausible condition but no way to satisfy its key flags. A translation can be absent in just one branch. A privacy page can quietly introduce a seventh color.

Diagram showing that a timezone shift changes the local clock display, not the elapsed instant or the game countdown.

A clock change is not elapsed time

The timezone test is intentionally modest. It is not a device lab. It is a Node test around the pause-and-resume path in TimeManager.

The test starts with six hours remaining, records the pause timestamp, changes process.env.TZ by twelve hours in either direction, advances the mocked epoch by one second, and resumes. The timer should lose only the game-time equivalent of that actual second. waiting_until must remain set; the source day must remain the same.

That distinction matters:

  • A timezone changes how an instant is displayed locally.
  • An epoch timestamp measures an instant.
  • The active countdown uses elapsed time, not a comparison between local calendar dates.

The test passed in both directions. It proves the Node-level pause/resume calculation is not fooled by the timezone offset. It does not prove every Android manufacturer emits lifecycle events identically, and I won't describe it as an on-device test.

Fifteen declarations, two outcomes

The flag audit found fifteen names present in meta.json but absent from narrative sets_flags arrays.

Four had confirmed system writers:

  • ustyna_is_doctor and ustyna_is_informant are assigned by CharacterGenerator.
  • we_fractured is set by the state manager when trust reaches its fracture condition.
  • archive_afterlife_completed is set by PostscriptTransition after the final afterlife day.

The other eleven had no confirmed writer. They were removed from global_flags and recorded, one by one, in FLAG_LEDGER.md. The ledger is not a claim that every flag in the whole game is now perfectly used. It is an auditable disposition of this specific group of fifteen.

I added a checker and a test so those explanations cannot silently drift away from the source files. The count is no longer the proof; the writer evidence or removal record is.

“Reachable” needs a footnote

The ending verifier now looks for a choice sequence that sets the ending's required flags, in day order. It also checks whether the ending's numeric comparisons can fit within the configured resource and moral-axis domains.

That gives us a useful structural result: 33 of 33 endings have a path witness under those checks.

But there is a boundary to that claim. The verifier does not replay the entire game engine for each witness. It does not apply every passive system, every resource delta, every lifecycle transition, or every conditional branch exactly as a real save would. Numeric conditions are checked for feasibility within their configured ranges; the choice search focuses on the required flags.

So “reachable by path” here means a stronger statement than “the ending object exists,” but a narrower statement than “a full runtime simulation has reproduced the ending.” That distinction belongs in the test output and in this article. A future pass can replace the structural witness with an engine-backed replay.

During the audit, one ending used corruption even though the defined resource was testimony_corruption. The condition was aligned with the actual resource name, then the narrative bundle was regenerated. A successful assertion against the wrong name would have been a very convincing false green.

Diagram tracing a choice through a flag, a later read, an ending condition, and a verification gate.

Language parity and the policy page

The bilingual checker walks the day partitions and endings.json, collects localized field pairs, then checks both directions. If an _en field exists, its base field must exist. If a paired base field appears elsewhere, its _en counterpart must be present too. It also catches type mismatches and empty text on one side.

It found zero gaps across eleven paired field types in nine data files, including consequence_text.

The color gate had a different blind spot. It already scanned player-facing files under www/, but the privacy policy lives at docs/index.html. That page had two colors outside the approved palette. I changed both to #1F2326, then changed the gate to scan all CSS, JavaScript, and HTML under www/ plus HTML under docs/, regardless of what happens to be staged.

No exclusion list. No special-case comment. The file is in scope, and the color is in the palette.

Seven green gates—and one open gate

The full regression command completed all seven automated gates:

  1. Palette scan
  2. Ending audit
  3. Syntax check
  4. Test suite
  5. Production build
  6. Emoji-free bundle
  7. Restricted-terminology scan

The test suite reported 32/32. The ending audit reported 33/33 structural path witnesses. The production build and Android asset sync completed.

The historical performance baseline still records the measured FPS results from Stages 042–043: every measured scene cleared the 55 FPS threshold. The changes in this phase were checks, documentation, data declarations, and an ending condition name—not rendering work—so I preserved the measured values instead of inventing a fresh benchmark.

Then I checked the remaining phase-gate condition: the console audit.

The latest device-side report, from Stage 048, recorded four startup occurrences of:

Cannot read properties of undefined (reading 'triggerEvent') 

The report attributes these to an upstream Capacitor bridge timing issue. During the day-to-day gameplay window, it recorded zero errors and zero warnings. That is useful evidence, but it is not the same as a clean startup.

I did not have a device available for a fresh audit in this session. So I updated the baseline with the regression results, preserved the older FPS measurements, and left the phase gate marked not closed. The automated command passed. The stricter human condition did not have fresh evidence.

That is not a failed build. It is also not permission to call the phase complete.

The useful shape of “done”

The most popular NICHLYST post so far was about the structure that protects forty days of narrative inside a strict state machine: Eight Waves, One Canon. This phase was less dramatic. It asked whether the seams around that structure still held.

The answer is mixed, which is better than a polished but inaccurate status:

  • timezone offsets do not become countdown time in the tested pause/resume path;
  • the fifteen unassigned declarations have documented dispositions;
  • each ending has a structural choice-path witness under the verifier's stated limits;
  • the checked language pairs are complete;
  • the policy page is included in the color scan;
  • all seven automated gates pass;
  • a fresh device startup audit is still outstanding.

That last line is not an embarrassment to hide. It is the next action.

When the device is available, the remaining check is precise: clear the logs, cold-launch the app, capture the startup window, and determine whether the four bridge messages remain. If they do, the phase gate needs an explicit decision about that upstream condition. If they do not, the evidence can close the loop.

The point of a gate is not to make the status green. It is to stop me from painting it green before the evidence arrives.

One boundary at a time. One claim at a time. One honest status at a time.