"""Original code operand trace and isolated pitch-clamp execution.

No process is opened. No game bytes, files or executable code are changed.
"""
import importlib.util
import json
import math
import struct
import sys
from pathlib import Path

sys.path.insert(0, 'src/loader/runtime/profile-tools/dev/tools/enshrouded')
from discovery.pe import PE
from capstone import Cs, CS_ARCH_X86, CS_MODE_64
from unicorn.x86_const import UC_X86_REG_RSP, UC_X86_REG_RIP, UC_X86_REG_XMM1, UC_X86_REG_XMM6, UC_X86_REG_XMM8, UC_X86_REG_XMM12

directory = Path(__file__).parent
code = json.loads((directory/'game-glider-instructions.json').read_text())
reflection = json.loads(Path('src/loader/runtime/profile-tools/devdata/enshrouded-client-25966634-kfc4/live-discovery/reflection.json').read_text())
types = reflection['types']
by_name = {t['qualifiedName']:t for t in types}
selection = json.loads(Path('src/loader/runtime/profile-tools/devdata/creative-mode-146/current-tracking-selection-inventory-20261016.json').read_text())
tracking = {x['qualifiedName']:x for x in selection['entries']}
pe = PE(r'C:\Program Files (x86)\Steam\steamapps\common\Enshrouded\enshrouded.exe')
assert pe.sha256 == '4d6d43ea4a91e71c82d60b045dde932179c99e32e40657efe374688b2933589a'
c = Cs(CS_ARCH_X86, CS_MODE_64)


def fields_at(type_name, offset):
    results = []
    def visit(t, offset, path, depth=0):
        assert depth < 20
        for field in t.get('structFields',{}).values():
            target = types[field['type']]
            if field['dataOffset'] <= offset < field['dataOffset'] + target['size']:
                next_path = path + [field['name']]
                results.append(dict(path='.'.join(next_path), fieldType=target['qualifiedName'], localOffset=field['dataOffset']))
                visit(target, offset-field['dataOffset'], next_path, depth+1)
        if t.get('innerType') is not None:
            visit(types[t['innerType']], offset, path, depth+1)
    visit(by_name[type_name], offset, [])
    return results


def instructions(begin, end):
    return [dict(rva=i.address, mnemonic=i.mnemonic, operands=i.op_str, bytes=i.bytes.hex())
            for i in c.disasm(pe.read(begin,end-begin),begin)]


spec = importlib.util.spec_from_file_location('attribute_oracle',
    'src/loader/runtime/profile-tools/dev/tools/enshrouded/verify-attribute-vm.py')
vm = importlib.util.module_from_spec(spec)
spec.loader.exec_module(vm)
oracle = vm.Oracle(pe)
u = oracle.uc
def vector(*values):
    return int.from_bytes(struct.pack('<4f', *values), 'little')
def f32(value): return struct.unpack('<f',struct.pack('<f',value))[0]
lower = struct.unpack('<f',pe.read(0x26ae0e+8+0x129cefa,4))[0]
upper = struct.unpack('<f',pe.read(0x26ae24+8+0x1295a94,4))[0]
cases = []
for vertical_impulse in (-10.0,0.0,10.0):
    for target in (-math.pi/2,-0.5,0.0,lower,0.5,upper,math.pi):
        u.reg_write(UC_X86_REG_RSP,oracle.STACK+0xff08)
        u.reg_write(UC_X86_REG_XMM1,vector(0,vertical_impulse,0,0))
        u.reg_write(UC_X86_REG_XMM8,vector(target,0,0,0))
        u.reg_write(UC_X86_REG_XMM12,0)
        u.emu_start(pe.base+0x26adff,pe.base+0x26ae36,timeout=100000,count=10000)
        assert u.reg_read(UC_X86_REG_RIP)==pe.base+0x26ae36
        pitch = struct.unpack('<f',u.reg_read(UC_X86_REG_XMM6).to_bytes(16,'little')[:4])[0]
        expected = lower if vertical_impulse>0 else max(lower,min(upper,f32(target)))
        assert pitch == expected,(vertical_impulse,target,pitch,expected)
        cases.append(dict(targetPitch=f32(target),totalImpulseY=vertical_impulse,resultPitch=pitch,expected=expected))

operands = [
    ('keen::ecs::Actor',0x38,'Selected glider ItemId for ItemRegistry lookup'),
    ('keen::ecs::Actor',0x1b1,'Action playback state bit0 gates glider rotation path'),
    ('keen::ecs::ActorInput',0x18c,'Local movement input X/Y/Z before pitch calculation'),
    ('keen::ecs::PlayerInput',0x2c8,'DirectionalInputMode selects pitch speed or target-angle input'),
    ('keen::ecs::Impulse',4,'Summed impulse Y selects lower limit versus normal clamp'),
    ('keen::ecs::Actor',0xec4,'Computed glider orientation target after clamp'),
    ('keen::ecs::Actor',0xee0,'Orientation interpolation speed'),
    ('keen::ecs::Velocity',0,'Glider physics reads and writes integrated velocity state'),
    ('keen::ecs::DynamicLocomotion',0x18c,'Updraft factor rescales forward acceleration'),
]
result = dict(image=pe.identity(), processOpened=False, processWrites=False, nativeGameCalls=False,
    originalToolkitPatch=dict(siteRva=0x26ae0e, lowerPitchRadians=lower,
        replacementPitchRadians=-1.5700000524520874, upperPitchRadians=upper),
    fields=[dict(type=name, offset=offset, purpose=purpose, reflectedPaths=fields_at(name,offset),
        trackingSelection=tracking.get(name,{}).get('selection')) for name,offset,purpose in operands],
    slices=dict(pitchInput=instructions(0x26abba,0x26ac6c),
                pitchClamp=instructions(0x26ade8,0x26ae36),
                targetOrientation=instructions(0x26b15f,0x26b236),
                physicsRead=instructions(0x42b777,0x42b7ac),
                physicsWrite=instructions(0x42b917,0x42b9cd)),
    originalClampCases=cases,
    originalRotationModifier=dict(name='Movement_RotationMod',attributeId=0x9f54afbe,
        rootId=0x96071f78,index=11,component='keen::ecs::Movement',
        attributeLookupRva=0x26e694,valueReadRva=0x26e6c2,factorMultiplyRva=0x26e6c7,
        interpolationSpeedMultiplyRva=0x26e6d7,
        instructions=instructions(0x26e672,0x26e6dd),
        scope='Turn/interpolation factor, not the minimum pitch constant',
        runtimeLookup='Canonical parser attribute catalog, no hardcoded mod root ID'),
    conclusions=dict(finiteInputCannotLowerThisTargetBelowFiveDegrees=True,
        angleSpeedControlsStepNotMinimumAngle=True,
        targetOrientationRewrittenWithinSameActorRotationCall=True,
        velocityIsARealMutablePhysicsState=True,
        equalInfiniteFlightViaDataProven=False),
    limits=['No live gliding or Lua writes performed',
        'Velocity regulation would be a different behavior and is not silently substituted',
        'Frame component assignment uses engine registration order, including dynamic runtime aliases',
        'A shared floating-point offset without register provenance is not a type proof'])
out=directory/'infinite-flight-operands-20261016.json'
out.write_text(json.dumps(result,indent=2))
print(json.dumps(dict(cases=len(cases),passed=True,lower=lower,upper=upper,path=str(out))))
