diff --git a/CANAPLIA.md b/CANAPLIA.md new file mode 100644 index 0000000..f958c74 --- /dev/null +++ b/CANAPLIA.md @@ -0,0 +1,51 @@ +# Migration de Canaplia + +Ce document fixe le contrat commun des tickets [CANA-01 à CANA-09](https://git.botsu.net/koka/sanctuary/issues?state=open). Il permet à plusieurs agents de travailler sur des branches séparées sans recréer un onzième propriétaire de données. + +## Architecture arrêtée par CANA-01 + +Canaplia reste un namespace de compatibilité, pas un onzième sous-projet Gradle. + +| Responsabilité | Propriétaire | Contrat | +|---|---|---| +| cultures, variantes, récolte, compostage, résine et effets de gameplay | It's Alive | classes sous `fr.koka99cab.sanctuary26.itsalive.canaplia`, registres historiques `canaplia:*` | +| composition visuelle et accessibilité | Ambiance | `AmbianceVisualEffectApi` et `AmbianceVisualProfile` | +| progression et découvertes | Sanctuary | API publique de progression, sans copie des données de culture | +| climat et biomes | Another World / Ambiance | API publique du propriétaire, sans dépendance inverse | + +It's Alive fournit l'alias Fabric `canaplia` et dépend explicitement d'Ambiance. Ambiance ne dépend pas d'It's Alive : cette direction unique garde le graphe acyclique. Les trois payloads historiques `canaplia:hemp_visual_sync`, `canaplia:hemp_bad_trip` et `canaplia:hemp_visual_clear` sont réservés. Leur enregistrement est différé aux tickets CANA-07/CANA-08 afin d'ajouter un protocole versionné et borné une seule fois. + +Le catalogue `CanapliaLegacyCatalog` réserve trois blocs, un type de block entity, 64 items, seize recettes, trois loot tables, un SavedData et les seize clés de variante. CANA-01 ne les enregistre pas encore : CANA-02 possède l'enregistrement des familles de contenu. + +## Coordination des branches + +Chaque agent repart de `main` après fusion de CANA-01 et crée une branche limitée à un ticket : + +| Ticket | Branche suggérée | Peut commencer après | +|---|---|---| +| [#36 CANA-02](https://git.botsu.net/koka/sanctuary/issues/36) | `feature/canaplia-varieties` | CANA-01 | +| [#37 CANA-03](https://git.botsu.net/koka/sanctuary/issues/37) | `feature/canaplia-climate-discovery` | CANA-02 | +| [#38 CANA-04](https://git.botsu.net/koka/sanctuary/issues/38) | `feature/canaplia-hybridization` | CANA-02 et CANA-03 | +| [#39 CANA-05](https://git.botsu.net/koka/sanctuary/issues/39) | `feature/canaplia-dimensions` | CANA-02 et contrat CANA-03 | +| [#40 CANA-06](https://git.botsu.net/koka/sanctuary/issues/40) | `feature/canaplia-lunar-harvest` | CANA-02, CANA-04 et API Ambiance | +| [#41 CANA-07](https://git.botsu.net/koka/sanctuary/issues/41) | `feature/canaplia-server-effects` | CANA-01 et CANA-02 | +| [#42 CANA-08](https://git.botsu.net/koka/sanctuary/issues/42) | `feature/canaplia-visual-effects` | CANA-07 et API Ambiance | +| [#43 CANA-09](https://git.botsu.net/koka/sanctuary/issues/43) | `feature/canaplia-codex` | CANA-02 à CANA-06 et API Sanctuary | + +CANA-03 et CANA-07 peuvent avancer en parallèle après CANA-02. CANA-04 et CANA-05 peuvent ensuite avancer en parallèle si leurs changements partagés dans le catalogue sont annoncés dans les pull requests. + +Règles communes : + +- conserver les clés `common`, `tender`, `olive`, `dark`, `ashen`, `moss`, `gold`, `amber`, `bluish`, `pink`, `coral`, `purple`, `indigo`, `varieg`, `lunar` et `ember` ; +- produire les chemins d'items via `CanapliaVariantKey.legacyItemPath` afin que `common` conserve les quatre IDs sans préfixe ; +- ne pas ajouter `canaplia` à `settings.gradle` et ne pas créer de second stockage, shader ou protocole ; +- faire passer toute présentation par l'API Ambiance et toute progression par l'API Sanctuary ; +- signaler dans la pull request toute modification de `build.gradle`, `gradle.properties`, d'un manifest ou du catalogue partagé. + +## Licence et provenance + +La source historique `Farming/canaplia` version 2.0.0 déclare **All Rights Reserved**, copyright KOKA99CAB. CANA-01 n'en copie aucun fichier Java, texture, modèle, son ou JSON : seuls les identifiants et formes de données nécessaires à la compatibilité sont inventoriés. Tout le code et la documentation nouveaux de Sanctuary restent sous `GPL-3.0-or-later`. + +Un ticket ultérieur qui souhaite importer un fichier historique doit obtenir une autorisation explicite du détenteur KOKA99CAB pour le redistribuer sous la licence du dépôt, puis consigner le chemin source et le chemin cible. Sans cette autorisation, le code doit être réimplémenté et les ressources recréées sans copier les fichiers réservés. + +L'inventaire machine complet, y compris les IDs de sauvegarde et de payloads, est conservé dans `pack/migrations/canaplia-foundation-alpha32.json`. diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e84806..56e292f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ Ce journal suit les **versions du modpack**. Lorsqu'un seul module change, sa ve - Depuis `26.2.0-alpha.6`, le pack et les modules ont des versions indépendantes. I Like To Move It et Only Fun deviennent les cinquième et sixième modules actifs dans l'alpha 46. - Les versions ci-dessous ont été reconstituées à partir des JAR archivés, des packs locaux et du code source actuel. Les dates n'étant pas enregistrées de manière fiable, elles ne sont pas inventées. +## En développement — fondations Canaplia + +Modules modifiés : **It's Alive `0.0.0-alpha.32`** et **Ambiance `0.0.0-alpha.13`**. Le pack distribué reste en **`26.2.0-alpha.207`** jusqu'à la prochaine release. + +- fait fournir l'alias Fabric `canaplia` par It's Alive sans créer un onzième sous-projet ni un second propriétaire de données ; +- réserve les seize clés historiques, trois blocs, un type de block entity, 64 items, seize recettes, trois loot tables, un SavedData et trois payloads ; +- fixe une dépendance explicite et acyclique d'It's Alive vers l'API publique d'Ambiance ; +- ajoute à Ambiance un profil visuel générique dont les durées, couleurs et mouvements sont bornés, avec réduction des mouvements ; +- documente la licence All Rights Reserved de la source 2.0.0 et n'en copie aucun code ni asset dans cette fondation GPL ; +- publie le découpage des tickets CANA-02 à CANA-09 afin qu'ils puissent avancer sur des branches séparées après leurs dépendances. + ## `26.2.0-alpha.207` Module modifié : **Another World `0.0.0-alpha.55`**. Les neuf autres modules conservent leur version issue de l’alpha.206. diff --git a/README.md b/README.md index 798d288..21788d1 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ Licence : **GNU GPL 3.0 or Later** (`GPL-3.0-or-later`) | Fabric ID | Nom public | Responsabilité | État | |---|---|---|---| | `anotherworld` | Another World | choses matérielles | matière, photographie, instruments météo et 108 biomes natifs | -| `itsalive` | It's Alive ! | vivant | Creepers, créatures et agriculture (20 cultures, vergers, café et vanille) | -| `ambiance` | Ambiance | perception | atmosphère, météo, lumières dynamiques, temps réel et calendrier minecraftien | +| `itsalive` | It's Alive ! | vivant | Creepers, créatures, agriculture et fondations biologiques de Canaplia | +| `ambiance` | Ambiance | perception | atmosphère, météo, lumières dynamiques, temps réel, calendrier et contrat visuel partagé | | `redstoner` | Red-Stoner | calcul et signaux | ordinateurs, identité serveur, Drawer et Particuleur | | `iliketomoveit` | I Like To Move It | déplacement | portails, véhicules, grappin, poulies et waystones locaux | | `ouch` | Ouch | combat et destruction | arsenal actif (6 armes) et TNT météo | @@ -24,6 +24,8 @@ Les descriptions FR/EN/RU sont déclarées dans chaque `fabric.mod.json` et dans Version courante du pack distribué : **`26.2.0-alpha.207`**. Cette release distribue Another World **`0.0.0-alpha.55`** avec les soldes 64 bits pratiquement illimités du coffre-fort. +Dans le workspace en développement, [CANA-01](CANAPLIA.md) porte les fondations de Canaplia vers Minecraft 26.2. It's Alive **`0.0.0-alpha.32`** fournit l'alias `canaplia`, réserve les seize clés et les IDs historiques, tandis qu'Ambiance **`0.0.0-alpha.13`** expose un contrat visuel borné. Aucun contenu incomplet ni ancien payload non versionné n'est encore enregistré. + L’alpha.207 rend le stockage du coffre-fort pratiquement illimité : rubis, saphirs et émeraudes utilisent chacun un solde 64 bits. Les 27 anciennes piles sont additionnées automatiquement lors du chargement, le Shop continue de consommer directement ces soldes et les 20 % pillés sont crédités dans le coffre-fort de l’attaquant sans créer des millions d’entités ou de piles d’objets. L’alpha.206 exige une coque creuse de **3 × 3 × 3**, soit 26 Safe Blocks appartenant au même joueur, et conserve JEI en **`30.27.0.189`**. Le centre est libre et peut contenir un bloc caché. Les quatre images 48×48 se répartissent sur les faces extérieures du cube : façade, dessus, dessous et côtés/arrière. Les colonnes extrêmes de la façade et du dos sont désormais orientées correctement. Un ancien mur 3 × 3 ne s’active plus et doit être démonté puis reconstruit ; les comptes et soldes existants restent conservés. diff --git a/ambiance/build.gradle b/ambiance/build.gradle index 8485148..d1eb415 100644 --- a/ambiance/build.gradle +++ b/ambiance/build.gradle @@ -5,6 +5,14 @@ dependencies { compileOnly "maven.modrinth:YL57xq9U:oaD6KQls" } +tasks.register("ambianceVisualApiSmoke", JavaExec) { + group = "verification" + description = "Checks bounded visual-effect requests exposed to gameplay modules." + dependsOn tasks.named("testClasses") + classpath = sourceSets.test.runtimeClasspath + mainClass = "fr.koka99cab.sanctuary26.ambiance.api.visual.AmbianceVisualApiSmoke" +} + tasks.register("ambianceScheduleSmoke", JavaExec) { group = "verification" description = "Checks deterministic daily weather and the native eight-day lunar schedule." @@ -61,7 +69,7 @@ tasks.register("verifyAmbiance") { [(language): new JsonSlurper().parse(file("src/main/resources/assets/realtime/lang/${language}.json"))] } - if (project.version.toString() != "0.0.0-alpha.12" + if (project.version.toString() != "0.0.0-alpha.13" || migration.source?.pack_version != "26.2.0-alpha.125" || migration.target?.pack_version != "26.2.0-alpha.126" || migration.target?.modules?.ambiance != "0.0.0-alpha.1" @@ -304,4 +312,5 @@ tasks.register("verifyAmbiance") { tasks.named("check") { dependsOn tasks.named("verifyAmbiance") dependsOn tasks.named("ambianceScheduleSmoke") + dependsOn tasks.named("ambianceVisualApiSmoke") } diff --git a/ambiance/src/main/java/fr/koka99cab/sanctuary26/ambiance/api/visual/AmbianceVisualEffectApi.java b/ambiance/src/main/java/fr/koka99cab/sanctuary26/ambiance/api/visual/AmbianceVisualEffectApi.java new file mode 100644 index 0000000..af49e5e --- /dev/null +++ b/ambiance/src/main/java/fr/koka99cab/sanctuary26/ambiance/api/visual/AmbianceVisualEffectApi.java @@ -0,0 +1,38 @@ +package fr.koka99cab.sanctuary26.ambiance.api.visual; + +import java.util.Objects; +import net.minecraft.resources.Identifier; + +/** + * Public boundary used by gameplay modules that ask Ambiance to compose a + * temporary visual treatment. Networking and rendering remain Ambiance-owned. + */ +public final class AmbianceVisualEffectApi { + public static final int CONTRACT_VERSION = 1; + public static final int MAX_DURATION_TICKS = 10 * 60 * 20; + + private AmbianceVisualEffectApi() { + } + + public static Request request(Identifier source, AmbianceVisualProfile profile, int remainingTicks) { + return new Request(source, profile, remainingTicks); + } + + public record Request(Identifier source, AmbianceVisualProfile profile, int remainingTicks) { + public Request { + Objects.requireNonNull(source, "source"); + Objects.requireNonNull(profile, "profile"); + remainingTicks = Math.max(0, Math.min(MAX_DURATION_TICKS, remainingTicks)); + } + + public boolean expired() { + return this.remainingTicks == 0; + } + + public Request withReducedMotion(boolean reduceMotion) { + return reduceMotion + ? new Request(this.source, this.profile.withoutMotion(), this.remainingTicks) + : this; + } + } +} diff --git a/ambiance/src/main/java/fr/koka99cab/sanctuary26/ambiance/api/visual/AmbianceVisualProfile.java b/ambiance/src/main/java/fr/koka99cab/sanctuary26/ambiance/api/visual/AmbianceVisualProfile.java new file mode 100644 index 0000000..a04e01c --- /dev/null +++ b/ambiance/src/main/java/fr/koka99cab/sanctuary26/ambiance/api/visual/AmbianceVisualProfile.java @@ -0,0 +1,81 @@ +package fr.koka99cab.sanctuary26.ambiance.api.visual; + +/** + * Renderer-independent color and motion parameters composed by Ambiance. + * + *
Every value is finite and bounded at construction time so gameplay + * modules cannot send an invasive or unbounded profile to the client.
+ */ +public record AmbianceVisualProfile( + float intensity, + float tintRed, + float tintGreen, + float tintBlue, + float tintStrength, + float saturationDelta, + float contrastDelta, + float vignetteStrength, + float ghostingStrength, + float wobbleStrength, + float pulseStrength +) { + public static final AmbianceVisualProfile NONE = new AmbianceVisualProfile( + 0.0F, + 1.0F, + 1.0F, + 1.0F, + 0.0F, + 0.0F, + 0.0F, + 0.0F, + 0.0F, + 0.0F, + 0.0F + ); + + public AmbianceVisualProfile { + intensity = unit("intensity", intensity); + tintRed = unit("tintRed", tintRed); + tintGreen = unit("tintGreen", tintGreen); + tintBlue = unit("tintBlue", tintBlue); + tintStrength = unit("tintStrength", tintStrength); + saturationDelta = signedUnit("saturationDelta", saturationDelta); + contrastDelta = signedUnit("contrastDelta", contrastDelta); + vignetteStrength = unit("vignetteStrength", vignetteStrength); + ghostingStrength = unit("ghostingStrength", ghostingStrength); + wobbleStrength = unit("wobbleStrength", wobbleStrength); + pulseStrength = unit("pulseStrength", pulseStrength); + } + + /** Removes image motion while preserving the bounded color treatment. */ + public AmbianceVisualProfile withoutMotion() { + return new AmbianceVisualProfile( + this.intensity, + this.tintRed, + this.tintGreen, + this.tintBlue, + this.tintStrength, + this.saturationDelta, + this.contrastDelta, + this.vignetteStrength, + 0.0F, + 0.0F, + 0.0F + ); + } + + private static float unit(String field, float value) { + return bounded(field, value, 0.0F, 1.0F); + } + + private static float signedUnit(String field, float value) { + return bounded(field, value, -1.0F, 1.0F); + } + + private static float bounded(String field, float value, float minimum, float maximum) { + if (!Float.isFinite(value)) { + throw new IllegalArgumentException(field + " must be finite"); + } + return Math.max(minimum, Math.min(maximum, value)); + } +} diff --git a/ambiance/src/main/resources/fabric.mod.json b/ambiance/src/main/resources/fabric.mod.json index 2052ce5..a42a833 100644 --- a/ambiance/src/main/resources/fabric.mod.json +++ b/ambiance/src/main/resources/fabric.mod.json @@ -3,7 +3,7 @@ "id": "ambiance", "version": "${version}", "name": "Ambiance", - "description": "Owns world atmosphere, dynamic held lights, real time and the Minecraft calendar.", + "description": "Owns world atmosphere, dynamic held lights, real time, the Minecraft calendar and shared visual-effect contracts.", "authors": [ "KOKA99CAB" ], @@ -40,9 +40,9 @@ "lifecycle": "${lifecycle}", "pack_version": "${pack_version}", "descriptions": { - "fr_fr": "Gère l'atmosphère, les lumières dynamiques, le temps réel et le calendrier minecraftien.", - "en_us": "Owns world atmosphere, dynamic lights, real time and the Minecraft calendar.", - "ru_ru": "Управляет атмосферой, динамическим освещением, реальным временем и календарём Minecraft." + "fr_fr": "Gère l'atmosphère, les lumières dynamiques, le temps réel, le calendrier minecraftien et les contrats d'effets visuels partagés.", + "en_us": "Owns world atmosphere, dynamic lights, real time, the Minecraft calendar and shared visual-effect contracts.", + "ru_ru": "Управляет атмосферой, динамическим освещением, реальным временем, календарём Minecraft и общими контрактами визуальных эффектов." } } } diff --git a/ambiance/src/test/java/fr/koka99cab/sanctuary26/ambiance/api/visual/AmbianceVisualApiSmoke.java b/ambiance/src/test/java/fr/koka99cab/sanctuary26/ambiance/api/visual/AmbianceVisualApiSmoke.java new file mode 100644 index 0000000..4954c8d --- /dev/null +++ b/ambiance/src/test/java/fr/koka99cab/sanctuary26/ambiance/api/visual/AmbianceVisualApiSmoke.java @@ -0,0 +1,61 @@ +package fr.koka99cab.sanctuary26.ambiance.api.visual; + +import net.minecraft.resources.Identifier; + +public final class AmbianceVisualApiSmoke { + private AmbianceVisualApiSmoke() { + } + + public static void main(String[] arguments) { + AmbianceVisualProfile bounded = new AmbianceVisualProfile( + 2.0F, + -1.0F, + 0.5F, + 4.0F, + 2.0F, + -3.0F, + 3.0F, + 2.0F, + 0.4F, + 0.3F, + 0.2F + ); + assertEquals(1.0F, bounded.intensity(), "intensity upper bound"); + assertEquals(0.0F, bounded.tintRed(), "red lower bound"); + assertEquals(1.0F, bounded.tintBlue(), "blue upper bound"); + assertEquals(-1.0F, bounded.saturationDelta(), "saturation lower bound"); + assertEquals(1.0F, bounded.contrastDelta(), "contrast upper bound"); + + AmbianceVisualEffectApi.Request request = AmbianceVisualEffectApi.request( + Identifier.fromNamespaceAndPath("canaplia", "hemp_visual_sync"), + bounded, + Integer.MAX_VALUE + ); + assertEquals(AmbianceVisualEffectApi.MAX_DURATION_TICKS, request.remainingTicks(), "duration bound"); + AmbianceVisualProfile reduced = request.withReducedMotion(true).profile(); + assertEquals(0.0F, reduced.ghostingStrength(), "reduced ghosting"); + assertEquals(0.0F, reduced.wobbleStrength(), "reduced wobble"); + assertEquals(0.0F, reduced.pulseStrength(), "reduced pulse"); + + try { + new AmbianceVisualProfile( + Float.NaN, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F + ); + throw new AssertionError("non-finite profile value accepted"); + } catch (IllegalArgumentException expected) { + // Expected. + } + } + + private static void assertEquals(float expected, float actual, String label) { + if (Float.compare(expected, actual) != 0) { + throw new AssertionError(label + ": expected " + expected + ", got " + actual); + } + } + + private static void assertEquals(int expected, int actual, String label) { + if (expected != actual) { + throw new AssertionError(label + ": expected " + expected + ", got " + actual); + } + } +} diff --git a/build.gradle b/build.gradle index ab23c52..45f744d 100644 --- a/build.gradle +++ b/build.gradle @@ -3580,6 +3580,7 @@ project(":masterkey") { project(":itsalive") { dependencies { implementation project(":anotherworld") + implementation project(":ambiance") } } @@ -4169,7 +4170,7 @@ tasks.register("verifyCuisineCropsRelease") { def rice = file("itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/RiceCropBlock.java").text def bean = file("itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/BeanCropBlock.java").text if (rootProject.pack_version != "26.2.0-alpha.207" - || rootProject.itsalive_version != "0.0.0-alpha.31" + || rootProject.itsalive_version != "0.0.0-alpha.32" || release.pack_version != rootProject.pack_version || index.versionId != rootProject.pack_version || migration.source?.pack_version != "26.2.0-alpha.193" || migration.source?.modules != [itsalive: "0.0.0-alpha.26"] @@ -4310,7 +4311,7 @@ tasks.register("verifyQuestRequestsCauldronBatchesRelease") { def packwiz = file("pack/packwiz/pack.toml").text def sanctuaryManifest = new JsonSlurper().parse(file("sanctuary/src/main/resources/fabric.mod.json")) if (rootProject.pack_version != "26.2.0-alpha.207" - || rootProject.itsalive_version != "0.0.0-alpha.31" + || rootProject.itsalive_version != "0.0.0-alpha.32" || rootProject.sanctuary_version != "0.0.0-alpha.117" || migration.source?.pack_version != "26.2.0-alpha.196" || migration.target?.pack_version != "26.2.0-alpha.197" @@ -4360,7 +4361,7 @@ tasks.register("verifyFermentationBarrelAutomationRelease") { def packwizIndex = file("pack/packwiz/index.toml").text def itsAliveManifest = new JsonSlurper().parse(file("itsalive/src/main/resources/fabric.mod.json")) if (rootProject.pack_version != "26.2.0-alpha.207" - || rootProject.itsalive_version != "0.0.0-alpha.31" + || rootProject.itsalive_version != "0.0.0-alpha.32" || migration.source?.pack_version != "26.2.0-alpha.197" || migration.target?.pack_version != "26.2.0-alpha.198" || migration.target?.modules != [itsalive: "0.0.0-alpha.30", sanctuary: "0.0.0-alpha.114"] @@ -4410,7 +4411,7 @@ tasks.register("verifyQuestRequestContractsRelease") { def packwizIndex = file("pack/packwiz/index.toml").text def sanctuaryManifest = new JsonSlurper().parse(file("sanctuary/src/main/resources/fabric.mod.json")) if (rootProject.pack_version != "26.2.0-alpha.207" - || rootProject.itsalive_version != "0.0.0-alpha.31" + || rootProject.itsalive_version != "0.0.0-alpha.32" || rootProject.sanctuary_version != "0.0.0-alpha.117" || migration.source?.pack_version != "26.2.0-alpha.198" || migration.source?.modules?.sanctuary != "0.0.0-alpha.114" @@ -4465,7 +4466,7 @@ tasks.register("verifyWeatherTntAmbianceRelease") { def ouchManifest = new JsonSlurper().parse(file("ouch/src/main/resources/fabric.mod.json")) def rootBuild = file("build.gradle").text if (rootProject.pack_version != "26.2.0-alpha.207" - || rootProject.ambiance_version != "0.0.0-alpha.12" + || rootProject.ambiance_version != "0.0.0-alpha.13" || rootProject.ouch_version != "0.0.0-alpha.9" || migration.source?.pack_version != "26.2.0-alpha.199" || migration.source?.modules != [ambiance: "0.0.0-alpha.11", ouch: "0.0.0-alpha.8"] @@ -4586,7 +4587,7 @@ tasks.register("verifyWaystoneShopCulinaryAlpha202Release") { "sanctuary/src/main/resources/fabric.mod.json")) if (rootProject.pack_version != "26.2.0-alpha.207" || rootProject.anotherworld_version != "0.0.0-alpha.55" - || rootProject.itsalive_version != "0.0.0-alpha.31" + || rootProject.itsalive_version != "0.0.0-alpha.32" || rootProject.iliketomoveit_version != "0.0.0-alpha.14" || rootProject.sanctuary_version != "0.0.0-alpha.117" || migration.source?.pack_version != "26.2.0-alpha.201" @@ -5189,6 +5190,112 @@ tasks.register("verifySafeVaultUnlimitedAlpha207Release") { } } +tasks.register("verifyCanapliaFoundation") { + group = "verification" + description = "Checks CANA-01 ownership, legacy IDs, licensing and inter-module boundaries." + inputs.file(file("CANAPLIA.md")) + inputs.file(file("pack/migrations/canaplia-foundation-alpha32.json")) + inputs.file(file("itsalive/src/main/resources/fabric.mod.json")) + inputs.file(file("ambiance/src/main/resources/fabric.mod.json")) + inputs.files(fileTree("itsalive/src/main/resources/assets/canaplia/lang") { include "*.json" }) + inputs.files(fileTree("itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/canaplia") { include "*.java" }) + inputs.files(fileTree("ambiance/src/main/java/fr/koka99cab/sanctuary26/ambiance/api/visual") { include "*.java" }) + doLast { + def migration = new JsonSlurper().parse(file("pack/migrations/canaplia-foundation-alpha32.json")) + def itsAliveManifest = new JsonSlurper().parse(file("itsalive/src/main/resources/fabric.mod.json")) + def ambianceManifest = new JsonSlurper().parse(file("ambiance/src/main/resources/fabric.mod.json")) + def documentation = file("CANAPLIA.md").text + def rootBuild = file("build.gradle").text + def settingsText = file("settings.gradle").text + def itsAliveProjectStart = rootBuild.indexOf('project(":itsalive")') + def itsAliveDependencies = rootBuild.substring( + itsAliveProjectStart, + rootBuild.indexOf('\nproject(":anotherworld") {', itsAliveProjectStart) + ) + def expectedVariants = [ + "common", "tender", "olive", "dark", "ashen", "moss", "gold", "amber", + "bluish", "pink", "coral", "purple", "indigo", "varieg", "lunar", "ember" + ] + def expectedFamilies = ["hemp_seeds", "hemp_bud", "resin_block", "rolled_joint"] + def expectedItems = expectedVariants.collectMany { variant -> + expectedFamilies.collect { family -> + "canaplia:" + (variant == "common" ? family : "${variant}_${family}") + } + } + def expectedRecipes = expectedVariants.collect { variant -> + "canaplia:" + (variant == "common" ? "rolled_joint" : "${variant}_rolled_joint") + } + def languages = ["fr_fr", "en_us", "ru_ru"].collectEntries { locale -> + [(locale): new JsonSlurper().parse(file( + "itsalive/src/main/resources/assets/canaplia/lang/${locale}.json"))] + } + def catalogSource = file( + "itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaLegacyCatalog.java").text + def initializerSource = file( + "itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/ItsAliveMod.java").text + def visualApiSource = file( + "ambiance/src/main/java/fr/koka99cab/sanctuary26/ambiance/api/visual/AmbianceVisualEffectApi.java").text + if (rootProject.pack_version != "26.2.0-alpha.207" + || rootProject.itsalive_version != "0.0.0-alpha.32" + || rootProject.ambiance_version != "0.0.0-alpha.13" + || migration.issue != 35 + || migration.source?.fabric_id != "canaplia" + || migration.source?.version != "2.0.0" + || migration.source?.minecraft != "26.1.2" + || migration.source?.license != "All Rights Reserved" + || migration.source?.copyright_holder != "KOKA99CAB" + || migration.target?.pack_version != "26.2.0-alpha.207" + || migration.target?.modules != [itsalive: "0.0.0-alpha.32", ambiance: "0.0.0-alpha.13"] + || migration.target?.standalone_module_created != false + || migration.target?.legacy_mod_id_provided_by != "itsalive" + || migration.ownership?.living_and_agriculture != "itsalive" + || migration.ownership?.visual_composition_and_accessibility != "ambiance" + || migration.ownership?.dependency_cycle_introduced != false + || migration.variants != expectedVariants + || migration.registries?.blocks != [ + "canaplia:hemp_crop", "canaplia:hemp_top", "canaplia:resin_block"] + || migration.registries?.block_entity_types != ["canaplia:resin_block"] + || migration.registries?.items != expectedItems + || migration.registries?.registration_owner != "CANA-02" + || migration.registries?.registered_by_foundation != false + || migration.persistence?.saved_data_ids != ["canaplia:hemp_bud_composters"] + || migration.network?.legacy_payloads != [ + "canaplia:hemp_visual_sync", "canaplia:hemp_bad_trip", "canaplia:hemp_visual_clear"] + || migration.network?.legacy_protocol != "unversioned" + || migration.network?.target_owner != "ambiance" + || migration.network?.target_visual_contract_version != 1 + || migration.network?.maximum_visual_duration_ticks != 12000 + || migration.resources?.languages != ["fr_fr", "en_us", "ru_ru"] + || migration.resources?.recipes != expectedRecipes + || migration.resources?.legacy_assets_imported_by_foundation != false + || migration.license_and_provenance?.new_foundation_code_and_documentation != "GPL-3.0-or-later" + || migration.license_and_provenance?.legacy_java_and_resources != "All Rights Reserved" + || migration.license_and_provenance?.copied_legacy_source_files != [] + || migration.license_and_provenance?.copied_legacy_binary_assets != [] + || migration.compatibility?.legacy_namespace_preserved != true + || migration.compatibility?.registry_ids_changed != false + || migration.compatibility?.network_protocol_changed != false + || migration.compatibility?.pack_release_updated != false + || migration.distribution?.packwiz_index_refreshed != false + || itsAliveManifest.provides != ["canaplia"] + || itsAliveManifest.depends?.ambiance != ">=0.0.0-alpha.13" + || itsAliveManifest.custom?.sanctuary26?.data_version != 1 + || itsAliveManifest.custom?.sanctuary26?.network_protocol != 3 + || ambianceManifest.custom?.sanctuary26?.data_version != 2 + || ambianceManifest.custom?.sanctuary26?.network_protocol != 4 + || !itsAliveDependencies.contains('implementation project(":ambiance")') + || settingsText.contains('"canaplia"') + || !catalogSource.contains('NAMESPACE = "canaplia"') + || !catalogSource.contains('requireSize("items", ITEM_IDS, 64)') + || !initializerSource.contains("CanapliaFoundation.initialize()") + || !visualApiSource.contains("MAX_DURATION_TICKS = 10 * 60 * 20") + || languages.any { locale, translations -> !translations.containsKey("description.canaplia") } + || !(36..43).every { issue -> documentation.contains("issues/${issue}") }) { + throw new GradleException("The CANA-01 Canaplia foundation contract is incomplete") + } + } +} + tasks.named("verifyWorkspace") { dependsOn tasks.named("verifyWaystoneShopCulinaryAlpha202Release") dependsOn tasks.named("verifySafeVaultFeature") @@ -5197,6 +5304,7 @@ tasks.named("verifyWorkspace") { dependsOn tasks.named("verifySafeVaultCubeAlpha205Release") dependsOn tasks.named("verifySafeVaultCtmAlpha206Release") dependsOn tasks.named("verifySafeVaultUnlimitedAlpha207Release") + dependsOn tasks.named("verifyCanapliaFoundation") } tasks.named("check") { diff --git a/gradle.properties b/gradle.properties index d8998b2..afdabb7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,13 +12,13 @@ fabric_api_version=0.158.0+26.2 # Standby modules stay at zero until their migration really begins. mod_version=0.0.0-alpha.0 module_lifecycle=standby -ambiance_version=0.0.0-alpha.12 +ambiance_version=0.0.0-alpha.13 ambiance_lifecycle=active redstoner_version=0.0.0-alpha.10 redstoner_lifecycle=active anotherworld_version=0.0.0-alpha.55 anotherworld_lifecycle=active -itsalive_version=0.0.0-alpha.31 +itsalive_version=0.0.0-alpha.32 itsalive_lifecycle=active ouch_version=0.0.0-alpha.9 ouch_lifecycle=active diff --git a/itsalive/build.gradle b/itsalive/build.gradle index 618ae11..a70c312 100644 --- a/itsalive/build.gradle +++ b/itsalive/build.gradle @@ -3,6 +3,14 @@ import groovy.json.JsonSlurper import javax.imageio.ImageIO import java.security.MessageDigest +tasks.register("canapliaFoundationSmoke", JavaExec) { + group = "verification" + description = "Checks the stable Canaplia namespace, variant keys and Ambiance visual boundary." + dependsOn tasks.named("testClasses") + classpath = sourceSets.test.runtimeClasspath + mainClass = "fr.koka99cab.sanctuary26.itsalive.canaplia.CanapliaFoundationSmoke" +} + tasks.register("culinaryBatchingSmoke", JavaExec) { group = "verification" description = "Checks one-stack-per-ingredient cauldron batch quantities." @@ -417,7 +425,7 @@ tasks.register("verifyItsAlive") { def climateSource = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/ClimateCropForaging.java").text def pineappleSource = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/PineappleItem.java").text def worldgenSource = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/ItsAliveAgricultureWorldGeneration.java").text - if (project.version.toString() != "0.0.0-alpha.31" + if (project.version.toString() != "0.0.0-alpha.32" || climateMigration.target?.pack_version != "26.2.0-alpha.157" || climateMigration.target?.modules?.itsalive != "0.0.0-alpha.24" || !climateSource.contains("PlayerBlockBreakEvents.AFTER.register") @@ -1174,7 +1182,7 @@ tasks.register("verifyCulinaryReliability") { } } } - if (project.version.toString() != "0.0.0-alpha.31" + if (project.version.toString() != "0.0.0-alpha.32" || migration.target?.modules?.itsalive != "0.0.0-alpha.28" || migration.culinary_fixes?.food_press_contents_destroyed_on_break != true || migration.culinary_fixes?.comparator_contract?.blocks?.size() != 6 @@ -1212,7 +1220,7 @@ tasks.register("verifyCauldronBatching") { def recipe = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/recipe/CulinaryProcessRecipe.java").text def cauldron = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/entity/CookingCauldronBlockEntity.java").text def timed = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/block/entity/AbstractTimedCulinaryBlockEntity.java").text - if (project.version.toString() != "0.0.0-alpha.31" + if (project.version.toString() != "0.0.0-alpha.32" || migration.target?.modules?.itsalive != "0.0.0-alpha.29" || migration.cauldron_batches?.logical_stack_limit != 64 || migration.cauldron_batches?.identity != "same_item_same_components" @@ -1250,7 +1258,7 @@ tasks.register("verifyFermentationBarrelAutomation") { def partialMatching = file("src/main/java/fr/koka99cab/sanctuary26/itsalive/culinary/recipe/CulinaryPartialMatching.java").text def blockstate = new JsonSlurper().parse( file("src/main/resources/assets/itsalive/blockstates/fermentation_barrel.json")) - if (project.version.toString() != "0.0.0-alpha.31" + if (project.version.toString() != "0.0.0-alpha.32" || migration.source?.modules?.itsalive != "0.0.0-alpha.29" || migration.target?.modules?.itsalive != "0.0.0-alpha.30" || migration.fermentation_barrel?.maximum_units_per_input_slot != 1 @@ -1298,7 +1306,7 @@ tasks.register("verifyCulinaryLecternRelease") { [(locale): new JsonSlurper().parse(file( "src/main/resources/assets/itsalive/lang/${locale}.json"))] } - if (project.version.toString() != "0.0.0-alpha.31" + if (project.version.toString() != "0.0.0-alpha.32" || migration.target?.modules?.itsalive != "0.0.0-alpha.31" || migration.culinary_lectern?.book_tag != "minecraft:lectern_books" || migration.culinary_lectern?.owner_catalog_shared_while_placed != true @@ -1324,6 +1332,7 @@ tasks.register("verifyCulinaryLecternRelease") { tasks.named("check") { dependsOn tasks.named("verifyItsAlive") + dependsOn tasks.named("canapliaFoundationSmoke") dependsOn tasks.named("verifyCulinaryReliability") dependsOn tasks.named("verifyCauldronBatching") dependsOn tasks.named("culinaryBatchingSmoke") diff --git a/itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/ItsAliveMod.java b/itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/ItsAliveMod.java index 05972d0..d01f4d1 100644 --- a/itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/ItsAliveMod.java +++ b/itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/ItsAliveMod.java @@ -1,5 +1,6 @@ package fr.koka99cab.sanctuary26.itsalive; +import fr.koka99cab.sanctuary26.itsalive.canaplia.CanapliaFoundation; import fr.koka99cab.sanctuary26.itsalive.network.ItsAliveNetworking; import fr.koka99cab.sanctuary26.itsalive.culinary.knowledge.CulinaryRecipeKnowledge; import fr.koka99cab.sanctuary26.itsalive.registry.ItsAliveRegistries; @@ -24,6 +25,7 @@ public final class ItsAliveMod implements ModInitializer { @Override public void onInitialize() { + CanapliaFoundation.initialize(); ItsAliveRegistries.initialize(); ItsAliveNetworking.initialize(); CulinaryRecipeKnowledge.initialize(); diff --git a/itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaFoundation.java b/itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaFoundation.java new file mode 100644 index 0000000..95de280 --- /dev/null +++ b/itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaFoundation.java @@ -0,0 +1,31 @@ +package fr.koka99cab.sanctuary26.itsalive.canaplia; + +import fr.koka99cab.sanctuary26.ambiance.api.visual.AmbianceVisualEffectApi; +import fr.koka99cab.sanctuary26.itsalive.ItsAliveMod; + +/** Initializes the compatibility contract without registering unfinished content. */ +public final class CanapliaFoundation { + public static final int FOUNDATION_VERSION = 1; + private static boolean initialized; + + private CanapliaFoundation() { + } + + public static void initialize() { + if (initialized) { + return; + } + CanapliaLegacyCatalog.validate(); + if (AmbianceVisualEffectApi.CONTRACT_VERSION != 1) { + throw new IllegalStateException("Unsupported Ambiance visual contract"); + } + initialized = true; + ItsAliveMod.LOGGER.info( + "[Canaplia] Foundation v{} reserves {} variants and {} historical item IDs; visual contract v{} is ready.", + FOUNDATION_VERSION, + CanapliaLegacyCatalog.variantKeys().size(), + CanapliaLegacyCatalog.ITEM_IDS.size(), + AmbianceVisualEffectApi.CONTRACT_VERSION + ); + } +} diff --git a/itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaLegacyCatalog.java b/itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaLegacyCatalog.java new file mode 100644 index 0000000..6d33118 --- /dev/null +++ b/itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaLegacyCatalog.java @@ -0,0 +1,81 @@ +package fr.koka99cab.sanctuary26.itsalive.canaplia; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import net.minecraft.resources.Identifier; + +/** + * Compatibility inventory for the identifiers emitted by Canaplia 2.0. + * Registration is deliberately left to CANA-02 and later gameplay tickets. + */ +public final class CanapliaLegacyCatalog { + public static final String NAMESPACE = "canaplia"; + public static final List