Author player-facing after-action science narratives and reading-level bounds
verifiedAgainst 3629ae9 · verifiedOn 2026-09-11.
What you will build
Section titled “What you will build”In Voxamine, when a vessel reaction completes (e.g. roasting copper ore with charcoal, calcining quicklime, or pyrolyzing wood), the game does not show an arcane numerical error code, nor does it grant the player omniscient insight into unobserved hidden variables (MASTERPLAN.md §34.2).
Instead, the presentation layer derives an honest, readable after-action account answering the six core questions of §34.2:
- What changed? (
AfterActionSection.WhatChanged) - What evidence do we have? (
AfterActionSection.WhatEvidence) - What caused it? (
AfterActionSection.WhatCaused) - Why did it end? (
AfterActionSection.WhyItEnded) - Where did it go? (
AfterActionSection.WhereItWent) - How trustworthy is this? (
AfterActionSection.HowTrustworthy)
Crucially, this narrative is strictly bounded by observational evidence:
- If no thermometer was fitted to the vessel, the narrative never fabricates a temperature.
- Causal language is bounded: the text says “caused by” only when a reaction was positively identified; otherwise it says “consistent with” or “could not tell”.
- Every sentence preserves its causal provenance (
AfterActionLine.SourceEventId). - Every sentence is held to an automated Flesch–Kincaid grade-level ceiling ($\le 10$) in
ScienceReadingLevel.cs. - Every technical term is defined in plain language in
ScienceGlossary.cs.
// Example: Narrating a completed vessel run with limited sensors:List<AfterActionLine> lines = ScienceAfterActionNarrator.Narrate(account, access);
foreach (AfterActionLine line in lines){ // Evaluates: "[WhatCaused] The change was consistent with a reduction reaction." // Preserves: line.Section, line.Evidence, line.SourceEventId Assert.That(ScienceReadingLevel.FleschKincaidGrade(line.Text), Is.LessThanOrEqualTo(10d));}Where this sits
Section titled “Where this sits”The narrative system sits between the raw deterministic chemistry solver and player presentation:
- Chemistry Solver (
ScienceVesselRun&ScienceEventLog) records committed simulation events (ScienceEvent) during vessel ticks. - After-Action Builder (
ScienceAfterActionBuilder) converts event logs into factual claims (ScienceAfterActionClaim), filtering by the apparatus’s installed sensor channels (ScienceObservationAccess). - Narrator (
ScienceAfterActionNarrator) renders claims into ordered, evidence-bounded sentences (AfterActionLine) across the six §34.2 sections. - Readability & Glossary (
ScienceReadingLevel&ScienceGlossary) enforces sentence simplicity (grade $\le 10$) and registers first-use definitions. - HUD & Journal (
VesselRunViewRuntime&JournalNudgeMath) presents latest-run cards in world space and weights journal notifications (Routine,Notable,Major).
flowchart TD
subgraph SOLVER["Chemistry Solver"]
RUN["ScienceVesselRun\n(Ticks & Phase Transitions)"]
EVENTS["ScienceEventLog\n(Committed ScienceEvents)"]
end
subgraph CLAIMS["Factual Extraction"]
ACCESS["ScienceObservationAccess\n(Contents, Temp, Pressure, Reaction Identity)"]
BUILDER["ScienceAfterActionBuilder\n(Filters events by access)"]
CLAIMS_BUF["ScienceAfterActionClaim Buffer"]
end
subgraph PROSE["Narrative Generation"]
NARRATOR["ScienceAfterActionNarrator\n(6-section monotonic prose)"]
READING["ScienceReadingLevel\n(Flesch-Kincaid Grade ≤ 10)"]
GLOSSARY["ScienceGlossary\n(Plain-language technical definitions)"]
end
subgraph VIEW["Player Presentation"]
CARD["VesselRunViewRuntime\n(In-world targeted vessel card)"]
NUDGE["JournalNudgeMath\n(Routine / Notable / Major weight)"]
end
RUN --> EVENTS
EVENTS --> BUILDER
ACCESS --> BUILDER
BUILDER --> CLAIMS_BUF
CLAIMS_BUF --> NARRATOR
NARRATOR --> READING
NARRATOR --> GLOSSARY
NARRATOR --> CARD
NARRATOR --> NUDGE
Before you start
Section titled “Before you start”Read Docs/MASTERPLAN.md §34.2 (“The journal’s player-facing text”) and Docs/HANDOFF.md §34.2, then inspect:
ScienceAfterActionNarrator.cs, the six-section prose generator.ScienceReadingLevel.cs, the Flesch–Kincaid readability estimator.ScienceGlossary.cs, the first-use plain-language dictionary.ScienceAfterActionNarratorTests.cs, edit-mode tests asserting reading level and evidence bounds.
Keep the fundamental principles in mind:
| Rule | Meaning | Violation Example |
|---|---|---|
| Simplify sentence, not chemistry | Use clear, short sentence structures; never dumb down or hide actual chemical reactions. | Bad: “The rock magically changed.” Good: “The malachite reacted with carbon to produce copper.” |
| Evidence bounding | A sentence can only claim what active sensors actually measured. | Claiming “heated to 1150 K” when no thermometer was present. |
| Monotonic section ordering | Lines always render in strict 1–6 section order. | Displaying “How trustworthy” before “What changed”. |
| Reading level guardrail | Prose must stay at or below Flesch–Kincaid grade 10. | Convoluted academic phrasing with excessive nested clauses. |
The build, step by step
Section titled “The build, step by step”1. The Six Core Narrative Sections
Section titled “1. The Six Core Narrative Sections”In ScienceAfterActionNarrator.cs, every generated sentence belongs to exactly one section:
public enum AfterActionSection : byte{ WhatChanged = 1, WhatEvidence = 2, WhatCaused = 3, WhyItEnded = 4, WhereItWent = 5, HowTrustworthy = 6,}Every line produced is encapsulated in an immutable struct carrying:
Section: which of the six questions it answers.TemplateId: stable string identifier allowing future localization without rewriting logic.Text: plain-English readable sentence.Evidence: whether it represents a direct measurement, a visual observation, or an inference.SourceEventId: committed simulation event ID backing this claim.
2. Evidence-Bounded Causal Language
Section titled “2. Evidence-Bounded Causal Language”In ScienceAfterActionNarrator.cs, causal sentences adjust strictly based on ScienceObservationChannels:
// When the apparatus carries reaction-identification instrumentation:if ((access.Channels & ScienceObservationChannels.ReactionIdentity) != 0){ destination.Add(new AfterActionLine( AfterActionSection.WhatCaused, "caused-by-identified-reaction", $"The change was caused by {reactionName}.", ScienceEvidenceKind.Measured, sourceEventId));}// When only contents or bulk properties were observed:else{ destination.Add(new AfterActionLine( AfterActionSection.WhatCaused, "consistent-with-reaction", $"The change was consistent with {reactionName}.", ScienceEvidenceKind.Inferred, sourceEventId));}If neither temperature nor pressure sensors exist on the vessel, no sensor values are displayed under WhatEvidence:
destination.Add(new AfterActionLine( AfterActionSection.WhatEvidence, "temperature-unobserved", "Temperature was not measured.", ScienceEvidenceKind.DirectObservation, sourceEventId));3. Flesch–Kincaid Grade Level Bounding
Section titled “3. Flesch–Kincaid Grade Level Bounding”In ScienceReadingLevel.cs, measure sentence complexity:
$$\text{Grade} = 0.39 \left(\frac{\text{words}}{\text{sentences}}\right) + 11.8 \left(\frac{\text{syllables}}{\text{word}}\right) - 15.59$$
public static double FleschKincaidGrade(string text){ if (string.IsNullOrWhiteSpace(text)) return 0d;
int sentences = CountSentences(text); int words = 0; int syllables = 0;
foreach (string word in EnumerateWords(text)) { words++; syllables += CountSyllables(word); }
if (words == 0) return 0d;
double wordsPerSentence = words / (double)Math.Max(1, sentences); double syllablesPerWord = syllables / (double)words; return (0.39d * wordsPerSentence) + (11.8d * syllablesPerWord) - 15.59d;}The heuristic syllable counter splits vowel clusters and accounts for silent trailing es. Any generated sentence scoring above TargetGradeCeiling = 10d fails the test suite.
4. Plain-Language Glossary Registration
Section titled “4. Plain-Language Glossary Registration”In ScienceGlossary.cs, define every technical term in plain language for its first-use tooltip:
Definitions["stoichiometry"] = "The fixed whole-number ratio in which substances react. " + "Two parts hydrogen always join with one part oxygen to make water, never some other mix.";
Definitions["calcination"] = "Heating a mineral hard enough to drive a gas out of it, leaving a different solid behind. " + "Heating limestone this way makes quicklime and gives off carbon dioxide.";Technical words are never hidden or deleted to lower a readability score; instead, they are taught clearly on first encounter.
5. Run Significance and Journal Nudges
Section titled “5. Run Significance and Journal Nudges”Completed runs are classified by RunSignificance to drive player journal notifications:
Routine: Brief tick with negligible change (minor notification).Notable: Significant mass change, gas vent, or phase transition (standard notification).Major: Confirmed newly identified chemical reaction or catastrophic vessel failure (prominent notification).
Verify
Section titled “Verify”1. Execute EditMode Tests Headlessly
Section titled “1. Execute EditMode Tests Headlessly”Run the dedicated after-action test suite:
/opt/unity/Editor/Unity -batchmode -nographics \ -projectPath /home/soulwax/workspace/engines/unity/minecraft/Minecraft-HD \ -runTests -testPlatform EditMode \ -testFilter VoxelSandbox.Tests.ScienceAfterActionNarratorTestsVerify that all key assertions pass:
AllGeneratedProseMeetsTheReadingLevelCeiling: runs Flesch–Kincaid evaluation on every single sentence generated across all channel combinations, verifying score $\le 10.0$.SectionOrderIsStrictlyMonotonicAndEverySentenceHasASection: ensures section numbers strictly ascend ($1 \to 6$) with no gaps or inversions.SentenceCausalLanguageIsBoundedByAvailableChannels: confirms “caused by” never appears withoutReactionIdentitychannel.UnobservedChannelsNeverLeakNumbersIntoProse: proves absent gauges never leak numeric values.EveryNarratedSentencePreservesItsSourceEventId: checks causal traceability for every line.
Now do your own
Section titled “Now do your own”To add a new narrative template (e.g. Electrochemical Cell Depletion or Supercritical Fluid Extraction):
- Add Template ID: Define a new constant string in
ScienceAfterActionNarrator. - Assign Section: Place the line in the appropriate
AfterActionSection. - Register Glossary Terms: If using new terminology (“overpotential”, “supercritical”), add plain definitions in
ScienceGlossary.cs. - Audit Reading Level: Check the generated prose with
ScienceReadingLevel.FleschKincaidGradeto ensure it scores $\le 10$. - Add Test Assertions: Add test cases to
ScienceAfterActionNarratorTests.cs.
Pitfalls
Section titled “Pitfalls”| Symptom | Cause | Fix |
|---|---|---|
| Text says “caused by calcination” when no analyzer was attached. | Failed to check ScienceObservationChannels.ReactionIdentity before selecting template. |
Restrict “caused by” to measured reaction identity; use “consistent with” for inferred reactions. |
| Narrative mentions exact degrees Kelvin when no thermometer was fitted. | Read solver temperature directly instead of gating on ScienceObservationChannels.Temperature. |
Gate temperature readouts on installed sensor channels. |
| Test suite fails with reading level exceeded (> 10). | Authored sentence was too long or used excessive polysyllabic non-glossary words. | Break long sentences into two concise sentences. |
| Lines display out of order in the HUD. | Appended lines to list without sorting by AfterActionSection. |
Always emit lines grouped by AfterActionSection in monotonic ascending order ($1 \to 6$). |
| Cannot trace which solver tick caused a vessel failure line. | Passed 0UL instead of sourceEventId to AfterActionLine constructor. |
Always propagate event.EventId into AfterActionLine.SourceEventId. |
User-contributed notes
Corrections, clarifications, and practical tips for this page. Anonymous is fine — a name is optional. Basic Markdown works:
**bold**,*italic*,`code`, and links.Notes policy
Notes are lightly filtered for spam and may be edited or removed. Keep them about this page — no support requests, no personal data, nothing you would not publish. Links are limited and marked
nofollow.