From 1a8a9835067235d4fe2000cfffb52d210c623c4b Mon Sep 17 00:00:00 2001 From: koka Date: Sat, 29 Aug 2026 02:04:28 +0200 Subject: [PATCH] feat(canaplia): add 26.2 foundation --- CANAPLIA.md | 51 ++++ CHANGELOG.md | 11 + README.md | 6 +- ambiance/build.gradle | 11 +- .../api/visual/AmbianceVisualEffectApi.java | 38 +++ .../api/visual/AmbianceVisualProfile.java | 81 +++++++ ambiance/src/main/resources/fabric.mod.json | 8 +- .../api/visual/AmbianceVisualApiSmoke.java | 61 +++++ build.gradle | 120 +++++++++- gradle.properties | 4 +- itsalive/build.gradle | 19 +- .../sanctuary26/itsalive/ItsAliveMod.java | 2 + .../itsalive/canaplia/CanapliaFoundation.java | 31 +++ .../canaplia/CanapliaLegacyCatalog.java | 81 +++++++ .../itsalive/canaplia/CanapliaVariantKey.java | 42 ++++ .../canaplia/CanapliaVisualContract.java | 26 ++ .../resources/assets/canaplia/lang/en_us.json | 3 + .../resources/assets/canaplia/lang/fr_fr.json | 3 + .../resources/assets/canaplia/lang/ru_ru.json | 3 + itsalive/src/main/resources/fabric.mod.json | 14 +- .../canaplia/CanapliaFoundationSmoke.java | 52 ++++ .../canaplia-foundation-alpha32.json | 224 ++++++++++++++++++ 22 files changed, 866 insertions(+), 25 deletions(-) create mode 100644 CANAPLIA.md create mode 100644 ambiance/src/main/java/fr/koka99cab/sanctuary26/ambiance/api/visual/AmbianceVisualEffectApi.java create mode 100644 ambiance/src/main/java/fr/koka99cab/sanctuary26/ambiance/api/visual/AmbianceVisualProfile.java create mode 100644 ambiance/src/test/java/fr/koka99cab/sanctuary26/ambiance/api/visual/AmbianceVisualApiSmoke.java create mode 100644 itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaFoundation.java create mode 100644 itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaLegacyCatalog.java create mode 100644 itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaVariantKey.java create mode 100644 itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaVisualContract.java create mode 100644 itsalive/src/main/resources/assets/canaplia/lang/en_us.json create mode 100644 itsalive/src/main/resources/assets/canaplia/lang/fr_fr.json create mode 100644 itsalive/src/main/resources/assets/canaplia/lang/ru_ru.json create mode 100644 itsalive/src/test/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaFoundationSmoke.java create mode 100644 pack/migrations/canaplia-foundation-alpha32.json 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 ITEM_FAMILIES = List.of( + "hemp_seeds", + "hemp_bud", + "resin_block", + "rolled_joint" + ); + public static final List BLOCK_IDS = ids("hemp_crop", "hemp_top", "resin_block"); + public static final List BLOCK_ENTITY_TYPE_IDS = ids("resin_block"); + public static final List SAVED_DATA_IDS = ids("hemp_bud_composters"); + public static final List PAYLOAD_IDS = ids( + "hemp_visual_sync", + "hemp_bad_trip", + "hemp_visual_clear" + ); + public static final List LOOT_TABLE_IDS = ids( + "blocks/hemp_crop", + "blocks/hemp_top", + "blocks/resin_block" + ); + public static final List ITEM_IDS = createFamilyIds(ITEM_FAMILIES); + public static final List RECIPE_IDS = createFamilyIds(List.of("rolled_joint")); + + private CanapliaLegacyCatalog() { + } + + public static Identifier id(String path) { + return Identifier.fromNamespaceAndPath(NAMESPACE, path); + } + + public static List variantKeys() { + return List.of(CanapliaVariantKey.values()).stream().map(CanapliaVariantKey::key).toList(); + } + + public static void validate() { + requireSize("variants", variantKeys(), 16); + requireSize("blocks", BLOCK_IDS, 3); + requireSize("block entity types", BLOCK_ENTITY_TYPE_IDS, 1); + requireSize("items", ITEM_IDS, 64); + requireSize("saved data", SAVED_DATA_IDS, 1); + requireSize("payloads", PAYLOAD_IDS, 3); + requireSize("loot tables", LOOT_TABLE_IDS, 3); + requireSize("recipes", RECIPE_IDS, 16); + } + + private static List createFamilyIds(List families) { + List identifiers = new ArrayList<>(CanapliaVariantKey.values().length * families.size()); + for (CanapliaVariantKey variant : CanapliaVariantKey.values()) { + for (String family : families) { + identifiers.add(id(variant.legacyItemPath(family))); + } + } + return List.copyOf(identifiers); + } + + private static List ids(String... paths) { + return java.util.Arrays.stream(paths).map(CanapliaLegacyCatalog::id).toList(); + } + + private static void requireSize(String label, List values, int expectedSize) { + Set unique = new HashSet<>(values); + if (values.size() != expectedSize || unique.size() != expectedSize) { + throw new IllegalStateException( + "Invalid Canaplia " + label + " catalog: expected " + expectedSize + " unique values, got " + values + ); + } + } +} diff --git a/itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaVariantKey.java b/itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaVariantKey.java new file mode 100644 index 0000000..bea9f01 --- /dev/null +++ b/itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaVariantKey.java @@ -0,0 +1,42 @@ +package fr.koka99cab.sanctuary26.itsalive.canaplia; + +import java.util.Arrays; +import java.util.Optional; + +/** Stable machine keys from Canaplia 2.0. */ +public enum CanapliaVariantKey { + COMMON("common"), + TENDER("tender"), + OLIVE("olive"), + DARK("dark"), + ASHEN("ashen"), + MOSS("moss"), + GOLD("gold"), + AMBER("amber"), + BLUISH("bluish"), + PINK("pink"), + CORAL("coral"), + PURPLE("purple"), + INDIGO("indigo"), + VARIEG("varieg"), + LUNAR("lunar"), + EMBER("ember"); + + private final String key; + + CanapliaVariantKey(String key) { + this.key = key; + } + + public String key() { + return this.key; + } + + public String legacyItemPath(String family) { + return this == COMMON ? family : this.key + "_" + family; + } + + public static Optional byKey(String key) { + return Arrays.stream(values()).filter(variant -> variant.key.equals(key)).findFirst(); + } +} diff --git a/itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaVisualContract.java b/itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaVisualContract.java new file mode 100644 index 0000000..f4f144f --- /dev/null +++ b/itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaVisualContract.java @@ -0,0 +1,26 @@ +package fr.koka99cab.sanctuary26.itsalive.canaplia; + +import fr.koka99cab.sanctuary26.ambiance.api.visual.AmbianceVisualEffectApi; +import fr.koka99cab.sanctuary26.ambiance.api.visual.AmbianceVisualProfile; + +/** One-way bridge from It's Alive gameplay state to Ambiance presentation. */ +public final class CanapliaVisualContract { + private CanapliaVisualContract() { + } + + public static AmbianceVisualEffectApi.Request normal(AmbianceVisualProfile profile, int remainingTicks) { + return AmbianceVisualEffectApi.request(CanapliaLegacyCatalog.PAYLOAD_IDS.get(0), profile, remainingTicks); + } + + public static AmbianceVisualEffectApi.Request badTrip(AmbianceVisualProfile profile, int remainingTicks) { + return AmbianceVisualEffectApi.request(CanapliaLegacyCatalog.PAYLOAD_IDS.get(1), profile, remainingTicks); + } + + public static AmbianceVisualEffectApi.Request clear() { + return AmbianceVisualEffectApi.request( + CanapliaLegacyCatalog.PAYLOAD_IDS.get(2), + AmbianceVisualProfile.NONE, + 0 + ); + } +} diff --git a/itsalive/src/main/resources/assets/canaplia/lang/en_us.json b/itsalive/src/main/resources/assets/canaplia/lang/en_us.json new file mode 100644 index 0000000..a63cf59 --- /dev/null +++ b/itsalive/src/main/resources/assets/canaplia/lang/en_us.json @@ -0,0 +1,3 @@ +{ + "description.canaplia": "Canaplia compatibility foundation: server-side biology is owned by It's Alive and visual composition by Ambiance." +} diff --git a/itsalive/src/main/resources/assets/canaplia/lang/fr_fr.json b/itsalive/src/main/resources/assets/canaplia/lang/fr_fr.json new file mode 100644 index 0000000..70414b6 --- /dev/null +++ b/itsalive/src/main/resources/assets/canaplia/lang/fr_fr.json @@ -0,0 +1,3 @@ +{ + "description.canaplia": "Fondation de compatibilité Canaplia : la biologie serveur appartient à It's Alive et la composition visuelle à Ambiance." +} diff --git a/itsalive/src/main/resources/assets/canaplia/lang/ru_ru.json b/itsalive/src/main/resources/assets/canaplia/lang/ru_ru.json new file mode 100644 index 0000000..a5e6155 --- /dev/null +++ b/itsalive/src/main/resources/assets/canaplia/lang/ru_ru.json @@ -0,0 +1,3 @@ +{ + "description.canaplia": "Основа совместимости Canaplia: серверная биология принадлежит It's Alive, а визуальная композиция — Ambiance." +} diff --git a/itsalive/src/main/resources/fabric.mod.json b/itsalive/src/main/resources/fabric.mod.json index 1506d42..fcd794c 100644 --- a/itsalive/src/main/resources/fabric.mod.json +++ b/itsalive/src/main/resources/fabric.mod.json @@ -3,7 +3,7 @@ "id": "itsalive", "version": "${version}", "name": "It's Alive !", - "description": "Owns living creatures, ecosystems and farming.", + "description": "Owns living creatures, ecosystems, farming and Canaplia's server-side biology.", "authors": [ "KOKA99CAB" ], @@ -13,6 +13,9 @@ "license": "GPL-3.0-or-later", "icon": "assets/itsalive/icon.png", "environment": "*", + "provides": [ + "canaplia" + ], "entrypoints": { "main": [ "fr.koka99cab.sanctuary26.itsalive.ItsAliveMod" @@ -33,7 +36,8 @@ "minecraft": "~26.2", "java": ">=25", "fabric-api": ">=0.154.2+26.2", - "anotherworld": "*" + "anotherworld": "*", + "ambiance": ">=0.0.0-alpha.13" }, "custom": { "sanctuary26": { @@ -44,9 +48,9 @@ "lifecycle": "${lifecycle}", "pack_version": "${pack_version}", "descriptions": { - "fr_fr": "Possède le vivant, les écosystèmes et l'agriculture.", - "en_us": "Owns living creatures, ecosystems and farming.", - "ru_ru": "Отвечает за живых существ, экосистемы и сельское хозяйство." + "fr_fr": "Possède le vivant, les écosystèmes, l'agriculture et la biologie serveur de Canaplia.", + "en_us": "Owns living creatures, ecosystems, farming and Canaplia's server-side biology.", + "ru_ru": "Отвечает за живых существ, экосистемы, сельское хозяйство и серверную биологию Canaplia." } } } diff --git a/itsalive/src/test/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaFoundationSmoke.java b/itsalive/src/test/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaFoundationSmoke.java new file mode 100644 index 0000000..2cc0363 --- /dev/null +++ b/itsalive/src/test/java/fr/koka99cab/sanctuary26/itsalive/canaplia/CanapliaFoundationSmoke.java @@ -0,0 +1,52 @@ +package fr.koka99cab.sanctuary26.itsalive.canaplia; + +import fr.koka99cab.sanctuary26.ambiance.api.visual.AmbianceVisualEffectApi; +import fr.koka99cab.sanctuary26.ambiance.api.visual.AmbianceVisualProfile; +import java.util.HashSet; +import java.util.List; + +public final class CanapliaFoundationSmoke { + private CanapliaFoundationSmoke() { + } + + public static void main(String[] arguments) { + CanapliaLegacyCatalog.validate(); + assertEquals(List.of( + "common", "tender", "olive", "dark", "ashen", "moss", "gold", "amber", + "bluish", "pink", "coral", "purple", "indigo", "varieg", "lunar", "ember" + ), CanapliaLegacyCatalog.variantKeys(), "variant keys"); + assertEquals(64, CanapliaLegacyCatalog.ITEM_IDS.size(), "item count"); + assertEquals(64, new HashSet<>(CanapliaLegacyCatalog.ITEM_IDS).size(), "unique item count"); + assertTrue(CanapliaLegacyCatalog.ITEM_IDS.contains(CanapliaLegacyCatalog.id("hemp_seeds")), "common seeds"); + assertTrue(CanapliaLegacyCatalog.ITEM_IDS.contains(CanapliaLegacyCatalog.id("pink_hemp_seeds")), "pink seeds"); + assertTrue(CanapliaLegacyCatalog.ITEM_IDS.contains(CanapliaLegacyCatalog.id("varieg_rolled_joint")), "varieg joint"); + assertEquals(CanapliaVariantKey.PINK, CanapliaVariantKey.byKey("pink").orElseThrow(), "pink lookup"); + assertTrue(CanapliaVariantKey.byKey("pinkish").isEmpty(), "unknown lookup"); + + AmbianceVisualEffectApi.Request request = CanapliaVisualContract.normal( + AmbianceVisualProfile.NONE, + Integer.MAX_VALUE + ); + assertEquals(CanapliaLegacyCatalog.id("hemp_visual_sync"), request.source(), "normal payload identity"); + assertEquals(AmbianceVisualEffectApi.MAX_DURATION_TICKS, request.remainingTicks(), "visual duration bound"); + assertTrue(CanapliaVisualContract.clear().expired(), "clear request"); + } + + private static void assertTrue(boolean condition, String label) { + if (!condition) { + throw new AssertionError(label); + } + } + + private static void assertEquals(Object expected, Object actual, String label) { + if (!expected.equals(actual)) { + 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/pack/migrations/canaplia-foundation-alpha32.json b/pack/migrations/canaplia-foundation-alpha32.json new file mode 100644 index 0000000..e125692 --- /dev/null +++ b/pack/migrations/canaplia-foundation-alpha32.json @@ -0,0 +1,224 @@ +{ + "schema_version": 1, + "issue": 35, + "source": { + "legacy_module": "Farming/canaplia", + "fabric_id": "canaplia", + "version": "2.0.0", + "minecraft": "26.1.2", + "fabric_loader": "0.19.2", + "fabric_api": "0.148.0+26.1.2", + "java": 25, + "license": "All Rights Reserved", + "copyright_holder": "KOKA99CAB" + }, + "target": { + "pack_version": "26.2.0-alpha.207", + "minecraft": "26.2", + "fabric_loader": "0.19.3", + "fabric_api": "0.158.0+26.2", + "java": 25, + "modules": { + "itsalive": "0.0.0-alpha.32", + "ambiance": "0.0.0-alpha.13" + }, + "standalone_module_created": false, + "legacy_mod_id_provided_by": "itsalive" + }, + "ownership": { + "living_and_agriculture": "itsalive", + "gameplay_effects_and_persistence": "itsalive", + "visual_composition_and_accessibility": "ambiance", + "progression": "sanctuary", + "registry_namespace": "canaplia", + "dependency_direction": [ + "itsalive -> ambiance", + "ambiance -> sanctuary" + ], + "dependency_cycle_introduced": false + }, + "variants": [ + "common", + "tender", + "olive", + "dark", + "ashen", + "moss", + "gold", + "amber", + "bluish", + "pink", + "coral", + "purple", + "indigo", + "varieg", + "lunar", + "ember" + ], + "registries": { + "blocks": [ + "canaplia:hemp_crop", + "canaplia:hemp_top", + "canaplia:resin_block" + ], + "block_entity_types": [ + "canaplia:resin_block" + ], + "items": [ + "canaplia:hemp_seeds", + "canaplia:hemp_bud", + "canaplia:resin_block", + "canaplia:rolled_joint", + "canaplia:tender_hemp_seeds", + "canaplia:tender_hemp_bud", + "canaplia:tender_resin_block", + "canaplia:tender_rolled_joint", + "canaplia:olive_hemp_seeds", + "canaplia:olive_hemp_bud", + "canaplia:olive_resin_block", + "canaplia:olive_rolled_joint", + "canaplia:dark_hemp_seeds", + "canaplia:dark_hemp_bud", + "canaplia:dark_resin_block", + "canaplia:dark_rolled_joint", + "canaplia:ashen_hemp_seeds", + "canaplia:ashen_hemp_bud", + "canaplia:ashen_resin_block", + "canaplia:ashen_rolled_joint", + "canaplia:moss_hemp_seeds", + "canaplia:moss_hemp_bud", + "canaplia:moss_resin_block", + "canaplia:moss_rolled_joint", + "canaplia:gold_hemp_seeds", + "canaplia:gold_hemp_bud", + "canaplia:gold_resin_block", + "canaplia:gold_rolled_joint", + "canaplia:amber_hemp_seeds", + "canaplia:amber_hemp_bud", + "canaplia:amber_resin_block", + "canaplia:amber_rolled_joint", + "canaplia:bluish_hemp_seeds", + "canaplia:bluish_hemp_bud", + "canaplia:bluish_resin_block", + "canaplia:bluish_rolled_joint", + "canaplia:pink_hemp_seeds", + "canaplia:pink_hemp_bud", + "canaplia:pink_resin_block", + "canaplia:pink_rolled_joint", + "canaplia:coral_hemp_seeds", + "canaplia:coral_hemp_bud", + "canaplia:coral_resin_block", + "canaplia:coral_rolled_joint", + "canaplia:purple_hemp_seeds", + "canaplia:purple_hemp_bud", + "canaplia:purple_resin_block", + "canaplia:purple_rolled_joint", + "canaplia:indigo_hemp_seeds", + "canaplia:indigo_hemp_bud", + "canaplia:indigo_resin_block", + "canaplia:indigo_rolled_joint", + "canaplia:varieg_hemp_seeds", + "canaplia:varieg_hemp_bud", + "canaplia:varieg_resin_block", + "canaplia:varieg_rolled_joint", + "canaplia:lunar_hemp_seeds", + "canaplia:lunar_hemp_bud", + "canaplia:lunar_resin_block", + "canaplia:lunar_rolled_joint", + "canaplia:ember_hemp_seeds", + "canaplia:ember_hemp_bud", + "canaplia:ember_resin_block", + "canaplia:ember_rolled_joint" + ], + "registration_owner": "CANA-02", + "registered_by_foundation": false + }, + "persistence": { + "saved_data_ids": [ + "canaplia:hemp_bud_composters" + ], + "saved_fields": [ + "pos", + "bud_count", + "source_variant", + "remaining_burn_ticks" + ], + "legacy_data_version": "unversioned", + "migration_policy": "preserve_ids_then_add_monotone_data_version_before_schema_change" + }, + "network": { + "legacy_payloads": [ + "canaplia:hemp_visual_sync", + "canaplia:hemp_bad_trip", + "canaplia:hemp_visual_clear" + ], + "legacy_protocol": "unversioned", + "target_owner": "ambiance", + "target_visual_contract_version": 1, + "maximum_visual_duration_ticks": 12000, + "profile_values_bounded": true, + "payload_registration_deferred_to": [ + "CANA-07", + "CANA-08" + ] + }, + "resources": { + "asset_namespace": "canaplia", + "data_namespace": "canaplia", + "languages": [ + "fr_fr", + "en_us", + "ru_ru" + ], + "loot_tables": [ + "canaplia:blocks/hemp_crop", + "canaplia:blocks/hemp_top", + "canaplia:blocks/resin_block" + ], + "recipes": [ + "canaplia:rolled_joint", + "canaplia:tender_rolled_joint", + "canaplia:olive_rolled_joint", + "canaplia:dark_rolled_joint", + "canaplia:ashen_rolled_joint", + "canaplia:moss_rolled_joint", + "canaplia:gold_rolled_joint", + "canaplia:amber_rolled_joint", + "canaplia:bluish_rolled_joint", + "canaplia:pink_rolled_joint", + "canaplia:coral_rolled_joint", + "canaplia:purple_rolled_joint", + "canaplia:indigo_rolled_joint", + "canaplia:varieg_rolled_joint", + "canaplia:lunar_rolled_joint", + "canaplia:ember_rolled_joint" + ], + "legacy_texture_files": 224, + "legacy_asset_files": 533, + "legacy_data_files": 20, + "legacy_assets_imported_by_foundation": false + }, + "license_and_provenance": { + "new_foundation_code_and_documentation": "GPL-3.0-or-later", + "legacy_java_and_resources": "All Rights Reserved", + "copied_legacy_source_files": [], + "copied_legacy_binary_assets": [], + "compatibility_metadata_only": true, + "future_import_requires_explicit_copyright_holder_authorization": true + }, + "compatibility": { + "legacy_namespace_preserved": true, + "legacy_mod_id_provided": true, + "registry_ids_changed": false, + "save_rewrite_performed": false, + "itsalive_data_version": 1, + "itsalive_network_protocol": 3, + "ambiance_data_version": 2, + "ambiance_network_protocol": 4, + "network_protocol_changed": false, + "pack_release_updated": false + }, + "distribution": { + "packwiz_index_refreshed": false + } +} -- 2.54.0