From abe6f20f712f9b22b949c83c13959c1608a27bff Mon Sep 17 00:00:00 2001 From: stanley-1028 Date: Sun, 21 Jun 2026 21:11:55 +0800 Subject: [PATCH 1/4] fix: stabilize CI and package installs Ensure verification uses existing Gradle tasks, prevent mismatched release assets, and make package installs resolve through real registry adapters. Co-authored-by: Cursor --- .github/workflows/build.yml | 12 +- .github/workflows/checkstyle.yml | 8 +- .../com/francium/manager/PackageManager.java | 75 +++++++++- .../francium/manager/PackageManagerTest.java | 131 ++++++++++++++++++ 4 files changed, 214 insertions(+), 12 deletions(-) create mode 100644 francium-manager/src/test/java/com/francium/manager/PackageManagerTest.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6407c00..169238d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -143,6 +143,16 @@ jobs: - name: Make gradlew executable run: chmod +x gradlew + - name: Validate release tag matches project version + shell: bash + run: | + project_version=$(./gradlew properties -q --no-daemon | awk -F': ' '/^version:/ { print $2; exit }') + tag_version="${GITHUB_REF_NAME#v}" + if [ "$project_version" != "$tag_version" ]; then + echo "Release tag ${GITHUB_REF_NAME} does not match Gradle project version ${project_version}" + exit 1 + fi + - name: Install jpackage system dependencies shell: bash run: | @@ -218,7 +228,7 @@ jobs: run: | mkdir -p release # Move all distributable files to release/ - find . -name "*.jar" -path "*/francium-jar-*" -exec cp {} release/ \; + find . -path "*/build/libs/*-all.jar" -exec cp {} release/ \; find . -name "*.exe" -exec cp {} release/ \; find . -name "*.dmg" -exec cp {} release/ \; find . -name "*.deb" -exec cp {} release/ \; diff --git a/.github/workflows/checkstyle.yml b/.github/workflows/checkstyle.yml index 8513ffd..fac0eba 100644 --- a/.github/workflows/checkstyle.yml +++ b/.github/workflows/checkstyle.yml @@ -11,6 +11,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + - name: Make gradlew executable + run: chmod +x gradlew - name: Set up JDK 21 uses: actions/setup-java@v5 with: @@ -18,7 +20,5 @@ jobs: distribution: 'temurin' - name: Setup Gradle uses: gradle/actions/setup-gradle@v6 - - name: Run Checkstyle - run: ./gradlew checkstyleMain - - name: Run Tests - run: ./gradlew test + - name: Run Gradle verification + run: ./gradlew check --no-daemon diff --git a/francium-manager/src/main/java/com/francium/manager/PackageManager.java b/francium-manager/src/main/java/com/francium/manager/PackageManager.java index 0d21df9..e857a48 100644 --- a/francium-manager/src/main/java/com/francium/manager/PackageManager.java +++ b/francium-manager/src/main/java/com/francium/manager/PackageManager.java @@ -1,8 +1,6 @@ package com.francium.manager; import com.francium.api.PublicApi; -import com.francium.manager.CurseForgeAdapter; -import com.francium.manager.ModrinthAdapter; import java.io.IOException; import java.io.File; import java.nio.file.*; @@ -54,8 +52,6 @@ public PackageManager(Path modsDir, Path cacheDir) { .build(); this.gson = new GsonBuilder().setPrettyPrinting().create(); this.registries = new ArrayList<>(); - // ★ BUG FIX: 移除重複的 registry URL - this.registries.add("https://api.modrinth.com/v2"); // Modrinth API v2 (官方) this.registries.add("https://registry.francium.dev/v1"); // Francium 官方 registry (開發中) this.cache = new HashMap<>(); this.modrinth = new ModrinthAdapter(); @@ -146,8 +142,14 @@ public InstallReport install(String modId, String versionConstraint) throws Exce } } - // Step 5: 寫入鎖定檔案 - writeLockFile(resolved); + // Step 5: 全部下載成功後才寫入鎖定檔案,避免 lock file 記錄未安裝完成的 mod + if (report.errors.isEmpty()) { + try { + writeLockFile(resolved); + } catch (IOException e) { + report.errors.add("Failed to write lock file: " + e.getMessage()); + } + } report.success = report.errors.isEmpty(); return report; @@ -253,6 +255,7 @@ private void fetchRegistryIndex(String registryUrl) throws Exception { if (response.statusCode() == 200) { RegistryIndex index = gson.fromJson(response.body(), RegistryIndex.class); + if (index == null || index.mods == null) return; for (RegistryMod mod : index.mods) { cache.computeIfAbsent(mod.modId, k -> new ArrayList<>()).add(mod); } @@ -265,6 +268,25 @@ private RegistryMod fetchModInfo(String modId) throws Exception { return cache.get(modId).get(0); } + RegistryMod registryMod = fetchFromFranciumRegistries(modId); + if (registryMod != null) { + return cacheAndReturn(registryMod); + } + + RegistryMod modrinthMod = modrinth.fetchProject(modId); + if (modrinthMod != null && hasInstallableVersions(modrinthMod)) { + return cacheAndReturn(modrinthMod); + } + + RegistryMod curseForgeMod = fetchFromCurseForge(modId); + if (curseForgeMod != null && hasInstallableVersions(curseForgeMod)) { + return cacheAndReturn(curseForgeMod); + } + + return null; + } + + private RegistryMod fetchFromFranciumRegistries(String modId) { for (String registry : registries) { try { HttpRequest request = HttpRequest.newBuilder() @@ -277,7 +299,6 @@ private RegistryMod fetchModInfo(String modId) throws Exception { if (response.statusCode() == 200) { RegistryMod mod = gson.fromJson(response.body(), RegistryMod.class); - cache.put(modId, List.of(mod)); return mod; } } catch (Exception e) { @@ -287,6 +308,38 @@ private RegistryMod fetchModInfo(String modId) throws Exception { return null; } + private RegistryMod fetchFromCurseForge(String modId) { + if (!curseForge.isAvailable()) return null; + + try { + Long curseForgeId = parseLongOrNull(modId); + if (curseForgeId == null) { + curseForgeId = curseForge.resolveModId(modId); + } + return curseForgeId != null ? curseForge.fetchProject(curseForgeId) : null; + } catch (Exception e) { + LOGGER.warn("CurseForge lookup failed for {}: {}", modId, e.getMessage()); + return null; + } + } + + private Long parseLongOrNull(String value) { + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + return null; + } + } + + private RegistryMod cacheAndReturn(RegistryMod mod) { + cache.put(mod.modId, List.of(mod)); + return mod; + } + + private boolean hasInstallableVersions(RegistryMod mod) { + return mod.versions != null && !mod.versions.isEmpty(); + } + private RegistryMod.ModVersion selectBestVersion(RegistryMod mod, String constraint) { if (constraint == null || constraint.equals("*")) { return mod.versions.isEmpty() ? null : mod.versions.get(0); // 最新穩定版 @@ -395,6 +448,10 @@ private boolean resolveTree(String modId, RegistryMod.ModVersion version, } private void downloadMod(String url, Path dest, String expectedSha256) throws Exception { + if (url == null || url.isBlank()) { + throw new IOException("Missing download URL"); + } + HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .GET() @@ -417,6 +474,10 @@ private void downloadMod(String url, Path dest, String expectedSha256) throws Ex } } + Path parent = dest.getParent(); + if (parent != null) { + Files.createDirectories(parent); + } Files.write(dest, data); } diff --git a/francium-manager/src/test/java/com/francium/manager/PackageManagerTest.java b/francium-manager/src/test/java/com/francium/manager/PackageManagerTest.java new file mode 100644 index 0000000..7ad52cc --- /dev/null +++ b/francium-manager/src/test/java/com/francium/manager/PackageManagerTest.java @@ -0,0 +1,131 @@ +package com.francium.manager; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.sun.net.httpserver.HttpServer; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.util.HexFormat; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class PackageManagerTest { + @TempDir + Path tempDir; + + @Test + void installDownloadsModAndWritesLockFile() throws Exception { + byte[] modBytes = "fake mod jar".getBytes(StandardCharsets.UTF_8); + + try (TestRegistry registry = new TestRegistry()) { + registry.respond("/mod/example-mod", """ + { + "modId": "example-mod", + "versions": [ + { + "version": "1.0.0", + "downloadUrl": "%s/files/example-mod.jar", + "sha256": "%s", + "dependencies": {}, + "optionalDependencies": {} + } + ] + } + """.formatted(registry.baseUrl(), sha256(modBytes))); + registry.respond("/files/example-mod.jar", modBytes); + registry.start(); + + Path modsDir = tempDir.resolve("mods"); + PackageManager manager = packageManagerUsing(registry.baseUrl(), modsDir); + + PackageManager.InstallReport report = manager.install("example-mod", null); + + assertTrue(report.success, report.errors::toString); + assertTrue(Files.exists(modsDir.resolve("example-mod-1.0.0.jar"))); + assertTrue(Files.exists(modsDir.resolve("francium-lock.json"))); + } + } + + @Test + void installDoesNotWriteLockFileWhenDownloadUrlIsMissing() throws Exception { + try (TestRegistry registry = new TestRegistry()) { + registry.respond("/mod/broken-mod", """ + { + "modId": "broken-mod", + "versions": [ + { + "version": "1.0.0", + "dependencies": {}, + "optionalDependencies": {} + } + ] + } + """); + registry.start(); + + Path modsDir = tempDir.resolve("mods"); + PackageManager manager = packageManagerUsing(registry.baseUrl(), modsDir); + + PackageManager.InstallReport report = manager.install("broken-mod", null); + + assertFalse(report.success); + assertFalse(Files.exists(modsDir.resolve("francium-lock.json"))); + } + } + + @SuppressWarnings("unchecked") + private PackageManager packageManagerUsing(String registryUrl, Path modsDir) throws Exception { + PackageManager manager = new PackageManager(modsDir, tempDir.resolve("cache")); + Field registries = PackageManager.class.getDeclaredField("registries"); + registries.setAccessible(true); + List values = (List) registries.get(manager); + values.clear(); + values.add(registryUrl); + return manager; + } + + private static String sha256(byte[] data) throws Exception { + return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(data)); + } + + private static class TestRegistry implements AutoCloseable { + private final HttpServer server; + + TestRegistry() throws IOException { + server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); + } + + String baseUrl() { + return "http://127.0.0.1:" + server.getAddress().getPort(); + } + + void respond(String path, String body) { + respond(path, body.getBytes(StandardCharsets.UTF_8)); + } + + void respond(String path, byte[] body) { + server.createContext(path, exchange -> { + exchange.sendResponseHeaders(200, body.length); + try (var out = exchange.getResponseBody()) { + out.write(body); + } + }); + } + + void start() { + server.start(); + } + + @Override + public void close() { + server.stop(0); + } + } +} From d4cecce04851b712e73b1d456b771e107f779db9 Mon Sep 17 00:00:00 2001 From: stanley-1028 Date: Sun, 21 Jun 2026 21:51:21 +0800 Subject: [PATCH 2/4] Align release metadata with Gradle 2.3.0 and guard against version drift. Add verifyReleaseMetadata to check so CI fails when README, loader properties, or distribution scripts fall out of sync with project.version. Co-authored-by: Cursor --- README.md | 2 +- build.gradle | 33 +++++++++++++++++++ build_dist.bat | 10 +++++- .../main/resources/francium-loader.properties | 4 +-- 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0e3ddd1..8402611 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ public class QuickStart { } ``` -完整的 API 文檔請參閱 [Javadoc](https://jitpack.io/com/github/stanley-1028/francium-loader/v2.2.0/javadoc/)。 +完整的 API 文檔請參閱 [Javadoc](https://jitpack.io/com/github/stanley-1028/francium-loader/v2.3.0/javadoc/)。 --- diff --git a/build.gradle b/build.gradle index 763eab2..0cc5c5c 100644 --- a/build.gradle +++ b/build.gradle @@ -53,6 +53,39 @@ allprojects { } } +tasks.register('verifyReleaseMetadata') { + group = 'verification' + description = 'Verify release metadata matches the Gradle project version.' + + doLast { + def projectVersion = project.version.toString() + + def metadataFile = file('francium-core/src/main/resources/francium-loader.properties') + def metadata = new Properties() + metadataFile.withInputStream { metadata.load(it) } + def metadataVersion = metadata.getProperty('francium.version') + if (metadataVersion != projectVersion) { + throw new GradleException( + "francium.version (${metadataVersion}) must match Gradle version (${projectVersion})" + ) + } + + def readme = file('README.md').getText('UTF-8') + def expectedJitPack = "francium-loader:v${projectVersion}" + def expectedMaven = "francium-loader:${projectVersion}" + def expectedJavadocs = "/francium-loader/v${projectVersion}/javadoc/" + [expectedJitPack, expectedMaven, expectedJavadocs].each { expected -> + if (!readme.contains(expected)) { + throw new GradleException("README.md is missing release metadata: ${expected}") + } + } + } +} + +tasks.named('check') { + dependsOn tasks.named('verifyReleaseMetadata') +} + // Common configuration for all subprojects subprojects { apply plugin: 'java' diff --git a/build_dist.bat b/build_dist.bat index ba6beb8..ab02344 100644 --- a/build_dist.bat +++ b/build_dist.bat @@ -6,9 +6,17 @@ REM ============================================ setlocal enabledelayedexpansion set PROJECT_DIR=%~dp0 -set VERSION=1.2.0 set DIST_DIR=%PROJECT_DIR%build\dist +for /f "tokens=2 delims=:" %%v in ('call "%PROJECT_DIR%gradlew.bat" properties -q --no-daemon ^| findstr /b "version:"') do ( + set "VERSION=%%v" +) +set "VERSION=!VERSION: =!" +if not defined VERSION ( + echo FAILED: Unable to determine Gradle project version + exit /b 1 +) + echo ========================================== echo Francium Loader v%VERSION% - Distribution echo ========================================== diff --git a/francium-core/src/main/resources/francium-loader.properties b/francium-core/src/main/resources/francium-loader.properties index 923f9c9..99e300f 100644 --- a/francium-core/src/main/resources/francium-loader.properties +++ b/francium-core/src/main/resources/francium-loader.properties @@ -1,5 +1,5 @@ # Francium Mod Loader version properties # Auto-detected by Nova Launcher and other consumers -francium.version=1.2.0 -francium.build.date=2026-05-12 +francium.version=2.3.0 +francium.build.date=2026-06-18 francium.minimum.java=21 From 1a8f92cbb2b19405c33ff78e09531ff2d25ae9a3 Mon Sep 17 00:00:00 2001 From: stanley-1028 Date: Mon, 22 Jun 2026 13:29:08 +0800 Subject: [PATCH 3/4] fix(api): fix Javadoc compilation errors and update ROADMAP.md - Fix HTML heading sequence error in PublicApi.java (replace

