diff --git a/README.md b/README.md index 388da49..028e7c7 100644 --- a/README.md +++ b/README.md @@ -87,14 +87,18 @@ season enrollment, and instability controls. The client setting An operator can inspect Ages and instability with `/rivencraft age` and find libraries with `/rivencraft library nearest`. The creative-only Link Modifier can edit link properties and retire an Age; use it carefully and keep backups. +Binder status messages are off by default. An OP can run +`/rivencraft binder messages enable` or `/rivencraft binder messages disable` +to control them; only OPs see the diagnostics even when enabled. ## Project and development This repository is the development source. See the [current implementation](docs/CURRENT_IMPLEMENTATION.md), [milestone summaries](docs/MILESTONES.md), [technical document index](docs/README.md), and [issues](https://truenas-scale.tail180fb1.ts.net:8443/dnorberg/rivencraft/issues). -Milestone 7 is in progress; its [regression matrix](docs/MILESTONE_7_REGRESSION.md) -distinguishes automated checks from manual checks and known limitations. +Milestone 7 is complete in 1.0.0.2. Its [acceptance record](docs/MILESTONE_7_ACCEPTANCE.md) +and [regression matrix](docs/MILESTONE_7_REGRESSION.md) distinguish automated +checks from manual evidence and known limitations. ```sh ./gradlew build diff --git a/docs/CURRENT_IMPLEMENTATION.md b/docs/CURRENT_IMPLEMENTATION.md index eceb970..bc6b3e4 100644 --- a/docs/CURRENT_IMPLEMENTATION.md +++ b/docs/CURRENT_IMPLEMENTATION.md @@ -1,6 +1,6 @@ # Current implementation (Milestone 7 development) -This is the maintained technical overview of Rivencraft as of the 0.6.11.0 +This is the maintained technical overview of Rivencraft as of the 1.0.0.2 slice. It describes implemented behavior, not every behavior in Mystcraft Legacy. [The README](../README.md) is the player introduction; [milestone summaries](MILESTONES.md) explain how the implementation arrived @@ -41,9 +41,11 @@ and [Age administration](AGES.md). terrain starts from exposed surfaces. If no safe arrival site exists, the Age-creation path can construct a non-dropping Crystal Platform at Y=63. - The server catalog accepts typed data-pack symbols. The Binder preserves - written order and diagnoses conflicts. Symbol Pages are composed at runtime - from Legacy glyph assets; number pages 0–24 set color strength and, when - immediately before a Length, Angle, or Phase modifier, their numeric value. + written order and diagnoses conflicts. Its status readout is off by default; + `/rivencraft binder messages enable` makes it visible only to OPs. Symbol + Pages are composed at runtime from Legacy glyph assets. Number pages 0–24 + set color strength or, immediately before a Length, Angle, Phase, or Size + modifier, its numeric value. Celestial pages can create multiple suns/moons with independent color, size, angle, period, phase, and direction; authored eclipses affect light. - The Writing Desk stores tabs and copies known Symbol Pages with paper and @@ -92,5 +94,5 @@ This is not an exact port of every Legacy generator, page, hazard, or client renderer. Modern safety budgets and NeoForge's dimension/biome APIs require adaptations. The [Milestone 7 regression matrix](MILESTONE_7_REGRESSION.md) separates tested behavior from unverified multiplayer/restart/optional-mod -combinations. Issue [#84](https://truenas-scale.tail180fb1.ts.net:8443/dnorberg/rivencraft/issues/84) -is the final Milestone 7 acceptance and release gate. +combinations. The [Milestone 7 acceptance record](MILESTONE_7_ACCEPTANCE.md) +records release evidence and remaining validation limits. diff --git a/docs/MILESTONES.md b/docs/MILESTONES.md index b2f22c9..8c52b3c 100644 --- a/docs/MILESTONES.md +++ b/docs/MILESTONES.md @@ -54,12 +54,13 @@ adaptations. See [M6 acceptance](MILESTONE_6_ACCEPTANCE.md), [Legacy instability audit](LEGACY_INSTABILITY_AUDIT.md), and [runtime policy](INSTABILITY_RUNTIME.md). -## 7 — Structures and polish (in progress) +## 7 — Structures and polish (complete in 1.0.0.2) Added Legacy-layout Age libraries, page-bearing stands, crystal portals and receptacles, the handheld dropped-book entity, data-driven recipes, advancements, asset/audio polish, optional Ruins, compatibility updates, and -expanded sky/glyph pages. Issue #83 covers regression, migration, and document -consolidation; issue #84 is the final milestone acceptance gate. See the -[M7 Legacy audit](LEGACY_M7_AUDIT.md), [M7 regression matrix](MILESTONE_7_REGRESSION.md), -and [M7 compatibility review](MILESTONE_7_COMPATIBILITY_REVIEW.md). +expanded sky/glyph pages. Issue #83 covered regression, migration, and document +consolidation; issue #84 records the final milestone acceptance. See the +[M7 acceptance](MILESTONE_7_ACCEPTANCE.md), [M7 Legacy audit](LEGACY_M7_AUDIT.md), +[M7 regression matrix](MILESTONE_7_REGRESSION.md), and +[M7 compatibility review](MILESTONE_7_COMPATIBILITY_REVIEW.md). diff --git a/docs/MILESTONE_7_ACCEPTANCE.md b/docs/MILESTONE_7_ACCEPTANCE.md new file mode 100644 index 0000000..f03bbfc --- /dev/null +++ b/docs/MILESTONE_7_ACCEPTANCE.md @@ -0,0 +1,72 @@ +# Milestone 7 acceptance — 1.0.0.2 + +Milestone 7 is complete for the supported NeoForge 1.21.1 feature set. This is +not a claim of total Mystcraft Legacy parity or exhaustive optional-mod +certification. The [current implementation](CURRENT_IMPLEMENTATION.md) is the +up-to-date behavior summary; the [regression matrix](MILESTONE_7_REGRESSION.md) +separates automated evidence from manual scenarios. + +## Scope and issue gate + +The Gitea audit for issue #84 found no other open issues. Required existing +#14 (cave terrain), #43 (glyph numbers), #44 (sky), and #60 (old-save policy) +were closed. M7 implementation and review issues #75–#83, #85–#90, and +later additions #91–#94 were closed before this acceptance. No required +issue was silently deferred. + +| Legacy concept | Rivencraft implementation or deliberate difference | +| --- | --- | +| Cave terrain and writing | Cave terrain uses its cave/Nether biome set and a modern Y=64-start spawn search. Existing non-cave terrain still supports caves as features. See [Age profiles](AGE_PROFILES.md) and [grammar](WRITING_GRAMMAR.md). | +| Libraries | The recognizable Legacy small-library layout and sparse candidate spacing are ported as a structure template with a chest and up to five page-bearing Book Stands. It generates only in Ages and only on suitable surfaces; player-selectable Legacy or Age-only loot is supported. See [Legacy M7 audit](LEGACY_M7_AUDIT.md). | +| Crystal portal and Receptacle | Connected Crystal Blocks support vertical and horizontal portals. The held book stays in the Receptacle; removing it removes the surface. Rivencraft validates loaded bounds and block protection, saves owner pointers, and uses book-travel rules. Optional Immersive Portals supplies a live destination view, with an animated fallback while unavailable. | +| Handheld Linking/Descriptive Books | The used book remains behind as a rendered book entity at the origin. The exact Legacy entity pickup rule is not copied: modern contact pickup is used. Return Links default to disabled but remain configurable. | +| Recipes and discovery | JSON crafting recipes and recipe-book unlocks cover the supported writing/crystal progression. Optional JEI covers the Binder, Writing Desk, and Ink Mixer; a Lectern conversion has no JEI guide. Blank Pages are not craftable. Optional Create/Ponder teaches portal construction. See [JEI review](MILESTONE_7_JEI_REVIEW.md). | +| Models, sounds, and advancements | Legacy-sourced assets are credited in [asset provenance](ASSET_PROVENANCE.md); 1.21.1-compatible models and renderer adaptations are documented in the [visual audit](MILESTONE_7_VISUAL_AUDIT.md). Server-owned advancements cover book, writing, Age, and portal progress; the unimplemented Quinn equivalent is explicitly omitted in [advancement notes](MILESTONE_7_ADVANCEMENTS.md). | +| Numbers, sky, and extra items | Base-25 number glyphs, celestial modifiers and eclipses, Symbol Portfolios, the OP-only Link Modifier, and optional Ruins pages were follow-up additions. Legacy page semantics are adapted to the modern authoring plan rather than a binary copy of 1.12.2 worldgen. | + +## Compatibility and migration + +NeoForge **21.1.264** is the minimum 21.1 build. A player passed the +singleplayer client with the optional stack, then a local dedicated server +including Immersive Portals, before the minimum was raised. See the +[compatibility decision](MILESTONE_7_COMPATIBILITY_REVIEW.md). JEI +19.69.0.457 and Serene Seasons 10.1.0.9 received their own player checks; +Create/Ponder, Immersive Portals, TerraBlender/biome add-ons, Serene Seasons, +JEI, and Ruins remain optional. Ecliptic Seasons is deliberately not +integrated: its 1.21.1 public API does not support the required live Age +registration and per-Age calendar control. Missing optional mods must not +prevent the core mod from loading. + +Known older writing/book, Age-plan, and workstation formats have explicit +migration or stable-default handling. An Age registry and its dimension +folders must be backed up together. Removed mods, missing custom registry +objects, and arbitrary prerelease saves are **not** guaranteed to load. +Retiring an Age through the creative Link Modifier archives its data and +invalidates its UUID; that intentional destructive operation is not a +migration. See the [old-save boundary](MILESTONE_7_REGRESSION.md#old-save-boundary-60). + +## Release validation and limits + +The 0.6.11.0 regression candidate passed `./gradlew --offline build` and +fresh-world GameTest runs: core **91/91**, generation **129/129**, and +environment **103/103**. The player reported an in-game pass for that +candidate. The 1.0.0.1 release candidate additionally allows Number Pages to +set Celestial Size. The 1.0.0.2 release makes Binder status diagnostics an +OP-only, default-off world setting. Its offline build and core GameTests +passed (93/93), and the player reported an in-game pass. The +preceding 1.0.0.0 candidate passed core and generation GameTests. Its +environment archive stalled in the first 50-test batch, repeatedly printing +its field without completing; it was stopped and is **not** counted as a +1.0.0.0–1.0.0.2 pass. The previous 0.6.11.0 run of that same suite passed 103/103. +The focused sky, season, and number-page in-game checks in the regression +matrix remain useful regression checks, not claimed full-matrix passes. The +Gitea release changelog records the tested JAR and verified SHA-256. + +These results do not prove every two-player race, full restart, migration +from every development world, or optional-mod combination. The specific +manual matrix remains in [M7 regression](MILESTONE_7_REGRESSION.md); those +broader combinations are known validation limits, not claimed passes. Modern +chunk and tick budgets, safer portal validation, 1.21.1 world-height and +biome APIs, and the dynamic-dimension backend are deliberate deviations from +Legacy. A complete Legacy worldgen or link-property port is outside this +milestone. diff --git a/docs/MILESTONE_7_REGRESSION.md b/docs/MILESTONE_7_REGRESSION.md index 94c5bf0..aa10ba8 100644 --- a/docs/MILESTONE_7_REGRESSION.md +++ b/docs/MILESTONE_7_REGRESSION.md @@ -1,7 +1,7 @@ # Milestone 7 regression and migration matrix (issue #83) -This is a **working** acceptance record for the 0.6.11.0 candidate, not the -Milestone 7 completion sign-off. Issue #84 owns the final gate. A passing +This is the dated regression record for the 0.6.11.0 candidate. The +[Milestone 7 acceptance](MILESTONE_7_ACCEPTANCE.md) owns the final sign-off. A passing GameTest is server-side evidence; it is not proof of client rendering, multiplayer timing, or every optional-mod combination. @@ -11,7 +11,7 @@ multiplayer timing, or every optional-mod combination. | --- | --- | --- | | Dedicated server, core linking, book/Age identity, old writing and workstation formats | `./gradlew --offline runGameTestServer`; includes `CoreGameTests`, `AgePersistenceGameTests`, `CrystalPortalGameTests`, M7 crafting/advancement checks | 91/91 required GameTests passed on NeoForge 21.1.264; clean shutdown and fresh-world removal. | | Deterministic structures and terrain | `./gradlew --offline runGameTestServer -ParchivedGameTestSuites=generation`; includes library template/spacing/loot/stand checks, cave profiles, vanilla structures, crystal formations, fissures | 129/129 passed after correcting one village test to assert its own contribution rather than a seed-dependent total; fresh-world removal. | -| Sky, glyph numbers, seasons, creative/JEI page feed | `./gradlew --offline runGameTestServer -ParchivedGameTestSuites=environment` | 103/103 passed on the current candidate, including issue #94's number-page check; fresh-world removal. | +| Sky, glyph numbers, seasons, creative/JEI page feed | `./gradlew --offline runGameTestServer -ParchivedGameTestSuites=environment` | 103/103 passed on the 0.6.11.0 candidate, including issue #94's number-page check; fresh-world removal. | | Packaged models and data | `Milestone7ResourceGameTests.coreModelsResolveBundledTextures` in the core suite; parses critical block models, checks their Rivencraft texture references and the library NBT resource | Passed in the 91-test core run. This checks resource presence, not visual orientation or transparency. | | Recipe behavior | `Milestone7CraftingGameTests` and `LinkingBookRecipeGameTests` through the loaded Recipe Manager | Passed in the core run; confirms crystal and receptacle quantities, ink bucket return, no Blank Page crafting, and Linking Book ingredients. | | Portal state/save-load | `CrystalPortalGameTests` plus `AgePersistenceGameTests` in the core suite | Passed; covers activation/release, fallback orientation, horizontal/underside placement, uninterrupted surface, cooldown, retired Age and seed/plan identity. Full server restart remains manual. | @@ -29,7 +29,14 @@ and deletes it after pass or fail. See [GameTest guidance](GAMETESTS.md). Do not retry a storage-stalled GameTest: stop it, remove that automated scenario, and give its exact steps to the player. No run in this issue has stalled so far. The older full-archive storage stall is historical and is not -counted as a current pass. +counted as a current pass. On the subsequent **1.0.0.0 release candidate**, +the environment archive stalled during its first 50-test batch and was +stopped. The 103/103 result above applies to 0.6.11.0 only, not to 1.0.0.0 +or the later 1.0.0.1–1.0.0.2 candidates. For the 1.0.0.2 manual handoff, create an Age +with Number Pages immediately before Celestial Angle, Phase, and Size pages; +confirm each affects the intended body rather than becoming a stray page. Also verify a +fixed-season Age remains fixed across a restart while the Overworld remains +independent. Do not rerun the stalled suite merely to obtain a green count. ## Old-save boundary (#60) @@ -60,6 +67,7 @@ this candidate. | Optional renderers | With neither Create nor Immersive Portals, confirm Legacy fallback. With Create alone, confirm Ponder snapshots and portal guide. With Immersive Portals, confirm live view, Explore exit, sky/sound isolation, portal travel, and fallback while chunks load. With both, `AUTO` should choose Immersive Portals. | | Optional world/recipe mods | Test JEI guides/search, TerraBlender with a biome-producing addon (for example Biomes O' Plenty), Serene's six season modes plus Overworld independence/restart, and Ruins template selection/spawn weighting. Repeat startup without optional mods. | | Migration | Copy a backed-up older development world. Verify books and Notebooks retain their Age/page IDs, a desk and stand keep contents, old Ages load or provide a clear error, and removing a custom symbol retains its unresolved ID. Do not test by modifying the only copy. | +| Binder diagnostics | As a non-OP, verify the Binder still creates books but shows no instability/seeded/status messages. As an OP, run `/rivencraft binder messages enable`, reopen the Binder, and verify those messages appear only for the OP. Disable them and restart the world; the setting should remain disabled. | Historical evidence: the player approved NeoForge 21.1.264 on singleplayer and a dedicated server with Immersive Portals; JEI 19.69.0.457 guides, Serene diff --git a/docs/README.md b/docs/README.md index 1e99bc6..404967b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,7 +13,7 @@ former behavior or loader version. | M4 | [Writing data](WRITING_DATA.md), [book presentation](DESCRIPTIVE_BOOK_PRESENTATION.md), [Ink Mixer audit](INK_MIXER_MODIFIER_AUDIT.md) | | M5 | [Acceptance](MILESTONE_5_ACCEPTANCE.md), [Legacy grammar audit](LEGACY_AGE_GRAMMAR_AUDIT.md), [writing grammar](WRITING_GRAMMAR.md), [symbol semantics](SYMBOL_SEMANTICS.md), [TerraBlender](TERRABLENDER_COMPATIBILITY.md), [Serene Seasons](SERENE_SEASONS_COMPATIBILITY.md) | | M6 | [Acceptance](MILESTONE_6_ACCEPTANCE.md), [regression](MILESTONE_6_REGRESSION.md), [Legacy audit](LEGACY_INSTABILITY_AUDIT.md), [score](INSTABILITY_SCORE.md), [live blocks](LIVE_BLOCK_INSTABILITY.md), [runtime](INSTABILITY_RUNTIME.md), [diagnostics](INSTABILITY_DIAGNOSTICS.md), [status effects](INSTABILITY_STATUS_EFFECTS.md), [decay](INSTABILITY_DECAY.md), [environmental hazards](INSTABILITY_ENVIRONMENTAL.md), [cloud platform](AGE_CLOUD_PLATFORM.md) | -| M7 | [Regression and manual matrix](MILESTONE_7_REGRESSION.md), [Legacy audit](LEGACY_M7_AUDIT.md), [visual/audio](MILESTONE_7_VISUAL_AUDIT.md), [advancements](MILESTONE_7_ADVANCEMENTS.md), [JEI](MILESTONE_7_JEI_REVIEW.md), [Serene review](MILESTONE_7_SERENE_REVIEW.md), [compatibility](MILESTONE_7_COMPATIBILITY_REVIEW.md), [Ruins](RUINS_COMPATIBILITY.md) | +| M7 | [Acceptance](MILESTONE_7_ACCEPTANCE.md), [regression and manual matrix](MILESTONE_7_REGRESSION.md), [Legacy audit](LEGACY_M7_AUDIT.md), [visual/audio](MILESTONE_7_VISUAL_AUDIT.md), [advancements](MILESTONE_7_ADVANCEMENTS.md), [JEI](MILESTONE_7_JEI_REVIEW.md), [Serene review](MILESTONE_7_SERENE_REVIEW.md), [compatibility](MILESTONE_7_COMPATIBILITY_REVIEW.md), [Ruins](RUINS_COMPATIBILITY.md) | [Asset provenance](ASSET_PROVENANCE.md) applies across milestones. Detailed audits are intentionally retained for source attribution and migration diff --git a/docs/SYMBOL_SEMANTICS.md b/docs/SYMBOL_SEMANTICS.md index e889019..3db87c0 100644 --- a/docs/SYMBOL_SEMANTICS.md +++ b/docs/SYMBOL_SEMANTICS.md @@ -35,6 +35,7 @@ The Writing Desk retains 88 collection tabs, numbered from zero as in Legacy. Le | `period_modifier` | JSON number in `[0, 2]`; zero is stopped time. An immediately preceding D'ni number page overrides it with digit × 0.08. | | `angle_modifier` | JSON number in `[0, 360)` degrees. An immediately preceding D'ni number page overrides it with digit × 15°, wrapping 360° to 0°. | | `phase_modifier` | JSON number in `[0, 360)` degrees; the compiler converts it to turns. An immediately preceding D'ni number page overrides it with digit × 15°, wrapping 360° to 0°. | +| `size_modifier` | JSON scale in `[0.25, 3]`. An immediately preceding D'ni number page overrides it with a linear 0.25×–3× scale: digit 0 is 0.25× and digit 24 is 3×. Without a number, Small/Normal/Large remain 0.5×/1×/2×. | | `block` | Canonical namespaced material ID for a compatible following feature; registered full-collision blocks and source fluids receive generated Pages. | | `feature` | Canonical namespaced feature-effect ID; the compiler must recognize and validate it. This includes supported decorations and structures. | diff --git a/gradle.properties b/gradle.properties index 57c3d06..a1b2daa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -36,7 +36,7 @@ mod_name=Rivencraft # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=LGPL-3.0-or-later # The mod version. Rivencraft uses the x.y.z.b policy documented in README.md. -mod_version=0.6.11.0 +mod_version=1.0.0.2 immersive_portals_version=6.0.7 terrablender_version=4.1.0.8 serene_seasons_version_id=pHEgQQUE diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeSkyProfile.java b/src/main/java/net/dnorberg/rivencraft/book/AgeSkyProfile.java index db9bc09..a82132a 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeSkyProfile.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeSkyProfile.java @@ -86,7 +86,7 @@ public record AgeSkyProfile(Style style, int color, List
bodies) { case PERIOD_MODIFIER -> period = CelestialPageValues.value(semantics, number); case ANGLE_MODIFIER -> angle = (float) CelestialPageValues.value(semantics, number); case PHASE_MODIFIER -> phase = (float) (CelestialPageValues.value(semantics, number) / 360.0D); - case SIZE_MODIFIER -> size = (float) ((SymbolSemantics.NumberValue) semantics.value()).amount(); + case SIZE_MODIFIER -> size = (float) CelestialPageValues.value(semantics, number); case CELESTIAL_COLOR -> bodyColors.add(color( ((SymbolSemantics.ChoiceValue) semantics.value()).id())); case DIRECTION_MODIFIER -> clockwise = ((SymbolSemantics.ChoiceValue) semantics.value()) diff --git a/src/main/java/net/dnorberg/rivencraft/book/CelestialPageValues.java b/src/main/java/net/dnorberg/rivencraft/book/CelestialPageValues.java index 9f12f0a..e9531c3 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/CelestialPageValues.java +++ b/src/main/java/net/dnorberg/rivencraft/book/CelestialPageValues.java @@ -11,6 +11,7 @@ final class CelestialPageValues { return switch (semantics.role()) { case PERIOD_MODIFIER -> number * 0.08D; case ANGLE_MODIFIER, PHASE_MODIFIER -> (number * 15.0D) % 360.0D; + case SIZE_MODIFIER -> 0.25D + number * (2.75D / 24.0D); default -> throw new IllegalArgumentException("Not a celestial numeric modifier"); }; } diff --git a/src/main/java/net/dnorberg/rivencraft/book/CelestialSizeGameTests.java b/src/main/java/net/dnorberg/rivencraft/book/CelestialSizeGameTests.java new file mode 100644 index 0000000..ef93daa --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/book/CelestialSizeGameTests.java @@ -0,0 +1,64 @@ +package net.dnorberg.rivencraft.book; + +import java.util.List; +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.writing.OrderedWriting; +import net.dnorberg.rivencraft.writing.SymbolReference; +import net.dnorberg.rivencraft.writing.WritingEntry; +import net.dnorberg.rivencraft.writing.WritingGrammar; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.gametest.GameTestHolder; +import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; + +/** Focused core coverage without launching the storage-heavy environment archive. */ +@GameTestHolder(Rivencraft.MOD_ID) +@PrefixGameTestTemplate(false) +public final class CelestialSizeGameTests { + private CelestialSizeGameTests() {} + + @GameTest(template = "empty") + public static void adjacentNumberSetsNextCelestialSize(GameTestHelper helper) { + OrderedWriting writing = new OrderedWriting(List.of(WritingEntry.linkPanel(), + page("number/0"), page("size/small"), page("celestial/sun"), + page("number/24"), page("size/large"), page("celestial/moon"), + page("number/12"), page("size/normal"), page("celestial/sun"))); + helper.assertValueEqual(WritingGrammar.parse(writing).writingInstability(), 0, + "Adjacent number and size pages must be valid writing"); + AgeTraits seeded = AgeTraits.select(94L); + AgeGenerationPlan plan = AgeGenerationPlan.compile(writing, 94L, + AgeGenerationProfiles.STANDARD, seeded, helper.getLevel().registryAccess()); + AgeSkyProfile sky = AgeSkyProfile.compile(plan, AgeEnvironmentCompiler.apply(plan, seeded)); + helper.assertValueEqual(sky.bodies().size(), 3, "Three authored bodies must remain distinct"); + helper.assertValueEqual(sky.bodies().get(0).size(), 0.25F, "Digit zero selects minimum size"); + helper.assertValueEqual(sky.bodies().get(1).size(), 3.0F, "Digit 24 selects maximum size"); + helper.assertValueEqual(sky.bodies().get(2).size(), 1.625F, "Digit 12 selects midpoint size"); + helper.assertValueEqual(AgeSkyProfile.compile(AgeGenerationPlan.load(plan.save()), + AgeEnvironmentCompiler.apply(plan, seeded)), sky, + "Numbered sizes must survive an Age-plan save/load round trip"); + + OrderedWriting plain = new OrderedWriting(List.of(WritingEntry.linkPanel(), + page("size/small"), page("celestial/sun"), + page("size/normal"), page("celestial/moon"), + page("size/large"), page("celestial/sun"))); + AgeGenerationPlan plainPlan = AgeGenerationPlan.compile(plain, 95L, + AgeGenerationProfiles.STANDARD, AgeTraits.select(95L), helper.getLevel().registryAccess()); + AgeSkyProfile plainSky = AgeSkyProfile.compile(plainPlan, + AgeEnvironmentCompiler.apply(plainPlan, AgeTraits.select(95L))); + helper.assertValueEqual(plainSky.bodies().get(0).size(), 0.5F, "Small retains its fixed value"); + helper.assertValueEqual(plainSky.bodies().get(1).size(), 1.0F, "Normal retains its fixed value"); + helper.assertValueEqual(plainSky.bodies().get(2).size(), 2.0F, "Large retains its fixed value"); + + OrderedWriting separated = new OrderedWriting(List.of(WritingEntry.linkPanel(), + page("number/24"), page("direction/clockwise"), + page("size/large"), page("celestial/sun"))); + helper.assertValueEqual(WritingGrammar.parse(separated).writingInstability(), 100, + "An intervening page must not allow a number to modify size"); + helper.succeed(); + } + + private static WritingEntry page(String path) { + return WritingEntry.symbol(new SymbolReference(ResourceLocation.fromNamespaceAndPath("rivencraft", path))); + } +} diff --git a/src/main/java/net/dnorberg/rivencraft/book/HandheldBookTravelGameTests.java b/src/main/java/net/dnorberg/rivencraft/book/HandheldBookTravelGameTests.java index 5c3d325..f2b0176 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/HandheldBookTravelGameTests.java +++ b/src/main/java/net/dnorberg/rivencraft/book/HandheldBookTravelGameTests.java @@ -43,8 +43,9 @@ public final class HandheldBookTravelGameTests { helper.assertTrue(player.getInventory().getItem(1).is(Items.DIAMOND), "An unrelated inventory item must remain with the player"); var drops = helper.getLevel().getEntitiesOfClass(DroppedBookEntity.class, new AABB(source, source).inflate(2.0), - entity -> entity.getItem().is(ModItems.LINKING_BOOK.get())); - helper.assertValueEqual(drops.size(), 1, "Exactly one recoverable book must remain at the departure point"); + entity -> entity.getItem() == book); + helper.assertValueEqual(drops.size(), 1, + "Exactly one entity for this active book must remain at the departure point"); helper.assertTrue(drops.getFirst().getItem() == book, "The model-rendered entity must retain the exact active book stack"); helper.assertTrue(drops.getFirst().copy() instanceof DroppedBookEntity, diff --git a/src/main/java/net/dnorberg/rivencraft/client/screen/BookBinderScreen.java b/src/main/java/net/dnorberg/rivencraft/client/screen/BookBinderScreen.java index 62e0ef4..1a504f0 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/screen/BookBinderScreen.java +++ b/src/main/java/net/dnorberg/rivencraft/client/screen/BookBinderScreen.java @@ -107,7 +107,7 @@ public final class BookBinderScreen extends AbstractContainerScreen