Newest first. One entry per session or verified step: what changed · how it was tested · result · follow-ups.
2026-10-06: Swords point the Elden Ring way; bows ride in the right hand; shots survive menus (protocol v13)
- User: "a normal Skyrim sword looks like a dagger in the animation; running with the bow is weird; I can't shoot arrows."
- Measured: ER's
R_Weaponsits at a fixed angle in the hand (constant hand-local axes through idle, walk, most of a swing) with the blade on its +Y; Skyrim'sWEAPONnode (held at its bind local) pointed the sword up where ER's points forward/down. ER carries a bow sideways in the right hand and moves it to the left only for draw/hold/release (x36010/x36020/x36000); Skyrim kept it in the left. Bow shots failed after menus: stale input made ER put its own weapon/ammo back, and the first R1 after re-equipping hit an empty quiver. - Changed: protocol v13: PoseState
blade(L/R_Weapon +Y) + PoseFlagBowLeft, PoseBindthumb. SkyrimPose.cpp: AimWeapon turnsWEAPONso the weapon mesh's blade (bound centre) follows ER's; CarryBow movesSHIELD(the bow) to the right hand's grip, upper limb along ER's, except while BowLeft; hands fit two landmarks (middle finger + thumb, from the Havok skeleton: a 9 deg roll fix, not the cause). ERstance.rs: stale input while connected keeps the mapped weapon/ammo (Skyrim paused in a menu). - Tested: run-tests green (FrameFit test added). Contact sheets, both games: steel sword idle/walk/R1 point forward and swing like ER's sheets (before: blade up); hunting bow idle/run in the right hand, draw in the left, back to the right after the shot, arrow launched. Earlier session: 4/4 shots including one right after the Tween menu. Not tested: daggers/two-handers sheet by sheet, crossbows, shields and left-hand weapons (unchanged).
- User: "the bow doesn't work properly" (wrong or odd animation). Side-view screenshots (virtual right stick to orbit): the body matched ER's full draw, but the bow hung at the right hip.
- Cause:
Pose.cppCarry treated the bow's own skinned bones as loose transforms (its tree set was built before the bow was equipped) and moved them a second time with the nearest bone of Skyrim's animation. Fix: the tree set is rebuilt every frame. - Ruled out by measurement: ER aiming up (camera pitch 0 at full draw) and a late aim pass (arm elevation the same in three task groups). ER's ammo is kept at its count while we use it: 26 bridged shots, no empty quiver.
- Tested: run-tests green; bow side view after the fix = ER's pose (bow upright in the left hand, right hand at the face); sword contact sheet (walk, light, heavy): sword in hand, body on the ground. ER was dismounted by the user (it had loaded on Torrent).
2026-10-05: Elden Ring stays off your pad in Skyrim menus; no more "horse" (Torrent) on the Skyrim body
- User report: "with a sword I go through the map and it's like I'm on a horse; the bow doesn't work properly".
- Cause (logs): ER blanked the physical DualSense only while Skyrim's input was fresh. In Skyrim's inventory (paused, stale input) ER read the pad for 4 s and a menu press summoned Torrent: riding anims 100000/101004/120100, pose pelvis 0.84 m down (riding posture), ER's position (and so Skyrim's) followed the horse through the terrain. The ER save then loaded mounted, so later bow shots were mounted shots (44039700) and every swing looked like riding.
- Fixed: ER blanks the pad whenever Skyrim is connected (
remote.rs); while ER is on Torrent (CSChrRideModule.is_mounted/ is_mounting,game::is_riding) its pose isn't streamed and its movement isn't passed to Skyrim (pose_stream.rs,park.rs), with a warning. ER keeps its ammo count while we use it (stance.rs; each ER shot used one of its 20 arrows). - Tested: run-tests green; both games, Skyrim console open 5 s: ER log stays "physical pad blanked"; mounted ER: "on Torrent ... ignored" warnings, Skyrim not moved. Not solved: dismounting from code (Event Action key, E, X: no effect; action_requests writes are rebuilt by the engine); the user dismounts once by hand. Ammo top-up and the bow animation recheck are pending (needs ER on foot).
- User request: "Bows also need an animation" (a Skyrim bow made ER swing its own melee weapon, no arrow).
- Probe: ER fires only owned ammo equipped the full way (param id + gaitem handle + inventory index + equip entry; a param id alone
plays the empty-quiver gesture 50050). Release = anim x36000 (draw x36010, hold x36020).
docs/research/elden-ring-combat.md. - Changed: protocol v12 (
er_ammo, AttackKind Shot); WeaponMap bow/crossbow + ammo; ERstance.rsequip_ammo/restore,attack.rsShot at release; SkyrimCombat.cppShoot (Projectile::LaunchAE 44108 with the equipped bow and ammo along the camera's aim, one arrow removed). Research switch-ErAmmo, fake-peer--hold-ms. - Tested: run-tests green; ER alone (visible): draw/hold/release on screen; both games, console hunting bow + iron arrows, virtual pad hold-release x4: four ER releases → four Skyrim arrows launched (log), contact sheet shows ER's bow draw on the Skyrim body. Not tested: crossbows, arrows actually hitting a target, aim feel.
- Changed: protocol v11 (InputState
hurt_totalf64 running share +respawn_seq; PlayerFlagDowned). Skyrimbridge/Health.cpp(adapted from SkyCraft BridgePlayerDamage/HitSink/KillPlayer, MIT): every health change refunded and forwarded as a share of max health, bar = ER's HP share, ER i-frames block any damage, Downed → kill, save load → respawn. ERer-plugin/src/health.rs: share → ER HP (held at 1, Downed), sub-HP remainder carried, baseline kept across Skyrim pauses (first version lost damage taken in the console). - Tested: run-tests green (+ ER unit test); both games: wolf bites 0.5–0.8% → ER 1450 → 1443 → 1431…; console damage 100 (25%) → ER 1088, heal 50 → 1269, bar 300 → 350/400; fatal damage → Skyrim death (the bool essential flag doesn't stop it) → reload → ER refilled to 1450. Not tested: arrows/spells during a roll (i-frame block for non-melee), user feel.
- Probe: an ER weapon id written into the right-hand ChrAsm slots (both copies) switches the moveset and attack rating, no equip call
(
-ErWeaponresearch switch; fake-peer--stance/--weapon). The per-weapon probe sessions first failed to build (a v10 edit landed mid-probe); redone in one session with live swaps. - Changed: protocol v10 InputState
er_weapon; Skyrimbridge/WeaponMap.{h,cpp}(kind + material tier → ER id + level,SkyrimXER.ini [Weapons]); ERstance.rswrites/restores it (slot choice from the save's ids: picking from our own write flipped the slot every frame). - Tested: run-tests green (weapon cases); fake Skyrim, 7 ER classes + a +21 sword; both games, console-equipped iron dagger, steel sword (+3), iron war axe, mace, greatsword, battleaxe, warhammer, daedric sword (+21): each mapped, distinct moveset (anim groups 20/23/30/33/25/32/35), AR 102–390; contact sheets of a sword and a warhammer swing look different. User feel test pending.
- User report: hits land and look right, but make no sound.
PlayImpactEffectonly spawns the visuals. - Fix:
Combat.cppPlayHitSound plays the weapon's impact sound for the target race's blood impact material (fallback: any of the weapon's impacts) at the hit node. Tested: virtual-pad swing on a wolf, sound handle played (log); user listening test pending.
- Probe (stage A): ER's hit window found in memory:
action_modifiers_flagsbit 9/10 + action_flag +0x1d0, 2-3 frames mid-sweep in every swing (docs/research/elden-ring-combat.md). The dump tool (-ErDump) now records every frame of attack anims plus three untyped regions (VirtualQuery-checked); a first version misreadmodules(an OwnedPtr) and crashed ER once. - ER:
er-plugin/src/attack.rs: hit window, attack rating from ER's params and stats, motion value by attack kind (one-/two-handed light 300xx/320xx, heavy 305xx/325xx, skill 40xxx), one swing per animation play. Protocol v9: PlayerState attack fields + AttackActive. - Skyrim:
bridge/Combat.cpp(targets in reach in front of the shown body, poise build-up → stagger, SkyCraft's HitData → 38586 apply, fallback DoDamage),bridge/CombatMath.h(ER defense curve, level defense, armour absorption, HP scaling),SkyrimXER.ini[Combat] fDamageScale(deployed with the DLL). XInputGetCapabilities hooked too: Skyrim never polled a pad it didn't find at startup (the dev virtual pad failed with the DualSense unplugged). - Tested: run-tests green (5 damage-model cases); ER alone + fake-peer: one window per swing (light/heavy/skill); both games, console wolves, greatsword two-handed, virtual pad: light hits 22→12→1 HP, a heavy kill, one hit per swing after the two-window fix; AR logged (phys 88 fire 75). Not yet: user playtest, AR checked against ER's status screen, bandits/armoured NPCs.
- Bug: ER froze 36 s while loading. Skyrim timed it out and, in the same tick, connected on ER's queued Hello. On the next tick it read
the same ER (same attach#) beating again as a restart, dropped the connection and sent a Hello, which ER (not connected yet) took
without replying. Skyrim then waited forever (
connected=falseuntil both games restarted). - Fix (both sides,
protocol/src/link.rs+skse/src/bridge/Link.cpp): the same attach# coming back is not a restart (an existing connection is kept, logged "beats again; still connected"); while the peer is alive and no handshake happened, the Hello is re-sent every 2 s (HELLO_RETRY_MS), so a swallowed Hello heals itself. - Tested: new Rust tests
stall_reconnect_never_deadlocks(replays the field order; failed on the old code) andhello_is_retried_until_answered; the same replay in the C++ selftest;tests/run-tests.ps1green. Live: ER suspended for 10 s (NtSuspendProcess) → reconnected one tick after resume; during load-in both new paths fired ("beats again", "Hello re-sent" → CONNECTED). - Seen, not fixed: ER itself freezes for 2–12 s at times during load-in (its own log goes silent); the link now rides through it.
- Changed (Skyrim):
bridge/Gamepad.cppreads the DualSense over HID (USB/Bluetooth);bridge/PadReport.hparses reports and holds the layouts;hooks/XInput.cppanswers Skyrim'sXInputGetState(IAT, ordinal import) with the gameplay layout (Elden Ring's buttons, Skyrim's extras on Cross/Triangle/Square/d-pad/Options/Create) or plain Xbox positions in menus; touchpad opens the map (UIMessageQueue); L1/R1 also switch Journal tabs; menu triggers all-or-nothing; buttons held across a layout switch are ignored until released.hooks/AttackSwallow.cppkeeps vanilla mouse attacks off;Input.cppreads the left stick (analog) and mouse attack/guard;Bridge.cppforwards ER buttons and draws a sheathed weapon first. - Protocol v8:
Button+= Attack, StrongAttack, Guard, Skill. ER:remote.rswrites them to ER's virtual keys and, while bridged, releases every action key and analog value ER read from the physical pad (pad::clear_virtual). A first bulk clear of the bitset wrote past it (itsinteger_countlooks like bytes, not words); replaced the same session by per-key release. - Dev tools: virtual pad (
bridge/PadScript.*,tools/pad-input.ps1,dev.ps1 -SkyrimPad),fake-peer --buttons. - Tested: fake-peer → ER played Attack 42030000, StrongAttack 42030500/505 (charge), Guard 42034000, Skill 712040000. User pad tests:
stick walk/run, Circle roll/sprint, R1 swings; their report "touchpad opens the pause menu, tabs don't switch" traced to Steam Input's
second XInput copy of the pad being Skyrim's source. After the fix: virtual pad + screenshots (map, Journal tabs R1/R2/L1, Tween), then
the user's pad (map twice, 15 tab switches, Tween).
tests/run-tests.ps1green (pad parser/layout/script cases). - Not done: ER attacks deal no damage (P5); Skyrim weapon type isn't mapped to an ER weapon yet; keyboard heavy attack/weapon art; rumble; Bluetooth untested. Open bug: link deadlock after a long ER stall.
- Changed: new
skse/src/hooks/PlayerHit.{h,cpp}: thecall 38586(apply hit to victim) at melee handler 38627+0x4A8 is replaced after a byte check (adapted from SkyCraftCombat.cpp, MIT). On the player while ER's i-frames are on, the hit is dropped whole; every hit on the player is logged ([hit] skipped/landed).Bridge.cppsets the flag every frame from fresh PlayerState only.main.cppallocates a 64 B trampoline.tools/game-input.psm1: atypestep and all letters/digits (for console commands). - Tested (both games, wolves via
player.placeatme 23abe N, key-script rolls): 6 hits skipped, each inside a logged IFrame window; 12 landed outside; F10 off → 7/7 landed. No stagger on skipped hits; stamina drains as before.tests/run-tests.ps1green. - Not done: arrows and spells (other hit paths) still land during rolls → P5. User roll-through feel test pending.
- Changed: new
skse/src/bridge/Stamina.{h,cpp}, called every frame frombridge::OnFrame. While the link is on and ER is in the world, the player's Stamina = Skyrim max × ER stamina/max, set through the damage modifier (ModActorValuevfunc, read back to check it took). Max usesGetActorValueMax(AE id 38469, verified withaddrlib-check). Link off / stale / dead → untouched, so Skyrim's own stamina returns. Pattern adapted from FalloutCraftfo_combat.cpp:151-188(MIT; NOTICES row,licenses/FalloutCraft.txt, README credit). - Tested (both games,
-ErForceCombat on, key scripts): 6 rolls + a 4 s sprint, then 16 chained rolls. ER 136 → 8 matched Skyrim 400 → 24 at every logged step, then refilled to full together. No "didn't take" warning. The HUD bar was visible and shrinking (screenshot). F10 off →mirror off, on →mirror on. - Not tested: the exact 0 case (ER stopped at 8: it refused or spaced the last rolls by its own rules).
- User report: in the run and sprint, the body direction and lean looked off (weapon sheathed and drawn); then the sprint still looked off.
- Probe: a new
[body]line on both sides (er-plugin/src/body.rs,Pose.cppMeasureBody), every 120 moving frames. It gives the face, hip and chest yaw plus forward/side lean relative to the travel direction, so the sides compare without matching clocks. A bounded frame-matched[bodydump]probe compared every bone over 330 sprint frames; it was removed before this commit (method indocs/research/skyrim-hooks.md). - Ruled out by measurement: ER draws the body with
ChrCtrl.model_matrix, whose yaw equals the physics yaw (within 1°), with at most 4° tilt (docs/research/elden-ring-pose.md). - Fix 1, NPC COM turns with the yaw: COM kept Skyrim's animation rotation, which faces the actor's heading (often 60–180° from the body's facing), so the spine's root sat beside or in front of the hips. Running leaned 4° more than ER, running back toward the camera 11° more. After the fix: hip, chest and lean are within 4° of ER for run, sprint, strafe and back-run, weapon sheathed and drawn.
- Fix 2, spine deltas conjugated by their bind fit (
fit⁻¹·delta·fit, Spine/Spine1/Spine2): ER's sprint chest twist swung Skyrim's Spine2 (14° further forward in bind) round in a cone. Upper chest side sway was ±22° against ER's ±9°; now ±11.5°. Trunk error mean 5.2° → 2.5°, limbs unchanged (≤ 1°), no change at rest. - Tested: agent key scripts in both games, side-view contact sheets, the frame-matched comparison, then the user's feel test
(no issue reported).
tests/run-tests.ps1all passed.
- Changed: new
skse/src/bridge/Locomotion.{h,cpp}. While the bridge is on, ER drives the player:- Skyrim's keys go to ER's stick (Caps Lock walk = stick 0.6) with Locomote +
cam_yaw= look − W. W = Skyrim heading − ER yaw, fixed at each start. - ER's interpolated displacement is turned by W and goes to the controller velocity (
movement::Follow, lifted out of Movement.cpp). - The pose faces ER yaw + W.
MoveSwallowrefuses Skyrim's movement keys andmoveInputVecis zeroed. Sprint hold = ER sprint (no vanilla kick). - Suspended = vanilla, nothing to ER: jump (
bInJumpState), falling > 0.5 s, swimming, sneaking, mounted, furniture, stagger/knockdown, dead, kill move, menu. - Yield = vanilla + stage A Sprint-roll: attack, block, spell, weapon draw/sheathe (
IsEquipping/IsUnequipping), a stage A dodge in progress. SKYRIMXER_LOCO=0= stage A only.tools/game-inputknows CapsLock.
- Skyrim's keys go to ER's stick (Caps Lock walk = stick 0.6) with Locomote +
- Tested (both games, auto load-in, key scripts, screenshots):
- W run 99 %, D strafe 98 %, S run 100 %, sprint 100 % (anim 20210), Caps Lock walk 100 % (20010); 0–1 stalls per 2 s window.
- Roll from a run: 27110 → 20110 with no hand-back.
- A wall stops the player: the first sprint ran into a house corner, and the screenshot shows the body against the wall.
- Jump: vanilla, then back on landing.
- F10 off/on: stop/start. R draw/sheathe: vanilla for the whole animation (32/20 frames).
- Mouse turn ~90° standing: the body keeps its facing (screenshot).
- Frame time p95 17 ms, hook p99 ~100 µs (unchanged).
tests/run-tests.ps1all passed.
- Fixed during testing:
- A 6-frame "in the air" limit broke a sprint off a porch. Now: jump state, or 30 frames.
- The pose blinked for 2 frames at each restart (ER's Active flag lags a frame); it's forced Active while running.
- Review agent (diff before the commit) found that a roll interrupted by a yield/suspend was picked up half-way by the stage A path.
Fixed (
movement::Reset(spent)) and retested: jump mid-roll → no stage A start. Also fixed: override cleared when there's no controller, a Sprint hold begun under locomotion never becomes vanilla sprint, midair counter reset. - Not done: the user's feel test. Jumps and other vanilla states show Skyrim's own heading, so the body can flip (seen on a jump toward the camera: the jump faced away). Combat stamina drain while sprinting was only measured ER-side (B1). NPCs still see Skyrim's own heading (the visible body turns, the actor doesn't). Footstep sounds untested.
- Changed: protocol v7: new
InputFlag::Locomote; InputStatecam_yaw= Skyrim's look as a yaw in ER's world; PlayerStatecam_yaw= ER's camera yaw (filled now). No layout change. ERremote.rs, with Locomote: the stick is forwarded every frame and turned bycam_yaw − ER camera yaw, and Dodge is forwarded for the whole hold (tap = roll, hold = sprint). Without it, the dodge-only path is unchanged.pose_stream.rs: Active while Locomote is on.fake-peer skyrim --walk DEG. - Tested:
tests/run-tests.ps1all passed. Real ER +fake-peer skyrim --walk 0(cam_yaw 0/90/180/−90°, 3 s runs): ER yaw = the wanted yaw with 0° error in all 4 directions, run anim 20110, stop 22100, pose Active on/off with Locomote. - Not done: the Skyrim side doesn't send Locomote yet (B3), so in-game behaviour is unchanged.
- Changed:
game::camera_yaw()(ER camera view matrix → yaw in the player's convention).actions::LocoTest, a scripted stick/Dodge self-test (dev.ps1 -ErSelfTest walk|sprint) that logs speed, yaw, camera yaw, anim and stamina. - Tested (ER only, hidden, pinned): stick 0.6 = walk 1.5 m/s, 1.0 = run 3.93 m/s, below ~0.3 nothing. Stick right/back = camera
+90°/+180° (sign confirmed). Dodge held while running = sprint 5.95 m/s (−10 stamina/s, combat forced); tap = roll back into the
run. The hidden camera never turns by itself. Real position stayed on the spot. Notes:
docs/research/elden-ring-input.md. - Plan correction: Skyrim ↔ ER facing uses a world offset fixed when the mode starts, not a per-frame camera offset (that would spin the body with the mouse). Next: B2 (protocol v7, ER input in the mode).
- Problem: the player stalled on 22–27 of ~60 roll frames.
Actor::ApplyCurrentrefuses a new current on alternate frames. - Probe (plan mode, one key script, 5 dodges per mover; numbers in
docs/research/skyrim-hooks.md):velocityTime = 0before ApplyCurrent: still refused on alternate frames.- Controller linear velocity after PlayerCharacter::Update: overwritten.
velocityModafter the update: overwritten.- Hook on
SetLinearVelocityImpl: works. The player's controller uses the proxy class's second vtable (AE 240560), found by looking up its vtable RVA in the Address Library.
- Changed: new
skse/src/hooks/ControllerVelocity.cpp(lock-free override, player only, Skyrim keeps vertical velocity).Movement.cppsets ER's velocity there instead of ApplyCurrent. New[move] smoothline: ER's own change + per-frame realized/ER speeds. - Tested (both games, auto load-in, key script backstep + W/D/S/A rolls):
- 0 stalls in all 5 dodges, 98–100 % of ER's distance (before: 12–22 stalls, 83–91 %).
- Speed = ER's speed one frame later.
- Wall (Whiterun stone wall, 3 left rolls): 2.64 m, then 0.21 m (blocked), then 0.67 m sliding along it. The screenshot shows the player on the ground, not in the wall.
- Not done: the user hasn't felt it yet. The "< 15 % frame-to-frame change" bar is ER's own roll curve (18–35 %).
- User: "my right hand stays up after a roll; backsteps look like I'm holding something". ER's test character two-hands a colossal sword, so ER played the weapon-holding variants (backstep 12027010) and the pose copied them.
- Changed:
- Protocol v6: InputState
stance(enum Stance). - Skyrim sends it from IsWeaponDrawn + the right-hand weapon type.
er-plugin/src/stance.rsStanceSync writes ER's ChrAsm (live + saved), never mid-dodge, and puts the save's stance back when the bridge lets go: fists for Unarmed, the lightest right-hand weapon for OneHanded, the heaviest two-handed for TwoHanded.-ErStancedev override.
- Protocol v6: InputState
- Tested:
- Probe: 12027010 → 2027010 (one-handed) → 27010 (fists).
- Both games: Skyrim Unarmed → ER fists, backstep anim 27010; the contact sheet shows the arms down after a roll.
run-tests.ps1green.- Not tested: drawing a weapon in Skyrim (the test save is unarmed).
- User: "the hands, wrists and feet stay in place when rolling, causing it to stretch".
- Cause (probe): fingers and toes aren't nodes in the player's tree. The skinned meshes skin them through loose animation-output transforms (hands: 34 of 36 bones outside the tree), which kept Skyrim's animation.
- Fix:
Pose.cppCarry moves each one rigidly with its nearest posed bone every frame. Pointers are re-read live. - Tested: contact sheet of a forward roll: hands and feet stay on the limbs through the tumble; limb check 0°; hook ≤ 0.17 ms. A first probe that read nodes out of those pointers crashed Skyrim (they aren't nodes); removed.
2026-10-05: LOCO-PLAN stage A (partial): smoother pose, v5, whole-roll follow; movement smoothing stuck
- Why: the user's playtest said the roll is jittery, the distance is off, the arms stretch and running after a roll is weird.
The user chose full ER locomotion (
docs/LOCO-PLAN.md). - Changed:
- Protocol v5:
time_us(QPC µs) in PlayerState/PoseState, PlayerStatecam_yaw, PoseBone 20 → 24 (upper-arm twist), newPoseBindslot (ER bind directions → Skyrim fits). skse/src/bridge/Timeline.*(new, adapted from SkyCraft): interpolates ER's stamped samples on Skyrim's clock.- Pose: limb-only fits, twist bones posed, pauldrons held at bind, fits from PoseBind.
- Movement: interpolated steps, feed-forward steering, follows the whole roll unless the player steers (then hands back with the pose),
[move] smoothstats. now_us/NowUs.
- Protocol v5:
- Tested (agent-run):
run-tests.ps1green.- Timeline delay 17-18 ms, 0 late frames (was 90-222 per 5 s).
- Limb check 0°; fits on limbs only (21/26/5-7°).
- Skyrim follows the whole roll: ER 3.5 m, Skyrim 3.15-3.3 m (90-94%).
- Contact sheets: rolls turn and tumble correctly.
- Not solved: the player's own motion still stalls on ~23 of 60 roll frames (ApplyCurrent refuses currents; 3 attempts,
docs/research/skyrim-hooks.md). Stuck-rule handoff in STATUS. Stage B (ER locomotion) not started.
- Changed:
skse/src/bridge/Pose.cpp(rewritten from the F7 proof): the applier.skse/src/bridge/Rig.h(new, written by a forked subagent): C++ mirror ofrig.rs+ Slerp/RotationArc, 11 selftest checks.Bridge.cppreads PoseState;movement::RollHeading();hooks/PlayerUpdate.cppdropped the proof calls.tools/game-inputgainedmouse dx dy;tools/screenshot.ps1(new) takes contact sheets so the agent can look at the game.
- Tested (agent-run, no user input):
- vs
fake-peer er --pose-always: segment error 0° (limbs/spine), 9–11° (hands/feet). - Both games: forward roll plays (tuck, tumble, recover), Skyrim moved 99% of ER's distance.
- Right roll: the body faces +90° and tumbles right.
- Hook ≤ 0.14 ms, frame time unchanged.
run-tests.ps1green.
- vs
- Review (subagent) fixes:
- The pelvis offset now moves NPC COM, so hips and torso drop together (it had moved only the legs).
- The facing is held through the blend-out, and the yaw is measured from the root's real heading.
NiPointerroot (no dangling bones after a 3D rebuild). Resolve is retried every 60 frames.- Non-finite or non-unit pose data is rejected. No allocation per frame.
- Retested: forward and left roll, whole body tumbles, 99% distance, ≤ 83 µs.
- Not done: fingers/toes, foot IK, interpolation above 60 fps, bind taken from the first skinned mesh (armour can tilt it), user's visual judgement.
- Changed:
tools/game-input.psm1/.ps1(new): window focus plus SendInput scan-code key scripts. A guard stops the script and releases every key if the game isn't in front.skse/src/bridge/AutoLoad.cpp(new): with envSKYRIMXER_AUTOLOADset, it loads that save when the main menu opens.launch.ps1 -AutoLoadpasses the newest.ess.dev.ps1loads in by default (ER: E/Enter at the title until the player spawns; Skyrim: wait for the save, then focus).-Manualkeeps the old flow;-SkyrimKeysplays a key script.
- Tested:
- Skyrim alone: save loaded ~11 s after the menu, focused; the key script showed up as
move y=1andDodge down/up. - ER alone: in world after 8 presses, roll self-test as before.
- Both: in world, CONNECTED,
PlayerState fresh, Skyrim focused. - Guard: a script for an unfocused game sends nothing.
- Skyrim alone: save loaded ~11 s after the menu, focused; the key script showed up as
- Findings:
LoadMostRecentSaveGamefails at main-menu open (the save list is still empty), so the scripts pass the save by name. Under Chrome Remote Desktop onlySwitchToThisWindowtakes the focus.
- Changed:
protocol/src/rig.rs(new): quaternion math, ER → Skyrim model basis as a matrix change (handles ER's left-handed axes), bind deltas, model-space compose; 7 unit tests.er-plugin/src/pose_stream.rs(new): resolves the skeleton once (20 bones by name, bind pose from the reference pose + parents, basis measured on it), then every frame in ChrIns_PostPhysics writes PoseState (deltas in Skyrim's basis, pelvis offset, yaw, Active while a dodge animation plays).pose.rshelpers shared,park::is_dodge_animshared. - Tested: two ER-only self-test runs (user pressed Continue). Basis right −X / forward −Z / up +Y, det −1. Backsteps: Active 81
frames, pelvis 56°, drop 0.54 m. Rolls in 4 directions: all anim 27110 (direction is in yaw), Active 101 frames, pelvis 179°, drop
0.84 m, write ≤ 0.27 ms.
cargo testgreen. Skyrim doesn't apply the pose yet (step 5). Findings:docs/research/elden-ring-pose.md.
- Changed: protocol v4 (
Local\SkyrimXER_v4): seqlock slotPoseStateat 0x400 (368 B: flagsPoseFlag::Active, frame, time_ms, bone_count, yaw, pelvis_offset[3], rot[4 × 20]), enumPoseBone(20 bones, parents first), constantPOSE_BONE_COUNT. Regenerated.fake-peer erwrites a pose every frame (Active while dodging, or always with--pose-always): pelvis yaw ±30°, right upper arm and left thigh pitch, 1 Hz. The C++ test peer logs[pose] Active on/offwith the swing and a unit-length check. - Tested:
tests/run-tests.ps1all green: Rust + C++ PoseState round trips, interop A (Rust ER → C++ Skyrim: 20 bones, pelvis swing 30°, 0 bad quaternions per dodge). Both plugins rebuilt against v4; the Skyrim DLL deployed. Not run in either game (no behaviour change yet).
- Changed:
skse/src/bridge/Pose.{h,cpp}(new): logs the third-person bone tree and each skinned geometry's bones (the bind-pose source) once. While F7 is held it turns the pelvis 45° right afterPlayerCharacter::Update, usinglocal.rotate+UpdateDownwardPass, and traces whether the write is still there at the next Update.bridge/Input:KeyHeld(scan code).hooks/PlayerUpdate.cppcalls the pose code before and after the original Update.
- Tested: Skyrim, keyboard. User: "F7 brought the legs out". A write after Update shows on screen; the animation re-poses the skeleton before the next Update.
- A trial
UpdateAnimation(vfunc 0x7D) hook showed it runs on a worker thread, once per frame. The hook was removed again. tests/run-tests.ps1green.
- A trial
- Result: ✔ step 2 accepted. Notes:
docs/research/skyrim-hooks.md"Posing the player's skeleton". Also checked SkyCraft-SkateBridge and modern-warfare-2-ai (Skyrim + Skate plans). Neither has pose code yet; both plan the samerig.rsretarget we use. Next: step 3, protocol v4 PoseState.
- Changed:
er-plugin/src/pose.rs(new, research):pose_probe=1/tools/dev.ps1 -ErPoseProbe. A one-time object-graph walk from the opaque pose pointers, using safe reads (ReadProcessMemory on the own process) and RTTI names. It detects transform and bone-name arrays, then samples them during rolls, and logs the parents, the bind pose and the key bones.- Cargo: windows features
Win32_System_Diagnostics_Debug,Win32_System_Threading.
- Tested: 2 ER runs with the roll self-test (user pressed Continue). Found
ChrIns+0x398→CSFD4LocationHkaPoseImporter→ hkaPose: skeleton (150 named bones, parents, bind) plus local and model pose. During a roll all model bones move (pelvis 0.94 → 0.27 m); idle stays under 1°. Notes:docs/research/elden-ring-pose.md. - Result: ✔ step 1 accepted. Next: step 2, the Skyrim one-bone proof.
- Changed:
docs/POSE-PLAN.md(new): plan for P4 step 5. ER plays the roll on the hidden character; its bone pose is retargeted onto the Skyrim player every frame. The plan has 6 steps and starts with an ER pose memory probe. P4-PLAN step 5 links to it.- Studied Killcraft and 2010-rust-rewrite-mashup next to the existing references. THIRD-PARTY-NOTICES credits both.
- The AI workflow doc (
docs/ai/CLAUDE.md§14) now lists, for each problem (pose, interpolation, hits, HUD, camera, input, debug tools), where the reference projects solved it, plus a "when stuck" checklist.
- Tested: n/a (docs). Cited reference files checked to exist.
- Result: ✔ plan approved in direction by the user. Next: POSE-PLAN step 1 (ER pose probe).
- Changed:
skse/src/bridge/VanillaInput.{h,cpp}(new): Sneak pressed through the game's SneakHandler with a synthetic ButtonEvent; Silent Roll perk added while the bridge is on (removed on F10 if added).Movement.cpp: trick = Sneak press → SprintStart once sneaking (retried) → forward move input for 0.48 s → sprint off + Sneak press; body turn via SetHeading + camera free rotation; chained rolls keep sneak; the "press + ER moving" chain rule only for backsteps (it falsely restarted dodges in a roll's recovery).kSneakRollTrick= false. - Tested: both games, keyboard, 2 runs (~45 rolls). Sneak toggles cleanly, the roll plays, distances 98–101 %; but only forward (the
third-person camera re-aligns the body each frame), and run 1's weapon-drawn rolls got no SprintStart. User: "it isn't working".
tests/run-tests.ps1green. - Result: ✖ vanilla route stopped (3 attempts). Next: stream ER's bone pose onto the Skyrim skeleton (STATUS handoff), user's direction ("do what the other merged games do").
2026-10-04: P4 step 4b: the hidden ER character is pinned (ER only)
- Changed:
er-plugin/src/park.rsrewritten: every frame (ChrIns_PostPhysics) the character's horizontal step is added to a virtual position and the character is put back on its spot (physics position +chr_proxy_pos_update_requested); PlayerState.pos = the virtual position (remote.rs, same task as the pin inlib.rs). A jump > 1.5 m in one frame = new spot.pinconfig key / dev.ps1-ErNoPin.[park] dodgeline per dodge (virtual distance, max drift). dev.ps1 deletes the old ER log before launching (a just-killed ER's "in world" line ended-WaitInWorldruns early). - Tested: ER self-test A/B: free 3.09/3.51/2.19/0.07/3.51/0.08/3.30/3.51 m (wall), pinned 3.51 m ×8, max drift 0.17 m,
i-frames 27 frames. Both games (keyboard, W/A/S/D + 10 chained): 40+ rolls at 92–100 % of ER's distance, ER real position constant.
tests/run-tests.ps1green. - Result: ✔ user request "the ER character must not move unless rolling" (it doesn't move at all now). Skyrim unchanged.
- Changed:
skse/src/bridge/Movement.cpp(new): while ER plays a dodge, ER's per-frame delta → character frame → Skyrim along the roll direction (camera yaw + move keys), applied closed-loop throughActor::ApplyCurrent; ends at ER's TAE movement-cancel window (new PlayerFlag MoveCancel, ERgame.rs) or when ER stops; chained rolls/backsteps detected.hooks/MoveSwallow.cpp(MovementHandler::CanProcess, AE 208715): movement keys off during a dodge. Tap = dodge / hold = sprint: ER drops the stick after 20 frames (remote.rsDASH_AFTER), Skyrim lets its sprint through as a fresh press (SprintSwallow.cpp). ERpark.rs: the hidden character returns to its spot after each dodge.bridge/AnimProbe.cpp: bounded[anim]graph-event log. Roll-animation experiments (step 5) are inMovement.cpp, switched off. - Tested: both games, keyboard, ~10 user runs. Final: rolls 96–99 % of ER's distance, a wall stops the roll, camera calm, rolls follow
the camera, spam chains, backsteps chain, Shift hold sprints at ~510 u/s (vanilla sprint), the hidden character snaps back.
tests/run-tests.ps1green (MoveCancel enum). - Result: ✔ step 4. Step 5 (animation) stuck after 2 attempts, handoff in STATUS. Open: ER walls still shorten some rolls.
- Changed: protocol v3 (
Local\SkyrimXER_v3): InputState_pad1→flags(InputFlag InCombat, BridgeOn), PlayerFlag InCombat. SkyrimBridge.cppwrites them (IsInCombat(), F10) and logs[combat]edges of both games. ER:remote.rshands the wanted state tocombat::MirrorCombat(ChrIns_AILogic, every frame, both ways); bridge off or stale input = ER decides. PlayerState reports ER's state. - Tested:
tests/run-tests.ps1green. Both games (keyboard): out of combat 3/3 rolls free; NPC set hostile (startcombat player) → 4/4 rolls cost 12 stamina; afterkill3/3 free again; all rolls with i-frames. Holding Sprint dashes (ER rule) and drains stamina in combat only. - Result: ✔ step 3. Next: step 4, the Skyrim player follows the roll.
- Changed:
er-plugin/src/combat.rs:in_combat/set_in_combat(CSChrDataModule +0x19a bit 0x40 = out of combat),CombatWatch(logs ER's combat-state edges every session),ForceCombat(-ErForceCombat on|off, one write per frame in ChrIns_AILogic), research tools behind-ErDump:Dump(raw PlayerIns/PlayerGameData/module snapshots →logs/combat_dump.bin) andSpEffectWatch.log.rsremembers the log dir;config.rs+tools/dev.ps1got the new switches. - Tested: (1) SpEffect watch while the user rolled calm/in combat: no SpEffect explains it (ruled out). (2) Memory dump of the same sequence, analysed offline: one bit separates 7 free from 8 costly rolls. (3) Self-tests: written in every group, only ChrIns_NaviCache changes it back; forced in combat with no enemy, 5/5 rolls cost 12 stamina. The final single-group write (AILogic) follows from (3) and gets re-checked in step 3's both-games test.
- Result: ✔ step 2. Next: step 3, protocol v3 carries Skyrim's combat state to ER.
- Changed: P4 plan (
docs/P4-PLAN.md, ROADMAP P4/P5 edited after the user's choices). Newskse/src/hooks/SprintSwallow.cpp:SprintHandler::CanProcess(vtable AE 208717, vfunc 0x1) refuses Sprint presses whilebridge::SwallowSprint()(bridge on + connected + ER in world); releases pass.Input.cpp: F10 toggles the bridge (off = nothing forwarded or swallowed), HUD message. ERremote.rs: the move stick is forwarded only while Dodge is held and 10 frames after, so the hidden character no longer walks with Skyrim. - Tested: both games, keyboard (user). Bridged Shift+W ≈ 366 u/s (plain run 356), bridge off ≈ 499 (vanilla sprint); F10 both ways; plain walks sent nothing to ER; 5/5 directional rolls with i-frames.
- Result: ✔ step 1. Next: step 2, ER combat state (rolls free out of combat).
- Changed: README "Made with Claude" section (built with Claude Opus 5.5 in Claude Code, guided by a project CLAUDE.md) linking a new
public copy
docs/ai/CLAUDE.md(the project's agent instructions, cleaned of machine paths and private notes; copy it to the repo root to use it). Repository layout + Legal lines mention it. - Tested: docs only; scanned the copy for private strings.
- Changed: bounded
[coords]samples on both sides (SkyrimBridge.cppSampleCoords: position +GetAngleZ(); ERremote.rssample_coordsin ChrIns_PostPhysics: position + yaw + move), max 1500 lines per session, only while moving. Newprotocol/src/coords.rs(Local forward/right/up, ER/Skyrim delta ⇄ local, yaw delta) and its C++ mirrorskse/src/bridge/Coords.h; unit tests in both languages with the measured segments.bridge::OnFramenow gets the player pointer from the hook. - Tested: both games, the user walked W/D/S/A ~3 s each. Skyrim: W 2.7°, D 90.1°, S 176.5° off the heading (A hit an obstacle).
ER: 63/63 fast samples run at yaw + 180°; W→D turned +90°, later facings predicted to 0.1° with "right = yaw + 90°".
tests/run-tests.ps1green. - Result: ✔ conventions in DESIGN §6 /
docs/research/coordinates.md. P3 done. Next: P4 plan.
- Changed:
er-plugin/src/pad.rs: analog slot helpers (analog_slots,set_analog,poll_analog,set_move,move_polls).remote.rs: freshmove_x/move_yfrom Skyrim → ER's MoveForwards/Backwards/Left/Right analog slots every frame while held, one release write, logged on change.actions.rs:selftest=roll(direction cycled per pulse, held 10 frames before the press until 5 after the release), the watch line shows the polled stick. Skyrimbridge/Input.cpp: Forward/Back/Strafe Left/Strafe Right user events →MoveAxes();Bridge.cppwrites them into InputState and logs changes.tools/dev.ps1 -ErSelfTest roll. No protocol change (fields were in v2). - Tested: ER self-test 1: forward/right rolled, back/left only backstepped (positive values ignored there). Self-test 2 with negative
Backwards/Left: 7/7 short taps in all four directions roll (27110, dodge flag 27 frames). Both games (user, keyboard): 8/8 Sprint + direction
→ roll, Skyrim
IFrame on34–51 ms after the release and off ~450 ms later, HUD "i-frames yes"; 2/2 Sprint alone → backstep, "i-frames no".tests/run-tests.ps1green. - Result: ✔ P3 accept met (stamina drop in combat earlier, i-frame window now). Next: coordinate/yaw test, the last P3 item.
- Changed:
er-plugin/src/game.rs:iframe=action_modifiers_flagsbit 1 (dodging, TAE FLAG_AS_DODGING) or an invincibility bit (0/3/5), was bit 0 only.actions.rs: the Watcher logs HP, opens a watch window on every dodge animation (real presses too), and logs every HP loss with the modifier bits and the frame offset into the last dodge ([probe] HIT). Word-diff probe trimmed to the action-flag module. - Tested in-game (ER only): (1) self-test backsteps out of combat with a special-effect diff and a per-task-group sample of the modifier bits:
no effect and no bit during backsteps. (2) Combat run: a backstep-while-walking became a roll and showed bit 1 for ~27 frames.
(3) User play test, ~20 rolls + some backsteps against an enemy: bit 1 set for 26–27 frames from each roll's first frame, 0 of 46 hits
landed while it was set; backsteps set nothing and were hit at +1..+34 frames.
tests/run-tests.ps1green. - Result: ✔ the i-frame source is found and published as
PlayerFlag::IFrame. Skyrim's Sprint injects a backstep (no i-frames), so the both-games IFrame check moves to step 5 (roll direction). Details:docs/research/elden-ring-state.md.
- Changed:
er-plugin/src/actions.rsWatcher: the dev-only word-diff probe now covers three regions (action_flag,event= CSChrEventModule,chr_flags= ChrIns+0x1c4..0x1cb), and the per-frame[action]line shows rawev_flags. - Tested in-game (ER only, self-test, 10 backsteps): no region shows an i-frame window.
ev_flagsis a constant 0xff; the chr_flags bits flip irregularly. Details:docs/research/elden-ring-state.md. - Result: ✘ second failed attempt at the same problem → stuck-loop rule: handoff written (STATUS "Handoff notes"); the next try runs in a fresh session.
- Changed: README: author's "About this project" section, "What works today" / "Not working yet", input badge, Tested setup says keyboard + mouse (controller support not added yet, why, and that it's planned for P4), dev-loop commands updated. ROADMAP: P3 ticks match reality, controller support added to P4, installer added to P8, new "Beyond the roadmap: ideas" section.
- Tested: docs only.
- Tested in-game (both games, ER visible, enemy aggroed in m60_42_37_00): Left Shift in Skyrim → ER backstep 12027010 → Skyrim log + HUD
stamina 136→128(a backstep costs 8 stamina in combat; out of combat it costs 0, which is why earlier runs showed none). The enemy's hits showed as HP 1450→1186. I-frame flag still not found: the action-flag word diff in combat shows only the anim-length markers (+0x10 bit 0, +0x40 bit 15disable_turning, +0x1d8) and a combat bit (+0x10 bit 4). - Fixed: a slot read can fail when the writer is pre-empted mid-write (all 64 tries torn).
run-testscaught the watcher logging a falsestalebecause of this. All readers (PlayerWatch C++/Rust, ERDodgeFromSkyrim, fake ER) now keep the last good copy and judge staleness by its age; before, ER would also have released a held Dodge for a frame. Stamina regen is now onestamina regen A→Bline instead of one line per frame. - Tested:
tests/run-tests.ps1green 3× in a row; both plugins build. - Result: the P3 accept's stamina part ✔ (with timestamps). The i-frame window is still open.
- Changed:
skse/src/bridge/Hud.cpp(ShowHUDMessage, AE id 52933, checked with addrlib-check).Bridge.cppUpdateHud: top-left notifications for ER connected/lost, ER character in/out of the world, and one summary per Dodge press 0.6 s later (ER anim reaction, stamina before→lowest, i-frames seen). Each one is also logged as[hud]. - Tested in-game (both games, keyboard): connected + in-world messages appeared; 6 Left Shift taps → 6 ×
ER dodge: anim 12027010 | stamina 136->136 | i-frames no(backstep in animation group 12; the character's stance changed since step 2, idle = 12000000). - Result: ✔. Still open: stamina/i-frames need combat (no enemy aggroed in these runs).
2026-10-04: P3 step 4b: the loop runs in both games (Skyrim Sprint → hidden ER backstep → state back)
- Changed:
er-plugin/src/remote.rs:DodgeFromSkyrim(in WorldChrMan_Prepare, replaces the self-test unlessselftest=dodge) holds Backstep (+ BackstepTapped on the press frame) while InputState is fresh + connected + Dodge held; it releases on stale/lost.publish_statewrites PlayerState every frame (ChrIns_PostPhysics in world, FrameBegin while not in world).bridge::shared().actions.rs: the watch window now always runs, plus a bounded word-diff probe of the action-flag module (i-frame search). - Tested in-game: (1) real ER (hidden) +
fake-peer skyrim --dodge-every 4: every press after spawn-in backstepped (anim 27010 +9 frames); fake-peer killed mid-hold → ERInputState stale266 ms later + release, then the link timeout. (2) Both games (keyboard in Skyrim): Left Shift tap →[ER] Dodge down9 ms later →[SKY] anim 0→27010~170 ms after the press, for 3 taps + a hold; both games at 60 fps (Skyrim p50 17.0 ms).tests/run-tests.ps1passes. - Not shown yet: stamina stays 101 and no i-frame flag gets set (real backsteps behave the same). Findings:
docs/research/elden-ring-state.md. - Result: step 4 loop ✔. The P3 accept's stamina-drop + i-frame part is still open.
- Changed:
skse/src/hooks/PlayerUpdate.cpp(PlayerCharacter::Update vfunc 0xAD),skse/src/bridge/Input.cpp(input sink: Sprint user event held → Dodge; logs the first event per device and each user event once),Bridge.cppOnFrame(writes InputState, reads PlayerState throughPlayerWatch.h,[perf]p50/p95/p99 every 5 s, frames now in Heartbeat).tools/addrlib-check.ps1checks Address Library ids.launch.ps1 -SkyrimVia steam|loader(default loader). - Tested in-game (Skyrim +
fake-peer er, keyboard): the ids were checked first (all present). Sprint (Left Shift) taps →Dodge down→ PlayerState stamina 100→80 + IFrame on ~17 ms later, IFrame off after 1 s, regen; a hold works as well. Frame time p50 16.8 ms, hook ≈ 2 µs. Clean exit → fake ER saw Bye.tests/run-tests.ps1passes. - Found: Skyrim gets no gamepad events when started by skse64_loader outside Steam (no Steam Input). The Steam launch option
"...skse64_loader.exe" %command%fails ("too many free args"). The controller is deferred (user decision). Notes:docs/research/skyrim-hooks.md. - Result: step 4 Skyrim side ✔ (keyboard). Next: ER side (injector from InputState, PlayerState publisher).
- Changed: schema v2 (
Local\SkyrimXER_v2):InputStateslot @0x200 (sky→er),PlayerStateslot @0x300 (er→sky),Button/PlayerFlagbit enums,SLOT_STALE_MS/SLOT_READ_TRIES. protogen requiresSLOT_*structs to start withseq: u32. Newprotocol/src/slot.rs+ C++ mirrorskse/src/bridge/Slot.h(seqlock, body copied as u32 atomics).LinkShared(region +connected) on both links for lock-free game-thread access, published only after a valid join. fake-peer + C++ peer: link thread + frame loop, Dodge pulses (--dodge-every), fake ER dodge model, PlayerState edge log. - Tested (no game):
tests/run-tests.ps1all green. Covered: protogen (incl. the slot rule); Rust slot tests (round trip, dead-writer repair, fresh, 0.5 s concurrent torn-read check); linkLinkSharedasserts; the C++ selftest (same + a threaded torn-read check, ~9 M reads); interop A (C++ Skyrim Dodge → Rust ER dodges → C++ sees stamina 100→80 and IFrame on/off ~31 ms later, stale once after the crash); interop B. Both plugins rebuilt + deployed (dev.ps1 -NoLaunch). - Result: P3 step 3 ✔. Next: step 4 (wire the loop in both games).
2026-10-04: P3 step 2 done: injected backstep works with ER hidden
- Changed:
er-plugin/src/window.rs: the focus spoof also runs at WorldChrMan_Prepare (PadStep re-setsis_back_ground_windowevery frame while ER isn't foreground).er-plugin/src/focus.rs: change-only focus-flag probe (dev,probe=1). - Tested in-game (ER only): focus probe run (visible, user switched windows): backsteps stopped while another window was foreground,
is_back_ground_windowwas 1 after PadStep. After the fix, hidden run, hands off: 7/8 pulses backstepped (the miss was during spawn-in), 60 fps.tests/run-tests.ps1passes. - Result: P3 step 2 ✔. Open note: i-frame/dodging flags read 0 during backsteps (real ones too), see research note.
- Changed:
er-plugin/src/pad.rs(describe,poll_mask,layers: read-only input diagnostics),actions.rs(wide per-frame probe: full action masks, all key polls, device layers; self-test also raisesBackstepTappedon the press frame; holds 4/9/30).tools/dev.ps1refuses to launch ER without Steam running. - Tested in-game (ER only, window visible, 2 runs): probe compared a real DualSense tap with ours: the real one also raises
BackstepTappedfor one frame, which makes the release a backstep request (bit 16). With that added, the self-test backsteps (anim 27010) on its own; the user saw it.tests/run-tests.ps1passes. - Result: step 2 ✔ while ER is focused. Still open: the action module ignores the pad while ER isn't the foreground window
(the hidden case). Details:
docs/research/elden-ring-input.md("session 3").
- Changed:
er-plugin/src/actions.rs: the probe logs the action-request gating masks every 120 frames (possible, cancels, disabled, queued, animation flags) and a queued bit per task group. The self-test cycles hold lengths 4/8/12/20/30 frames and logs the engine's hold timer. Dev-only, off by default. - Tested in-game (ER only, 3 runs, window visible): the virtual press reaches the action-request module like a real press; no dodge for any hold length; a real DualSense tap in the same session backsteps with identical module values.
- Result: step 2 still ✘, narrowed: the module isn't the missing link. Findings in
docs/research/elden-ring-input.md("session 2 results"). Also: me3 needs Steam running (Steam is required to run this gamein the me3 log).
2026-10-04: Phase 3 started: ER runs hidden; dodge injection research
- Changed: P3 plan (
docs/P3-PLAN.md).er-plugin:window.rs(each frame: focus flags spoofed; hide the window while the player is in the world, show it again at the title),game.rs(player snapshot: HP/FP/stamina, anim, i-frame/dodging/hyperarmor flags, poise, position, yaw, map),config.rs(skyrimxer_er.cfgnext to the DLL),actions.rs+pad.rs(dodge self-test + frame-phase probe, dev-only, off by default).tools/dev.ps1:-Restart,-ErVisible,-ErSelfTest,-ErInjectGroup,-ErProbe.tools/build.ps1: retries the DLL copy after a kill. - Tested in-game (ER only): the window hides on load-in and ER keeps running at 60 fps hidden; the state line reads stamina 101/101, HP 455/455,
map m10_01_00_00. Probe of a real dodge press recorded where ER sets its action bits (
docs/research/elden-ring-input.md). - Result: step 1 ✔. Step 2 (injected dodge) ✘ so far: writing
action_requestsis overwritten by the engine; a held virtual pad key is seen by ER's pad poll but doesn't reach the player's action requests. Next hypotheses are in the research note. - Workflow pass (same day): README rewritten (diagram, progress table, tested setup incl. DualSense over USB as the only test controller,
safety section, one-command dev table).
dev.ps1 -WaitInWorld Nwaits for the load-in and prints the ER results itself;tools/stop-games.ps1(+ sharedStop-Gameincommon.psm1);tools/README.mdrefreshed. DESIGN §9: controller open question. Tested: scripts parse,dev.ps1 -Target er -NoLaunchOK,stop-games.ps1closed a hidden ER.
- Changed: Protocol schema v1 (
protocol/schema/messages.toml) + generatortools/protogen(rejects implicit padding, checks the region map, emits size/offset asserts for C++ and Rust,--checkstale test). Rust crateskyrimxer-protocol(region, SPSC rings, link state machine) used byer-pluginandtools/fake-peer. C++ mirrorskse/src/bridge/Link.cpp+Bridge.cpp, andskyrimxer_link_test.exe.tests/run-tests.ps1.collect-logs.ps1now prints[link]lines (heartbeat lines summarised). - Tested without games:
tests/run-tests.ps1passes: 10 Rust tests (ring wrap/full/corrupt, handshake, timeout, reconnect, Bye, restart, header mismatch), 9 C++ selftest checks, Rust↔C++ cross-process (crash → timeout; clean exit → Bye). - Tested in-game (agent-run, both games at the main menu):
- ER created the region, Skyrim opened it on kDataLoaded:
CONNECTED: handshake okon both sides within 50 ms. - ~7 min connected: heartbeat events every 5 s both ways, 0 dropped. ER
framesrose ~300 per 5 s (≈ 60 fps while unfocused at the title). eldenring.exeforce-killed → Skyrim:LOST: ER ... heartbeat timeout (last beat 2031 ms ago), Skyrim kept running.- ER relaunched alone →
attach#2, reconnected without restarting Skyrim. - Skyrim window closed → clean exit, ER:
LOST: Skyrim ... shutting down+Skyrim said Bye (reason=Quit), ER kept running. No crash logs.
- ER created the region, Skyrim opened it on kDataLoaded:
- Bugs fixed during the work: Hello read before its sender was detected looked like a restart (fix: check peer before reading events, and
answer a Hello received while connected). ER log level name
warn→warningto match spdlog. - Next: P3.
- Changed: Added
tools/dev.ps1(build → deploy → backup → launch → wait for plugin ready lines → collect logs). AI-agent files are now local-only, and history was rewritten so they never appear in it. Logging format moved todocs/DESIGN.md§8 and the release checklist torelease/README.md, so public docs are self-contained. Added a local pre-commit guard against private data and game/binary files. - Tested:
dev.ps1 -NoLaunch(full build + deploy in 9 s). The pre-commit guard blocks a staged Windows user path and passes clean files. The fulldev.ps1launch path gets its first real run in P2.
- Changed: Added
skse/(CMake + Ninja preset, VS-bundled vcpkg pinned to registry baseline 00c5775, CommonLibVR-ng submodule @39f9d07, AE only,src/main.cpp). Added the Cargo workspace +er-plugin/(eldenring-rs @59fbd3b,lib.rs+log.rs, panic=unwind). Added tools: build, deploy, backup-saves, launch, collect-logs, common.psm1. Addedlocal/paths.json. - Tested in-game (both games at once via
tools/launch.ps1):- Skyrim:
SkyrimXER v0.1.0.0 loaded, runtime 1.7.104.0→kDataLoaded. skse64.log: "loaded correctly". - ER:
loaded→game version supported, task system ready→per-frame task registered→ user loaded the test character →main player spawned (in world)→main player gone (menu/loading). me3 attach config: our native,savefile: skyrimxer.sl2,start_online: false.
- Skyrim:
- Bugs fixed: (1)
SKSE::Initreplaced our logger, fixed withSKSE::InitInfo{ .log = false }, re-tested OK. (2) build.ps1 aborted on the VS dev shell's harmless stderr under PS 5.1 +Stop, fixed by running that call withContinueand checking the exit code. - Build times: SKSE cold 385 s / incremental 10 s. ER cold 4m39s / incremental ~2 s.
- Next: P2 shared memory + heartbeat.
- Decided: CommonLibVR-ng 10.1.0 (
aepreset; hasRUNTIME_SSE_1_7_104). eldenring-rs v0.14 (supports exe 2.7.1.0 WW). libER dropped. Project license GPL-3.0-or-later (user choice; every CommonLib supporting 1.7.104 is GPL-3.0).LICENSEadded. - Found: ER input can be injected as
ChrActionsbits onCSChrActionRequestModule(no OS keys). Focus flag isDLUserInputManagerImpl.is_game_window_focused. Notes indocs/research/elden-ring-input.md. - Tested: eldenring-rs
apply-speffectexample built (29 s). CommonLibaebuilt with VS 2026 + bundled vcpkg (590 s), unit tests 25/25.tools/setup-check.ps1written and passing. - Moved to P3: arena choice, movement-direction source, both-games-at-once performance.
- Ran:
me3 launch -g eldenring --savefile skyrimxer.sl2after backing up ER saves tolocal/save-backups/. - Result ✔: me3 attached to "ELDEN RING 1.17.1.0 Worldwide". Hooks applied (filesystem, allocators, assets, skip_logos). Arxan detected
and the attach deferred cleanly. No EAC process. All save I/O redirected to
skyrimxer.sl2(a copy of ER0000 made on first use), andER0000.sl2was untouched. Zero WARN/ERROR lines. Log:logs/me3-smoke-stderr.txt+%LOCALAPPDATA%\garyttierney\me3\data\logs\transient-profile\. - Next: Phase 0 library checks (CommonLib fork for 1.7.104, eldenring-rs for 1.17.1).
- Changed: Installed me3 0.13.0 (official signed installer, silent). Installed Crash Logger 1.25.0 (GitHub) into
Data\SKSE\Plugins. Found that the installed SKSE 2.2.6 targeted 1.6.1170, which doesn't match the game's 1.7.104. Replaced it with SKSE 2.3.1 (127 overwritten files backed up tolocal/backups/skse-2.2.6/). Installed Address Library v13 All in One (containsversionlib-1-7-104-0.bin). Every change is listed inlocal/install-manifest.json. - Found: me3 profiles can't store a save file, so launches must pass
--savefile skyrimxer.sl2. me3 blocks matchmaking by default. MO2 is installed but has no instance, so files went straight intoData\. - Tested: File versions checked (
skse64_1_7_104.dll/ loader = 2.3.1).me3 info→ installation Found. Not yet tested in-game. - In-game check (same day): Launched through
skse64_loader.exe.skse64.log: SKSE 2.3.1 initialized, CrashLogger "loaded correctly", kDataLoaded reached. ✔ The Skyrim side of the toolchain works. - Next: me3 smoke test with Elden Ring (no natives,
--savefile skyrimxer.sl2), then the rest of Phase 0.
- Changed: Created the repo structure, local agent-rules file, design/roadmap/recon docs, whitelist
.gitignore, me3 profile template, and folder READMEs. Rangit init. Cloned the reference repos intoreference/(ignored). - Environment found: Skyrim
SkyrimSE.exe1.7.104.0 + SKSE 2.2.6 (no Address Library yet). Elden Ringeldenring.exe2.7.1.0. VS 2026 C++ ✔, Rust ✔, git ✔. Missing: me3, Address Library. - Decision: Skyrim plugin uses SkyCraft's CMake +
alandtse/CommonLibVR(ng) submodule setup, so xmake isn't needed. Fake-peer test processes are planned for P2. - Tested:
git status --ignoredconfirms only docs/config are tracked and game/build/local folders are ignored. - Next: Phase 0 (checking tools & versions). See
STATUS.md.