with

) to allow successful Javadoc generation. - Update ROADMAP.md to mark Nova Launcher integration and API Javadoc tasks as completed. Co-authored-by: Cursor --- ROADMAP.md | 9 ++++----- .../src/main/java/com/francium/api/PublicApi.java | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 04b75cc..40cdeb9 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -24,7 +24,7 @@ | MultiMC/Prism 匯入 | ✅ v2.3.0 新增 | | 文件 (DEVELOPER_GUIDE.md) | ✅ | | Maven Central 發布 | ❌ | -| Nova Launcher 整合 | ❌ | +| Nova Launcher 整合 | ✅ | --- @@ -45,13 +45,13 @@ ## 🟡 P1:開發者體驗 -- [ ] Nova Launcher 整合端到端驗證 +- [x] Nova Launcher 整合端到端驗證 ✅ - [x] 範例模組 (Hello World Francium Mod) ✅ v2.3.0 - [x] 預編譯 example-mod JAR ✅ v2.3.0 - [x] Gradle 模板專案 (francium-mod-template) ✅ - [x] 模組開發者文件 (DEVELOPER_GUIDE.md) ✅ - [x] `francium.json` 完整欄位說明 ✅ -- [ ] API Javadoc 上線 +- [x] API Javadoc 上線 ✅ --- @@ -89,7 +89,6 @@ v2.3.0 (2026-06-18): 下一步: ├── Mapping 數據庫繼續擴充至 60 類 / 800+ 方法 - ├── Nova Launcher 整合驗證 ├── Maven Central 發布 - └── API Javadoc 上線 + └── 社群推廣與文檔完善 ``` diff --git a/francium-api/src/main/java/com/francium/api/PublicApi.java b/francium-api/src/main/java/com/francium/api/PublicApi.java index d78654e..0141567 100644 --- a/francium-api/src/main/java/com/francium/api/PublicApi.java +++ b/francium-api/src/main/java/com/francium/api/PublicApi.java @@ -19,13 +19,13 @@ * time without notice. Consumers should avoid depending on them directly. *

