|
| 1 | +# Architecture |
| 2 | + |
| 3 | +## Scope |
| 4 | + |
| 5 | +The plugin replaces only Fire Emblem: Three Houses' unit level-up stat-roll |
| 6 | +path. It does not provide selectable growth modes, perfect growths, zero growths, |
| 7 | +random recruitment, overlays, or ROM file replacement. |
| 8 | + |
| 9 | +The implementation has four boundaries: |
| 10 | + |
| 11 | +- `growth.rs` contains deterministic, game-independent accumulation math. |
| 12 | +- `level_up.rs` combines FE3H growth sources and decides whether to calculate, |
| 13 | + restore, or defer to vanilla behavior. |
| 14 | +- `persistence.rs` owns the save-backed byte layout. |
| 15 | +- `game/` and `plugin.rs` contain the FE3H 1.2.0 layouts, offsets, raw memory |
| 16 | + access, and Skyline hook. |
| 17 | + |
| 18 | +## Runtime flow |
| 19 | + |
| 20 | +The Skyline entry point verifies the title ID and the application display |
| 21 | +version before installing the hook at FE3H 1.2.0 text offset `0x003D3020`. |
| 22 | +Every hook call validates its unit pointer, target level, character ID, class |
| 23 | +ID, data-table entries, and persistent save unit before using fixed growths. |
| 24 | +If any required input is unavailable, the hook calls the original game |
| 25 | +function. |
| 26 | + |
| 27 | +For a valid level-up call, the hook: |
| 28 | + |
| 29 | +1. Reads the unit's current level and ten stored stats. |
| 30 | +2. Reads personal growths and personal stat caps from `PersonData`. |
| 31 | +3. Reads player class growths from `ClassData`. |
| 32 | +4. Calls the game's unit ability-parameter function for growth bonus `0x3A`. |
| 33 | +5. Loads validated fixed-growth state from the persistent save unit. |
| 34 | +6. Restores a cached result for a duplicate target level, or calculates every |
| 35 | + missing level through the requested target. |
| 36 | +7. Stores normalized counters and the result cache before applying the level |
| 37 | + and stats to the unit passed by the game. |
| 38 | + |
| 39 | +Level 1 follows the original game function and then clears the 21 bytes owned |
| 40 | +by this plugin for that unit. Level 2 is always calculated from a fresh seed |
| 41 | +when the current unit is level 1, so a matching stale cache cannot be restored. |
| 42 | + |
| 43 | +## Stat order and growth formula |
| 44 | + |
| 45 | +FE3H stores the ten fields in this order: |
| 46 | + |
| 47 | +```text |
| 48 | +HP, Str, Mag, Dex, Spd, Lck, Def, Res, Mov, Cha |
| 49 | +``` |
| 50 | + |
| 51 | +The initial counter for each field is: |
| 52 | + |
| 53 | +```text |
| 54 | +personal_growth mod 100 |
| 55 | +``` |
| 56 | + |
| 57 | +For every gained level: |
| 58 | + |
| 59 | +```text |
| 60 | +effective_growth = max(personal_growth + class_growth + ability_bonus, 0) |
| 61 | +points = points + effective_growth |
| 62 | +stat_gain = floor(points / 100) |
| 63 | +points = points mod 100 |
| 64 | +``` |
| 65 | + |
| 66 | +The ability bonus is omitted for Movement. Stat gains are bounded by the raw |
| 67 | +character cap stored in `PersonData`. If a stat is already capped, its counter |
| 68 | +is frozen so that it can resume if a later cap change permits another point. |
| 69 | +Saint Statue maximum-stat bonuses still need hardware verification; do not |
| 70 | +claim exact cap parity for those bonuses before that test passes. |
| 71 | + |
| 72 | +This matches the observable fixed-growth model used by Fire Emblem Engage: |
| 73 | +personal growth seeds the hidden counter, then total current growth is added |
| 74 | +at each level. It intentionally differs from My 3H Plugin's fixed branch, |
| 75 | +which reverse engineering shows seeds a new state by applying one extra total |
| 76 | +growth step. |
| 77 | + |
| 78 | +## Persistence |
| 79 | + |
| 80 | +The plugin uses the same unused `Unit.class_level` range as My 3H Plugin: |
| 81 | + |
| 82 | +| Slot | Bytes | Meaning | |
| 83 | +| --- | ---: | --- | |
| 84 | +| `60` | 1 | Most recent target level | |
| 85 | +| `61..70` | 10 | Growth-point remainders, one per stat | |
| 86 | +| `71..80` | 10 | Cached HP and nine non-HP stats | |
| 87 | + |
| 88 | +The owned half-open range is `class_level[60..81]`. No separate version byte |
| 89 | +or signature is written. |
| 90 | + |
| 91 | +A stored state is accepted only when: |
| 92 | + |
| 93 | +- its target level is between 2 and 99; |
| 94 | +- every counter is between 0 and 99; |
| 95 | +- cached HP is nonzero; |
| 96 | +- cached stats do not exceed the character's caps; and |
| 97 | +- it represents the current level or the target being repeated. |
| 98 | + |
| 99 | +Otherwise the state is treated as uninitialized. This provides a natural |
| 100 | +fallback for new runs and NG+ without adding another save marker. Save deletion |
| 101 | +deletes the state with the save because no external sidecar file exists. |
| 102 | + |
| 103 | +The cached result makes an immediate Divine Pulse replay or another duplicate |
| 104 | +call for the same target deterministic. Multi-level rewind behavior still |
| 105 | +requires real-hardware testing. |
| 106 | + |
| 107 | +## Confirmed profile facts |
| 108 | + |
| 109 | +The FE3H 1.2.0 profile uses Build ID |
| 110 | +`89048449BA238C8CF565518B83BF02D3` and title ID `010055D009F78000`. |
| 111 | +The following facts were cross-checked against public FE3H structures and the |
| 112 | +reference plugin binary: |
| 113 | + |
| 114 | +- level-up function offset `0x003D3020`; |
| 115 | +- ability-parameter function offset `0x000A7E80`; |
| 116 | +- persistent-unit lookup offset `0x003CAF30`; |
| 117 | +- person and class table offsets and entry layout; |
| 118 | +- `Unit` level, stat, and `class_level` offsets; |
| 119 | +- storage slots `60..80` and duplicate-result cache behavior; and |
| 120 | +- the omission of the ability growth bonus for Movement. |
| 121 | + |
| 122 | +The NRO is compile-tested for the Skyline Switch target. None of these facts |
| 123 | +should be described as hardware-verified until the hardware plan passes. |
0 commit comments