OpenPrestige

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
bbldrizzyandClaude Sonnet 5.5 committed 2026-10-21 21:24:22 +01:00
commit d3d4a988eb
774 files changed
+120915

No files matched your search

+78
View File
@@ -0,0 +1,78 @@
# BOOT_NOTES (agent "boot", 2026-10-19)
Goal: the deobfuscated Prestige client boots under Fabric (dev client, `./gradlew runClient`), `Prestige.init()` completes
and the UI bridge binds to the live modules.
## Findings (root causes, in order of discovery)
1. **Two copies of the client were loaded.** `build.gradle` has `implementation files("client/work/client-runtime.jar")`
(a Yarn-named jar on the dev classpath). A jar in `run/mods` is a *second* copy (loaded as a mod and remapped
intermediary->named by Fabric Loader at runtime). Whichever won the race was used, so every earlier "fix" that only
went into `run/mods` seemed flaky (the log showed classes loaded from `client/work/client-runtime.jar`, static keys = 0).
Decision: **the classpath jar `client/work/client-runtime.jar` is the one used by the dev client; `run/mods` stays empty.**
(If you want to use `run/mods` instead, the jar must be INTERMEDIARY-named incl. intermediary AW: `work/client-boot.jar`,
and `build.gradle`'s `implementation files(...)` line must be removed. Loom's loader remaps mods-folder jars itself.)
2. **ZKM-like key generator is stateful.** Every class (358 of them) computes its decryption key in `<clinit>` through
`hc.a(seed1, seed2, lookupClass).a(const)` (`hb`/`hc` = an interpreter with shared, mutable cached objects:
`this.a = a ^ arg ^ d`, instance cache sorted by `identityHashCode`...). The result depends on **how often / in which order the
generator was called before**, so the values obtained under Fabric (different class-init order) differ from the values
the isolated child JVMs of `IndyResolve` see (e.g. `cJ.g` = 0 / 182876734801923 instead of 22310984220293).
Wrong key -> wrong table index -> `Index 184919 out of bounds for length 81` in `bO.a`.
3. The old `IndyResolve` "clinit cut" (a `goto` over the table initialisation) was *not* the real problem for these classes
(only for the tables' decoded content); but it also removed the table init, so it is now disabled for the runtime jar.
4. `FixFlow` recomputed frames with `getCommonSuperClass -> Object` fallback => `VerifyError` (`Object` not assignable to `PlayerEntity`).
Now it resolves common super classes through a URLClassLoader over MC + libs + the jar.
## Fix (pipeline `client/tools/build_runtime.sh`, replaces the previous ad-hoc jars)
* `IndyResolve -Dno.clinitcut=true`: no clinit cut. Instead `keyFix()` replaces, in every `<clinit>`,
`ldc s1; ldc s2; MethodHandles.lookup().lookupClass(); hc.a(JJObject); ldc c; ha.a(J)J` by `ldc <value recorded by the isolated child run>`
(`IndyRec.recH` -> `info.rec["H"+ordinal]`). Result: 357 of 358 classes get the constant; the whole table init (`h`/`i` arrays,
`a()V`, DES decrypt, `ldc` tables) stays intact and runs at run time. The 1 remaining class is `gf` (no child value; see below).
* Module constructors are hooked: `PatchBootstrap` inserts `dev.prestige.ui.bridge.Bootstrap.pre(this)` after `super()` in all 122
subclasses of `dV` (the native module normally injects the settings before the ctor body runs).
* `FixFlow` (frames, `getCommonSuperClass` via real classes) -> `work/client-boot.jar` (intermediary-named, usable in a mods folder
for Prism, with the Bootstrap hook; the hook class lives in `prestige-ui`).
* tiny-remapper intermediary->named + `fix_aw.py` -> `work/client-runtime.jar` (Yarn-named; this is what Gradle uses).
* `-Xverify:all` check of all 708 classes standalone: `client/work/t2/V.java` (ok=708 bad=0).
## Bridge
* `Bridge.bind()` is called from the first client tick (`Entry`). The manager (`Prestige.d`, class `cJ`) list is empty after
`Prestige.init()` (modules are registered by the native module), so `Bridge.buildModules` constructs every class of
`src/main/resources/prestige_ui/manifest_live.json` (copied from `liberate/work/deobf/manifest_live.json`), calls `Bootstrap.post(m)`
and registers the list with the manager's `a(List)` setter. Bootstrap = port of `work/deobf/gui/zkminit/Bootstrap.java`
(package `dev.prestige.ui.bridge`).
## Run
```
bash client/tools/build_runtime.sh # ~2 min; writes work/client-boot.jar (intermediary) + work/client-runtime.jar (yarn)
bash client/tools/boot_test.sh 200 # kills stale dev clients, runs ./gradlew runClient (timeout), copies the log to run/logs/boot_*.log
```
Log lines to look for: `[prestige-ui] bridge bound: 121 modules`.
## Result (verified 15:20)
* `Prestige.init()` completes (no STDERR in the log), `Prestige.d` manager exists; its module list is empty (native registers it).
* `[prestige-ui] bridge: built 121 modules from manifest, failed 0` / `[prestige-ui] bridge bound: 121 modules`
(log: `client/work/boot_success_latest.log`).
* UI click test (`run/cmd/ui.txt` command file, `-PuiCmd=`; new dev commands `pointmod <module>` / `state <module>`, `ui/DevCmd.java`):
`Aim Assist ui=false live=false` -> click on its toggle -> `ui=true live.a()=true enabledSetting.value=true` -> click -> false -> click -> true.
Screenshot `client/work/boot_toggle_on.png` (header "1 enabled", Aim Assist switch on). Note: the very first click after opening
is sometimes ignored (earlier run); a 40-frame wait after `open` makes it reliable.
## Not done / caveats
* Class `gf` has no recorded key value (child run produced no value) -> its `<clinit>` still calls the stateful `hc` generator
(`KEYFIX-NOVALUE dev/zprestige/prestige/gf 0` in `work/boot_indy.report.txt`). Not hit during boot; check when it is used.
* Module *behaviour* (onEnable hooks, event bus dispatch, rendering) is not verified; only construction + enable flag round trip.
* The enable flag round trip goes through the module's `dM` setting (`Bridge.LiveModule.setEnabled`); `onEnable`/`onDisable` side
effects depend on how the original dM listener fires (not checked).
* `run/mods` is empty on purpose (see Finding 1). `work/client-runtime.pre-boot.jar` is the previous classpath jar (backup).
* Source tree compile (client/src) was not touched.
## Update (agent "rename", 2026-10-19 16:45)
* API surface renamed by bytecode: `docs/mapping.tsv` (applied by `tools/rename.sh` = tiny-remapper + `Widen`, called from `build_runtime.sh` and
`build_source.sh`). dV->Module, i->Category, dK->Setting, dU->SettingHolder, dM/dN/dO/dP/dQ/dR/dS/dL/dT -> Bool/Color/Float/Int/Range/Enum/Multi/
Keybind/TextSetting, bO->EventBus, cJ->ModuleManager, e_->MenuModule, q->Gamemode, bP->Subscribe, Prestige.b/d -> eventBus/moduleManager,
plus the fields/methods the UI uses (name/description/value/min/max/low/high/options/selected/toggleMode, getModules/setModules, isEnabled ...).
The 121 module classes are mapped to `dev.zprestige.prestige.module.<category>.<PascalCaseName>` (section generated by `tools/gen_module_mapping.py`).
* Bridge/Bootstrap use the new names; manifest class/prop names are rewritten from `docs/manifest_live.obf.json` by `tools/rename_manifest.py`.
* Evidence: `work/boot_rename_success.log` (API rename, jar route), `work/boot_jar_modules_renamed.log` (+ module classes renamed),
`work/boot_source_modules_renamed.log` (source route, `-PwithClient=true`); all show `bridge bound: 121 modules` and the Aim Assist toggle round trip.
* Source route: see `TODO_COMPILE.md`.
+156
View File
@@ -0,0 +1,156 @@
# Handoff: client source refactor (packages + class names)
## Current state
The catastrophic part is done. All 400 obfuscated helper classes have been moved out of the root package into
18 meaningful sub-packages, and the source tree compiles and builds.
- Source tree: `project/client/src/main/java/dev/zprestige/prestige`
- 543 Java classes total:
- 21 already-named API classes intentionally left at the root (`Module`, `Setting`, `SettingHolder`, the 9
setting types, `EventBus`, `Category`, `ModuleManager`, `MenuModule`, `Gamemode`, `Native`, `Constants`,
`Subscribe`, `ListenerEntry`)
- `client/Prestige`
- 121 modules under `module/<category>/`
- 400 renamed helpers under `event/ hud/ render/ data/ type/ mixin/ world/ math/ combat/ inventory/ network/
reflect/ gui/ config/ util/ social/ annotation/ input/`
- Zero obfuscated class names remain at the root. Verified by listing the renamed jar: the root contains only the
21 API classes.
- `./gradlew.bat build -PwithClient=true` succeeds (`BUILD SUCCESSFUL`, one deprecation warning about
`ThreadDeath`). Artifacts: `project/build/libs/prestige-0.1.0.jar`,
`prestige-0.1.0-sources.jar`.
- **Boot has not been tested yet.** That is the first remaining step.
## What was changed
### New files (`project/client/tools/`)
- `gen_class_mapping.py` — generates `docs/classmap.tsv` (400 `C old new` rows) from the decompiled source by
classifying superclass/interface/mixin target/HUD description/shader path/record components/enum constants/
module references.
- `merge_mapping.py` — merges `docs/mapping.tsv` + `docs/classmap.tsv` into `work/rename.map.tsv`, refusing
duplicate owners.
- `clsmapping.py` — shared lookup used by the fixers:
- `Mapping(srcdir).path(old_source_name)` -> real path after renames
- `Mapping.source_to_new` -> class-token map for source snippets
- `rewrite_tokens()` — token rewrite that skips string literals and fully-qualified package paths
### Changed files
- `client/tools/rename.sh`
- now merges `docs/classmap.tsv` into the mapping before running tiny-remapper and `rename_manifest.py`.
- The class map therefore applies to both routes (source regeneration and runtime jar).
- `client/tools/fix_reflect.py`
- resolves `cU`/`cW`/`EventBus` through `clsmapping` instead of hardcoded root paths
- uses the renamed `Event.class` instead of `aH.class`
- `client/tools/fix_ctor.py`
- constructor-noise regex accepts both `aH.b();` and `Event.b();`
- `client/tools/manual_edits.py`
- uses `clsmapping.Mapping`; snippets are token-rewritten outside string literals
- removed one obsolete `PostShader` edit (the `i_cF` bridge no longer exists)
- the restored `TextRenderer` bridge now implements `a_WorldContract` (the method name generated after the
class rename)
- `client/tools/notry.txt`
- `ConfigManager.a(...)Lcom/google/gson/JsonObject;` updated to the new FQN. Without this the config loader
fails to decompile again.
- `docs/classmap.tsv` — 400 generated class rows (frozen; treat as source of truth).
- `docs/classmap_src.tsv` — source-tree name -> new internal name, used by `clsmapping.py` to survive the
`_`-suffixed case-collision twins (`cg_` is jar `cg`, `d_` is jar `d`). Also frozen.
### Naming decisions
- Events: `event/Event` (base `aH`), 70 `*Event` subclasses. Fieldless events are `TickEvent`, `TickEvent2`, ...
- HUD: `hud/HudElement` (base `b4`), names derived from the `super("Name", ...)` string, e.g.
`ModuleListElement`, `WatermarkElement`.
- Mixins: `mixin/<Target>Mixin` from `@CTransformer`, e.g. `MinecraftClientMixin`, `EntityMixin`,
`InGameHudMixin`. Duplicate targets get `2`, `3`, `4`.
- Shaders: `render/<Name>Shader` from the `.frag` path (`AuraOutlineShader`, `ChamsOutlineShader`, ...).
- Enums: `type/*` with semantic overrides (`BowMode`, `AnchorState`, `CartState`, `GlPrimitive`, `Easing`, ...).
- Records/data: `data/*` (`IntData`, `LongData`, `VecIntData`, ...).
- API classes stay at root on purpose. Moving them would require editing hardcoded FQNs in
`src/main/java/dev/prestige/ui/bridge/Bootstrap.java` and `Bridge.java`, and gains nothing for this task.
## Important caveats
- **Do not blindly re-run `gen_class_mapping.py` on the new tree.** It was written for the pre-refactor tree and
reads `client/work/renames.tsv`, which is overwritten by every `build_source.sh` run. The new live tree already
has the final names. `docs/classmap.tsv` + `docs/classmap_src.tsv` are the frozen mapping.
- `work/renames.tsv` now reflects the *renamed* jar, so its `old -> new` pairs are for collision handling of the
post-classmap jar, not the original obfuscated names.
- `manual_edits.py` token rewriting deliberately ignores single-letter class tokens (`C`, `I`, `M`, `q`, ...)
because method names are also single letters. Do not weaken that filter.
- `docs/mapping.tsv` still maps old jar names (`dV`, `d_`, ...) to API/module names. `d_` there is the module
`AutoCart`; the enum that the tree calls `d_` is jar class `d` (`type/BowMode`). This collision is handled by
`classmap_src.tsv`; do not "fix" it by hand.
- Some generated names are functionally consistent but semantically imperfect: `RenderHelperN`, `WorldContract`
(this is the text-renderer interface implemented by `TextRenderer`), `RenderContract*`. Renaming them is
cosmetic and requires regenerating the decompiled source (method names in these contracts are derived from the
class name).
- 5 method bodies are still TODO no-ops from the stripping stage. Search for `body lost` /
`unresolved ZKM reference` in the tree.
- Strings are not restored, and helper **member** names are still obfuscated. Only class/package names changed.
## Next steps (in order)
1. **Boot the source route.**
```powershell
cd project
.\gradlew.bat runClient -PwithClient=true --console=plain
```
or, for a timed logged run:
```bash
cd project/client
"C:/Program Files/Git/bin/bash.exe" -lc "bash tools/boot_test.sh 200 -PwithClient=true"
```
Use Git Bash, not the WSL `bash` in PATH (WSL has no `cygpath`/Windows Python).
Check `project/run/logs/latest.log` for `[prestige-ui] bridge bound: N modules` (expect 121) and for
`ClassNotFoundException` / `NoClassDefFoundError`. The manifest was rewritten (903 setting prop keys), so this
primarily validates module + setting binding after the package moves.
2. **Test the runtime jar route.** The class map is applied by `rename.sh`, so the boot jar should contain the same
renamed classes:
```bash
cd project/client
"C:/Program Files/Git/bin/bash.exe" -lc "bash tools/build_runtime.sh"
```
Then boot through the normal launcher path and compare the bridge line with step 1.
3. **Real input test.** Open the ClickGUI, HUD editor, toggle modules, change settings, move HUD elements. This was
never tested in this refactor; package moves can expose package-private access that `Widen` masked at bytecode
level but that no longer matches after decompilation.
4. **Restore the 5 lost bodies** (currently comments with `TODO(compile)`): `dE.run`, `av.run`, `ca.q`, `cb.q`,
`b4.q` in the old names. After renaming, locate with:
```powershell
Get-ChildItem client/src/main/java -Recurse -Filter *.java |
Select-String -Pattern 'body lost|unresolved ZKM reference'
```
`client/TODO_COMPILE.md` has the original phase notes.
5. **Restore strings / member names** if desired. `gen_mapping.py` is provenance only; add `F`/`M` rows to
`docs/mapping.tsv` and regenerate. Do not touch `docs/classmap.tsv` for member work.
6. **Update docs.** `client/TODO_COMPILE.md` and `liberate/HANDOFF_2.md` still describe the pre-refactor tree.
Record the new package layout, the classmap files, and the boot result.
## Useful commands / paths
- Root: `C:\Users\Tyler\Documents\Shared\prestige\liberate\project`
- Regenerate source into `client/work/src_gen` (does not touch live tree):
```bash
cd client
"C:/Program Files/Git/bin/bash.exe" -lc "bash tools/build_source.sh"
```
- Compile generated tree only:
```bash
"C:/Program Files/Git/bin/bash.exe" -lc "cd project/client && bash tools/compile.sh work/src_gen work/javac_final.log"
```
- Rename a jar manually:
```bash
"C:/Program Files/Git/bin/bash.exe" -lc "cd project/client && bash tools/rename.sh work/strip1.jar work/test.renamed.jar"
```
- Build from source: `.\gradlew.bat build -PwithClient=true`
- Manifest: `project/src/main/resources/prestige_ui/manifest_live.json`
- Bridge FQNs (unchanged): `project/src/main/java/dev/prestige/ui/bridge/{Bridge,Bootstrap}.java`
- Class map: `project/docs/classmap.tsv`, `project/docs/classmap_src.tsv`
- Merge check: `python client/tools/merge_mapping.py docs/mapping.tsv docs/classmap.tsv <out>`
+92
View File
@@ -0,0 +1,92 @@
# TODO_COMPILE - decompiled client source tree (client/src/main/java)
State (2026-10-19 16:45): the regenerated tree compiles with **0 javac errors** (543 files, `client/work/javac_s3.log`), builds with
`./gradlew build -PwithClient=true` (jar 10.5 MB) and **boots under Fabric** (`client/work/boot_source_modules_renamed.log`:
`bridge: built 121 modules from manifest, failed 0` / `bridge bound: 121 modules`, toggle test passes, screenshot
`client/work/boot_source_modules_renamed_on.png`). The classpath-jar route (`client/work/client-runtime.jar`) stays the default
(`./gradlew build` / `runClient`); the source route is opt-in with `-PwithClient=true` because it is a *lossy* reconstruction (see
"Known gaps").
## How to build
```
bash client/build_client.sh # regenerate tree -> client/src/main/{java,resources} -> ./gradlew build -PwithClient=true (~1.5 min)
bash client/build_client.sh --no-gradle # stop after installing the tree
bash client/build_client.sh --jar # only the jar route: tools/build_runtime.sh (indy-fixed, renamed, Yarn) ~1.5 min
bash client/build_client.sh --full # stage A too (IndyResolve -> FlowClean -> Strip = work/strip1.jar) + both routes (~10+ min)
bash client/tools/boot_test.sh 200 [-PwithClient=true] [-PuiCmd=<cmdfile>] # dev client, log copied to run/logs/boot_*.log
```
Dev tool flags: `-PclientJar=<yarn jar>` (swap the classpath jar), `-PdumpClasses=a,b` (force class init, see ClinitDump).
## Pipeline (all scripts in client/tools, driven by tools/build_source.sh)
1. stage A (not re-run by default): `run_indy.sh` -> `pipeline_a.sh` => `work/strip1.jar` (obfuscated names, ZKM refs resolved/stripped).
2. `rename.sh`: `docs/mapping.tsv` -> tiny-remapper (+ `Widen`: package-private -> public, needed because module classes move into
`module/<category>/`) => `work/strip.renamed.jar`. Same step is used by the jar route (`build_runtime.sh`) and regenerates
`src/main/resources/prestige_ui/manifest_live.json` (`rename_manifest.py`).
3. `NoTry` (tools/notry.txt): clears the exception table of `Prestige.init` and `aw.a(Object[])JsonObject` (Vineflower crashes on them).
4. `pipeline_b.sh`: remap to Yarn (+ keyword/case-collision/dedup renames), Vineflower, `extract_src.py`, `fix_sources.py`.
(Fixed: it now deletes `work/remapped.jar`/`decompiled.jar` first - tiny-remapper/Vineflower *merge* into an existing output file.)
5. javac-driven fixers (errors 406 -> 98 -> 0): `fix_diag.py` (raw for-each, record accessors), `fix_rethrow.py` (ZKM identity
rethrow handlers), `fix_ctor.py` (code before `super()` hoisted: constant key arithmetic evaluated and inlined, 58 ctors),
`fix_typed_values.py` (covariant `Setting.getValue()`), `fix_reflect.py` (rebuilds the lost `Method.invoke` chains in cU/cW/EventBus:
MethodHandles.lookup/privateLookupIn/findVarHandle/unreflect/asSpreader), `fix_diag2.py` (raw generics casts),
`manual_edits.py` (exact-text repairs, idempotent, fails loudly if upstream changes; resolves module classes through the mapping),
`fix_bootstrap.py` (source equivalent of the bytecode `PatchBootstrap`: `Bootstrap.pre(this)` in all 122 module constructors,
adds a constructor to modules that had none).
6. `compile.sh` = javac against `work/mc-yarn-aw.jar` + libs; `errsummary.py` summarises a log.
## Naming
`docs/mapping.tsv`: API surface (Module, Category, Setting + 9 setting types, SettingHolder, ModuleManager, EventBus, MenuModule, Gamemode,
Subscribe, ListenerEntry and their key fields/methods) and the 121 module classes (`module/<category>/<PascalCaseName>`, generated by
`gen_module_mapping.py` from `docs/manifest_live.obf.json`). Remaining classes keep obfuscated names; case-colliding twins carry a `_`
suffix (`ea` vs `eA` -> `ea_`) only in the *source* (Windows file system), the bridge accepts both.
## Known gaps (the source route is NOT behaviourally verified; the jar route is the reference)
* **Strip removed 275 "dead unresolved" methods** (`work/strip1.report.txt`, `DEADREMOVED`: dl 26, cA 18, dB 17, dA 13, H 10, G 10, ...,
dU 5 (SettingHolder: addFloat/addInt/addRange/addText/addMulti), bT 5, ...). Their callers were rewritten to throws or no-ops by Strip;
`bT.a_cF/f` are stubs that throw `UnsupportedOperationException`. Fixing this means making IndyResolve resolve the remaining
1591 `REMAIN UNFOLDED` sites (`work/boot_indy.report.txt`) instead of cutting.
* (2026-10-21: the 5 HUD/runnable bodies below are RECOVERED, see the 2026-10-21 section; only the 5 reconstructed ones remain approximations.)
* **10 `unresolved ZKM reference` bodies**: reconstructed `ge.<init>` (vertex offsets/stride = sum(count*bytes)), `dc.<init>`
(`fS.a(args)`), `de.<init>` (`g = bU.a(key)`, `j = new HashMap`), `g2.removeEldestEntry` (`size() > cap`), `dv.a`
(`glUniform4f(du.g, radii)`); **no-op with TODO**: `dE.run`, `av.run`, `ca.q`, `cb.q`, `b4.q` (HUD widget render bodies).
* **Constants lost by Strip** (`Object var0 = null` + `((Object[])var0)[k]`): recovered at runtime with `tools/java/ClinitDump.java` +
`bridge/DumpHook` (instrument `<clinit>` array loads in the runtime jar, run the dev client with `-PclientJar=... -PdumpClasses=...`,
log `client/work/clinit_dump.log`): cp (10 shader paths), ap/gB (uniform names), gf (GL types 5126/5121), fQ (49-entry XRay block
index table, `tools/clinit_tables.json`), c9 (quad buffer sizes 4/24 - dumped = guessed). **Inferred, not dumped**: `C` ("setRotation",
the table is [setPos, setRotation]), `W` ("getMatrix"), `I` ("guiProjectionMatrix" vs "itemsProjectionMatrix"), `du.g` ("roundness").
C and W cannot be initialised in a dump run (ClassTransform `TransformerManager` remapper is null at that point) - check them in game.
* Decompiler guesses: `Entity`/`PlayerEntity` common types of `?:` expressions (aG, cg_, fx_), `Optional<Vec3d>` (eu_), enum `valueOf(String,long)`
removed (callers pass only the string), `da_` ZKM string tables + bootstrap helpers deleted.
* Not exercised at all: module behaviour (onEnable hooks, event dispatch, rendering), HUD widgets, config download.
* ClassTransform (net.lenni0451, `client/libs/classtransform-bundled.jar`) is shaded into the mod jar in the source route.
The client's own fabric.mod.json is not copied; the project's one gets `entrypoints.main` + `accessWidener` injected by `processResources`.
## Next steps
1. Make IndyResolve (stage A) resolve all sites so Strip stops dead-removing methods; re-run `build_client.sh --full`; re-diff the tree.
2. Verify the inferred constants (C, W, I, du.g) and the 5 no-op HUD bodies against the runtime jar (indy sites there resolve at run time).
3. Behaviour checks module by module (jar route first), then flip the default to the source route and drop `client/work/client-runtime.jar`.
4. Rename the remaining ~420 helper classes / packages with `docs/mapping.tsv` (same mechanism, re-run both routes + boot test).
## 2026-10-21 update (agent "verify")
Layout: 543 classes = 21 API classes at the root + `client/Prestige` + 121 `module/<category>/*` + 400 helpers in 18 packages (event hud render data type mixin
world math combat inventory network reflect gui config util social annotation input). Frozen mapping files: `docs/classmap.tsv` (400 rows, jar names),
`docs/classmap_src.tsv` (source-tree names, `_` case twins); `docs/mapping.tsv` = API surface; merged by `tools/merge_mapping.py` -> `work/rename.map.tsv`.
Boot results (logs in `client/work/`):
* source route (`-PwithClient=true`): `boot_verify_source_final.log` - `bridge: built 121 modules, failed 0` / `bridge bound: 121 modules`, 0 ClassNotFound/NoClassDef/IllegalAccess,
Aim Assist toggle ui=true live.a()=true enabledSetting.value=true; HUD smoke (`state hudsmoke`, DevCmd): all 16 HUD `q()` + RenderHelper24.run + CobwebPlacerHelper.run ok
(`boot_verify_source_hudsmoke.png`).
* jar route (`tools/build_runtime.sh`): `boot_verify_jar_final.log` - 121 modules bound, toggle on/off round trips; HUD smoke: 15/16 q() ok, the first one fails with the
known stateful-key BADREF of `g2.removeEldestEntry` (AIOOBE index 188911/9; g2 = LRU map used by the shader/text cache) - jar-route-only, source route has the reconstruction.
Note the toggle test needs a few attempts: real mouse movement over the game window overrides the simulated cursor (Overlay.pollCursor).
Fixes: stale classes in `work/boot_yarn.jar` (tiny-remapper MERGES into an existing output; old-named `fT/fS/..` were loaded by name) -> `rm` before remap;
`Widen` now also widens private members (nest host/member links are lost by the moves; IllegalAccessError); unresolved reference tables still contain OLD class names ->
`tools/java/RecoverBodies.java` redirects every `Class.forName` of the client jar to `dev.prestige.ui.bridge.OldNames` (map `src/main/resources/prestige_ui/old_names.tsv`, written by
`tools/old_names.py` from rename.sh). No mixin json / fabric.mod.json / Bridge FQN change was needed (the client's mixins are ClassTransform `@CTransformer` classes applied
by the native module, not Fabric mixins; shader/resource paths are string literals and unchanged).
Recovered bodies (dE.run=RenderHelper24, av.run=CobwebPlacerHelper, ca.q=ModuleListElement, cb.q=ModuleNotificationsElement, b4.q=HudElement): decoded from the pre-strip jar
(`out/prestige-deobf-fixed.jar`) with `tools/recover_refs/` (brute-forces the per-method reference-table key; b4.q found by matching entries of another method:
`dB.l`=MatrixStack.l + `Color.WHITE`). Source: `tools/manual_edits.py` (also widens `Renderer2D.i/m`, `CobwebPlacer.E/F/d/o`); jar: `RecoverBodies`. Regeneration
(`build_source.sh`) verified: 0 javac errors, tree identical to `client/src/main/java`.
Remaining: dv.a / g2 / dc / de reconstructions are still inferred; g2 wrong key in the jar route; strings and member names still obfuscated.