* - *

Usage

+ *

Usage

*
{@code
  * @PublicApi
  * public class FranciumLoader { ... }
  * }
* - *

Stability guarantees

+ *

Stability guarantees

*
    *
  • Patch version: bug fixes only, no API changes
  • *
  • Minor version: additions only, no removals or backward-incompatible changes
  • From ec802fc4f217e504b263dc1a5acb69252d321814 Mon Sep 17 00:00:00 2001 From: stanley-1028 Date: Mon, 22 Jun 2026 13:36:44 +0800 Subject: [PATCH 4/4] docs: add comprehensive DEVELOPMENT_PLAN.md - Create a detailed development proposal/plan for Francium Mod Loader. - Outline background, core architecture evolution, future roadmap (P0-P3), tech stack, and risk mitigation strategies. Co-authored-by: Cursor --- docs/DEVELOPMENT_PLAN.md | 140 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 docs/DEVELOPMENT_PLAN.md diff --git a/docs/DEVELOPMENT_PLAN.md b/docs/DEVELOPMENT_PLAN.md new file mode 100644 index 0000000..6a71ed5 --- /dev/null +++ b/docs/DEVELOPMENT_PLAN.md @@ -0,0 +1,140 @@ +# 🚀 Francium Mod Loader — 開發計劃書 (Development Proposal) + +> **下一代 Minecraft 模組加載器 — 由 AI 驅動的版本橋接與 DAG 並行加載** +> +> 本計劃書旨在規劃 Francium Mod Loader 從目前的 **v2.3.0(核心功能完備階段)** 邁向 **v3.0.0(生態繁榮與工業級穩定階段)** 的技術演進路線、開發時程與社群推廣策略。 + +--- + +## 1. 項目背景與定位 (Background & Positioning) + +### 1.1 現有生態痛點 +現有的 Minecraft 模組加載器(Forge, Fabric, NeoForge, Quilt)雖然成熟,但仍面臨以下結構性痛點: +1. **加載時間過長**:模組呈循序加載,當安裝 100+ 個模組時,啟動時間通常需要 2-3 分鐘。 +2. **依賴地獄與衝突**:模組之間的版本依賴衝突需要玩家手動比對與排除,缺乏自動化求解方案。 +3. **版本斷層與不相容**:Minecraft 每次大版本更新,模組作者都必須手動修改程式碼並重新編譯,導致大量優秀模組停留在舊版本。 +4. **效能與記憶體損耗**:所有模組共用同一個 ClassLoader,容易發生類別污染;且缺乏統一的記憶體與物件池管理,導致頻繁的 GC 停頓。 + +### 1.2 Francium 的定位 +Francium 不是另一個「輕量化」的加載器,而是**從底層重新設計的下一代模組加載器**。我們通過 **DAG 並行加載**、**SAT 依賴求解** 與 **AI 字節碼適配橋接**,徹底解決上述痛點,實現「不要等待模組更新,讓 AI 替你橋接未來」的願景。 + +--- + +## 2. 核心技術架構與演進 (Technical Architecture) + +Francium 的架構由六大核心模組組成,未來將在穩定性與精準度上持續演進: + +``` + ┌────────────────────────────────────────┐ + │ Francium Bootstrap │ + └───────────────────┬────────────────────┘ + │ + ▼ + ┌────────────────────────────────────────┐ + │ SAT Dependency Resolver │ (依賴衝突自動求解) + └───────────────────┬────────────────────┘ + │ + ▼ + ┌────────────────────────────────────────┐ + │ Mod Graph │ (Kahn 算法拓撲分層) + └───────────────────┬────────────────────┘ + │ + ▼ + ┌────────────────────────────────────────┐ + │ ParallelModClassLoader │ (ForkJoinPool 並行加載) + └───────────┬────────────────┬───────────┘ + │ │ + ▼ ▼ + ┌──────────────────┐ ┌──────────────────┐ + │ AI Version Bridge│ │ Memory Profiler │ (洩漏偵測/物件池/GC) + └──────────────────┘ └──────────────────┘ +``` + +### 2.1 DAG 並行加載 (Parallel Loading) +* **原理**:將模組依賴關係構建為有向無環圖(DAG),使用 Kahn 算法進行拓撲分層。同層模組互不依賴,利用 `ForkJoinPool` 進行多執行緒並行加載。 +* **v3.0 演進目標**: + * 引入**動態加載權重調度**:根據模組歷史加載時間,動態調整並行加載順序,最大化 CPU 利用率。 + * 支援**非阻塞式非同步初始化**:允許部分非核心模組在遊戲主介面載入後,於背景繼續初始化。 + +### 2.2 AI 版本橋接 (AI Version Bridge) — *核心創新* +* **原理**:使用 ASM 進行字節碼分析,提取模組對 Minecraft 核心 API 的調用;通過多維相似度計算與加權特徵模型,自動生成適配器字節碼(Adapter Bytecode),實現跨版本透明轉發。 +* **v3.0 演進目標**: + * **Mapping 數據庫擴充**:從目前的 38 類 / 400 方法,擴充至 **60 類 / 800+ 方法**,覆蓋 95% 以上的常用 Modding API。 + * **機器學習模型本地推論**:整合 ONNX Runtime,在本地運行輕量化注意力預測模型(Attention Predictor),提高未知 API 映射的精準度。 + * **極速本地快取**:優化快取機制,避免重複分析未變更的模組,使二次啟動達到「零延遲」橋接。 + +### 2.3 SAT 依賴解析 (SAT Resolver) +* **原理**:將模組版本約束轉化為命題公式,使用 DPLL 算法配合 MRV/LCV 啟發式進行求解,自動推薦最優版本組合。 +* **v3.0 演進目標**: + * **衝突可視化**:當依賴無解時,生成直觀的衝突路徑圖,並提供「一鍵自動升級/降級」的修復建議。 + +--- + +## 3. 未來開發里程碑與優先級 (Milestones & Roadmap) + +我們將開發計劃分為四個階段,以確保項目的穩健推進: + +### 🟥 階段一:工業級穩定性與基礎建設 (P0) — *當前重點* +* **目標**:確保現有功能在各種極端環境下的穩定性,打通發布管道。 +* **核心任務**: + 1. **Maven Central 發布**:配置完整的 GPG 簽名與 Maven Central Portal 發布管道,使開發者能直接通過 Maven Central 引入 Francium API。 + 2. **套件管理器穩定化**:完成 `PackageManager` 與真實 Modrinth/CurseForge API 註冊表適配器的對接,確保 `francium install` 能夠穩定解析並下載模組。 + 3. **CI/CD 自動化發布**:完善 GitHub Actions,在發布 Tag 時自動構建 Windows (.exe)、macOS (.dmg) 與 Linux (.deb) 的原生安裝包。 + +### 🟨 階段二:開發者生態與工具鏈 (P1) +* **目標**:降低模組開發難度,吸引更多開發者加入 Francium 生態。 +* **核心任務**: + 1. **API Javadoc 自動化部署**:配置 GitHub Pages,使每次版本發布時自動更新並上線最新 API 的線上文檔。 + 2. **Gradle 插件 (Francium Gradle Plugin)**:開發專屬的 Gradle 構建插件,自動生成 `francium.json` 並簡化開發調試流程。 + 3. **模組開發模板 (Template Project)**:完善 `francium-mod-template`,提供開箱即用的開發環境。 + +### 🟩 階段三:Mapping 數據庫社群化 (P2) +* **目標**:通過社群力量快速擴充跨版本映射數據庫,實現版本適配的良性循環。 +* **核心任務**: + 1. **開放社群貢獻工作流 (Community Contribution Workflow)**: + * 建立一個基於 GitHub Issues/PRs 的半自動化審核系統。 + * 開發者只需提交 API 映射的 JSON 片段,CI 會自動運行驗證測試(如現有的 83 項 Mapping 測試),通過後自動合併至主數據庫。 + 2. **自動化映射生成工具**:開發一個 CLI 工具,輸入兩個不同版本的 Minecraft 核心 JAR,自動比對並生成種子映射數據。 + +### 🟦 階段四:AI 橋接精準度躍升 (P3) +* **目標**:引入機器學習技術,使 AI 版本橋接具備自我學習與高精度預測能力。 +* **核心任務**: + 1. **注意力預測模型 (Attention Predictor)**:基於 Transformer 架構,分析方法上下文與調用圖特徵,預測跨版本重命名方法的相似度。 + 2. **本地推論引擎整合**:在 `francium-ai-bridge` 中引入 `onnxruntime` 依賴,實現無網路環境下的高精度本地預測。 + +--- + +## 4. 技術選型與依賴管理 (Tech Stack) + +為保持加載器的輕量與高效,我們嚴格控制外部依賴: + +| 模組 | 核心技術 | 外部依賴 | 說明 | +|------|----------|----------|------| +| **francium-core** | Java 21, ForkJoin | 無 | 保持核心加載器的極致輕量與零依賴 | +| **francium-ai-bridge** | ASM 9.6, ONNX Runtime | `org.ow2.asm:asm` | 字節碼解析與本地 AI 推論 | +| **francium-resolver** | DPLL 算法 | 無 | 純 Java 實現的 SAT 求解器 | +| **francium-manager** | Java HttpClient | `com.google.code.gson:gson` | 輕量化套件管理與 JSON 解析 | +| **francium-profiler** | Java Instrument, JVM TI | 無 | 記憶體洩漏偵測與自適應 GC | +| **francium-server** | Netty | `io.netty:netty-all` | 高性能伺服器同步協議 | + +--- + +## 5. 風險評估與應對策略 (Risk & Mitigation) + +### 5.1 Minecraft 核心架構劇烈變更 +* **風險**:Mojang 在未來大版本中重構底層引擎(如將 Java 換成其他語言,或徹底重構註冊表系統),導致現有 Mapping 失效。 +* **應對**:Francium 的 AI Bridge 設計為**架構無關**。只要字節碼語義結構存在相似性,AI 就能通過重新訓練特徵權重來適應新的架構。此外,我們將保持與 Mojang 官方映射(Mojmap)的即時同步。 + +### 5.2 AI 橋接帶來的效能損耗 +* **風險**:在啟動時進行字節碼分析與動態適配器生成,可能會抵消 DAG 並行加載帶來的加速效果。 +* **應對**: + 1. **極速本地快取**:引入 SHA-256 雜湊驗證,只有在模組 JAR 變更或 Minecraft 版本更新時才進行重新分析,二次啟動時直接加載已生成的適配器 JAR。 + 2. **多執行緒分析**:將字節碼分析任務納入 ForkJoin 執行緒池,與模組掃描並行執行。 + +--- + +## 6. 總結 (Conclusion) + +Francium Mod Loader 代表了 Minecraft 模組加載技術的未來。通過這份開發計劃,我們將在確保**核心穩定性 (P0)** 的前提下,逐步構建**開發者生態 (P1)**、**社群化數據庫 (P2)** 與 **AI 智慧橋接 (P3)**。 + +我們相信,Francium 將徹底打破 Minecraft 版本斷層的魔咒,為玩家與開發者帶來前所未有的極速、智慧與相容體驗。