19 KiB
Mystcraft Legacy instability audit (Milestone 6, issue #63)
This is the behavior contract used to implement Milestone 6. It is pinned to Mystcraft Legacy source at commit 9bc8ddc. Values below are source defaults, not a claim that every historical config or server used them. Rivencraft now persists frozen writing-instability scores and provider plans and runs the implemented providers through a bounded, Age-scoped scheduler. The audit remains authoritative for Legacy behavior; the linked runtime documents identify modern adaptations and remaining gaps.
Score, grammar, and selection in Legacy
Legacy's AgeController calculates debug + symbol + block + book/base + bonus, then applies difficulty 0/1/2/3 multipliers of 0.25/0.5/1/1.75. Global instability is enabled by default; an Age also has its own enable flag. The unmodified score is not simply “number of bad pages.” Block profiling may delay a usable score: blockinstability is null until more than 400 Age chunks are profiled, and getInstabilityScore() returns zero until then. It is recalculated after additional profiling. ChunkProfiler compares watched block abundance with a baseline, subtracting only positive excess above each baseline. The baseline is profiled or read from balance config; InstabilityDataCalculator sets its default tolerance to 1.05, and default useconfigs=false. Rivencraft's later bounded implementation retains the threshold and tolerance, but not Legacy's spatial accessibility formula or unbounded profiling.
| Input | Verified Legacy behavior | Rivencraft M6 interpretation |
|---|---|---|
| Missing required controller | Terrain, biome controller, weather, and lighting are generated when missing. InstabilityData.missing.controller = 0; lighting explicitly adds zero. Missing pages themselves are not a penalty. |
Keep Age-seeded completion. No penalty solely for an omitted branch. |
| Repeated same symbol | addSymbol increments a per-IAgeSymbol count and adds that symbol's instabilityModifier(count). This method is symbol-specific; there is no universal “duplicate = N” rule. |
Distinguish legal repeated features/materials from repeated single-choice pages. Do not add a flat duplicate penalty to every occurrence. |
| Extra/conflicting controller | Registering a second terrain, biome, weather, or lighting controller adds InstabilityData.extra.controller = 500; the last registered controller replaces the prior one. This is separate from each symbol's own modifier. |
Eligible supported conflicts may become unstable rather than fatal in #65, with deterministic winner and a saved penalty. |
| Dangling modifier | An unconsumed modifier contributes its dangling value; a default Modifier has dangling=100. Replacing a pending modifier charges the old one. clearModifiers() charges 20% (clearPercentage=0.20) of each pending value, with Java integer compound-assignment truncation. |
Record the ordered offending page and a reason; calculate a deterministic penalty from the consumed/leftover modifier semantics, not from GUI position alone. |
| Unsupported/unmatched page | Legacy logs unmatched symbols during reconstruction and skips their logic; its AgeData.getBaseInstability() still has a TODO for calculating instability from pages. This is not a sound compatibility contract. |
Remains fatal for new books until semantic and runtime support exists; never silently erase the page or pretend its effect applied. Existing saved books retain their IDs. |
| Other penalties/bonuses | AgeController.addInstability(int) is available to symbol logic. The base/book score is persisted as a short; Legacy's InstabilityBonusManager also has dynamic player death/behavior components. |
Persist a versioned score breakdown for Age writing and environmental contributions. Do not infer death/troll mechanics from the mere existence of Legacy classes. |
The complete Legacy instabilityModifier audit found direct feature-page costs only for Mineshafts, Nether Fortresses, Strongholds, and Villages. Their first three occurrences cost zero; the fourth and every later occurrence adds 100. Every other Legacy feature class inherited SymbolBase's zero modifier. Rivencraft applies that exact rule to the four matching pages. By project balance decision, Bastions, Trial Chambers, Ancient Cities, Shipwrecks, Ocean Ruins, Pillager Outposts, Ruined Portals, Nether Fossils, Buried Treasure, and Trail Ruins use the same repeat rule. Matching Legacy features such as Caves, Crystal Formations, Dense Ores, Dungeons, Floating Islands, Huge Trees, Deep/Surface Lakes, Obelisks, Ravines, Skylands, Spheres, Spikes, Star Fissures, and Tendrils have no direct page cost. Legacy could still derive instability later from excess profiled blocks, especially ores. From 0.5.8.11, Rivencraft has a bounded live block-profile adaptation using the 400-chunk threshold, including diamond blocks as a deliberate non-Legacy extension. Other modern-only Rivencraft features remain neutral unless they increase watched-block abundance.
Bright Lighting adds 500 for every authored occurrence. A generated Bright Lighting controller also adds 500. This is independent of the 500-point extra-controller cost, so two authored Bright Lighting pages produce 1,500 total: 500 for each symbol and 500 for the repeated controller.
Source for controller and modifier defaults: InstabilityData, AgeController. The local 0.13.7.06 Modifier.class confirms dangling_default=100. Legacy grammar ranks weight which missing symbols are selected; they are not a direct instability score or deck tier. See grammar audit and random weights.
InstabilityController rounds its working score toward zero to the current smallest registered positive provider cost (500 by default), then rebuilds active effects when that rounded score changes. Each seeded deck preserves its saved card order; new cards are shuffled with the Age seed. For each deck it subtracts the deck entry cost, then walks cards in order, paying each provider activation cost until the next card cannot be afforded. A provider's occurrences become its level. Deck costs are gates, not mutually exclusive severity bands: at high score multiple decks can contribute effects. Provider .enabled config can remove cards. The actual effect set cannot be inferred from a score alone without the saved deck orders and enabled providers (Deck, InstabilityManager).
| Legacy deck | Entry cost | Examples of registered cards |
|---|---|---|
| basic | 0 | enemy regeneration/resistance, fatigue, hunger, poison, slowness, weakness |
| harsh | 2,500 | stronger/global potions, burning, lightning, wither |
| destructive | 10,000 | crumble, explosions, lightning, meteors, wither |
| eating | 15,000 | colored decay, blindness, nausea, wither |
| death | 20,000 | global blindness/nausea/wither, colored decay |
These entry costs are from InstabilityData.initialize(), not guaranteed first-activation scores: each card also costs 500–5,000 depending on provider. A zero-cost basic deck still needs enough score to buy its first card. stability.* numbers in that file are provider activation costs, despite the misleading field name.
Effect inventory and modern policy
InstabilityData.initialize() is the authoritative list of registered providers. Some implemented classes are commented out there; their existence does not mean they occur normally. Each Legacy environmental effect is called for chunks that the Age's runtime processes, often with its own chance. No per-Age global work budget is visible in these effect classes.
| Family | Legacy behavior | M6 disposition |
|---|---|---|
| Player and hostile-mob potions | Local/global blindness, fatigue, hunger, nausea, poison, slowness, weakness, wither; hostile regeneration/resistance (EffectPotion). |
Implemented in #67 with server-only, Age-scoped entity targeting and rate limits. No effects run in preview/snapshot worlds. |
| Colored decay | Blue, purple, red, white providers place advancing decay and extra-tick it; black provider is commented out. Decay handlers differ in spread/consumption (providers, handlers). | Implemented in #68 as bounded world mutation with protected spawn/portal areas and configurable shutdown. Black/green/yellow remain excluded because they are not proven active Legacy providers. |
| Lightning and explosions | Random per-chunk strike/explosion attempts (EffectLightning, EffectExplosions). |
Implemented in #69 with separate damage/fire/griefing policies and loaded-Age bounds; see INSTABILITY_RUNTIME.md. |
| Meteor, scorching | Meteor entity spawn and sky-exposed entity ignition (EffectMeteor, EffectScorched). |
#70 implements a bounded meteor descent/impact and the active burning card; see INSTABILITY_ENVIRONMENTAL.md. Legacy meteor/roar.ogg is reused. |
| Crumble and erosion | Crumble maps blocks to weaker states; erosion spreads adjacent fluids and has extra lake-generation methods (EffectCrumble, EffectErosion). |
#70 implements guarded fluid spread. Erosion's provider is absent from the active Legacy deck; it is deliberately enabled by default in this test slice without adding a saved deck card, per user request. |
| Extra ticks | Registered alongside blue/purple/red/white decay, targeting decay blocks (EffectExtraTicks). |
Implement only as a bounded decay helper in #68; do not accelerate arbitrary blocks/block entities. |
| Other dormant code | Black decay, global burning, bedrock crumble, erosion provider are commented out; ordinary burning (scorching) remains active. |
Erosion has been explicitly requested for #70 testing. The other dormant variants remain deferred; an authored symbol requiring one must fail clearly until supported. |
Assets in the supplied local tree: assets/mystcraft/textures/blocks/decay_{black,blue,green,purple,red,white,yellow}.png, corresponding decay block/item model JSON, and assets/mystcraft/sounds/entity/meteor/roar.ogg. Before importing, review attribution/license and fit modern 16×16 assets. Texture availability does not make an effect active. Use current Minecraft effects/sounds where adaptation is cleaner; no new artwork is needed for this audit.
Rivencraft boundary and current safety contract
WritingGrammar classifies INCOMPLETE as seeded completion and supported writing defects as accepted instability. AgeGenerationPlan.compile() freezes the original writing, repaired active-page indexes, and a provisional writing-instability score before world generation. #64 persists the versioned score breakdown, and #66 derives and persists the provider/deck choice from that frozen score. The plan must not mutate an already-created Age when a data pack changes. Server-side settings may disable execution, but do not rewrite the saved authored score or provider plan.
For #65, eligible-to-become-unstable means a known, supported page whose semantics and resulting world are still deterministic: repeated/conflicting single-choice pages and otherwise valid out-of-order or misplaced pages. The implementation retains every original page, uses the last single-choice page, sorts active categories, and saves 500 per extra/conflicting choice or 100 per misplaced page. Issue #71 adds the audited Bright Lighting and repeat-structure modifiers described above. Missing branches are explicitly classified as zero-cost seeded completion; legal repeated feature/material pages are not automatically errors, though a fourth matching Legacy structure now receives its source-defined cost. Remain fatal: missing/duplicate/misordered Link Panel, over-capacity book, unresolved or schema-v1 display-only symbol, unsupported weather/angle/runtime profile/biome/feature, unavailable required optional mod, and any combination the compiler cannot represent safely. Binder and unbound-book previews distinguish blocked writing, seeded completion, and accepted instability before binding; the server revalidates on output. This intentionally differs from Legacy's silent unmatched-symbol skip, universal controller cost assumptions, and modifier-specific dangling costs.
Legacy includes PlayerKilledBonus and PlayerTrollPenalty classes, but the pinned source contains no default provider registration for either. Rivencraft does not activate death, PvP, player-presence, or cross-dimension credit from dormant classes. Consequently #71 has no mutable per-Age bonus state to migrate, cap, reset, or persist: the stability bonus and runtime penalty are both zero, and the effective score equals the frozen base score. /rivencraft age instability [dimension] reports those four values and the disabled policy explicitly. This prevents unrelated players, deaths in other dimensions, and repeated deaths from changing an Age. A future opt-in policy must use separate versioned saved data and define its cap/reset rules before it can alter runtime selection.
Automated #65 checks cover classification, preserved input slots on rejection, and an accepted unstable preview. The live output/one-time Age creation check is assigned to in-game testing because the GameTest storage phase stalled when that transaction ran; the test world is not retained.
Issue #66 established a server-only scheduler with bounded per-Age actions, provider calls, levels, cooldowns, loaded-chunk candidates, and rotating active-Age admission. Issues #67–#70 registered the current status, decay, lightning, explosion, meteor, burning, and erosion handlers with protected spawn/link platforms, portal exclusions, and provider-specific damage or spread limits. Instability and world-mutating handlers are enabled by default for newly generated configs to match Legacy's active default; operators can disable them without changing a saved Age. The bounds and protection hooks are Rivencraft safety adaptations, not Legacy defaults. Score breakdown, selected effects, enablement, and budget suppression remain part of diagnostics issue #72. See the runtime policy.
M6 owns scoring, selection, effects, controls, and regression. M7 still owns portal/transport and broader polish; M6 must not depend on new M7 structures. An illegal biome/terrain combination that is presently unrepresentable remains a generation failure until a separate, safe compiler path exists; instability must not be used to disguise absent worldgen support. Existing pre-release saves need explicit migration behavior in #73 rather than a silent rescore.
Verification and open questions
- Inspected pinned source for
AgeController,AgeData,GrammarTree,InstabilityData,InstabilityManager,InstabilityController,Deck, all active provider families, effect classes,ChunkProfiler, andInstabilityDataCalculator; checked matching local 0.13.7.06 class names andModifier.dangling_defaultwithjavap. - The supplied extraction has no generated user balance config; registered defaults can be overridden or providers disabled. There is no single universal instability value for “wrong order” or “duplicate page.”
IAgeSymbol.instabilityModifier(count)implementations and runtime configs determine the final Legacy value. - Exact Legacy visual/decay behavior, dynamic bonus registration, and full 1.12 block-baseline parity need focused checks when their slices are implemented. Do not label proposed modern caps as source facts.