diff --git a/README.md b/README.md index 51f19a8..ed3c65d 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,8 @@ signatures and behaviour may still change in a minor release. ## Quick start -From nothing to a server that serves a stored world, in four steps. The last one needs no client. +From nothing to a server that serves a stored world, in five steps. Step 4 needs no client, and step +5 replaces the hand-written parts of step 2 with the third module. ### 1. Declare the dependency @@ -37,8 +38,12 @@ repositories { } dependencies { - implementation("net.onelitefeather:falco-anvil:0.3.0") - implementation("net.onelitefeather:falco-light:0.3.0") + // One version for all three, so they cannot drift into a combination nobody tested. + implementation(platform("net.onelitefeather:falco-bom:1.0.0")) + + implementation("net.onelitefeather:falco-anvil") // reading and writing Anvil worlds + implementation("net.onelitefeather:falco-light") // block and sky light + implementation("net.onelitefeather:falco-instance") // the instance and the chunk // Minestom is compileOnly in Falco, so it does not arrive with these // artefacts. Falco declares no version for it, on purpose. You pick it. @@ -48,7 +53,9 @@ dependencies { } ``` -The third module, the BOM that pins all three, Maven and snapshots are in +Take only the modules you need — a platform constrains a version for each, it does not pull one in. +Steps 2 to 4 below use the first two; step 5 uses all three. Individual coordinates, Maven and +snapshots are in [Installation](https://github.com/OneLiteFeatherNET/Falco/wiki/Installation). ### 2. Write the server @@ -93,17 +100,9 @@ public final class Bootstrap { } ``` -The listener is the explicit route: you decide which chunks are lit and when. There is a shorter one -that needs no listener at all — `instance.setChunkSupplier(scheduler.supplier())`, covered in -[Light Engine](https://github.com/OneLiteFeatherNET/Falco/wiki/Light-Engine). - -**That shorter route needs `falco-instance` on the classpath as well**, and the two lines above are -not enough for it. The chunks the supplier produces are `FalcoChunk`s — which is what lets one chunk -carry Falco's light *and* Falco's lifecycle instead of forcing a choice between them — and -`falco-instance` is `compileOnly` in `falco-light`, so it does not arrive with the artefact. Add -`implementation("net.onelitefeather:falco-instance:")` next to the two above before calling -`supplier()`; everything else in `falco-light`, including the `lighting.calculate` route used here, -works without it. +The listener is the explicit route: you decide which chunks are lit and when. Step 5 shows the +shorter one, where the chunks keep their own light and no listener is needed. Everything in +`falco-light` that this step uses works without `falco-instance` on the classpath. ### 3. Put a world where the loader looks @@ -135,6 +134,181 @@ flag — for a pre-lit world the engine is doing work nobody asked for. It earns without stored light and after blocks change at runtime. Which case is which is spelled out in [Light Engine](https://github.com/OneLiteFeatherNET/Falco/wiki/Light-Engine). +### 5. All three modules together + +Steps 2 to 4 use an `InstanceContainer` and drive the light yourself. The third module replaces both +of those decisions: + +```java +FalcoAnvilLoader loader = new FalcoAnvilLoader(Path.of("worlds", "lobby"), DimensionType.OVERWORLD.key()); + +// The scheduler takes the light service, not an instance. It reaches the world through +// the chunks its supplier builds. +ChunkLightScheduler scheduler = new ChunkLightScheduler(new ChunkLightService()); + +FalcoInstance instance = FalcoInstance.builder(DimensionType.OVERWORLD) + .chunkLoader(loader) + .chunkSupplier(scheduler.supplier()) + .autoChunkLoad(true) + .ownsLoader(true) // close the loader on shutdown + .saveOnShutdown(true) // and write the chunks first + .registerAndShutdownWith(MinecraftServer.getInstanceManager(), + MinecraftServer.getSchedulerManager()); +``` + +That is the whole server: no light listener, and no shutdown task written by hand. Three things +changed compared with step 2. + +**The light keeps itself up to date.** The supplier builds `FalcoLightingChunk`s, which report their +own block changes, loads and ticks to the scheduler. It lights the touched region and sends it, one +tick later, including the ring around it. Before `1.0.0` this combination did not exist: the lighting +chunk and the Falco chunk both extended Minestom's `DynamicChunk`, a class has one superclass, and a +server had to choose one of the two. + +**Unregistering the world actually unloads it.** `InstanceManager#unregisterInstance` unloads chunks +only for an `InstanceContainer`; for anything else it leaves every chunk, tick partition and entity +behind. `FalcoInstance` cleans up after itself, and that leak is the reason the module exists at all. + +**A chunk allocates what it uses.** Sections are created on the first write into them and every empty +one shares a single instance, which takes a fresh chunk from 192 objects and 6 848 bytes to 25 and +840. It is a count, not a timing — see +[What "high-performance" means here](#what-high-performance-means-here). + +Two things to know before building on it. `getSections()` materialises all 24 sections, because a +caller may write into what it gets — use `chunk.storage().views()` to only look. And a chunk supplier +producing anything but a `FalcoChunk` is refused, because such a chunk would be accepted everywhere +except the unload path. Lifecycle listeners, the storage accessors and the rest are in +[Instances and Chunks](https://github.com/OneLiteFeatherNET/Falco/wiki/Instances-And-Chunks). + +Minestom's own events are unaffected: `InstanceChunkLoadEvent`, `InstanceChunkUnloadEvent` and +`PlayerBlockBreakEvent` are dispatched here exactly as they are by a container, so listeners on the +`GlobalEventHandler` keep working. + +## Everything the three modules offer + +The five steps above are one path through Falco. This is the rest of it, so that what exists is +visible without reading three wiki pages first. Every snippet here is compiled by +`DocumentationSnippets` in `falco-demo`. + +### falco-anvil — reading and writing Anvil worlds + +The two-argument constructor is the whole of it for most servers. The builder is there when the +defaults do not fit: + +```java +FalcoAnvilLoader loader = FalcoAnvilLoader.builder() + .openRegionLimit(64) // region files kept open at once + .compressionLevel(2) // 1..9, the trade between write time and file size + .saveParallelism(4) // threads a saveChunks call may use + .dataVersion(4189) // what a written chunk claims to be + .diagnostics(new AnvilDiagnostics()) + .exceptionHandler(throwable -> log.warn("chunk load failed", throwable)) + .build(Path.of("worlds", "lobby"), DimensionType.OVERWORLD.key()); +``` + +**`diagnostics()` is the one to know about for a live server.** A world written by a different +version, or by a mod, contains blocks and biomes this loader cannot resolve — it substitutes and +counts rather than failing, and the counters are how you find out: + +```java +AnvilDiagnostics diagnostics = loader.diagnostics(); +diagnostics.reportUnknownBlock("mod:strange_block"); // true the first time, false after +``` + +`regionDirectory()` says which directory was resolved, `legacyLayout()` whether it fell back to the +pre-26.1 layout, and `openRegionCount()` how many files are open right now. `close()` flushes every +one of them and is what `ownsLoader(true)` calls for you. + +### falco-light — block and sky light + +Three entry points, in order of how much they do: + +```java +ChunkLightService lighting = new ChunkLightService(); + +lighting.calculate(chunk); // block light, this chunk +lighting.calculateSky(chunk); // sky light, this chunk +lighting.calculateWithNeighbours(instance, 0, 0); // both, and the ring around it + +int level = lighting.blockLightAt(chunk, 8, 40, 8); // read one position back +``` + +`calculateWithNeighbours` is the one to use when a chunk arrives from disk, because light crosses +chunk borders and a chunk lit alone has a dark seam. + +For a world that keeps itself lit, the scheduler does the bookkeeping. Its builder carries the knobs +that matter under load: + +```java +ChunkLightScheduler scheduler = ChunkLightScheduler.builder(lighting) + .executor(ChunkLightScheduler.defaultExecutor()) + .maxAreaSize(4) // chunks per side of one lighting area + .maxCachedChunks(256) // opacity tables kept between passes + .skyLight(ChunkLightScheduler.SkyLight.FROM_DIMENSION) + .onFailure(throwable -> log.error("lighting failed", throwable)) + .build(); +``` + +Two ways to drive it. On a `FalcoInstance`, `scheduler.supplier()` as in step 5 and nothing else. On +an `InstanceContainer`, hang a `ChunkLightListener` on the chunks and tick it yourself: + +```java +container.setChunkSupplier((instance, x, z) -> { + FalcoChunk chunk = new FalcoChunk(instance, x, z); + chunk.addLifecycleListener(new ChunkLightListener(scheduler)); + return chunk; +}); +MinecraftServer.getSchedulerManager().buildTask(() -> scheduler.onTick(container, System.currentTimeMillis())) + .repeat(TaskSchedule.tick(1)) + .schedule(); +``` + +And when something outside Falco changed the world, tell it: + +```java +scheduler.markChanged(instance, 0, 0); // this chunk needs relighting +scheduler.markChanged(instance, 0, 0, 8, 40, 8); // this position did +scheduler.markDirty(instance, 0, 0); // relight without an incremental path +``` + +### falco-instance — the instance, the chunk, shared views + +The builder is in step 5. Beyond it, the instance exposes its four parts, and the chunk exposes its +storage: + +```java +instance.registry(); // which chunks are loaded, by position +instance.lifecycle(); // loading, publishing, unloading, and the listeners +instance.blockWriter(); // the write path, including placement and destruction + +chunk.storage().views(); // read the sections without materialising them +chunk.storage().materialisedSections(); // how many actually exist +chunk.storage().shared(0); // is section 0 still the shared empty one +``` + +Lifecycle listeners are the extension point that replaced subclassing. Every method has a default: + +```java +instance.lifecycle().addListener(new ChunkLifecycleListener() { + @Override + public void onLoad(ChunkLifecycleEvent event) { + log.info("loaded {} {}", event.chunk().getChunkX(), event.chunk().getChunkZ()); + } +}); +``` + +They run **inside** the transition, before anybody else sees the chunk, which is what the light +engine needs — and why a throw from one fails the chunk load. For ordinary application code the +Minestom events named above are the right tool. + +Generation is the usual Minestom API, with one difference worth knowing: the generator is handed +copies of the section palettes and they are moved over only when it returns, so a generator that +fails halfway leaves the chunk exactly as it was rather than half built and published. + +```java +instance.setGenerator(unit -> unit.modifier().fillHeight(0, 40, Block.STONE)); +``` + ## Shared worlds Shared worlds are the one case `FalcoInstance` cannot serve, because `SharedInstance` takes an @@ -177,8 +351,16 @@ chunk from its own. The reasoning is in ## What "high-performance" means here -Measured, not asserted. Every figure comes from a JMH benchmark in this repository and is quoted -with the condition it was measured under. +Measured, not asserted. Every timing below comes from a JMH benchmark in this repository and is +quoted with the condition it was measured under. + +One claim is not a timing and is marked as such where it appears: **a chunk allocates its sections +when something writes into them**, which takes a fresh chunk from 192 objects and 6 848 bytes to 25 +and 840. That comes from jol rather than from JMH — it is a count of objects on a heap, it has no +spread, and it is unaffected by what else the machine was doing. It also says nothing about speed. +Whether a smaller chunk makes anything faster depends on allocation pressure and on the collector, +and nobody here has measured that. The instance benchmarks exist; they have never been run as a +baseline, and until they have, no timing about the instance or the chunk appears in this file. **The Anvil loader is 1.9× faster on two threads** — 1 181 ± 31 against 2 200 ± 445 µs/op, reading one chunk of 200 distinct block states. On one thread the intervals overlap and nothing is resolved diff --git a/falco-demo/src/test/java/net/onelitefeather/falco/demo/DocumentationSnippets.java b/falco-demo/src/test/java/net/onelitefeather/falco/demo/DocumentationSnippets.java new file mode 100644 index 0000000..d2cae06 --- /dev/null +++ b/falco-demo/src/test/java/net/onelitefeather/falco/demo/DocumentationSnippets.java @@ -0,0 +1,327 @@ +package net.onelitefeather.falco.demo; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.instance.Chunk; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.InstanceContainer; +import net.minestom.server.instance.InstanceManager; +import net.minestom.server.instance.block.Block; +import net.minestom.server.timer.TaskSchedule; +import net.minestom.server.world.DimensionType; +import net.onelitefeather.falco.anvil.AnvilDiagnostics; +import net.onelitefeather.falco.anvil.FalcoAnvilLoader; +import net.onelitefeather.falco.instance.ChunkLifecycleEvent; +import net.onelitefeather.falco.instance.ChunkLifecycleListener; +import net.onelitefeather.falco.instance.FalcoChunk; +import net.onelitefeather.falco.instance.FalcoInstance; +import net.onelitefeather.falco.instance.FalcoSharedInstance; +import net.onelitefeather.falco.light.ChunkLightListener; +import net.onelitefeather.falco.light.ChunkLightScheduler; +import net.onelitefeather.falco.light.ChunkLightService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.file.Path; +import java.util.UUID; + +/** + * Every code block the documentation shows, compiled: the quick start and the feature overview of + * the readme, and the wiki page Instances and chunks. + * + *

