From 656edf9500d363f1b394bf560d52515277c835e9 Mon Sep 17 00:00:00 2001 From: rsdadada Date: Sat, 18 Jul 2026 16:54:14 +0800 Subject: [PATCH 1/8] docs: design 1.20.1 incremental sync --- ...-18-1.20.1-26.1-incremental-sync-design.md | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-18-1.20.1-26.1-incremental-sync-design.md diff --git a/docs/superpowers/specs/2026-07-18-1.20.1-26.1-incremental-sync-design.md b/docs/superpowers/specs/2026-07-18-1.20.1-26.1-incremental-sync-design.md new file mode 100644 index 0000000..55e1732 --- /dev/null +++ b/docs/superpowers/specs/2026-07-18-1.20.1-26.1-incremental-sync-design.md @@ -0,0 +1,134 @@ +# 1.20.1 与 26.1 增量同步设计 + +## 背景与基线 + +当前目标分支以 `origin/1.20.1` 的 `e538403` 为基线。此前的同步提交 `c2bbc3e` 对应 26.1 的 `3598405`(tag `0.7.8-beta-26.1`),因此本次只评估 `3598405..origin/26.1/dev` 的后续增量。 + +其中适用于 1.20.1 的功能变化来自: + +- `8a180c9`:合并游泳/爬行禁用语义,增加鞘翅飞行禁用,并更新相关配置名、快捷键说明和翻译。 +- `120cf42`:在模型视图捕获期间暂停 Entity Model Features(EMF)动画。 +- `a0e473e`:恢复同名 Disable 配置草稿的实时预览。 + +`e538403` 是同步后加入的 1.20.1 专属 YSM 投影采样修复。本次不得修改或回退其 `YSMCompat`、`TetrahedralProbeSet` 及相关调用路径。 + +## 目标 + +在保持 Minecraft 1.20.1、Java 17、Architectury、Fabric 和 Forge 兼容的前提下,按 26.1 当前语义回移上述三项功能;保留现有 1.20.1 用户配置和 YSM 专属行为;将模组版本更新为 `0.7.9-beta`。 + +## 范围 + +预计修改: + +- `gradle.properties` +- `common/src/main/java/com/xtracr/realcamera/KeyMappings.java` +- `common/src/main/java/com/xtracr/realcamera/compat/CompatibilityHelper.java` +- `common/src/main/java/com/xtracr/realcamera/compat/DisableHelper.java` +- `common/src/main/java/com/xtracr/realcamera/config/ConfigScreen.java` +- `common/src/main/java/com/xtracr/realcamera/config/ModConfig.java` +- `common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java` +- `common/src/main/resources/assets/realcamera/lang/en_us.json` +- `common/src/main/resources/assets/realcamera/lang/zh_cn.json` + +明确排除: + +- README、Issue 模板和 CI 工作流更新。 +- Gradle 9.6、Java 25、NeoForge/26.1 构建结构及渲染管线。 +- 26.1 的 `StreamCodec` 清理;1.20.1 继续使用现有 `FriendlyByteBuf` 实现。 +- `rerenderModel` 翻译清理;该配置仍是 1.20.1 合理存在的版本差异。 +- 与本次三项功能无关的重构、格式化或兼容分支改动。 + +## 设计 + +### 禁用状态与配置迁移 + +运行语义与 26.1 对齐: + +- 游泳/爬行统一由 `Player.isVisuallySwimming()` 判断,并继续应用退出延迟。 +- 绑定模式新增 `disableWhenFlying`,由 `Player.isFallFlying()` 判断,不应用退出延迟。 +- 配置界面删除独立的 crawling 开关,增加 flying 开关。 +- `outTick` 的现行名称改为 `exitTick`,配置界面和中英文翻译键同步改名。 + +为兼容既有 1.20.1 配置,`ModConfig` 保留可迁移的废弃字段: + +- `Classic`:新字段 `int exitTick = 13`;旧字段 `Integer outTick`、`Integer swimOutTick`。 +- `Binding`:新字段 `boolean disableWhenFlying = false`、`int exitTick = 13`;旧字段 `Boolean disableWhenCrawling`、`Integer outTick`、`Integer swimOutTick`。 + +迁移规则必须确定且可重复: + +1. 若 `exitTick != 13`,保留该值。 +2. 若 `exitTick == 13` 且 `outTick != null`,迁移 `outTick`。 +3. 否则若 `exitTick == 13` 且 `swimOutTick != null`,迁移 `swimOutTick`。 +4. 将最终 `exitTick` 限制在 `[0, 40]`,随后把两个旧 tick 字段置空。 +5. 若旧 `disableWhenCrawling == true`,以逻辑或合并到 `disableWhenSwimming`,随后将旧字段置空。 + +Gson 默认不输出空字段,因此下一次正常保存会清理已迁移的旧键。现有公开 getter 保留为废弃别名,并新增以 `ExitTick`、`Flying` 命名的访问方法,避免不必要的二进制/源码兼容破坏。 + +### EMF 模型视图兼容 + +`CompatibilityHelper.initialize` 在检测到 `entity_model_features` 后: + +1. 通过反射加载 `traben.entity_model_features.EMFAnimationApi`。 +2. 要求 `getApiVersion() >= 9`。 +3. 通过擦除后的 `registerPauseCondition(Function.class)` 注册条件,返回当前模型视图捕获标记。 + +反射代码使用 Java 17 可编译的普通 lambda 参数名。类缺失、API 过旧、签名变化或调用失败时仅记录兼容警告,不阻止 RealCamera 启动。 + +1.20.1 没有 26.1 的 `captureRotatedEntity`/submit 管线,因此在 `ModelViewScreen.renderModelViewArea` 的实际实体捕获边界设置标记。进入时保存旧值并设为 `true`,在现有 `finally` 中恢复旧值以及实体旋转状态。这样既与 26.1 的作用域一致,也能在异常或未来嵌套捕获时正确恢复。 + +### Disable 草稿实时预览 + +构造临时 `BindTarget` 时: + +1. 复制当前正式 `disableConfigs`。 +2. 根据当前名称、纹理 ID、模式和矩形控件构造草稿。 +3. 仅当副本中存在同名条目时,用草稿替换该条目。 +4. 使用副本生成 `BindTarget`,不修改正式列表。 + +因此编辑既有 Disable 配置会立即反映在预览、导出和临时目标中;新建或改名草稿不会未经确认自动加入。正式保存仍由现有名称、纹理 ID 校验和 `syncDisableDraft` 负责。 + +保存目标后,仅在当前页面不是 `DISABLE` 时重新加载已保存目标;Disable 页面保留当前草稿编辑状态。这与 26.1 最终保存语义一致。 + +### 快捷键与翻译 + +同步 `8a180c9` 中与功能相关的中英文变更: + +- 上、下、左、右、前、后的注册/展示顺序与 26.1 对齐。 +- 补充旋转方向说明。 +- 添加 `disableWhenFlying`,移除独立 `disableWhenCrawling`。 +- 将 `outTick` 翻译键改为 `exitTick`。 +- 将手持物品禁用渲染的文案明确为身体渲染。 + +只修改 `en_us.json` 和 `zh_cn.json`,与源提交的翻译范围一致。 + +### 版本 + +仅把 1.20.1 的 `mod_version` 从 `0.7.8-beta` 更新为 `0.7.9-beta`。Gradle wrapper、Minecraft、加载器及依赖版本保持不变。 + +## 错误处理与稳定性 + +- 所有可选 EMF 访问均保持反射和独立 `try/catch`,无 EMF 时不解析其类。 +- EMF 捕获标记必须在 `finally` 中恢复进入前的值。 +- 配置迁移不得因缺失旧字段产生空指针,也不得覆盖非默认的新 tick 值。 +- Disable 预览只操作列表副本,避免未保存草稿污染运行配置或磁盘配置。 +- YSM 专属文件和投影状态恢复逻辑不改动。 + +## 验证 + +自动与定向验证: + +1. 使用 JDK 17;Windows 本地关闭 Gradle 并行,避免并行打包造成 JAR 文件锁竞争。 +2. 用旧 JSON 样例验证 `outTick`、`swimOutTick`、`disableWhenCrawling` 的加载、迁移和再次序列化,并验证新值优先级与范围限制。 +3. 串行执行 Common、Fabric、Forge 编译和完整 `build`,确认两个 remapped JAR 均生成。 +4. 核验正式 EMF 1.20.1 Fabric/Forge JAR 中 API v9 的类名与反射方法签名。 +5. 执行 `git diff --check` 和文件范围审计。 +6. 对 `YSMCompat.java`、`TetrahedralProbeSet.java` 与基线 `e538403` 做零差异检查。 + +运行时验证(可自动执行到环境允许的程度,其余明确交接): + +- Fabric、Forge 无 EMF 启动。 +- EMF 3.2+ 下打开模型视图时动画稳定,关闭后世界动画恢复。 +- 编辑同名 Disable 配置时无需保存即可预览,保存并重开后内容一致。 +- YSM 1.20.1 多角度绑定继续使用专属投影采样,不恢复旧侧面/roll 补偿要求。 + +仓库当前没有自动化测试框架。本次不为回移单独引入 JUnit/Mock 依赖;构建成功不被表述为 EMF/YSM 运行时场景已经验证。 From d7d29634f34f5c2b6b2e1c931365850f4978f203 Mon Sep 17 00:00:00 2001 From: rsdadada Date: Sat, 18 Jul 2026 17:53:08 +0800 Subject: [PATCH 2/8] docs: plan 1.20.1 incremental sync --- ...2026-07-18-1.20.1-26.1-incremental-sync.md | 777 ++++++++++++++++++ 1 file changed, 777 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-18-1.20.1-26.1-incremental-sync.md diff --git a/docs/superpowers/plans/2026-07-18-1.20.1-26.1-incremental-sync.md b/docs/superpowers/plans/2026-07-18-1.20.1-26.1-incremental-sync.md new file mode 100644 index 0000000..fc20b8a --- /dev/null +++ b/docs/superpowers/plans/2026-07-18-1.20.1-26.1-incremental-sync.md @@ -0,0 +1,777 @@ +# 1.20.1 / 26.1 Incremental Sync Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Backport the applicable post-`3598405` behavior from 26.1 to Minecraft 1.20.1 while preserving the branch-specific `e538403` YSM projection sampler and existing user configuration. + +**Architecture:** Apply semantic, file-level adaptations instead of merging branch trees. Align disable-state behavior, EMF pause registration, and Disable draft preview with 26.1; isolate 1.20.1 differences to Gson migration aliases, Java 17 syntax, and the legacy render capture boundary. + +**Tech Stack:** Java 17, Minecraft 1.20.1, Architectury Loom 1.10-SNAPSHOT, Fabric Loader 0.15.10/Fabric API 0.92.2, Forge 47.3.1, Gradle 8.14, Gson, Cloth Config. + +## Global Constraints + +- Base all implementation work on `e538403` plus the approved design/plan commits on `codex/1.20.1-incremental-sync-26.1`. +- Keep `common/src/main/java/com/xtracr/realcamera/compat/YSMCompat.java` and `common/src/main/java/com/xtracr/realcamera/compat/TetrahedralProbeSet.java` byte-for-byte unchanged from `e538403`. +- Do not change README files, Issue templates, CI workflows, Gradle wrapper, Minecraft/loader/dependency versions, mixins, access wideners, or `ConfigCodec`. +- Keep Java source compatible with release 17; do not copy 26.1 unnamed `_` lambda parameters or Java 25 syntax. +- Preserve old JSON values from `outTick`, `swimOutTick`, and `disableWhenCrawling`, then omit migrated nullable aliases on the next save. +- Keep `rerenderModel` code and translation keys because they remain valid on 1.20.1. +- Do not add persistent JUnit, Mock, or other test-framework dependencies. +- Run Gradle serially on Windows (`--no-parallel`) to avoid the observed output-JAR lock race. + +## File Structure + +- `ModConfig.java`: owns new config names, legacy aliases, deterministic migration, and compatibility getters. +- `DisableHelper.java`: consumes the migrated config and implements the 26.1 swimming/crawling/flying predicates. +- `ConfigScreen.java`: exposes only the current `exitTick` and `disableWhenFlying` settings. +- `KeyMappings.java`: aligns direction registration order with 26.1. +- `en_us.json`, `zh_cn.json`: provide current config and key labels without deleting 1.20.1-only labels. +- `CompatibilityHelper.java`: reflects into optional EMF API v9 and owns the model-view capture flag. +- `ModelViewScreen.java`: scopes the EMF flag and overlays the current same-name Disable draft into temporary targets. +- `gradle.properties`: advances only the RealCamera mod version. + +--- + +### Task 1: Disable-state schema, migration, and runtime predicates + +**Files:** +- Modify: `common/src/main/java/com/xtracr/realcamera/config/ModConfig.java:116-206,262-362` +- Modify: `common/src/main/java/com/xtracr/realcamera/compat/DisableHelper.java:27-64` +- Modify: `common/src/main/java/com/xtracr/realcamera/config/ConfigScreen.java:55-69,194-213` +- Temporarily create, run, then delete: `common/src/test/java/com/xtracr/realcamera/config/ModConfigMigrationCheck.java` +- Temporarily create, run, then delete: `.superpowers/run-migration-check.gradle` + +**Interfaces:** +- Consumes: Gson field-name deserialization and existing `ModConfig.clamp()` load path. +- Produces: `getClassicExitTick(): int`, `getBindingExitTick(): int`, `bindingDisableWhenFlying(): boolean`; deprecated aliases continue to return current values. + +- [ ] **Step 1: Add a dependency-free migration assertion program** + +Create the temporary test class with this complete content: + +```java +package com.xtracr.realcamera.config; + +import com.google.gson.Gson; + +public final class ModConfigMigrationCheck { + private static final Gson GSON = new Gson(); + + public static void main(String[] args) { + migratesLegacyFields(); + preservesNonDefaultCurrentFields(); + clampsMigratedValues(); + } + + private static void migratesLegacyFields() { + ModConfig config = read(""" + { + "classic": {"outTick": 7}, + "binding": { + "outTick": 9, + "disableWhenCrawling": true, + "disableWhenSwimming": false + } + } + """); + require(config.classic.exitTick == 7, "classic outTick migration"); + require(config.binding.exitTick == 9, "binding outTick migration"); + require(config.binding.disableWhenSwimming, "crawling-to-swimming migration"); + require(config.classic.outTick == null && config.classic.swimOutTick == null, "classic aliases cleared"); + require(config.binding.outTick == null && config.binding.swimOutTick == null, "binding tick aliases cleared"); + require(config.binding.disableWhenCrawling == null, "crawling alias cleared"); + String saved = GSON.toJson(config); + require(!saved.contains("outTick"), "saved JSON omits outTick"); + require(!saved.contains("swimOutTick"), "saved JSON omits swimOutTick"); + require(!saved.contains("disableWhenCrawling"), "saved JSON omits disableWhenCrawling"); + } + + private static void preservesNonDefaultCurrentFields() { + ModConfig config = read(""" + { + "classic": {"exitTick": 21, "outTick": 7}, + "binding": {"exitTick": 22, "outTick": 9} + } + """); + require(config.classic.exitTick == 21, "current classic exitTick wins"); + require(config.binding.exitTick == 22, "current binding exitTick wins"); + } + + private static void clampsMigratedValues() { + ModConfig config = read(""" + { + "classic": {"outTick": 99}, + "binding": {"swimOutTick": -5} + } + """); + require(config.classic.exitTick == 40, "classic exitTick upper clamp"); + require(config.binding.exitTick == 0, "binding exitTick lower clamp"); + require(!config.binding.disableWhenFlying, "flying defaults to false"); + } + + private static ModConfig read(String json) { + ModConfig config = GSON.fromJson(json, ModConfig.class); + config.clamp(); + return config; + } + + private static void require(boolean condition, String label) { + if (!condition) throw new AssertionError(label); + } +} +``` + +Create the temporary Gradle init script: + +```groovy +gradle.projectsEvaluated { + def commonProject = gradle.rootProject.project(':common') + commonProject.tasks.register('runMigrationCheck', JavaExec) { + dependsOn(commonProject.tasks.named('testClasses')) + classpath = commonProject.sourceSets.test.runtimeClasspath + mainClass = 'com.xtracr.realcamera.config.ModConfigMigrationCheck' + } +} +``` + +- [ ] **Step 2: Run the assertion program and verify the red state** + +Run: + +```powershell +$realCameraJdk17 = 'C:\Program Files\Eclipse Adoptium\jdk-17.0.16.8-hotspot' +$env:JAVA_HOME = $realCameraJdk17 +$env:Path = "$realCameraJdk17\bin;$env:Path" +.\gradlew.bat --no-daemon --no-parallel -I .superpowers/run-migration-check.gradle :common:runMigrationCheck +``` + +Expected: `:common:compileTestJava` fails because `exitTick`, nullable legacy aliases, and `disableWhenFlying` do not exist yet. + +- [ ] **Step 3: Replace the public config accessors** + +In `ModConfig`, replace the current tick/crawling accessor blocks with: + +```java +public int getClassicExitTick() { + return classic.exitTick; +} + +@Deprecated +public int getClassicOutTick() { + return getClassicExitTick(); +} + +@Deprecated +public int getClassicSwimOutTick() { + return getClassicExitTick(); +} + +@Deprecated +public boolean bindingDisableWhenCrawling() { + return bindingDisableWhenSwimming(); +} + +public boolean bindingDisableWhenFlying() { + return binding.disableWhenFlying; +} + +public int getBindingExitTick() { + return binding.exitTick; +} + +@Deprecated +public int getBindingOutTick() { + return getBindingExitTick(); +} + +@Deprecated +public int getBindingSwimOutTick() { + return getBindingExitTick(); +} +``` + +Keep the existing `classicDisableWhenSneaking`, `classicDisableWhenSwimming`, `bindingDisableWhenSneaking`, and `bindingDisableWhenSwimming` methods unchanged around these replacements. + +- [ ] **Step 4: Implement deterministic legacy migration in both config sections** + +Use these exact field layouts and migration blocks. + +For `Classic`: + +```java +public boolean disableWhenSneaking = false; +public boolean disableWhenSwimming = false; +public int exitTick = 13; +@Deprecated +public Integer outTick = null; +@Deprecated +public Integer swimOutTick = null; +``` + +At the start of `Classic.clamp()`, after the null `adjustMode` repair: + +```java +if (exitTick == 13) { + if (outTick != null) exitTick = outTick; + else if (swimOutTick != null) exitTick = swimOutTick; +} +outTick = null; +swimOutTick = null; +exitTick = Mth.clamp(exitTick, 0, 40); +``` + +Remove the old `swimOutTick` migration and `outTick` clamp. + +For `Binding`: + +```java +@Deprecated +public Boolean disableWhenCrawling = null; +public boolean disableWhenSneaking = false; +public boolean disableWhenSwimming = false; +public boolean disableWhenFlying = false; +public int exitTick = 13; +@Deprecated +public Integer outTick = null; +@Deprecated +public Integer swimOutTick = null; +``` + +In `Binding.clamp()`, after screen-key repair and before legacy binding-mode migration: + +```java +if (Boolean.TRUE.equals(disableWhenCrawling)) disableWhenSwimming = true; +disableWhenCrawling = null; +if (exitTick == 13) { + if (outTick != null) exitTick = outTick; + else if (swimOutTick != null) exitTick = swimOutTick; +} +outTick = null; +swimOutTick = null; +exitTick = Mth.clamp(exitTick, 0, 40); +``` + +Remove the old `swimOutTick` migration and `outTick` clamp; retain all other repairs and clamps. + +- [ ] **Step 5: Align runtime predicates with 26.1** + +Replace the three swimming/crawling registrations in `DisableHelper` with: + +```java +MAIN_FEATURE.registerOrInBinding(player -> ConfigFile.config().bindingDisableWhenSwimming() + && checkCondition(player, player.isVisuallySwimming(), ConfigFile.config().getBindingExitTick())); +MAIN_FEATURE.registerOrInClassic(player -> ConfigFile.config().classicDisableWhenSwimming() + && checkCondition(player, player.isVisuallySwimming(), ConfigFile.config().getClassicExitTick())); +MAIN_FEATURE.registerOrInBinding(player -> ConfigFile.config().bindingDisableWhenFlying() && player.isFallFlying()); +``` + +Rename the private delay parameter without changing behavior: + +```java +private static boolean checkCondition(Player player, boolean condition, int exitDelayTicks) { + if (condition) { + exitTick = player.tickCount; + return true; + } + if (exitTick > 0) { + int elapsedTicks = player.tickCount - exitTick; + if (elapsedTicks <= exitDelayTicks) return true; + exitTick = 0; + } + return false; +} +``` + +- [ ] **Step 6: Align the Cloth Config screen** + +Use `exitTick` for both existing tick fields: + +```java +classic.addEntry(entryBuilder.startIntField(LocUtil.CONFIG_OPTION("exitTick"), config.classic.exitTick) + .setDefaultValue(13) + .setMin(0) + .setMax(40) + .setTooltip(LocUtil.CONFIG_TOOLTIP("exitTick")) + .setSaveConsumer(i -> config.classic.exitTick = i) + .build()); +``` + +Replace the binding crawling toggle and old tick field with: + +```java +disableConfig.add(entryBuilder.startBooleanToggle(LocUtil.CONFIG_OPTION("disableWhenSwimming"), config.binding.disableWhenSwimming) + .setDefaultValue(false) + .setSaveConsumer(b -> config.binding.disableWhenSwimming = b) + .build()); +disableConfig.add(entryBuilder.startBooleanToggle(LocUtil.CONFIG_OPTION("disableWhenFlying"), config.binding.disableWhenFlying) + .setDefaultValue(false) + .setSaveConsumer(b -> config.binding.disableWhenFlying = b) + .build()); +disableConfig.add(entryBuilder.startIntField(LocUtil.CONFIG_OPTION("exitTick"), config.binding.exitTick) + .setDefaultValue(13) + .setMin(0) + .setMax(40) + .setTooltip(LocUtil.CONFIG_TOOLTIP("exitTick")) + .setSaveConsumer(i -> config.binding.exitTick = i) + .build()); +``` + +- [ ] **Step 7: Run the real migration assertions and compile common code** + +Run the Step 2 command again, followed by: + +```powershell +.\gradlew.bat --no-daemon --no-parallel :common:compileJava +``` + +Expected: `runMigrationCheck` exits 0 with no assertion errors; `:common:compileJava` succeeds under Java 17. + +- [ ] **Step 8: Remove temporary verification files and commit** + +Delete the two temporary files with `apply_patch`, then run: + +```powershell +git status --short +git add common/src/main/java/com/xtracr/realcamera/config/ModConfig.java common/src/main/java/com/xtracr/realcamera/compat/DisableHelper.java common/src/main/java/com/xtracr/realcamera/config/ConfigScreen.java +git commit -m "Sync 1.20.1 disable state behavior" +``` + +Expected: the temporary files are absent from both the worktree and commit. + +--- + +### Task 2: Key labels, translations, and release version + +**Files:** +- Modify: `common/src/main/java/com/xtracr/realcamera/KeyMappings.java:26-36` +- Modify: `common/src/main/resources/assets/realcamera/lang/en_us.json:16-27,50-72` +- Modify: `common/src/main/resources/assets/realcamera/lang/zh_cn.json:16-27,50-72` +- Modify: `gradle.properties:11` + +**Interfaces:** +- Consumes: `exitTick` and `disableWhenFlying` names produced by Task 1. +- Produces: valid English/Chinese UI keys and `0.7.9-beta` artifact version. + +- [ ] **Step 1: Verify the resource contract is red** + +Run: + +```powershell +$english = Get-Content -Raw common/src/main/resources/assets/realcamera/lang/en_us.json +$properties = Get-Content -Raw gradle.properties +if ($english -notmatch 'disableWhenFlying') { throw 'missing disableWhenFlying translation' } +if ($english -notmatch 'config\.option\.xtracr_realcamera\.exitTick') { throw 'missing exitTick translation' } +if ($properties -notmatch 'mod_version=0\.7\.9-beta') { throw 'version not synchronized' } +``` + +Expected: the command fails on the first missing synchronized value. + +- [ ] **Step 2: Align key registration order** + +Replace the six direction registrations with: + +```java +createKeyMapping("adjustUP", client -> ConfigFile.config().adjustOffsetY(1)); +createKeyMapping("adjustDOWN", client -> ConfigFile.config().adjustOffsetY(-1)); +createKeyMapping("adjustLEFT", client -> ConfigFile.config().adjustOffsetZ(1)); +createKeyMapping("adjustRIGHT", client -> ConfigFile.config().adjustOffsetZ(-1)); +createKeyMapping("adjustFRONT", client -> ConfigFile.config().adjustOffsetX(1)); +createKeyMapping("adjustBACK", client -> ConfigFile.config().adjustOffsetX(-1)); +``` + +- [ ] **Step 3: Apply the English translation replacements** + +Make these exact key/value changes while retaining all unrelated keys, including `rerenderModel`: + +```json +"config.option.xtracr_realcamera.disableRenderItems": "Disable body rendering when holding", +"config.option.xtracr_realcamera.disableWhenSneaking": "Disable main feature when sneaking", +"config.option.xtracr_realcamera.disableWhenSwimming": "Disable main feature when swimming(crawling)", +"config.option.xtracr_realcamera.disableWhenFlying": "Disable main feature when flying", +"config.option.xtracr_realcamera.exitTick": "Restriction exit delay (in ticks)", +"config.tooltip.xtracr_realcamera.exitTick": "The time before re-enabling main feature after exiting restricted states (e.g., swimming, crawling) (1 tick = 0.05s)", +"key.xtracr_realcamera.adjustUP": "Adjust UP (Rotate Up)", +"key.xtracr_realcamera.adjustDOWN": "Adjust DOWN (Rotate Down)", +"key.xtracr_realcamera.adjustLEFT": "Adjust LEFT (Rotate Left)", +"key.xtracr_realcamera.adjustRIGHT": "Adjust RIGHT (Rotate Right)", +"key.xtracr_realcamera.adjustFRONT": "Adjust FRONT (Roll Right)", +"key.xtracr_realcamera.adjustBACK": "Adjust BACK (Roll Left)" +``` + +Remove only the obsolete `disableWhenCrawling` and `outTick` translation entries replaced above. + +- [ ] **Step 4: Apply the Chinese translation replacements** + +Make the corresponding exact changes: + +```json +"config.option.xtracr_realcamera.disableRenderItems": "在手持...时禁用身体渲染", +"config.option.xtracr_realcamera.disableWhenSneaking": "潜行时禁用主要功能", +"config.option.xtracr_realcamera.disableWhenSwimming": "游泳(爬行)时禁用主要功能", +"config.option.xtracr_realcamera.disableWhenFlying": "飞行时禁用主要功能", +"config.option.xtracr_realcamera.exitTick": "禁用状态退出延迟(单位:tick)", +"config.tooltip.xtracr_realcamera.exitTick": "玩家停止某些状态(如游泳、爬行)后重启主要功能的时间(1 tick = 0.05s)", +"key.xtracr_realcamera.adjustUP": "向上调整(向上旋转)", +"key.xtracr_realcamera.adjustDOWN": "向下调整(向下旋转)", +"key.xtracr_realcamera.adjustLEFT": "向左调整(向左旋转)", +"key.xtracr_realcamera.adjustRIGHT": "向右调整(向右旋转)", +"key.xtracr_realcamera.adjustFRONT": "向前调整(向右滚转)", +"key.xtracr_realcamera.adjustBACK": "向后调整(向左滚转)" +``` + +Remove only the replaced `disableWhenCrawling` and `outTick` entries. + +- [ ] **Step 5: Advance only the mod version** + +Change: + +```properties +mod_version=0.7.9-beta +``` + +Leave Gradle wrapper, Minecraft, loader, API, Forge, Cloth Config, and Mod Menu versions unchanged. + +- [ ] **Step 6: Verify JSON syntax and the synchronized resource contract** + +Run the Step 1 command again, then: + +```powershell +Get-Content -Raw common/src/main/resources/assets/realcamera/lang/en_us.json | ConvertFrom-Json | Out-Null +Get-Content -Raw common/src/main/resources/assets/realcamera/lang/zh_cn.json | ConvertFrom-Json | Out-Null +rg -n 'disableWhenCrawling|xtracr_realcamera\.outTick' common/src/main/resources/assets/realcamera/lang common/src/main/java +``` + +Expected: both JSON parses succeed; `rg` finds only intentional deprecated Java aliases and no obsolete UI/resource references. + +- [ ] **Step 7: Commit the resource and version sync** + +```powershell +git add gradle.properties common/src/main/java/com/xtracr/realcamera/KeyMappings.java common/src/main/resources/assets/realcamera/lang/en_us.json common/src/main/resources/assets/realcamera/lang/zh_cn.json +git commit -m "Sync 1.20.1 config labels and version" +``` + +--- + +### Task 3: Entity Model Features pause compatibility + +**Files:** +- Modify: `common/src/main/java/com/xtracr/realcamera/compat/CompatibilityHelper.java:13-68` +- Modify: `common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java:556-578` + +**Interfaces:** +- Consumes: optional EMF class `traben.entity_model_features.EMFAnimationApi` with `getApiVersion()` and erased `registerPauseCondition(Function)`. +- Produces: `CompatibilityHelper.isRenderInScreen: boolean`, true only during model-view entity capture and restored afterward. + +- [ ] **Step 1: Verify the compatibility contract is red** + +Run: + +```powershell +$compat = Get-Content -Raw common/src/main/java/com/xtracr/realcamera/compat/CompatibilityHelper.java +$screen = Get-Content -Raw common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java +if ($compat -notmatch 'EMFAnimationApi') { throw 'EMF API registration is missing' } +if ($screen -notmatch 'CompatibilityHelper\.isRenderInScreen') { throw 'EMF capture scope is missing' } +``` + +Expected: the command fails because neither side of the integration exists. + +- [ ] **Step 2: Register the optional EMF pause condition** + +Add: + +```java +import java.util.function.Function; +``` + +Add the state field near the top of `CompatibilityHelper`: + +```java +public static boolean isRenderInScreen; +``` + +After the existing TACZ compatibility block, add: + +```java +if (isModLoaded("entity_model_features")) try { + Class emfAnimationApi = Class.forName("traben.entity_model_features.EMFAnimationApi"); + if ((int) emfAnimationApi.getMethod("getApiVersion").invoke(null) >= 9) { + Function pauseCondition = ignored -> isRenderInScreen; + Method registerPauseCondition = emfAnimationApi.getMethod("registerPauseCondition", Function.class); + registerPauseCondition.invoke(null, pauseCondition); + } else { + throw new IllegalStateException("EntityModelFeatures API is outdated; model-view animations may flicker"); + } +} catch (Exception e) { + RealCamera.LOGGER.warn("Compatibility with EntityModelFeatures is outdated: [{}] {}", e.getClass().getName(), e.getMessage()); +} +``` + +- [ ] **Step 3: Scope and restore the capture flag in the 1.20.1 render path** + +In `renderModelViewArea`, save and set the flag immediately before the existing `try`, then restore it first in `finally`: + +```java +boolean previousRenderInScreen = CompatibilityHelper.isRenderInScreen; +CompatibilityHelper.isRenderInScreen = true; +try { + entity.yBodyRot = 180.0f; + entity.setYRot(180.0f + (float) entityYawSlider.getNumber()); + entity.setXRot((float) entityPitchSlider.getNumber()); + entity.yHeadRot = entity.getYRot(); + entity.yHeadRotO = entity.getYRot(); + Vector3f offset = new Vector3f((float) modelX, (float) modelY, 0); + renderEntityWithAnalyser(graphics, x1, y1, x2, y2, modelScale, offset, quaternionf, entity); +} finally { + CompatibilityHelper.isRenderInScreen = previousRenderInScreen; + entity.yBodyRot = entityBodyYaw; + entity.setYRot(entityYaw); + entity.setXRot(entityPitch); + entity.yHeadRotO = entityPrevHeadYaw; + entity.yHeadRot = entityHeadYaw; +} +``` + +Do not modify `ModelAnalyser`, mixins, or YSM code. + +- [ ] **Step 4: Verify Java 17 compilation and source contract** + +Run the Step 1 command again, then: + +```powershell +$realCameraJdk17 = 'C:\Program Files\Eclipse Adoptium\jdk-17.0.16.8-hotspot' +$env:JAVA_HOME = $realCameraJdk17 +$env:Path = "$realCameraJdk17\bin;$env:Path" +.\gradlew.bat --no-daemon --no-parallel :common:compileJava +``` + +Expected: source contract and compilation succeed; no `_ ->` syntax is present. + +- [ ] **Step 5: Verify the reflected API against current official 1.20.1 jars** + +Run this temporary-download inspection: + +```powershell +$realCameraHeaders = @{ 'User-Agent' = 'Codex/RealCamera-sync' } +$realCameraUri = 'https://api.modrinth.com/v2/project/entity-model-features/version?game_versions=%5B%221.20.1%22%5D' +$realCameraVersions = Invoke-RestMethod -Headers $realCameraHeaders -Uri $realCameraUri +$realCameraTargets = @() +$realCameraTargets += $realCameraVersions | Where-Object { $_.version_number -eq '3.2.4-fabric-1.20.1' } +$realCameraTargets += $realCameraVersions | Where-Object { $_.version_number -eq '3.2.4-forge-1.20.1' } +$realCameraTempRoot = [System.IO.Path]::GetTempPath() +$realCameraTempDir = Join-Path $realCameraTempRoot ('codex-realcamera-emf-' + [guid]::NewGuid().ToString('N')) +New-Item -ItemType Directory -Path $realCameraTempDir | Out-Null +try { + foreach ($version in $realCameraTargets) { + $file = $version.files | Where-Object { $_.primary } | Select-Object -First 1 + if (-not $file) { $file = $version.files[0] } + $jarPath = Join-Path $realCameraTempDir $file.filename + Invoke-WebRequest -Headers $realCameraHeaders -Uri $file.url -OutFile $jarPath + $api = & javap -classpath $jarPath -public traben.entity_model_features.EMFAnimationApi + if ($api -notmatch 'public static int getApiVersion\(\)') { throw "getApiVersion missing from $($file.filename)" } + if ($api -notmatch 'registerPauseCondition\(java\.util\.function\.Function') { throw "registerPauseCondition missing from $($file.filename)" } + } +} finally { + $resolved = [System.IO.Path]::GetFullPath($realCameraTempDir) + $tempRootResolved = [System.IO.Path]::GetFullPath($realCameraTempRoot) + if ((Test-Path -LiteralPath $resolved) -and $resolved.StartsWith($tempRootResolved, [System.StringComparison]::OrdinalIgnoreCase)) { + Remove-Item -LiteralPath $resolved -Recurse -Force + } +} +``` + +Expected: both Fabric and Forge 3.2.4 jars expose the reflected methods; the verified temporary directory is removed. + +- [ ] **Step 6: Commit EMF compatibility** + +```powershell +git add common/src/main/java/com/xtracr/realcamera/compat/CompatibilityHelper.java common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java +git commit -m "Add EMF model view compatibility on 1.20.1" +``` + +--- + +### Task 4: Live Disable draft preview + +**Files:** +- Modify: `common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java:273-288,396-420,654-667` + +**Interfaces:** +- Consumes: current widget values and persisted `List disableConfigs`. +- Produces: temporary `BindTarget` values with a same-name draft overlay; persisted lists remain unchanged until existing save validation succeeds. + +- [ ] **Step 1: Verify the draft-overlay contract is red** + +Run: + +```powershell +$screen = Get-Content -Raw common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java +if ($screen -notmatch 'currentDisableConfig') { throw 'draft overlay is missing' } +if ($screen -notmatch 'newDisableConfigs') { throw 'copied disable list is missing' } +if ($screen -notmatch 'Category\.DISABLE\) loadBindTarget\(bindTarget\)') { throw 'save reload guard is missing' } +``` + +Expected: the command fails because the current target always uses only the persisted list. + +- [ ] **Step 2: Preserve edit state after save** + +After `ConfigFile.save()` in the target save callback, add: + +```java +if (toggleCategoryButton.getValue() != Category.DISABLE) loadBindTarget(bindTarget); +``` + +Keep `initWidgets(page)` immediately after it. + +- [ ] **Step 3: Overlay the current same-name draft into a copied list** + +At the start of `genBindTarget()`, before `initOffsetPairs()`, add: + +```java +DisableConfig currentDisableConfig = new DisableConfig( + disabledNameField.getValue(), + disabledIdField.getValue(), + disableModeButton.getValue() == 0, + rectWidgets.stream().map(UVRectangleWidget::toUVRectangle).toList()); +List newDisableConfigs = new ArrayList<>(disableConfigs); +for (int i = 0; i < newDisableConfigs.size(); i++) { + if (newDisableConfigs.get(i).name().equals(currentDisableConfig.name())) { + newDisableConfigs.set(i, currentDisableConfig); + } +} +``` + +Change only the final constructor argument: + +```java +return new BindTarget(nameField.getValue(), textureIdField.getValue(), priorityField.getNumber(), depthField.getNumber(), + targetConfig, bindConfig, offsets, newDisableConfigs); +``` + +Do not append unmatched drafts and do not mutate `disableConfigs` in `genBindTarget()`. + +- [ ] **Step 4: Verify the source contract and compile all platform sources** + +Run the Step 1 command again, then: + +```powershell +$realCameraJdk17 = 'C:\Program Files\Eclipse Adoptium\jdk-17.0.16.8-hotspot' +$env:JAVA_HOME = $realCameraJdk17 +$env:Path = "$realCameraJdk17\bin;$env:Path" +.\gradlew.bat --no-daemon --no-parallel :common:compileJava :fabric:compileJava :forge:compileJava +``` + +Expected: source contract and all three compilation tasks succeed. + +- [ ] **Step 5: Commit live preview support** + +```powershell +git add common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java +git commit -m "Restore live disable preview on 1.20.1" +``` + +--- + +### Task 5: Full build, preservation audit, and handoff evidence + +**Files:** +- Verify only; no production file changes. + +**Interfaces:** +- Consumes: all outputs from Tasks 1-4. +- Produces: Java 17 build evidence, Fabric/Forge artifacts, exact scope report, and explicit runtime-validation handoff. + +- [ ] **Step 1: Confirm a clean task boundary and Java 17 toolchain** + +Run: + +```powershell +git status --short --branch +$realCameraJdk17 = 'C:\Program Files\Eclipse Adoptium\jdk-17.0.16.8-hotspot' +$env:JAVA_HOME = $realCameraJdk17 +$env:Path = "$realCameraJdk17\bin;$env:Path" +java -version +.\gradlew.bat --stop +``` + +Expected: no uncommitted source changes; Java reports 17.0.16; Gradle daemons stop cleanly. + +- [ ] **Step 2: Run the CI-equivalent build serially** + +Run: + +```powershell +$realCameraCommit = git rev-parse HEAD +.\gradlew.bat --no-daemon --no-parallel clean build "-PcommitSHA=$realCameraCommit" +``` + +Expected: `BUILD SUCCESSFUL`; Common, Fabric, and Forge build; tests report `NO-SOURCE`; no JAR lock failure. + +- [ ] **Step 3: Inspect both remapped artifacts** + +Run: + +```powershell +$fabricJar = Get-ChildItem fabric/build/libs -Filter 'realcamera-1.20.1-fabric-0.7.9-beta-*.jar' | Where-Object Name -NotMatch 'sources|shadow' | Select-Object -First 1 +$forgeJar = Get-ChildItem forge/build/libs -Filter 'realcamera-1.20.1-forge-0.7.9-beta-*.jar' | Where-Object Name -NotMatch 'sources|shadow' | Select-Object -First 1 +if (-not $fabricJar) { throw 'Fabric remapped jar missing' } +if (-not $forgeJar) { throw 'Forge remapped jar missing' } +if ((& jar tf $fabricJar.FullName) -notcontains 'fabric.mod.json') { throw 'Fabric metadata missing' } +if ((& jar tf $forgeJar.FullName) -notcontains 'META-INF/mods.toml') { throw 'Forge metadata missing' } +``` + +Expected: both artifact checks exit 0. + +- [ ] **Step 4: Audit whitespace, scope, and 1.20.1-only YSM preservation** + +Run: + +```powershell +git diff --check e538403..HEAD +git diff --exit-code e538403..HEAD -- common/src/main/java/com/xtracr/realcamera/compat/YSMCompat.java common/src/main/java/com/xtracr/realcamera/compat/TetrahedralProbeSet.java +git diff --name-only e538403..HEAD +``` + +Expected: no whitespace errors; YSM diff exits 0; the name list contains only the two superpowers documents and the nine approved production/resource files. + +- [ ] **Step 5: Review the semantic delta against the three source commits** + +Run: + +```powershell +git diff --stat e538403..HEAD +git diff e538403..HEAD -- common/src/main/java common/src/main/resources/assets/realcamera/lang gradle.properties +git show --stat --oneline 8a180c9 120cf42 a0e473e +``` + +Confirm manually from the displayed patch: + +- `8a180c9`: disable-state behavior, current config names, key order, and translations are present; issue-template and codec changes are absent. +- `120cf42`: EMF registration and capture flag are present at the 1.20.1 render boundary; no 26.1 submit/mixin code appears. +- `a0e473e`: same-name draft overlay and non-Disable reload guard are present. +- `e538403`: both YSM files and their projection-driven implementation remain untouched. + +- [ ] **Step 6: Record runtime checks that remain interactive** + +If an interactive client session is available, run each loader separately: + +```powershell +.\gradlew.bat --no-daemon --no-parallel :fabric:runClient +.\gradlew.bat --no-daemon --no-parallel :forge:runClient +``` + +For each loader, verify without EMF that the client reaches the main menu and Model View opens. With EMF 3.2.4 plus its required ETF dependency, verify that model-view animations stop flickering and resume after closing the screen. Verify an existing same-name Disable entry previews unsaved rectangle/mode edits. Treat YSM multi-angle binding as a required manual regression check when YSM is available. + +If interactive checks cannot be completed, state exactly which scenarios remain unverified; do not infer them from build success. + +## Final Review Checklist + +- [ ] Approved design requirements map to Tasks 1-5. +- [ ] No persistent temporary migration harness remains. +- [ ] No obsolete UI references to `outTick` or `disableWhenCrawling` remain. +- [ ] Deprecated config aliases migrate and serialize away. +- [ ] EMF reflection remains optional and Java 17 compatible. +- [ ] EMF capture state restores the previous value in `finally`. +- [ ] Disable preview uses a copy and replaces only an existing same-name entry. +- [ ] Fabric and Forge remapped artifacts both exist at version `0.7.9-beta`. +- [ ] YSM projection sampling files have zero diff from `e538403`. +- [ ] README/user documentation, templates, CI, wrapper, codecs, mixins, and unrelated code are unchanged. From 7b1a555781a13c834322e6c3684cc3172ab5ac32 Mon Sep 17 00:00:00 2001 From: rsdadada Date: Sat, 18 Jul 2026 18:21:53 +0800 Subject: [PATCH 3/8] Sync 1.20.1 disable state behavior --- .../realcamera/compat/DisableHelper.java | 14 ++--- .../realcamera/config/ConfigScreen.java | 20 +++---- .../xtracr/realcamera/config/ModConfig.java | 59 ++++++++++++++----- 3 files changed, 60 insertions(+), 33 deletions(-) diff --git a/common/src/main/java/com/xtracr/realcamera/compat/DisableHelper.java b/common/src/main/java/com/xtracr/realcamera/compat/DisableHelper.java index 558e6d4..072a227 100644 --- a/common/src/main/java/com/xtracr/realcamera/compat/DisableHelper.java +++ b/common/src/main/java/com/xtracr/realcamera/compat/DisableHelper.java @@ -27,9 +27,11 @@ public class DisableHelper { static { MAIN_FEATURE.registerOrInBinding(player -> ConfigFile.config().bindingDisableWhenSneaking() && player.isCrouching()); MAIN_FEATURE.registerOrInClassic(player -> ConfigFile.config().classicDisableWhenSneaking() && player.isCrouching()); - MAIN_FEATURE.registerOrInBinding(player -> ConfigFile.config().bindingDisableWhenSwimming() && checkCondition(player, player.isSwimming(), ConfigFile.config().getBindingOutTick())); - MAIN_FEATURE.registerOrInClassic(player -> ConfigFile.config().classicDisableWhenSwimming() && checkCondition(player, player.isSwimming(), ConfigFile.config().getClassicOutTick())); - MAIN_FEATURE.registerOrInBinding(player -> ConfigFile.config().bindingDisableWhenCrawling() && checkCondition(player, player.isVisuallyCrawling(), ConfigFile.config().getBindingOutTick())); + MAIN_FEATURE.registerOrInBinding(player -> ConfigFile.config().bindingDisableWhenSwimming() + && checkCondition(player, player.isVisuallySwimming(), ConfigFile.config().getBindingExitTick())); + MAIN_FEATURE.registerOrInClassic(player -> ConfigFile.config().classicDisableWhenSwimming() + && checkCondition(player, player.isVisuallySwimming(), ConfigFile.config().getClassicExitTick())); + MAIN_FEATURE.registerOrInBinding(player -> ConfigFile.config().bindingDisableWhenFlying() && player.isFallFlying()); MAIN_FEATURE.registerOrInBinding(player -> { Item mainHand = player.getMainHandItem().getItem(); Item offHand = player.getOffhandItem().getItem(); @@ -48,16 +50,14 @@ public class DisableHelper { }); } - private static boolean checkCondition(Player player, boolean condition, int outTick) { + private static boolean checkCondition(Player player, boolean condition, int exitDelayTicks) { if (condition) { exitTick = player.tickCount; return true; } if (exitTick > 0) { int elapsedTicks = player.tickCount - exitTick; - if (elapsedTicks <= outTick) { - return true; - } + if (elapsedTicks <= exitDelayTicks) return true; exitTick = 0; } return false; diff --git a/common/src/main/java/com/xtracr/realcamera/config/ConfigScreen.java b/common/src/main/java/com/xtracr/realcamera/config/ConfigScreen.java index f6399e0..084c146 100644 --- a/common/src/main/java/com/xtracr/realcamera/config/ConfigScreen.java +++ b/common/src/main/java/com/xtracr/realcamera/config/ConfigScreen.java @@ -60,12 +60,12 @@ public static Screen create(Screen parent) { .setDefaultValue(false) .setSaveConsumer(b -> config.classic.disableWhenSwimming = b) .build()); - classic.addEntry(entryBuilder.startIntField(LocUtil.CONFIG_OPTION("outTick"), config.classic.outTick) + classic.addEntry(entryBuilder.startIntField(LocUtil.CONFIG_OPTION("exitTick"), config.classic.exitTick) .setDefaultValue(13) .setMin(0) .setMax(40) - .setTooltip(LocUtil.CONFIG_TOOLTIP("outTick")) - .setSaveConsumer(i -> config.classic.outTick = i) + .setTooltip(LocUtil.CONFIG_TOOLTIP("exitTick")) + .setSaveConsumer(i -> config.classic.exitTick = i) .build()); classic.addEntry(entryBuilder.startDoubleField(LocUtil.CONFIG_OPTION("scale"), config.classic.scale) .setDefaultValue(8.0) @@ -196,20 +196,20 @@ public static Screen create(Screen parent) { .setDefaultValue(false) .setSaveConsumer(b -> config.binding.disableWhenSneaking = b) .build()); - disableConfig.add(entryBuilder.startBooleanToggle(LocUtil.CONFIG_OPTION("disableWhenCrawling"), config.binding.disableWhenCrawling) - .setDefaultValue(false) - .setSaveConsumer(b -> config.binding.disableWhenCrawling = b) - .build()); disableConfig.add(entryBuilder.startBooleanToggle(LocUtil.CONFIG_OPTION("disableWhenSwimming"), config.binding.disableWhenSwimming) .setDefaultValue(false) .setSaveConsumer(b -> config.binding.disableWhenSwimming = b) .build()); - disableConfig.add(entryBuilder.startIntField(LocUtil.CONFIG_OPTION("outTick"), config.binding.outTick) + disableConfig.add(entryBuilder.startBooleanToggle(LocUtil.CONFIG_OPTION("disableWhenFlying"), config.binding.disableWhenFlying) + .setDefaultValue(false) + .setSaveConsumer(b -> config.binding.disableWhenFlying = b) + .build()); + disableConfig.add(entryBuilder.startIntField(LocUtil.CONFIG_OPTION("exitTick"), config.binding.exitTick) .setDefaultValue(13) .setMin(0) .setMax(40) - .setTooltip(LocUtil.CONFIG_TOOLTIP("outTick")) - .setSaveConsumer(i -> config.binding.outTick = i) + .setTooltip(LocUtil.CONFIG_TOOLTIP("exitTick")) + .setSaveConsumer(i -> config.binding.exitTick = i) .build()); disableConfig.add(entryBuilder.startStrList(LocUtil.CONFIG_OPTION("disableMainFeatureItems"), config.binding.disableMainFeatureItems) .setDefaultValue(List.of()) diff --git a/common/src/main/java/com/xtracr/realcamera/config/ModConfig.java b/common/src/main/java/com/xtracr/realcamera/config/ModConfig.java index 5d95a3f..e30a665 100644 --- a/common/src/main/java/com/xtracr/realcamera/config/ModConfig.java +++ b/common/src/main/java/com/xtracr/realcamera/config/ModConfig.java @@ -122,13 +122,18 @@ public boolean classicDisableWhenSwimming() { return classic.disableWhenSwimming; } + public int getClassicExitTick() { + return classic.exitTick; + } + + @Deprecated public int getClassicOutTick() { - return classic.outTick; + return getClassicExitTick(); } @Deprecated public int getClassicSwimOutTick() { - return getClassicOutTick(); + return getClassicExitTick(); } public double getClassicX() { @@ -184,8 +189,9 @@ public boolean hideBindingFailureMessage() { return binding.hideFailureMessage; } + @Deprecated public boolean bindingDisableWhenCrawling() { - return binding.disableWhenCrawling; + return bindingDisableWhenSwimming(); } public boolean bindingDisableWhenSneaking() { @@ -196,13 +202,22 @@ public boolean bindingDisableWhenSwimming() { return binding.disableWhenSwimming; } + public boolean bindingDisableWhenFlying() { + return binding.disableWhenFlying; + } + + public int getBindingExitTick() { + return binding.exitTick; + } + + @Deprecated public int getBindingOutTick() { - return binding.outTick; + return getBindingExitTick(); } @Deprecated public int getBindingSwimOutTick() { - return getBindingOutTick(); + return getBindingExitTick(); } public int getBindResultRetentionFrames() { @@ -263,7 +278,9 @@ public static class Classic { public AdjustMode adjustMode = AdjustMode.CAMERA; public boolean disableWhenSneaking = false; public boolean disableWhenSwimming = false; - public int outTick = 13; + public int exitTick = 13; + @Deprecated + public Integer outTick = null; @Deprecated public Integer swimOutTick = null; public double scale = 8.0; @@ -279,11 +296,13 @@ public static class Classic { private void clamp() { if (adjustMode == null) adjustMode = AdjustMode.CAMERA; - if (swimOutTick != null) { - if (outTick == 13) outTick = swimOutTick; - swimOutTick = null; + if (exitTick == 13) { + if (outTick != null) exitTick = outTick; + else if (swimOutTick != null) exitTick = swimOutTick; } - outTick = Mth.clamp(outTick, 0, 40); + outTick = null; + swimOutTick = null; + exitTick = Mth.clamp(exitTick, 0, 40); scale = Mth.clamp(scale, 0.0, 64.0); cameraX = Mth.clamp(cameraX, MIN_OFFSET_D, MAX_OFFSET_D); cameraY = Mth.clamp(cameraY, MIN_OFFSET_D, MAX_OFFSET_D); @@ -316,10 +335,14 @@ public static class Binding { public boolean adjustOffset = true; public boolean hideFailureMessage = false; public boolean renderStuckObjects = true; - public boolean disableWhenCrawling = false; + @Deprecated + public Boolean disableWhenCrawling = null; public boolean disableWhenSneaking = false; public boolean disableWhenSwimming = false; - public int outTick = 13; + public boolean disableWhenFlying = false; + public int exitTick = 13; + @Deprecated + public Integer outTick = null; @Deprecated public Integer swimOutTick = null; public int bindResultRetentionFrames = 2; @@ -337,15 +360,19 @@ private void clamp() { } catch (Exception e) { screenModifierKey = InputConstants.Type.KEYSYM.getOrCreate(InputConstants.KEY_LALT).getName(); } - if (swimOutTick != null) { - if (outTick == 13) outTick = swimOutTick; - swimOutTick = null; + if (Boolean.TRUE.equals(disableWhenCrawling)) disableWhenSwimming = true; + disableWhenCrawling = null; + if (exitTick == 13) { + if (outTick != null) exitTick = outTick; + else if (swimOutTick != null) exitTick = swimOutTick; } + outTick = null; + swimOutTick = null; + exitTick = Mth.clamp(exitTick, 0, 40); if (legacyBindingMode != null) { legacyMode = legacyBindingMode; legacyBindingMode = null; } - outTick = Mth.clamp(outTick, 0, 40); bindResultRetentionFrames = Math.max(bindResultRetentionFrames, 0); activeConfigIndex = Math.max(activeConfigIndex, 0); displacementSmoothFactor = Mth.clamp(displacementSmoothFactor, 0.0, 1.0); From 77b2865e3b66f4b3d874dd9382d98c1a1bd7be5a Mon Sep 17 00:00:00 2001 From: rsdadada Date: Sat, 18 Jul 2026 19:07:35 +0800 Subject: [PATCH 4/8] Sync 1.20.1 config labels and version --- .../com/xtracr/realcamera/KeyMappings.java | 4 ++-- .../assets/realcamera/lang/en_us.json | 22 +++++++++---------- .../assets/realcamera/lang/zh_cn.json | 22 +++++++++---------- gradle.properties | 2 +- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/common/src/main/java/com/xtracr/realcamera/KeyMappings.java b/common/src/main/java/com/xtracr/realcamera/KeyMappings.java index 599002e..1f2a8f0 100644 --- a/common/src/main/java/com/xtracr/realcamera/KeyMappings.java +++ b/common/src/main/java/com/xtracr/realcamera/KeyMappings.java @@ -25,12 +25,12 @@ public final class KeyMappings { }); createKeyMapping("toggleAdjustMode", client -> ConfigFile.config().cycleAdjustMode()); createKeyMapping("toggleCameraMode", client -> ConfigFile.config().setClassic(!ConfigFile.config().isClassic())); - createKeyMapping("adjustFRONT", client -> ConfigFile.config().adjustOffsetX(1)); - createKeyMapping("adjustBACK", client -> ConfigFile.config().adjustOffsetX(-1)); createKeyMapping("adjustUP", client -> ConfigFile.config().adjustOffsetY(1)); createKeyMapping("adjustDOWN", client -> ConfigFile.config().adjustOffsetY(-1)); createKeyMapping("adjustLEFT", client -> ConfigFile.config().adjustOffsetZ(1)); createKeyMapping("adjustRIGHT", client -> ConfigFile.config().adjustOffsetZ(-1)); + createKeyMapping("adjustFRONT", client -> ConfigFile.config().adjustOffsetX(1)); + createKeyMapping("adjustBACK", client -> ConfigFile.config().adjustOffsetX(-1)); createKeyMapping("activeConfigIndexNEXT", client -> ConfigFile.config().binding.activeConfigIndex += 1); createKeyMapping("activeConfigIndexPREV", client -> ConfigFile.config().binding.activeConfigIndex -= 1); createKeyMapping("activeConfigIndexRESET", client -> ConfigFile.config().binding.activeConfigIndex = 0); diff --git a/common/src/main/resources/assets/realcamera/lang/en_us.json b/common/src/main/resources/assets/realcamera/lang/en_us.json index 6742f95..dcfac60 100644 --- a/common/src/main/resources/assets/realcamera/lang/en_us.json +++ b/common/src/main/resources/assets/realcamera/lang/en_us.json @@ -14,17 +14,17 @@ "config.option.xtracr_realcamera.classicAdjustMode": "Adjust Mode", "config.option.xtracr_realcamera.disableConfig": "Disable Config", "config.option.xtracr_realcamera.disableMainFeatureItems": "Disable main feature when holding", - "config.option.xtracr_realcamera.disableRenderItems": "Disable rendering when holding", - "config.option.xtracr_realcamera.disableWhenCrawling": "Disable main feature when crawling", + "config.option.xtracr_realcamera.disableRenderItems": "Disable body rendering when holding", "config.option.xtracr_realcamera.disableWhenSneaking": "Disable main feature when sneaking", - "config.option.xtracr_realcamera.disableWhenSwimming": "Disable main feature when swimming", + "config.option.xtracr_realcamera.disableWhenSwimming": "Disable main feature when swimming(crawling)", + "config.option.xtracr_realcamera.disableWhenFlying": "Disable main feature when flying", "config.option.xtracr_realcamera.displacementSmoothFactor": "Displacement Smooth Factor", "config.option.xtracr_realcamera.dynamicCrosshair": "Dynamic Crosshair", "config.option.xtracr_realcamera.enabled": "Mod Enabled", "config.option.xtracr_realcamera.hideBindingFailureMessage": "Hide Binding Failure Message", "config.option.xtracr_realcamera.isClassic": "Classic Mode", "config.option.xtracr_realcamera.legacyBindingMode": "Legacy Binding Mode", - "config.option.xtracr_realcamera.outTick": "Restriction exit delay (in ticks)", + "config.option.xtracr_realcamera.exitTick": "Restriction exit delay (in ticks)", "config.option.xtracr_realcamera.pitch": "Pitch", "config.option.xtracr_realcamera.renderModel": "Render Player Model", "config.option.xtracr_realcamera.renderStuckObjects": "Render Stuck Objects", @@ -50,7 +50,7 @@ "config.tooltip.xtracr_realcamera.dynamicCrosshair": "When enabled, the crosshair will follow the player's view, ensuring consistent hit result\nRecommended to enable when Jade, WTHIT or similar mods are installed", "config.tooltip.xtracr_realcamera.isClassic": "Classic mode only makes simple changes to the position and rotation of camera", "config.tooltip.xtracr_realcamera.legacyBindingMode": "Using the binding mode of legacy version (v0.5), only vanilla model and actions based on vanilla model are supported\nIts properties can be set in 'LEGACY_MODE' of the %s screen when enabled", - "config.tooltip.xtracr_realcamera.outTick": "The time before re-enabling main feature after exiting restricted states (e.g., swimming, crawling) (1 tick = 0.05s)", + "config.tooltip.xtracr_realcamera.exitTick": "The time before re-enabling main feature after exiting restricted states (e.g., swimming, crawling) (1 tick = 0.05s)", "config.tooltip.xtracr_realcamera.renderModel": "Render player model in first-person perspective", "config.tooltip.xtracr_realcamera.renderStuckObjects": "Arrows, bee stings and other objects that can be stuck in the player's body", "config.tooltip.xtracr_realcamera.rerenderModel": "Render the model again at the original rendering time to avoid some visual problems caused by rendering early", @@ -61,12 +61,12 @@ "key.xtracr_realcamera.activeConfigIndexNEXT": "Switch to next configuration", "key.xtracr_realcamera.activeConfigIndexPREV": "Switch to previous configuration", "key.xtracr_realcamera.activeConfigIndexRESET": "Reset configuration index", - "key.xtracr_realcamera.adjustBACK": "Adjust BACK", - "key.xtracr_realcamera.adjustDOWN": "Adjust DOWN", - "key.xtracr_realcamera.adjustFRONT": "Adjust FRONT", - "key.xtracr_realcamera.adjustLEFT": "Adjust LEFT", - "key.xtracr_realcamera.adjustRIGHT": "Adjust RIGHT", - "key.xtracr_realcamera.adjustUP": "Adjust UP", + "key.xtracr_realcamera.adjustUP": "Adjust UP (Rotate Up)", + "key.xtracr_realcamera.adjustDOWN": "Adjust DOWN (Rotate Down)", + "key.xtracr_realcamera.adjustLEFT": "Adjust LEFT (Rotate Left)", + "key.xtracr_realcamera.adjustRIGHT": "Adjust RIGHT (Rotate Right)", + "key.xtracr_realcamera.adjustFRONT": "Adjust FRONT (Roll Right)", + "key.xtracr_realcamera.adjustBACK": "Adjust BACK (Roll Left)", "key.xtracr_realcamera.modelViewScreen": "Open Model View Screen", "key.xtracr_realcamera.toggleAdjustMode": "Toggle Adjust Mode", "key.xtracr_realcamera.toggleCameraMode": "Toggle Camera Mode", diff --git a/common/src/main/resources/assets/realcamera/lang/zh_cn.json b/common/src/main/resources/assets/realcamera/lang/zh_cn.json index 61b86c8..0f4ebc1 100644 --- a/common/src/main/resources/assets/realcamera/lang/zh_cn.json +++ b/common/src/main/resources/assets/realcamera/lang/zh_cn.json @@ -14,17 +14,17 @@ "config.option.xtracr_realcamera.classicAdjustMode": "调整模式", "config.option.xtracr_realcamera.disableConfig": "禁用配置", "config.option.xtracr_realcamera.disableMainFeatureItems": "在手持...时禁用主要功能", - "config.option.xtracr_realcamera.disableRenderItems": "在手持...时禁用渲染", - "config.option.xtracr_realcamera.disableWhenCrawling": "爬行时禁用主要功能", + "config.option.xtracr_realcamera.disableRenderItems": "在手持...时禁用身体渲染", "config.option.xtracr_realcamera.disableWhenSneaking": "潜行时禁用主要功能", - "config.option.xtracr_realcamera.disableWhenSwimming": "游泳时禁用主要功能", + "config.option.xtracr_realcamera.disableWhenSwimming": "游泳(爬行)时禁用主要功能", + "config.option.xtracr_realcamera.disableWhenFlying": "飞行时禁用主要功能", "config.option.xtracr_realcamera.displacementSmoothFactor": "位移平滑系数", "config.option.xtracr_realcamera.dynamicCrosshair": "动态十字准心", "config.option.xtracr_realcamera.enabled": "开启功能", "config.option.xtracr_realcamera.hideBindingFailureMessage": "隐藏绑定失败消息", "config.option.xtracr_realcamera.isClassic": "经典模式", "config.option.xtracr_realcamera.legacyBindingMode": "旧绑定模式", - "config.option.xtracr_realcamera.outTick": "禁用状态退出延迟(单位:tick)", + "config.option.xtracr_realcamera.exitTick": "禁用状态退出延迟(单位:tick)", "config.option.xtracr_realcamera.pitch": "俯仰角", "config.option.xtracr_realcamera.renderModel": "渲染玩家模型", "config.option.xtracr_realcamera.renderStuckObjects": "渲染卡在身上的物体", @@ -50,7 +50,7 @@ "config.tooltip.xtracr_realcamera.dynamicCrosshair": "在开启状态下,十字准心会随着玩家的视线移动,确保命中结果不受影响\n在安装了Jade、WTHIT或类似模组时建议启用", "config.tooltip.xtracr_realcamera.isClassic": "经典模式只对摄像头的位置和旋转做简单的修改", "config.tooltip.xtracr_realcamera.legacyBindingMode": "使用旧版本(v0.5)的绑定模式,仅支持原版模型和基于原版模型的动作\n开启后可在%s界面的'LEGACY_MODE'中设置属性", - "config.tooltip.xtracr_realcamera.outTick": "玩家停止某些状态(如游泳、爬行)后重启主要功能的时间(1 tick = 0.05s)", + "config.tooltip.xtracr_realcamera.exitTick": "玩家停止某些状态(如游泳、爬行)后重启主要功能的时间(1 tick = 0.05s)", "config.tooltip.xtracr_realcamera.renderModel": "在第一人称视角下渲染玩家模型", "config.tooltip.xtracr_realcamera.renderStuckObjects": "箭,蜂刺和其它会卡在玩家身体里的物体", "config.tooltip.xtracr_realcamera.rerenderModel": "在原本的渲染时机重新渲染模型,以避免一些因提前渲染导致的视觉问题", @@ -61,12 +61,12 @@ "key.xtracr_realcamera.activeConfigIndexNEXT": "切换到下一个配置", "key.xtracr_realcamera.activeConfigIndexPREV": "切换到上一个配置", "key.xtracr_realcamera.activeConfigIndexRESET": "重置配置索引", - "key.xtracr_realcamera.adjustBACK": "向后调整", - "key.xtracr_realcamera.adjustDOWN": "向下调整", - "key.xtracr_realcamera.adjustFRONT": "向前调整", - "key.xtracr_realcamera.adjustLEFT": "向左调整", - "key.xtracr_realcamera.adjustRIGHT": "向右调整", - "key.xtracr_realcamera.adjustUP": "向上调整", + "key.xtracr_realcamera.adjustUP": "向上调整(向上旋转)", + "key.xtracr_realcamera.adjustDOWN": "向下调整(向下旋转)", + "key.xtracr_realcamera.adjustLEFT": "向左调整(向左旋转)", + "key.xtracr_realcamera.adjustRIGHT": "向右调整(向右旋转)", + "key.xtracr_realcamera.adjustFRONT": "向前调整(向右滚转)", + "key.xtracr_realcamera.adjustBACK": "向后调整(向左滚转)", "key.xtracr_realcamera.modelViewScreen": "打开模型视图界面", "key.xtracr_realcamera.toggleAdjustMode": "切换调整模式", "key.xtracr_realcamera.toggleCameraMode": "切换摄像头模式", diff --git a/gradle.properties b/gradle.properties index a98c0b6..58a1109 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ forge_version=1.20.1-47.3.1 fabric_loader_version=0.15.10 #yarn_mappings=1.20.1+build.10 # Mod Properties -mod_version=0.7.8-beta +mod_version=0.7.9-beta maven_group=com.xtracr.realcamera archives_base_name=realcamera # Dependencies From c7f916a0f4cf5e668b9372cbb0b4fc1e7c436522 Mon Sep 17 00:00:00 2001 From: rsdadada Date: Sat, 18 Jul 2026 19:08:42 +0800 Subject: [PATCH 5/8] Add EMF model view compatibility on 1.20.1 --- .../realcamera/compat/CompatibilityHelper.java | 16 +++++++++++++++- .../xtracr/realcamera/gui/ModelViewScreen.java | 3 +++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/com/xtracr/realcamera/compat/CompatibilityHelper.java b/common/src/main/java/com/xtracr/realcamera/compat/CompatibilityHelper.java index fcc4df8..d1dba2a 100644 --- a/common/src/main/java/com/xtracr/realcamera/compat/CompatibilityHelper.java +++ b/common/src/main/java/com/xtracr/realcamera/compat/CompatibilityHelper.java @@ -12,8 +12,10 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.function.Function; public class CompatibilityHelper { + public static boolean isRenderInScreen; private static PlatformHelper platformHelper; private static Method NEA_playerTransformer_setDeltaTick; private static Field NEA_NEAnimationsLoader_INSTANCE; @@ -65,6 +67,18 @@ public static void initialize(PlatformHelper platformHelper) { } catch (Exception e) { RealCamera.LOGGER.warn("Compatibility with TACZ is outdated: [{}] {}", e.getClass().getName(), e.getMessage()); } + if (isModLoaded("entity_model_features")) try { + Class emfAnimationApi = Class.forName("traben.entity_model_features.EMFAnimationApi"); + if ((int) emfAnimationApi.getMethod("getApiVersion").invoke(null) >= 9) { + Function pauseCondition = ignored -> isRenderInScreen; + Method registerPauseCondition = emfAnimationApi.getMethod("registerPauseCondition", Function.class); + registerPauseCondition.invoke(null, pauseCondition); + } else { + throw new IllegalStateException("EntityModelFeatures API is outdated; model-view animations may flicker"); + } + } catch (Exception e) { + RealCamera.LOGGER.warn("Compatibility with EntityModelFeatures is outdated: [{}] {}", e.getClass().getName(), e.getMessage()); + } } private static boolean SBW_gunsIsZooming() { @@ -115,4 +129,4 @@ public static void forceSetCameraPos(Camera camera) { public static boolean isModLoaded(String modId) { return platformHelper.isModLoaded(modId); } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java b/common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java index 63bd7ea..e42f540 100644 --- a/common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java +++ b/common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java @@ -561,6 +561,8 @@ protected void renderModelViewArea(GuiGraphics graphics, LivingEntity entity) { float entityPitch = entity.getXRot(); float entityPrevHeadYaw = entity.yHeadRotO; float entityHeadYaw = entity.yHeadRot; + boolean previousRenderInScreen = CompatibilityHelper.isRenderInScreen; + CompatibilityHelper.isRenderInScreen = true; try { entity.yBodyRot = 180.0f; entity.setYRot(180.0f + (float) entityYawSlider.getNumber()); @@ -570,6 +572,7 @@ protected void renderModelViewArea(GuiGraphics graphics, LivingEntity entity) { Vector3f offset = new Vector3f((float) modelX, (float) modelY, 0); renderEntityWithAnalyser(graphics, x1, y1, x2, y2, modelScale, offset, quaternionf, entity); } finally { + CompatibilityHelper.isRenderInScreen = previousRenderInScreen; entity.yBodyRot = entityBodyYaw; entity.setYRot(entityYaw); entity.setXRot(entityPitch); From dce29bf59688bf3077673a9cab1f4459d7d8321c Mon Sep 17 00:00:00 2001 From: rsdadada Date: Sat, 18 Jul 2026 19:10:12 +0800 Subject: [PATCH 6/8] Restore live disable preview on 1.20.1 --- .../java/com/xtracr/realcamera/gui/ModelViewScreen.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java b/common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java index e42f540..4b31bef 100644 --- a/common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java +++ b/common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java @@ -284,6 +284,7 @@ else if (index > 0 && index <= rectWidgets.size()) { BindTarget bindTarget = genBindTarget(); ConfigFile.config().putBindTarget(bindTarget); ConfigFile.save(); + if (toggleCategoryButton.getValue() != Category.DISABLE) loadBindTarget(bindTarget); initWidgets(page); })); rows.addChild(priorityField = NumberField.ofInt(font, widgetWidth - 2, widgetHeight - 2, 0, priorityField), smallSettings).setTooltip(createTooltip("priority")); @@ -656,6 +657,12 @@ protected void loadBindTarget(BindTarget target) { protected BindTarget genBindTarget() { initOffsetPairs(); + DisableConfig currentDisableConfig = new DisableConfig(disabledNameField.getValue(), disabledIdField.getValue(), disableModeButton.getValue() == 0, rectWidgets.stream().map(UVRectangleWidget::toUVRectangle).toList()); + List newDisableConfigs = new ArrayList<>(disableConfigs); + for (int i = 0; i < newDisableConfigs.size(); i++) { + if (newDisableConfigs.get(i).name().equals(currentDisableConfig.name())) + newDisableConfigs.set(i, currentDisableConfig); + } TargetConfig targetConfig = new TargetConfig( forwardUField.getNumber(), forwardVField.getNumber(), upwardUField.getNumber(), upwardVField.getNumber(), posUField.getNumber(), posVField.getNumber()); BindConfig bindConfig = new BindConfig( bindXButton.getValue() == 0, bindYButton.getValue() == 0, bindZButton.getValue() == 0, bindRotButton.getValue() == 0); OffsetConfig offsets = new OffsetConfig() @@ -666,7 +673,7 @@ protected BindTarget genBindTarget() { .setPitch(offsetPitchPair.getNumber()) .setYaw(offsetYawPair.getNumber()) .setRoll(offsetRollPair.getNumber()); - return new BindTarget(nameField.getValue(), textureIdField.getValue(), priorityField.getNumber(), depthField.getNumber(), targetConfig, bindConfig, offsets, new ArrayList<>(disableConfigs)); + return new BindTarget(nameField.getValue(), textureIdField.getValue(), priorityField.getNumber(), depthField.getNumber(), targetConfig, bindConfig, offsets, newDisableConfigs); } private void clearDisableDraft() { From 57008c52c38bfcab0bf9c403f91a21d2dff95df8 Mon Sep 17 00:00:00 2001 From: rsdadada Date: Sat, 18 Jul 2026 19:23:31 +0800 Subject: [PATCH 7/8] Remove internal sync planning documents --- ...2026-07-18-1.20.1-26.1-incremental-sync.md | 777 ------------------ ...-18-1.20.1-26.1-incremental-sync-design.md | 134 --- 2 files changed, 911 deletions(-) delete mode 100644 docs/superpowers/plans/2026-07-18-1.20.1-26.1-incremental-sync.md delete mode 100644 docs/superpowers/specs/2026-07-18-1.20.1-26.1-incremental-sync-design.md diff --git a/docs/superpowers/plans/2026-07-18-1.20.1-26.1-incremental-sync.md b/docs/superpowers/plans/2026-07-18-1.20.1-26.1-incremental-sync.md deleted file mode 100644 index fc20b8a..0000000 --- a/docs/superpowers/plans/2026-07-18-1.20.1-26.1-incremental-sync.md +++ /dev/null @@ -1,777 +0,0 @@ -# 1.20.1 / 26.1 Incremental Sync Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Backport the applicable post-`3598405` behavior from 26.1 to Minecraft 1.20.1 while preserving the branch-specific `e538403` YSM projection sampler and existing user configuration. - -**Architecture:** Apply semantic, file-level adaptations instead of merging branch trees. Align disable-state behavior, EMF pause registration, and Disable draft preview with 26.1; isolate 1.20.1 differences to Gson migration aliases, Java 17 syntax, and the legacy render capture boundary. - -**Tech Stack:** Java 17, Minecraft 1.20.1, Architectury Loom 1.10-SNAPSHOT, Fabric Loader 0.15.10/Fabric API 0.92.2, Forge 47.3.1, Gradle 8.14, Gson, Cloth Config. - -## Global Constraints - -- Base all implementation work on `e538403` plus the approved design/plan commits on `codex/1.20.1-incremental-sync-26.1`. -- Keep `common/src/main/java/com/xtracr/realcamera/compat/YSMCompat.java` and `common/src/main/java/com/xtracr/realcamera/compat/TetrahedralProbeSet.java` byte-for-byte unchanged from `e538403`. -- Do not change README files, Issue templates, CI workflows, Gradle wrapper, Minecraft/loader/dependency versions, mixins, access wideners, or `ConfigCodec`. -- Keep Java source compatible with release 17; do not copy 26.1 unnamed `_` lambda parameters or Java 25 syntax. -- Preserve old JSON values from `outTick`, `swimOutTick`, and `disableWhenCrawling`, then omit migrated nullable aliases on the next save. -- Keep `rerenderModel` code and translation keys because they remain valid on 1.20.1. -- Do not add persistent JUnit, Mock, or other test-framework dependencies. -- Run Gradle serially on Windows (`--no-parallel`) to avoid the observed output-JAR lock race. - -## File Structure - -- `ModConfig.java`: owns new config names, legacy aliases, deterministic migration, and compatibility getters. -- `DisableHelper.java`: consumes the migrated config and implements the 26.1 swimming/crawling/flying predicates. -- `ConfigScreen.java`: exposes only the current `exitTick` and `disableWhenFlying` settings. -- `KeyMappings.java`: aligns direction registration order with 26.1. -- `en_us.json`, `zh_cn.json`: provide current config and key labels without deleting 1.20.1-only labels. -- `CompatibilityHelper.java`: reflects into optional EMF API v9 and owns the model-view capture flag. -- `ModelViewScreen.java`: scopes the EMF flag and overlays the current same-name Disable draft into temporary targets. -- `gradle.properties`: advances only the RealCamera mod version. - ---- - -### Task 1: Disable-state schema, migration, and runtime predicates - -**Files:** -- Modify: `common/src/main/java/com/xtracr/realcamera/config/ModConfig.java:116-206,262-362` -- Modify: `common/src/main/java/com/xtracr/realcamera/compat/DisableHelper.java:27-64` -- Modify: `common/src/main/java/com/xtracr/realcamera/config/ConfigScreen.java:55-69,194-213` -- Temporarily create, run, then delete: `common/src/test/java/com/xtracr/realcamera/config/ModConfigMigrationCheck.java` -- Temporarily create, run, then delete: `.superpowers/run-migration-check.gradle` - -**Interfaces:** -- Consumes: Gson field-name deserialization and existing `ModConfig.clamp()` load path. -- Produces: `getClassicExitTick(): int`, `getBindingExitTick(): int`, `bindingDisableWhenFlying(): boolean`; deprecated aliases continue to return current values. - -- [ ] **Step 1: Add a dependency-free migration assertion program** - -Create the temporary test class with this complete content: - -```java -package com.xtracr.realcamera.config; - -import com.google.gson.Gson; - -public final class ModConfigMigrationCheck { - private static final Gson GSON = new Gson(); - - public static void main(String[] args) { - migratesLegacyFields(); - preservesNonDefaultCurrentFields(); - clampsMigratedValues(); - } - - private static void migratesLegacyFields() { - ModConfig config = read(""" - { - "classic": {"outTick": 7}, - "binding": { - "outTick": 9, - "disableWhenCrawling": true, - "disableWhenSwimming": false - } - } - """); - require(config.classic.exitTick == 7, "classic outTick migration"); - require(config.binding.exitTick == 9, "binding outTick migration"); - require(config.binding.disableWhenSwimming, "crawling-to-swimming migration"); - require(config.classic.outTick == null && config.classic.swimOutTick == null, "classic aliases cleared"); - require(config.binding.outTick == null && config.binding.swimOutTick == null, "binding tick aliases cleared"); - require(config.binding.disableWhenCrawling == null, "crawling alias cleared"); - String saved = GSON.toJson(config); - require(!saved.contains("outTick"), "saved JSON omits outTick"); - require(!saved.contains("swimOutTick"), "saved JSON omits swimOutTick"); - require(!saved.contains("disableWhenCrawling"), "saved JSON omits disableWhenCrawling"); - } - - private static void preservesNonDefaultCurrentFields() { - ModConfig config = read(""" - { - "classic": {"exitTick": 21, "outTick": 7}, - "binding": {"exitTick": 22, "outTick": 9} - } - """); - require(config.classic.exitTick == 21, "current classic exitTick wins"); - require(config.binding.exitTick == 22, "current binding exitTick wins"); - } - - private static void clampsMigratedValues() { - ModConfig config = read(""" - { - "classic": {"outTick": 99}, - "binding": {"swimOutTick": -5} - } - """); - require(config.classic.exitTick == 40, "classic exitTick upper clamp"); - require(config.binding.exitTick == 0, "binding exitTick lower clamp"); - require(!config.binding.disableWhenFlying, "flying defaults to false"); - } - - private static ModConfig read(String json) { - ModConfig config = GSON.fromJson(json, ModConfig.class); - config.clamp(); - return config; - } - - private static void require(boolean condition, String label) { - if (!condition) throw new AssertionError(label); - } -} -``` - -Create the temporary Gradle init script: - -```groovy -gradle.projectsEvaluated { - def commonProject = gradle.rootProject.project(':common') - commonProject.tasks.register('runMigrationCheck', JavaExec) { - dependsOn(commonProject.tasks.named('testClasses')) - classpath = commonProject.sourceSets.test.runtimeClasspath - mainClass = 'com.xtracr.realcamera.config.ModConfigMigrationCheck' - } -} -``` - -- [ ] **Step 2: Run the assertion program and verify the red state** - -Run: - -```powershell -$realCameraJdk17 = 'C:\Program Files\Eclipse Adoptium\jdk-17.0.16.8-hotspot' -$env:JAVA_HOME = $realCameraJdk17 -$env:Path = "$realCameraJdk17\bin;$env:Path" -.\gradlew.bat --no-daemon --no-parallel -I .superpowers/run-migration-check.gradle :common:runMigrationCheck -``` - -Expected: `:common:compileTestJava` fails because `exitTick`, nullable legacy aliases, and `disableWhenFlying` do not exist yet. - -- [ ] **Step 3: Replace the public config accessors** - -In `ModConfig`, replace the current tick/crawling accessor blocks with: - -```java -public int getClassicExitTick() { - return classic.exitTick; -} - -@Deprecated -public int getClassicOutTick() { - return getClassicExitTick(); -} - -@Deprecated -public int getClassicSwimOutTick() { - return getClassicExitTick(); -} - -@Deprecated -public boolean bindingDisableWhenCrawling() { - return bindingDisableWhenSwimming(); -} - -public boolean bindingDisableWhenFlying() { - return binding.disableWhenFlying; -} - -public int getBindingExitTick() { - return binding.exitTick; -} - -@Deprecated -public int getBindingOutTick() { - return getBindingExitTick(); -} - -@Deprecated -public int getBindingSwimOutTick() { - return getBindingExitTick(); -} -``` - -Keep the existing `classicDisableWhenSneaking`, `classicDisableWhenSwimming`, `bindingDisableWhenSneaking`, and `bindingDisableWhenSwimming` methods unchanged around these replacements. - -- [ ] **Step 4: Implement deterministic legacy migration in both config sections** - -Use these exact field layouts and migration blocks. - -For `Classic`: - -```java -public boolean disableWhenSneaking = false; -public boolean disableWhenSwimming = false; -public int exitTick = 13; -@Deprecated -public Integer outTick = null; -@Deprecated -public Integer swimOutTick = null; -``` - -At the start of `Classic.clamp()`, after the null `adjustMode` repair: - -```java -if (exitTick == 13) { - if (outTick != null) exitTick = outTick; - else if (swimOutTick != null) exitTick = swimOutTick; -} -outTick = null; -swimOutTick = null; -exitTick = Mth.clamp(exitTick, 0, 40); -``` - -Remove the old `swimOutTick` migration and `outTick` clamp. - -For `Binding`: - -```java -@Deprecated -public Boolean disableWhenCrawling = null; -public boolean disableWhenSneaking = false; -public boolean disableWhenSwimming = false; -public boolean disableWhenFlying = false; -public int exitTick = 13; -@Deprecated -public Integer outTick = null; -@Deprecated -public Integer swimOutTick = null; -``` - -In `Binding.clamp()`, after screen-key repair and before legacy binding-mode migration: - -```java -if (Boolean.TRUE.equals(disableWhenCrawling)) disableWhenSwimming = true; -disableWhenCrawling = null; -if (exitTick == 13) { - if (outTick != null) exitTick = outTick; - else if (swimOutTick != null) exitTick = swimOutTick; -} -outTick = null; -swimOutTick = null; -exitTick = Mth.clamp(exitTick, 0, 40); -``` - -Remove the old `swimOutTick` migration and `outTick` clamp; retain all other repairs and clamps. - -- [ ] **Step 5: Align runtime predicates with 26.1** - -Replace the three swimming/crawling registrations in `DisableHelper` with: - -```java -MAIN_FEATURE.registerOrInBinding(player -> ConfigFile.config().bindingDisableWhenSwimming() - && checkCondition(player, player.isVisuallySwimming(), ConfigFile.config().getBindingExitTick())); -MAIN_FEATURE.registerOrInClassic(player -> ConfigFile.config().classicDisableWhenSwimming() - && checkCondition(player, player.isVisuallySwimming(), ConfigFile.config().getClassicExitTick())); -MAIN_FEATURE.registerOrInBinding(player -> ConfigFile.config().bindingDisableWhenFlying() && player.isFallFlying()); -``` - -Rename the private delay parameter without changing behavior: - -```java -private static boolean checkCondition(Player player, boolean condition, int exitDelayTicks) { - if (condition) { - exitTick = player.tickCount; - return true; - } - if (exitTick > 0) { - int elapsedTicks = player.tickCount - exitTick; - if (elapsedTicks <= exitDelayTicks) return true; - exitTick = 0; - } - return false; -} -``` - -- [ ] **Step 6: Align the Cloth Config screen** - -Use `exitTick` for both existing tick fields: - -```java -classic.addEntry(entryBuilder.startIntField(LocUtil.CONFIG_OPTION("exitTick"), config.classic.exitTick) - .setDefaultValue(13) - .setMin(0) - .setMax(40) - .setTooltip(LocUtil.CONFIG_TOOLTIP("exitTick")) - .setSaveConsumer(i -> config.classic.exitTick = i) - .build()); -``` - -Replace the binding crawling toggle and old tick field with: - -```java -disableConfig.add(entryBuilder.startBooleanToggle(LocUtil.CONFIG_OPTION("disableWhenSwimming"), config.binding.disableWhenSwimming) - .setDefaultValue(false) - .setSaveConsumer(b -> config.binding.disableWhenSwimming = b) - .build()); -disableConfig.add(entryBuilder.startBooleanToggle(LocUtil.CONFIG_OPTION("disableWhenFlying"), config.binding.disableWhenFlying) - .setDefaultValue(false) - .setSaveConsumer(b -> config.binding.disableWhenFlying = b) - .build()); -disableConfig.add(entryBuilder.startIntField(LocUtil.CONFIG_OPTION("exitTick"), config.binding.exitTick) - .setDefaultValue(13) - .setMin(0) - .setMax(40) - .setTooltip(LocUtil.CONFIG_TOOLTIP("exitTick")) - .setSaveConsumer(i -> config.binding.exitTick = i) - .build()); -``` - -- [ ] **Step 7: Run the real migration assertions and compile common code** - -Run the Step 2 command again, followed by: - -```powershell -.\gradlew.bat --no-daemon --no-parallel :common:compileJava -``` - -Expected: `runMigrationCheck` exits 0 with no assertion errors; `:common:compileJava` succeeds under Java 17. - -- [ ] **Step 8: Remove temporary verification files and commit** - -Delete the two temporary files with `apply_patch`, then run: - -```powershell -git status --short -git add common/src/main/java/com/xtracr/realcamera/config/ModConfig.java common/src/main/java/com/xtracr/realcamera/compat/DisableHelper.java common/src/main/java/com/xtracr/realcamera/config/ConfigScreen.java -git commit -m "Sync 1.20.1 disable state behavior" -``` - -Expected: the temporary files are absent from both the worktree and commit. - ---- - -### Task 2: Key labels, translations, and release version - -**Files:** -- Modify: `common/src/main/java/com/xtracr/realcamera/KeyMappings.java:26-36` -- Modify: `common/src/main/resources/assets/realcamera/lang/en_us.json:16-27,50-72` -- Modify: `common/src/main/resources/assets/realcamera/lang/zh_cn.json:16-27,50-72` -- Modify: `gradle.properties:11` - -**Interfaces:** -- Consumes: `exitTick` and `disableWhenFlying` names produced by Task 1. -- Produces: valid English/Chinese UI keys and `0.7.9-beta` artifact version. - -- [ ] **Step 1: Verify the resource contract is red** - -Run: - -```powershell -$english = Get-Content -Raw common/src/main/resources/assets/realcamera/lang/en_us.json -$properties = Get-Content -Raw gradle.properties -if ($english -notmatch 'disableWhenFlying') { throw 'missing disableWhenFlying translation' } -if ($english -notmatch 'config\.option\.xtracr_realcamera\.exitTick') { throw 'missing exitTick translation' } -if ($properties -notmatch 'mod_version=0\.7\.9-beta') { throw 'version not synchronized' } -``` - -Expected: the command fails on the first missing synchronized value. - -- [ ] **Step 2: Align key registration order** - -Replace the six direction registrations with: - -```java -createKeyMapping("adjustUP", client -> ConfigFile.config().adjustOffsetY(1)); -createKeyMapping("adjustDOWN", client -> ConfigFile.config().adjustOffsetY(-1)); -createKeyMapping("adjustLEFT", client -> ConfigFile.config().adjustOffsetZ(1)); -createKeyMapping("adjustRIGHT", client -> ConfigFile.config().adjustOffsetZ(-1)); -createKeyMapping("adjustFRONT", client -> ConfigFile.config().adjustOffsetX(1)); -createKeyMapping("adjustBACK", client -> ConfigFile.config().adjustOffsetX(-1)); -``` - -- [ ] **Step 3: Apply the English translation replacements** - -Make these exact key/value changes while retaining all unrelated keys, including `rerenderModel`: - -```json -"config.option.xtracr_realcamera.disableRenderItems": "Disable body rendering when holding", -"config.option.xtracr_realcamera.disableWhenSneaking": "Disable main feature when sneaking", -"config.option.xtracr_realcamera.disableWhenSwimming": "Disable main feature when swimming(crawling)", -"config.option.xtracr_realcamera.disableWhenFlying": "Disable main feature when flying", -"config.option.xtracr_realcamera.exitTick": "Restriction exit delay (in ticks)", -"config.tooltip.xtracr_realcamera.exitTick": "The time before re-enabling main feature after exiting restricted states (e.g., swimming, crawling) (1 tick = 0.05s)", -"key.xtracr_realcamera.adjustUP": "Adjust UP (Rotate Up)", -"key.xtracr_realcamera.adjustDOWN": "Adjust DOWN (Rotate Down)", -"key.xtracr_realcamera.adjustLEFT": "Adjust LEFT (Rotate Left)", -"key.xtracr_realcamera.adjustRIGHT": "Adjust RIGHT (Rotate Right)", -"key.xtracr_realcamera.adjustFRONT": "Adjust FRONT (Roll Right)", -"key.xtracr_realcamera.adjustBACK": "Adjust BACK (Roll Left)" -``` - -Remove only the obsolete `disableWhenCrawling` and `outTick` translation entries replaced above. - -- [ ] **Step 4: Apply the Chinese translation replacements** - -Make the corresponding exact changes: - -```json -"config.option.xtracr_realcamera.disableRenderItems": "在手持...时禁用身体渲染", -"config.option.xtracr_realcamera.disableWhenSneaking": "潜行时禁用主要功能", -"config.option.xtracr_realcamera.disableWhenSwimming": "游泳(爬行)时禁用主要功能", -"config.option.xtracr_realcamera.disableWhenFlying": "飞行时禁用主要功能", -"config.option.xtracr_realcamera.exitTick": "禁用状态退出延迟(单位:tick)", -"config.tooltip.xtracr_realcamera.exitTick": "玩家停止某些状态(如游泳、爬行)后重启主要功能的时间(1 tick = 0.05s)", -"key.xtracr_realcamera.adjustUP": "向上调整(向上旋转)", -"key.xtracr_realcamera.adjustDOWN": "向下调整(向下旋转)", -"key.xtracr_realcamera.adjustLEFT": "向左调整(向左旋转)", -"key.xtracr_realcamera.adjustRIGHT": "向右调整(向右旋转)", -"key.xtracr_realcamera.adjustFRONT": "向前调整(向右滚转)", -"key.xtracr_realcamera.adjustBACK": "向后调整(向左滚转)" -``` - -Remove only the replaced `disableWhenCrawling` and `outTick` entries. - -- [ ] **Step 5: Advance only the mod version** - -Change: - -```properties -mod_version=0.7.9-beta -``` - -Leave Gradle wrapper, Minecraft, loader, API, Forge, Cloth Config, and Mod Menu versions unchanged. - -- [ ] **Step 6: Verify JSON syntax and the synchronized resource contract** - -Run the Step 1 command again, then: - -```powershell -Get-Content -Raw common/src/main/resources/assets/realcamera/lang/en_us.json | ConvertFrom-Json | Out-Null -Get-Content -Raw common/src/main/resources/assets/realcamera/lang/zh_cn.json | ConvertFrom-Json | Out-Null -rg -n 'disableWhenCrawling|xtracr_realcamera\.outTick' common/src/main/resources/assets/realcamera/lang common/src/main/java -``` - -Expected: both JSON parses succeed; `rg` finds only intentional deprecated Java aliases and no obsolete UI/resource references. - -- [ ] **Step 7: Commit the resource and version sync** - -```powershell -git add gradle.properties common/src/main/java/com/xtracr/realcamera/KeyMappings.java common/src/main/resources/assets/realcamera/lang/en_us.json common/src/main/resources/assets/realcamera/lang/zh_cn.json -git commit -m "Sync 1.20.1 config labels and version" -``` - ---- - -### Task 3: Entity Model Features pause compatibility - -**Files:** -- Modify: `common/src/main/java/com/xtracr/realcamera/compat/CompatibilityHelper.java:13-68` -- Modify: `common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java:556-578` - -**Interfaces:** -- Consumes: optional EMF class `traben.entity_model_features.EMFAnimationApi` with `getApiVersion()` and erased `registerPauseCondition(Function)`. -- Produces: `CompatibilityHelper.isRenderInScreen: boolean`, true only during model-view entity capture and restored afterward. - -- [ ] **Step 1: Verify the compatibility contract is red** - -Run: - -```powershell -$compat = Get-Content -Raw common/src/main/java/com/xtracr/realcamera/compat/CompatibilityHelper.java -$screen = Get-Content -Raw common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java -if ($compat -notmatch 'EMFAnimationApi') { throw 'EMF API registration is missing' } -if ($screen -notmatch 'CompatibilityHelper\.isRenderInScreen') { throw 'EMF capture scope is missing' } -``` - -Expected: the command fails because neither side of the integration exists. - -- [ ] **Step 2: Register the optional EMF pause condition** - -Add: - -```java -import java.util.function.Function; -``` - -Add the state field near the top of `CompatibilityHelper`: - -```java -public static boolean isRenderInScreen; -``` - -After the existing TACZ compatibility block, add: - -```java -if (isModLoaded("entity_model_features")) try { - Class emfAnimationApi = Class.forName("traben.entity_model_features.EMFAnimationApi"); - if ((int) emfAnimationApi.getMethod("getApiVersion").invoke(null) >= 9) { - Function pauseCondition = ignored -> isRenderInScreen; - Method registerPauseCondition = emfAnimationApi.getMethod("registerPauseCondition", Function.class); - registerPauseCondition.invoke(null, pauseCondition); - } else { - throw new IllegalStateException("EntityModelFeatures API is outdated; model-view animations may flicker"); - } -} catch (Exception e) { - RealCamera.LOGGER.warn("Compatibility with EntityModelFeatures is outdated: [{}] {}", e.getClass().getName(), e.getMessage()); -} -``` - -- [ ] **Step 3: Scope and restore the capture flag in the 1.20.1 render path** - -In `renderModelViewArea`, save and set the flag immediately before the existing `try`, then restore it first in `finally`: - -```java -boolean previousRenderInScreen = CompatibilityHelper.isRenderInScreen; -CompatibilityHelper.isRenderInScreen = true; -try { - entity.yBodyRot = 180.0f; - entity.setYRot(180.0f + (float) entityYawSlider.getNumber()); - entity.setXRot((float) entityPitchSlider.getNumber()); - entity.yHeadRot = entity.getYRot(); - entity.yHeadRotO = entity.getYRot(); - Vector3f offset = new Vector3f((float) modelX, (float) modelY, 0); - renderEntityWithAnalyser(graphics, x1, y1, x2, y2, modelScale, offset, quaternionf, entity); -} finally { - CompatibilityHelper.isRenderInScreen = previousRenderInScreen; - entity.yBodyRot = entityBodyYaw; - entity.setYRot(entityYaw); - entity.setXRot(entityPitch); - entity.yHeadRotO = entityPrevHeadYaw; - entity.yHeadRot = entityHeadYaw; -} -``` - -Do not modify `ModelAnalyser`, mixins, or YSM code. - -- [ ] **Step 4: Verify Java 17 compilation and source contract** - -Run the Step 1 command again, then: - -```powershell -$realCameraJdk17 = 'C:\Program Files\Eclipse Adoptium\jdk-17.0.16.8-hotspot' -$env:JAVA_HOME = $realCameraJdk17 -$env:Path = "$realCameraJdk17\bin;$env:Path" -.\gradlew.bat --no-daemon --no-parallel :common:compileJava -``` - -Expected: source contract and compilation succeed; no `_ ->` syntax is present. - -- [ ] **Step 5: Verify the reflected API against current official 1.20.1 jars** - -Run this temporary-download inspection: - -```powershell -$realCameraHeaders = @{ 'User-Agent' = 'Codex/RealCamera-sync' } -$realCameraUri = 'https://api.modrinth.com/v2/project/entity-model-features/version?game_versions=%5B%221.20.1%22%5D' -$realCameraVersions = Invoke-RestMethod -Headers $realCameraHeaders -Uri $realCameraUri -$realCameraTargets = @() -$realCameraTargets += $realCameraVersions | Where-Object { $_.version_number -eq '3.2.4-fabric-1.20.1' } -$realCameraTargets += $realCameraVersions | Where-Object { $_.version_number -eq '3.2.4-forge-1.20.1' } -$realCameraTempRoot = [System.IO.Path]::GetTempPath() -$realCameraTempDir = Join-Path $realCameraTempRoot ('codex-realcamera-emf-' + [guid]::NewGuid().ToString('N')) -New-Item -ItemType Directory -Path $realCameraTempDir | Out-Null -try { - foreach ($version in $realCameraTargets) { - $file = $version.files | Where-Object { $_.primary } | Select-Object -First 1 - if (-not $file) { $file = $version.files[0] } - $jarPath = Join-Path $realCameraTempDir $file.filename - Invoke-WebRequest -Headers $realCameraHeaders -Uri $file.url -OutFile $jarPath - $api = & javap -classpath $jarPath -public traben.entity_model_features.EMFAnimationApi - if ($api -notmatch 'public static int getApiVersion\(\)') { throw "getApiVersion missing from $($file.filename)" } - if ($api -notmatch 'registerPauseCondition\(java\.util\.function\.Function') { throw "registerPauseCondition missing from $($file.filename)" } - } -} finally { - $resolved = [System.IO.Path]::GetFullPath($realCameraTempDir) - $tempRootResolved = [System.IO.Path]::GetFullPath($realCameraTempRoot) - if ((Test-Path -LiteralPath $resolved) -and $resolved.StartsWith($tempRootResolved, [System.StringComparison]::OrdinalIgnoreCase)) { - Remove-Item -LiteralPath $resolved -Recurse -Force - } -} -``` - -Expected: both Fabric and Forge 3.2.4 jars expose the reflected methods; the verified temporary directory is removed. - -- [ ] **Step 6: Commit EMF compatibility** - -```powershell -git add common/src/main/java/com/xtracr/realcamera/compat/CompatibilityHelper.java common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java -git commit -m "Add EMF model view compatibility on 1.20.1" -``` - ---- - -### Task 4: Live Disable draft preview - -**Files:** -- Modify: `common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java:273-288,396-420,654-667` - -**Interfaces:** -- Consumes: current widget values and persisted `List disableConfigs`. -- Produces: temporary `BindTarget` values with a same-name draft overlay; persisted lists remain unchanged until existing save validation succeeds. - -- [ ] **Step 1: Verify the draft-overlay contract is red** - -Run: - -```powershell -$screen = Get-Content -Raw common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java -if ($screen -notmatch 'currentDisableConfig') { throw 'draft overlay is missing' } -if ($screen -notmatch 'newDisableConfigs') { throw 'copied disable list is missing' } -if ($screen -notmatch 'Category\.DISABLE\) loadBindTarget\(bindTarget\)') { throw 'save reload guard is missing' } -``` - -Expected: the command fails because the current target always uses only the persisted list. - -- [ ] **Step 2: Preserve edit state after save** - -After `ConfigFile.save()` in the target save callback, add: - -```java -if (toggleCategoryButton.getValue() != Category.DISABLE) loadBindTarget(bindTarget); -``` - -Keep `initWidgets(page)` immediately after it. - -- [ ] **Step 3: Overlay the current same-name draft into a copied list** - -At the start of `genBindTarget()`, before `initOffsetPairs()`, add: - -```java -DisableConfig currentDisableConfig = new DisableConfig( - disabledNameField.getValue(), - disabledIdField.getValue(), - disableModeButton.getValue() == 0, - rectWidgets.stream().map(UVRectangleWidget::toUVRectangle).toList()); -List newDisableConfigs = new ArrayList<>(disableConfigs); -for (int i = 0; i < newDisableConfigs.size(); i++) { - if (newDisableConfigs.get(i).name().equals(currentDisableConfig.name())) { - newDisableConfigs.set(i, currentDisableConfig); - } -} -``` - -Change only the final constructor argument: - -```java -return new BindTarget(nameField.getValue(), textureIdField.getValue(), priorityField.getNumber(), depthField.getNumber(), - targetConfig, bindConfig, offsets, newDisableConfigs); -``` - -Do not append unmatched drafts and do not mutate `disableConfigs` in `genBindTarget()`. - -- [ ] **Step 4: Verify the source contract and compile all platform sources** - -Run the Step 1 command again, then: - -```powershell -$realCameraJdk17 = 'C:\Program Files\Eclipse Adoptium\jdk-17.0.16.8-hotspot' -$env:JAVA_HOME = $realCameraJdk17 -$env:Path = "$realCameraJdk17\bin;$env:Path" -.\gradlew.bat --no-daemon --no-parallel :common:compileJava :fabric:compileJava :forge:compileJava -``` - -Expected: source contract and all three compilation tasks succeed. - -- [ ] **Step 5: Commit live preview support** - -```powershell -git add common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java -git commit -m "Restore live disable preview on 1.20.1" -``` - ---- - -### Task 5: Full build, preservation audit, and handoff evidence - -**Files:** -- Verify only; no production file changes. - -**Interfaces:** -- Consumes: all outputs from Tasks 1-4. -- Produces: Java 17 build evidence, Fabric/Forge artifacts, exact scope report, and explicit runtime-validation handoff. - -- [ ] **Step 1: Confirm a clean task boundary and Java 17 toolchain** - -Run: - -```powershell -git status --short --branch -$realCameraJdk17 = 'C:\Program Files\Eclipse Adoptium\jdk-17.0.16.8-hotspot' -$env:JAVA_HOME = $realCameraJdk17 -$env:Path = "$realCameraJdk17\bin;$env:Path" -java -version -.\gradlew.bat --stop -``` - -Expected: no uncommitted source changes; Java reports 17.0.16; Gradle daemons stop cleanly. - -- [ ] **Step 2: Run the CI-equivalent build serially** - -Run: - -```powershell -$realCameraCommit = git rev-parse HEAD -.\gradlew.bat --no-daemon --no-parallel clean build "-PcommitSHA=$realCameraCommit" -``` - -Expected: `BUILD SUCCESSFUL`; Common, Fabric, and Forge build; tests report `NO-SOURCE`; no JAR lock failure. - -- [ ] **Step 3: Inspect both remapped artifacts** - -Run: - -```powershell -$fabricJar = Get-ChildItem fabric/build/libs -Filter 'realcamera-1.20.1-fabric-0.7.9-beta-*.jar' | Where-Object Name -NotMatch 'sources|shadow' | Select-Object -First 1 -$forgeJar = Get-ChildItem forge/build/libs -Filter 'realcamera-1.20.1-forge-0.7.9-beta-*.jar' | Where-Object Name -NotMatch 'sources|shadow' | Select-Object -First 1 -if (-not $fabricJar) { throw 'Fabric remapped jar missing' } -if (-not $forgeJar) { throw 'Forge remapped jar missing' } -if ((& jar tf $fabricJar.FullName) -notcontains 'fabric.mod.json') { throw 'Fabric metadata missing' } -if ((& jar tf $forgeJar.FullName) -notcontains 'META-INF/mods.toml') { throw 'Forge metadata missing' } -``` - -Expected: both artifact checks exit 0. - -- [ ] **Step 4: Audit whitespace, scope, and 1.20.1-only YSM preservation** - -Run: - -```powershell -git diff --check e538403..HEAD -git diff --exit-code e538403..HEAD -- common/src/main/java/com/xtracr/realcamera/compat/YSMCompat.java common/src/main/java/com/xtracr/realcamera/compat/TetrahedralProbeSet.java -git diff --name-only e538403..HEAD -``` - -Expected: no whitespace errors; YSM diff exits 0; the name list contains only the two superpowers documents and the nine approved production/resource files. - -- [ ] **Step 5: Review the semantic delta against the three source commits** - -Run: - -```powershell -git diff --stat e538403..HEAD -git diff e538403..HEAD -- common/src/main/java common/src/main/resources/assets/realcamera/lang gradle.properties -git show --stat --oneline 8a180c9 120cf42 a0e473e -``` - -Confirm manually from the displayed patch: - -- `8a180c9`: disable-state behavior, current config names, key order, and translations are present; issue-template and codec changes are absent. -- `120cf42`: EMF registration and capture flag are present at the 1.20.1 render boundary; no 26.1 submit/mixin code appears. -- `a0e473e`: same-name draft overlay and non-Disable reload guard are present. -- `e538403`: both YSM files and their projection-driven implementation remain untouched. - -- [ ] **Step 6: Record runtime checks that remain interactive** - -If an interactive client session is available, run each loader separately: - -```powershell -.\gradlew.bat --no-daemon --no-parallel :fabric:runClient -.\gradlew.bat --no-daemon --no-parallel :forge:runClient -``` - -For each loader, verify without EMF that the client reaches the main menu and Model View opens. With EMF 3.2.4 plus its required ETF dependency, verify that model-view animations stop flickering and resume after closing the screen. Verify an existing same-name Disable entry previews unsaved rectangle/mode edits. Treat YSM multi-angle binding as a required manual regression check when YSM is available. - -If interactive checks cannot be completed, state exactly which scenarios remain unverified; do not infer them from build success. - -## Final Review Checklist - -- [ ] Approved design requirements map to Tasks 1-5. -- [ ] No persistent temporary migration harness remains. -- [ ] No obsolete UI references to `outTick` or `disableWhenCrawling` remain. -- [ ] Deprecated config aliases migrate and serialize away. -- [ ] EMF reflection remains optional and Java 17 compatible. -- [ ] EMF capture state restores the previous value in `finally`. -- [ ] Disable preview uses a copy and replaces only an existing same-name entry. -- [ ] Fabric and Forge remapped artifacts both exist at version `0.7.9-beta`. -- [ ] YSM projection sampling files have zero diff from `e538403`. -- [ ] README/user documentation, templates, CI, wrapper, codecs, mixins, and unrelated code are unchanged. diff --git a/docs/superpowers/specs/2026-07-18-1.20.1-26.1-incremental-sync-design.md b/docs/superpowers/specs/2026-07-18-1.20.1-26.1-incremental-sync-design.md deleted file mode 100644 index 55e1732..0000000 --- a/docs/superpowers/specs/2026-07-18-1.20.1-26.1-incremental-sync-design.md +++ /dev/null @@ -1,134 +0,0 @@ -# 1.20.1 与 26.1 增量同步设计 - -## 背景与基线 - -当前目标分支以 `origin/1.20.1` 的 `e538403` 为基线。此前的同步提交 `c2bbc3e` 对应 26.1 的 `3598405`(tag `0.7.8-beta-26.1`),因此本次只评估 `3598405..origin/26.1/dev` 的后续增量。 - -其中适用于 1.20.1 的功能变化来自: - -- `8a180c9`:合并游泳/爬行禁用语义,增加鞘翅飞行禁用,并更新相关配置名、快捷键说明和翻译。 -- `120cf42`:在模型视图捕获期间暂停 Entity Model Features(EMF)动画。 -- `a0e473e`:恢复同名 Disable 配置草稿的实时预览。 - -`e538403` 是同步后加入的 1.20.1 专属 YSM 投影采样修复。本次不得修改或回退其 `YSMCompat`、`TetrahedralProbeSet` 及相关调用路径。 - -## 目标 - -在保持 Minecraft 1.20.1、Java 17、Architectury、Fabric 和 Forge 兼容的前提下,按 26.1 当前语义回移上述三项功能;保留现有 1.20.1 用户配置和 YSM 专属行为;将模组版本更新为 `0.7.9-beta`。 - -## 范围 - -预计修改: - -- `gradle.properties` -- `common/src/main/java/com/xtracr/realcamera/KeyMappings.java` -- `common/src/main/java/com/xtracr/realcamera/compat/CompatibilityHelper.java` -- `common/src/main/java/com/xtracr/realcamera/compat/DisableHelper.java` -- `common/src/main/java/com/xtracr/realcamera/config/ConfigScreen.java` -- `common/src/main/java/com/xtracr/realcamera/config/ModConfig.java` -- `common/src/main/java/com/xtracr/realcamera/gui/ModelViewScreen.java` -- `common/src/main/resources/assets/realcamera/lang/en_us.json` -- `common/src/main/resources/assets/realcamera/lang/zh_cn.json` - -明确排除: - -- README、Issue 模板和 CI 工作流更新。 -- Gradle 9.6、Java 25、NeoForge/26.1 构建结构及渲染管线。 -- 26.1 的 `StreamCodec` 清理;1.20.1 继续使用现有 `FriendlyByteBuf` 实现。 -- `rerenderModel` 翻译清理;该配置仍是 1.20.1 合理存在的版本差异。 -- 与本次三项功能无关的重构、格式化或兼容分支改动。 - -## 设计 - -### 禁用状态与配置迁移 - -运行语义与 26.1 对齐: - -- 游泳/爬行统一由 `Player.isVisuallySwimming()` 判断,并继续应用退出延迟。 -- 绑定模式新增 `disableWhenFlying`,由 `Player.isFallFlying()` 判断,不应用退出延迟。 -- 配置界面删除独立的 crawling 开关,增加 flying 开关。 -- `outTick` 的现行名称改为 `exitTick`,配置界面和中英文翻译键同步改名。 - -为兼容既有 1.20.1 配置,`ModConfig` 保留可迁移的废弃字段: - -- `Classic`:新字段 `int exitTick = 13`;旧字段 `Integer outTick`、`Integer swimOutTick`。 -- `Binding`:新字段 `boolean disableWhenFlying = false`、`int exitTick = 13`;旧字段 `Boolean disableWhenCrawling`、`Integer outTick`、`Integer swimOutTick`。 - -迁移规则必须确定且可重复: - -1. 若 `exitTick != 13`,保留该值。 -2. 若 `exitTick == 13` 且 `outTick != null`,迁移 `outTick`。 -3. 否则若 `exitTick == 13` 且 `swimOutTick != null`,迁移 `swimOutTick`。 -4. 将最终 `exitTick` 限制在 `[0, 40]`,随后把两个旧 tick 字段置空。 -5. 若旧 `disableWhenCrawling == true`,以逻辑或合并到 `disableWhenSwimming`,随后将旧字段置空。 - -Gson 默认不输出空字段,因此下一次正常保存会清理已迁移的旧键。现有公开 getter 保留为废弃别名,并新增以 `ExitTick`、`Flying` 命名的访问方法,避免不必要的二进制/源码兼容破坏。 - -### EMF 模型视图兼容 - -`CompatibilityHelper.initialize` 在检测到 `entity_model_features` 后: - -1. 通过反射加载 `traben.entity_model_features.EMFAnimationApi`。 -2. 要求 `getApiVersion() >= 9`。 -3. 通过擦除后的 `registerPauseCondition(Function.class)` 注册条件,返回当前模型视图捕获标记。 - -反射代码使用 Java 17 可编译的普通 lambda 参数名。类缺失、API 过旧、签名变化或调用失败时仅记录兼容警告,不阻止 RealCamera 启动。 - -1.20.1 没有 26.1 的 `captureRotatedEntity`/submit 管线,因此在 `ModelViewScreen.renderModelViewArea` 的实际实体捕获边界设置标记。进入时保存旧值并设为 `true`,在现有 `finally` 中恢复旧值以及实体旋转状态。这样既与 26.1 的作用域一致,也能在异常或未来嵌套捕获时正确恢复。 - -### Disable 草稿实时预览 - -构造临时 `BindTarget` 时: - -1. 复制当前正式 `disableConfigs`。 -2. 根据当前名称、纹理 ID、模式和矩形控件构造草稿。 -3. 仅当副本中存在同名条目时,用草稿替换该条目。 -4. 使用副本生成 `BindTarget`,不修改正式列表。 - -因此编辑既有 Disable 配置会立即反映在预览、导出和临时目标中;新建或改名草稿不会未经确认自动加入。正式保存仍由现有名称、纹理 ID 校验和 `syncDisableDraft` 负责。 - -保存目标后,仅在当前页面不是 `DISABLE` 时重新加载已保存目标;Disable 页面保留当前草稿编辑状态。这与 26.1 最终保存语义一致。 - -### 快捷键与翻译 - -同步 `8a180c9` 中与功能相关的中英文变更: - -- 上、下、左、右、前、后的注册/展示顺序与 26.1 对齐。 -- 补充旋转方向说明。 -- 添加 `disableWhenFlying`,移除独立 `disableWhenCrawling`。 -- 将 `outTick` 翻译键改为 `exitTick`。 -- 将手持物品禁用渲染的文案明确为身体渲染。 - -只修改 `en_us.json` 和 `zh_cn.json`,与源提交的翻译范围一致。 - -### 版本 - -仅把 1.20.1 的 `mod_version` 从 `0.7.8-beta` 更新为 `0.7.9-beta`。Gradle wrapper、Minecraft、加载器及依赖版本保持不变。 - -## 错误处理与稳定性 - -- 所有可选 EMF 访问均保持反射和独立 `try/catch`,无 EMF 时不解析其类。 -- EMF 捕获标记必须在 `finally` 中恢复进入前的值。 -- 配置迁移不得因缺失旧字段产生空指针,也不得覆盖非默认的新 tick 值。 -- Disable 预览只操作列表副本,避免未保存草稿污染运行配置或磁盘配置。 -- YSM 专属文件和投影状态恢复逻辑不改动。 - -## 验证 - -自动与定向验证: - -1. 使用 JDK 17;Windows 本地关闭 Gradle 并行,避免并行打包造成 JAR 文件锁竞争。 -2. 用旧 JSON 样例验证 `outTick`、`swimOutTick`、`disableWhenCrawling` 的加载、迁移和再次序列化,并验证新值优先级与范围限制。 -3. 串行执行 Common、Fabric、Forge 编译和完整 `build`,确认两个 remapped JAR 均生成。 -4. 核验正式 EMF 1.20.1 Fabric/Forge JAR 中 API v9 的类名与反射方法签名。 -5. 执行 `git diff --check` 和文件范围审计。 -6. 对 `YSMCompat.java`、`TetrahedralProbeSet.java` 与基线 `e538403` 做零差异检查。 - -运行时验证(可自动执行到环境允许的程度,其余明确交接): - -- Fabric、Forge 无 EMF 启动。 -- EMF 3.2+ 下打开模型视图时动画稳定,关闭后世界动画恢复。 -- 编辑同名 Disable 配置时无需保存即可预览,保存并重开后内容一致。 -- YSM 1.20.1 多角度绑定继续使用专属投影采样,不恢复旧侧面/roll 补偿要求。 - -仓库当前没有自动化测试框架。本次不为回移单独引入 JUnit/Mock 依赖;构建成功不被表述为 EMF/YSM 运行时场景已经验证。 From ca60a9c8eec1f190635d51f376f66a7574bdfd28 Mon Sep 17 00:00:00 2001 From: rsdadada Date: Sat, 18 Jul 2026 19:31:40 +0800 Subject: [PATCH 8/8] ci: retry build