Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0493bbf57 | ||
|
|
78271bd9d1 | ||
|
|
4bd6576bba | ||
|
|
e3e2c63048 | ||
|
|
6a4d0c530b |
No files matched your search
@@ -1,9 +1,13 @@
|
||||
# Purpur Ignis agent entrypoint
|
||||
|
||||
The canonical knowledge bundle is the sibling `../somc-okf/` repository, remotely at https://git.garvis.dev/dmg/somc-okf/src/branch/main/index.md.
|
||||
The canonical technical/process wiki is the sibling `../somc-okf/` repository, remotely at https://git.garvis.dev/dmg/somc-okf/src/branch/main/index.md.
|
||||
|
||||
Before changes read its root index, `processes/development.md`, `runbooks/development-cycle.md`, `projects/purpur-ignis/index.md`, `projects/purpur-ignis/engineering.md`, and the relevant `user-stories/purpur-ignis/` documents. Follow the parent workspace instructions. Do not create a local knowledge/design bundle.
|
||||
Stories, acceptance criteria, status and evidence belong in [Kaneo Social Minecraft](https://cloud.kaneo.app/dmg/SM), with subjects `[purpur-ignis] Descriptive title` (no `US-###` or `FS-*` prefix). Use the configured authenticated `kaneo` MCP server per [Kaneo access and migration](https://git.garvis.dev/dmg/somc-okf/src/branch/main/processes/development.md#kaneo-access-and-migration); report unavailable access rather than creating local story copies. OpenProject is read-only history.
|
||||
|
||||
Before changes read its root index, `processes/development.md`, `runbooks/development-cycle.md`, `projects/purpur-ignis/index.md`, `projects/purpur-ignis/engineering.md`, and the relevant Kaneo tickets with subject prefix `[purpur-ignis]`. Follow the parent workspace instructions. Do not create a local knowledge/design bundle.
|
||||
|
||||
Use Java 25 and the declared Purpur API. Test behavior first with meaningful failures, implement minimally, then run `./gradlew clean check jar`. Never mark server/client behavior verified through domain tests alone.
|
||||
|
||||
Follow the full [development process](https://git.garvis.dev/dmg/somc-okf/src/branch/main/processes/development.md) and [development cycle](https://git.garvis.dev/dmg/somc-okf/src/branch/main/runbooks/development-cycle.md) for approvals, test-first/full-suite verification, required Gitea PR checks/reviews and merge, and merged-tree verification before ticket completion. Never push source `main` directly.
|
||||
|
||||
Source/wiki publication, local-only GitOps preparation, and explicitly approved production release are separate boundaries. Never push GitOps during development. Do not expose credentials or place them in process arguments. Repository creation does not authorize credential provisioning.
|
||||
@@ -13,7 +13,7 @@ Requires Java 17+ to launch Gradle; the wrapper resolves a Java 25 toolchain for
|
||||
./gradlew clean jar -PreleaseVersion=0.1.0
|
||||
```
|
||||
|
||||
The runtime requires Java 25. New JARs are `build/libs/purpur-ignis-<version>.jar`. Targets Purpur `26.2.build.2618-stable` and requires **ProtocolLib 5.4.0** for Mask helmet projection. Neither server API nor ProtocolLib is bundled. Source builds do not require the private wiki.
|
||||
The runtime requires Java 25. New JARs are `build/libs/purpur-ignis-<version>.jar`. Targets Purpur `26.3.build.2651-stable` and requires **ProtocolLib 5.4.0** for Mask helmet projection. Neither server API nor ProtocolLib is bundled. Source builds do not require the private wiki.
|
||||
|
||||
## Knowledge and delivery
|
||||
|
||||
|
||||
+6
-6
@@ -17,10 +17,10 @@ tasks.withType<JavaCompile>().configureEach {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("org.purpurmc.purpur:purpur-api:26.2.build.2618-stable")
|
||||
compileOnly("org.purpurmc.purpur:purpur-api:26.3.build.2651-stable")
|
||||
compileOnly("net.dmulloy2:ProtocolLib:5.4.0")
|
||||
testImplementation("net.dmulloy2:ProtocolLib:5.4.0")
|
||||
testImplementation("org.purpurmc.purpur:purpur-api:26.2.build.2618-stable")
|
||||
testImplementation("org.purpurmc.purpur:purpur-api:26.3.build.2651-stable")
|
||||
testImplementation(platform("org.junit:junit-bom:5.13.4"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
testImplementation("org.mockito:mockito-core:5.18.0")
|
||||
@@ -36,24 +36,24 @@ tasks.test {
|
||||
// Native API/event/delivery regressions run without starting a listening server.
|
||||
val ignisRuntime = layout.buildDirectory.dir("ignis-runtime")
|
||||
val downloadIgnisRuntime = tasks.register("downloadIgnisRuntime") {
|
||||
val launcher = ignisRuntime.map { it.file("purpur-26.2-2618.jar") }
|
||||
val launcher = ignisRuntime.map { it.file("purpur-26.3-2651.jar") }
|
||||
outputs.file(launcher)
|
||||
doLast {
|
||||
val file = launcher.get().asFile
|
||||
file.parentFile.mkdirs()
|
||||
val connection = URI("https://api.purpurmc.org/v2/purpur/26.2/2618/download").toURL().openConnection()
|
||||
val connection = URI("https://api.purpurmc.org/v2/purpur/26.3/2651/download").toURL().openConnection()
|
||||
connection.connectTimeout = 30_000
|
||||
connection.readTimeout = 120_000
|
||||
val bytes = connection.getInputStream().use { it.readBytes() }
|
||||
val digest = MessageDigest.getInstance("SHA-256").digest(bytes).joinToString("") { "%02x".format(it) }
|
||||
check(digest == "4a32d046a118804d89ca74ba89b798c98f6d8d1f310c18077ac573597049de31") { "Purpur 2618 checksum mismatch" }
|
||||
check(digest == "0677c1febecfbc2e5b0a7f76abd647b70a1f38d3cbdf3dd6a82e401b4262a598") { "Purpur 2651 checksum mismatch" }
|
||||
file.writeBytes(bytes)
|
||||
}
|
||||
}
|
||||
val prepareIgnisRuntime = tasks.register<JavaExec>("prepareIgnisRuntime") {
|
||||
dependsOn(downloadIgnisRuntime)
|
||||
javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(25) }
|
||||
classpath = files(ignisRuntime.map { it.file("purpur-26.2-2618.jar") })
|
||||
classpath = files(ignisRuntime.map { it.file("purpur-26.3-2651.jar") })
|
||||
mainClass = "io.papermc.paperclip.Main"
|
||||
jvmArgs("-Dpaperclip.patchonly=true")
|
||||
workingDir(ignisRuntime)
|
||||
|
||||
@@ -29,9 +29,9 @@ class AnnouncementPluginLifecycleTest {
|
||||
var platform = mockStatic(Bukkit.class, call -> switch (call.getMethod().getName()) {
|
||||
case "getServer" -> server;
|
||||
case "isPrimaryThread" -> true;
|
||||
case "getVersion" -> "Purpur 2618 (MC: 26.2)";
|
||||
case "getMinecraftVersion" -> "26.2";
|
||||
case "getBukkitVersion" -> "26.2-R0.1-SNAPSHOT";
|
||||
case "getVersion" -> "Purpur 2651 (MC: 26.3)";
|
||||
case "getMinecraftVersion" -> "26.3";
|
||||
case "getBukkitVersion" -> "26.3-R0.1-SNAPSHOT";
|
||||
default -> call.callRealMethod();
|
||||
})) {
|
||||
var packets = new ArrayList<PacketListener>();
|
||||
|
||||
@@ -187,6 +187,8 @@ final class NativeDaggerFixture implements AutoCloseable {
|
||||
doCallRealMethod().when(nativePlayer).getBoundingBox();
|
||||
when(nativePlayer.getDeltaMovement()).thenReturn(Vec3.ZERO);
|
||||
when(nativePlayer.getKnownMovement()).thenReturn(Vec3.ZERO);
|
||||
when(nativePlayer.getClientPositionAndRotation()).thenAnswer(call ->
|
||||
net.minecraft.core.PositionAndRotation.of(nativePlayer.position(), nativePlayer.getYRot(), nativePlayer.getXRot()));
|
||||
doCallRealMethod().when(nativePlayer).setPos(anyDouble(), anyDouble(), anyDouble());
|
||||
doCallRealMethod().when(nativePlayer).setBoundingBox(any(net.minecraft.world.phys.AABB.class));
|
||||
doCallRealMethod().when(nativePlayer).setPosRaw(anyDouble(), anyDouble(), anyDouble());
|
||||
@@ -273,8 +275,10 @@ final class NativeDaggerFixture implements AutoCloseable {
|
||||
var key = net.minecraft.resources.ResourceKey.<net.minecraft.world.item.crafting.Recipe<?>>create(
|
||||
net.minecraft.core.registries.Registries.RECIPE, net.minecraft.resources.Identifier.fromNamespaceAndPath("spigotignis", "dagger"));
|
||||
var holder = new net.minecraft.world.item.crafting.RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe>(key, nativeRecipe);
|
||||
var recipes = new net.minecraft.world.item.crafting.RecipeManager(org.bukkit.craftbukkit.CraftRegistry.getMinecraftRegistry());
|
||||
recipes.recipes = net.minecraft.world.item.crafting.RecipeMap.create(List.of(holder));
|
||||
var recipes = NativeRuntime.recipes();
|
||||
recipes.recipes = net.minecraft.world.item.crafting.RecipeMap.createEmpty();
|
||||
recipes.recipes.addRecipe(holder);
|
||||
recipes.finalizeRecipeLoading(net.minecraft.world.flag.FeatureFlags.VANILLA_SET);
|
||||
when(level.recipeAccess()).thenReturn(recipes);
|
||||
menu.craftSlots.setCurrentRecipe(holder);
|
||||
menu.resultSlots.setRecipeUsed(holder);
|
||||
|
||||
@@ -51,9 +51,9 @@ class NativeEchoAnnouncementTest {
|
||||
try (var platform = mockStatic(Bukkit.class, call -> switch (call.getMethod().getName()) {
|
||||
case "getServer" -> server;
|
||||
case "isPrimaryThread" -> true;
|
||||
case "getVersion" -> "Purpur 2618 (MC: 26.2)";
|
||||
case "getMinecraftVersion" -> "26.2";
|
||||
case "getBukkitVersion" -> "26.2-R0.1-SNAPSHOT";
|
||||
case "getVersion" -> "Purpur 2651 (MC: 26.3)";
|
||||
case "getMinecraftVersion" -> "26.3";
|
||||
case "getBukkitVersion" -> "26.3-R0.1-SNAPSHOT";
|
||||
default -> call.callRealMethod();
|
||||
})) {
|
||||
UUID owner = UUID.randomUUID(), receipt = UUID.randomUUID();
|
||||
|
||||
@@ -21,6 +21,7 @@ import net.minecraft.server.permissions.LevelBasedPermissionSet;
|
||||
import net.minecraft.tags.TagLoader;
|
||||
import net.minecraft.util.Util;
|
||||
import net.minecraft.world.flag.FeatureFlags;
|
||||
import net.minecraft.world.item.crafting.RecipeManager;
|
||||
import net.minecraft.world.level.DataPackConfig;
|
||||
import net.minecraft.world.level.WorldDataConfiguration;
|
||||
import org.bukkit.craftbukkit.CraftRegistry;
|
||||
@@ -29,6 +30,7 @@ import org.bukkit.craftbukkit.CraftRegistry;
|
||||
final class NativeRuntime {
|
||||
private NativeRuntime() { }
|
||||
private static boolean ready;
|
||||
private static RecipeManager recipes;
|
||||
static synchronized void bootstrap() throws Exception {
|
||||
if (ready) { return; }
|
||||
SharedConstants.tryDetectVersion();
|
||||
@@ -40,12 +42,12 @@ final class NativeRuntime {
|
||||
try (var resources = new MultiPackResourceManager(PackType.SERVER_DATA, packs.openAllSelected())) {
|
||||
LayeredRegistryAccess<RegistryLayer> layers = RegistryLayer.createRegistryAccess();
|
||||
List<Registry.PendingTags<?>> tags = TagLoader.loadTagsForExistingRegistries(resources, layers.getLayer(RegistryLayer.STATIC));
|
||||
List<HolderLookup.RegistryLookup<?>> lookups = TagLoader.buildUpdatedLookups(layers.getAccessForLoading(RegistryLayer.WORLDGEN), tags);
|
||||
RegistryAccess.Frozen worldgen = RegistryDataLoader.load(resources, lookups,
|
||||
RegistryDataLoader.WORLDGEN_REGISTRIES, Util.backgroundExecutor()).join();
|
||||
layers = layers.replaceFrom(RegistryLayer.WORLDGEN, worldgen);
|
||||
List<HolderLookup.RegistryLookup<?>> lookups = TagLoader.buildUpdatedLookups(layers.getAccessForLoading(RegistryLayer.WORLD), tags);
|
||||
RegistryAccess.Frozen world = RegistryDataLoader.load(resources, lookups,
|
||||
RegistryDataLoader.WORLD_REGISTRIES, Util.backgroundExecutor()).join();
|
||||
layers = layers.replaceFrom(RegistryLayer.WORLD, world);
|
||||
RegistryAccess.Frozen dimensions = RegistryDataLoader.load(resources,
|
||||
Stream.concat(lookups.stream(), worldgen.listRegistries()).toList(),
|
||||
Stream.concat(lookups.stream(), world.listRegistries()).toList(),
|
||||
RegistryDataLoader.DIMENSION_REGISTRIES, Util.backgroundExecutor()).join();
|
||||
layers = layers.replaceFrom(RegistryLayer.DIMENSIONS, dimensions);
|
||||
Class.forName(org.bukkit.Registry.class.getName());
|
||||
@@ -54,7 +56,16 @@ final class NativeRuntime {
|
||||
Util.backgroundExecutor(), Runnable::run).join();
|
||||
datapack.updateComponentsAndStaticRegistryTags();
|
||||
CraftRegistry.setMinecraftRegistry(layers.compositeAccess().freeze());
|
||||
recipes = datapack.getRecipeManager();
|
||||
recipes.finalizeRecipeLoading(flags);
|
||||
}
|
||||
ready = true;
|
||||
}
|
||||
|
||||
static RecipeManager recipes() {
|
||||
if (!ready) {
|
||||
throw new IllegalStateException("Native runtime has not been bootstrapped");
|
||||
}
|
||||
return recipes;
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,9 @@ class PurpurDistributionTest {
|
||||
assertEquals(version, metadata.get("version"));
|
||||
assertEquals("SpigotIgnis", metadata.get("name"), "Retain the data directory and persistent namespace");
|
||||
assertEquals("games.dmg.spigotignis.SpigotIgnisPlugin", metadata.get("main"));
|
||||
assertEquals("1.21", metadata.get("api-version"));
|
||||
assertTrue(((Map<?, ?>) metadata.get("commands")).keySet().containsAll(
|
||||
java.util.Set.of("ignis", "ignisadmin")));
|
||||
assertTrue(((Map<?, ?>) metadata.get("permissions")).containsKey("ignis.admin"));
|
||||
assertEquals(java.util.List.of("ProtocolLib"), metadata.get("depend"));
|
||||
var descriptor = new org.bukkit.plugin.PluginDescriptionFile(jar.getInputStream(jar.getJarEntry("plugin.yml")));
|
||||
@@ -42,10 +45,18 @@ class PurpurDistributionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void releaseAndCiAgreeWithThePurpurDistributionNameAndToolchain() throws Exception {
|
||||
void releaseAndCiAgreeWithThePurpur26_3DistributionAndToolchain() throws Exception {
|
||||
String build = Files.readString(Path.of("build.gradle.kts"));
|
||||
assertTrue(build.contains("purpur-api:26.2.build.2618-stable"));
|
||||
assertTrue(build.contains("purpur-api:26.3.build.2651-stable"));
|
||||
assertTrue(build.contains("purpur-26.3-2651.jar"));
|
||||
assertTrue(build.contains("https://api.purpurmc.org/v2/purpur/26.3/2651/download"));
|
||||
assertTrue(build.contains("0677c1febecfbc2e5b0a7f76abd647b70a1f38d3cbdf3dd6a82e401b4262a598"));
|
||||
assertTrue(build.contains("net.dmulloy2:ProtocolLib:5.4.0"));
|
||||
assertFalse(build.contains("26.2") || build.contains("2618"));
|
||||
assertFalse(build.contains("org.spigotmc:spigot-api"));
|
||||
String readme = Files.readString(Path.of("README.md"));
|
||||
assertTrue(readme.contains("Purpur `26.3.build.2651-stable`"));
|
||||
assertFalse(readme.contains("26.2") || readme.contains("2618"));
|
||||
for (String workflow : new String[] {"ci.yml", "release.yml"}) {
|
||||
String content = Files.readString(Path.of(".gitea/workflows", workflow));
|
||||
assertTrue(content.contains("java-version: 25"));
|
||||
|
||||
Reference in new issue
Block a user