This class is never run and asserts nothing. Its whole value is failing to compile — a snippet + * somebody copies is the one piece of documentation that can be wrong in a way no reader forgives, + * and a page cannot be kept honest by rereading it. Writing the first version already caught one: + * the light engine block said {@code new ChunkLightScheduler(instance)}, and that constructor takes + * a {@code ChunkLightService}.

+ * + *

When a snippet in either document changes, change it here too. When this stops compiling, the + * document is wrong rather than this class.

+ */ +final class DocumentationSnippets { + + private static final Logger log = LoggerFactory.getLogger(DocumentationSnippets.class); + + private DocumentationSnippets() { + } + + // ---- readme, quick start ------------------------------------------------------------------ + + /** + * Step 4 — the two operations without a listener around them. + * + * @param instance the instance to read from + * @param lighting the light service + * @return the block light level the snippet prints + */ + static int quickStartCheckWithoutClient(FalcoInstance instance, ChunkLightService lighting) { + Chunk chunk = instance.loadChunk(0, 0).join(); + lighting.calculate(chunk); + + return lighting.blockLightAt(chunk, 8, 40, 8); + } + + /** + * Step 5 — all three modules together. + * + * @return the instance the snippet builds + */ + static FalcoInstance quickStartAllThreeModules() { + FalcoAnvilLoader loader = new FalcoAnvilLoader(Path.of("worlds", "lobby"), DimensionType.OVERWORLD.key()); + + ChunkLightScheduler scheduler = new ChunkLightScheduler(new ChunkLightService()); + + return FalcoInstance.builder(DimensionType.OVERWORLD) + .chunkLoader(loader) + .chunkSupplier(scheduler.supplier()) + .autoChunkLoad(true) + .ownsLoader(true) + .saveOnShutdown(true) + .registerAndShutdownWith(MinecraftServer.getInstanceManager(), + MinecraftServer.getSchedulerManager()); + } + + /** + * Shared worlds — a view that does not write its settings into the container. + * + * @return the view the snippet builds + */ + static FalcoSharedInstance quickStartSharedWorld() { + InstanceManager manager = MinecraftServer.getInstanceManager(); + InstanceContainer world = manager.createInstanceContainer(); + world.setChunkSupplier(FalcoChunk::new); + + FalcoSharedInstance view = new FalcoSharedInstance(UUID.randomUUID(), world); + manager.registerSharedInstance(view); + return view; + } + + // ---- readme, everything the three modules offer -------------------------------------------- + + /** + * falco-anvil — the builder, for when the two-argument constructor does not fit. + * + * @return the loader the snippet builds + */ + static FalcoAnvilLoader overviewAnvilBuilder() { + return FalcoAnvilLoader.builder() + .openRegionLimit(64) + .compressionLevel(2) + .saveParallelism(4) + .dataVersion(4189) + .diagnostics(new AnvilDiagnostics()) + .exceptionHandler(throwable -> log.warn("chunk load failed", throwable)) + .build(Path.of("worlds", "lobby"), DimensionType.OVERWORLD.key()); + } + + /** + * falco-anvil — the counters that say what a world contained and this loader could not resolve. + * + * @param loader the loader to ask + * @return what the counter answered + */ + static boolean overviewDiagnostics(FalcoAnvilLoader loader) { + AnvilDiagnostics diagnostics = loader.diagnostics(); + return diagnostics.reportUnknownBlock("mod:strange_block"); + } + + /** + * falco-light — the three entry points, in order of how much they do. + * + * @param instance the instance the chunk belongs to + * @param chunk the chunk to light + * @return the block light level the snippet reads back + */ + static int overviewLightEntryPoints(Instance instance, Chunk chunk) { + ChunkLightService lighting = new ChunkLightService(); + + lighting.calculate(chunk); + lighting.calculateSky(chunk); + lighting.calculateWithNeighbours(instance, 0, 0); + + return lighting.blockLightAt(chunk, 8, 40, 8); + } + + /** + * falco-light — the scheduler builder and the knobs that matter under load. + * + * @param lighting the light service the scheduler drives + * @return the scheduler the snippet builds + */ + static ChunkLightScheduler overviewSchedulerBuilder(ChunkLightService lighting) { + return ChunkLightScheduler.builder(lighting) + .executor(ChunkLightScheduler.defaultExecutor()) + .maxAreaSize(4) + .maxCachedChunks(256) + .skyLight(ChunkLightScheduler.SkyLight.FROM_DIMENSION) + .onFailure(throwable -> log.error("lighting failed", throwable)) + .build(); + } + + /** + * falco-light — driving the scheduler from a plain container rather than a FalcoInstance. + * + * @param container the container to hang the listener on + * @param scheduler the scheduler to report to + */ + static void overviewSchedulerOnContainer(InstanceContainer container, ChunkLightScheduler scheduler) { + container.setChunkSupplier((instance, x, z) -> { + FalcoChunk chunk = new FalcoChunk(instance, x, z); + chunk.addLifecycleListener(new ChunkLightListener(scheduler)); + return chunk; + }); + MinecraftServer.getSchedulerManager() + .buildTask(() -> scheduler.onTick(container, System.currentTimeMillis())) + .repeat(TaskSchedule.tick(1)) + .schedule(); + } + + /** + * falco-light — telling the scheduler what changed outside Falco. + * + * @param instance the instance the chunk belongs to + * @param scheduler the scheduler to tell + */ + static void overviewMarkChanged(Instance instance, ChunkLightScheduler scheduler) { + scheduler.markChanged(instance, 0, 0); + scheduler.markChanged(instance, 0, 0, 8, 40, 8); + scheduler.markDirty(instance, 0, 0); + } + + /** + * falco-instance — the four parts of an instance, and reading a chunk without materialising it. + * + * @param instance the instance to take apart + * @param chunk the chunk to read + * @return how many sections actually exist + */ + static int overviewInstanceParts(FalcoInstance instance, FalcoChunk chunk) { + instance.registry(); + instance.lifecycle(); + instance.blockWriter(); + + chunk.storage().views(); + chunk.storage().shared(0); + return chunk.storage().materialisedSections(); + } + + /** + * falco-instance — a lifecycle listener and a generator. + * + * @param instance the instance to configure + */ + static void overviewListenerAndGenerator(FalcoInstance instance) { + instance.lifecycle().addListener(new ChunkLifecycleListener() { + @Override + public void onLoad(ChunkLifecycleEvent event) { + log.info("loaded {} {}", event.chunk().getChunkX(), event.chunk().getChunkZ()); + } + }); + + instance.setGenerator(unit -> unit.modifier().fillHeight(0, 40, Block.STONE)); + } + + // ---- wiki, Instances and chunks ------------------------------------------------------------ + + /** + * The shortest form the wiki page shows. + * + * @return the instance the snippet builds + */ + static FalcoInstance wikiShortestForm() { + return FalcoInstance.builder(DimensionType.OVERWORLD) + .register(MinecraftServer.getInstanceManager()); + } + + /** + * The chunk loader form, with the three flags that belong together. + * + * @return the instance the snippet builds + */ + static FalcoInstance wikiWithChunkLoader() { + FalcoAnvilLoader loader = new FalcoAnvilLoader(Path.of("worlds", "lobby"), DimensionType.OVERWORLD.key()); + + return FalcoInstance.builder(DimensionType.OVERWORLD) + .chunkLoader(loader) + .autoChunkLoad(true) + .ownsLoader(true) + .saveOnShutdown(true) + .registerAndShutdownWith(MinecraftServer.getInstanceManager(), + MinecraftServer.getSchedulerManager()); + } + + /** + * The light engine combination. + * + * @return the instance the snippet builds + */ + static FalcoInstance wikiWithLightEngine() { + FalcoAnvilLoader loader = new FalcoAnvilLoader(Path.of("worlds", "lobby"), DimensionType.OVERWORLD.key()); + ChunkLightScheduler scheduler = new ChunkLightScheduler(new ChunkLightService()); + + return FalcoInstance.builder(DimensionType.OVERWORLD) + .chunkLoader(loader) + .chunkSupplier(scheduler.supplier()) + .autoChunkLoad(true) + .register(MinecraftServer.getInstanceManager()); + } + + /** + * The two-consumer lifecycle route of the builder. + * + * @param manager the manager to register with + * @return the instance the snippet builds + */ + static FalcoInstance wikiLifecycleConsumers(InstanceManager manager) { + return FalcoInstance.builder(DimensionType.OVERWORLD) + .chunkLifecycle( + chunk -> report("loaded", chunk), + chunk -> report("unloaded", chunk)) + .register(manager); + } + + /** + * The full listener, with every method the interface offers. + * + * @param instance the instance to register on + */ + static void wikiFullLifecycleListener(FalcoInstance instance) { + instance.lifecycle().addListener(new ChunkLifecycleListener() { + @Override + public void onPublish(ChunkLifecycleEvent event) { + } + + @Override + public void onLoad(ChunkLifecycleEvent event) { + } + + @Override + public void onTick(ChunkLifecycleEvent event) { + } + + @Override + public void onUnload(ChunkLifecycleEvent event) { + } + + @Override + public void onBlockChange(FalcoChunk chunk, int x, int y, int z, Block block) { + } + }); + } + + /** + * The read-only storage accessors of the table on that page. + * + * @param chunk the chunk to read + * @return how many sections actually exist + */ + static int wikiReadWithoutMaterialising(FalcoChunk chunk) { + chunk.storage().view(0); + chunk.storage().views(); + chunk.storage().shared(0); + return chunk.storage().materialisedSections(); + } + + private static void report(String what, Chunk chunk) { + log.info("{} {} {}", what, chunk.getChunkX(), chunk.getChunkZ()); + } +} diff --git a/falco-demo/src/test/java/net/onelitefeather/falco/demo/DocumentationSnippets.java.new b/falco-demo/src/test/java/net/onelitefeather/falco/demo/DocumentationSnippets.java.new new file mode 100644 index 0000000..e69de29 diff --git a/gradle.properties b/gradle.properties index 7c569c9..c5bc50a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,4 +6,4 @@ org.gradle.parallel=true #org.gradle.logging.level=info org.gradle.jvmargs=-Xmx2G -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -apiBaselineVersion=0.3.0 +apiBaselineVersion=1.0.0 diff --git a/gradle/api-breaks.properties b/gradle/api-breaks.properties index cd800df..0c59370 100644 --- a/gradle/api-breaks.properties +++ b/gradle/api-breaks.properties @@ -9,20 +9,13 @@ # # Format: .classExcludes = [, ...] -baseline = 0.3.0 +baseline = 1.0.0 -# net.onelitefeather.falco.light.FalcoLightingChunk +# No exceptions are in force. # -# The class became `final` and now extends net.onelitefeather.falco.instance.FalcoChunk instead of -# Minestom's DynamicChunk (US-3.06 of docs/superpowers/specs/2026-08-01-falco-instance-chunk-design.md). -# `final` is a real break: anyone who subclassed it no longer can. It is deliberate -- the light -# engine and the chunk lifecycle had to end up on one instance, which is what the whole storage -# rewrite was for -- and admissible because the package documents every public type in it as -# experimental. -# -# japicmp additionally reports `setBlock(int, int, int, Block, BlockHandler$Placement, -# BlockHandler$Destroy)` and `tick(long)` as removed. Both are wrong. The methods are still public on -# FalcoChunk and callers keep them by inheritance; japicmp cannot see that because FalcoChunk lives -# in another module and `ignoreMissingClasses` is on, so an inherited member looks removed. Verified -# with `javap -p` on falco-instance's FalcoChunk.class -- both are `public` there. -falco-light.classExcludes = net.onelitefeather.falco.light.FalcoLightingChunk +# The one this file was created for -- net.onelitefeather.falco.light.FalcoLightingChunk becoming +# `final` and extending FalcoChunk instead of Minestom's DynamicChunk (US-3.06) -- was a break +# against 0.3.0 and is not one against 1.0.0, because 1.0.0 is the release that shipped it. Raising +# apiBaselineVersion made the build demand this file be looked at again, which is what the baseline +# key is for, and the answer was to delete the entry rather than carry it forward. From here on the +# class is compared like any other, and a further break in it fails the build.