"""Reproduce original frame-fade activation and shared radius/threshold binding.

Read-only pinned-image analysis; never opens or calls the game. This does not
duplicate the complete clock proof in Trace-OriginalGalaxyFrameClock.py.
"""
from pathlib import Path
import hashlib
import json
import struct
import pefile
from capstone import Cs, CS_ARCH_X86, CS_MODE_64

ROOT = Path(__file__).resolve().parents[1]
IMAGE = ROOT / 'artifacts/ship-model-reference/client-copy/EliteDangerous64.exe'
OUT = ROOT / 'artifacts/galaxy-query-research/power-outer-sources/frame-fade-state'
raw = IMAGE.read_bytes()
digest = hashlib.sha256(raw).hexdigest()
assert digest == '6891f70a021fa9f3a0e00458c284f48d1226e68ae7fc02ad48081aed47857e93'
pe = pefile.PE(data=raw, fast_load=True)
base = pe.OPTIONAL_HEADER.ImageBase
decoder = Cs(CS_ARCH_X86, CS_MODE_64)
proofs = []


def guard(rva, expected, meaning):
    code = bytes.fromhex(expected)
    assert pe.get_data(rva, len(code)) == code, hex(rva)
    instructions = list(decoder.disasm(code, rva))
    assert sum(i.size for i in instructions) == len(code)
    proofs.append(dict(rva=hex(rva), bytes=code.hex(), sha256=hashlib.sha256(code).hexdigest(),
        meaning=meaning, instructions=[dict(rva=hex(i.address), instruction=i.mnemonic+' '+i.op_str) for i in instructions]))


# The general component interface is at base8. Its activation method's
# this+1A0 store is therefore the original input component's base1A8.
assert struct.unpack('<Q', pe.get_data(0x552B3E0 + 0x60, 8))[0] == base + 0x3B37630
guard(0x3B37630, '40534883ec20488bd94883c1f8e89ea90100488d4b60488d1583414a02e84e99cdfc',
      'Activation receives component+8, binds controls and enables its base68 dependency.')
guard(0x3B3765C, 'c783a001000000000000', 'Store zero to interface1A0 = component1A8.')

# Settings is a dependency-interface ID. The actual renderer publishes its
# interface+8, and the input consumer subtracts8 before its virtual30 call.
guard(0x3AC89A6, '8b0564804d02498d4e08418986e80000004989aef00000004989aef8000000',
      'Input constructor reads type ID5FA0A10 and binds its pointer atbaseF0.')
guard(0x3AF6100, '488b8bf00000000f283592c733014883c1f8488b01ff5030',
      'Target consumer loads inputF0, subtracts8, and calls the actual Settings virtual30.')
guard(0x3A5E4E0, '4883ec28488b024533c04883c1f84c8bd24c8d49584d0f44c84d85c974044d8d41088b1508255402498bcaff104883c428c3',
      'Renderer provider returns component58+8 for dependency type5FA0A10.')
guard(0x3A06803, '488d05fe8bb10148894658', 'Renderer component58 vtable is551F408.')
assert struct.unpack('<Q', pe.get_data(0x551F408 + 0x30, 8))[0] == base + 0x1A3C960
guard(0x1A3C960, '488d8128020000c3',
      'Settings virtual30 returns this228 = renderer component280, not another configuration threshold.')
guard(0x3AB5A60, '0f100af30f590dbd6e3e02f30f118928020000c3',
      'Current radius publisher writes the same interface228 / component280.')

# The input callback preserves its float. The companion clock trace closes
# its source, seconds conversion and normal frame clamps.
assert struct.unpack('<Q', pe.get_data(0x5529D98, 8))[0] == base + 0x3ABA020
guard(0x3ABA020, '33c0488d4a984885d20f28ca480f44c8e93bbd0300',
      'Callback5529D98[0] converts dependency this back to input component and copies XMM2 to XMM1 unchanged.')

result = dict(imageSha256=digest, gameProcessAccess=False, gameCalls=0, networkRequests=0,
    activation=dict(interface='component8 / vtable552B3E0', virtual='60 ->3B37630',
        currentTransition='component1A8', initialTransition=0, resetOccursAtActivation=True),
    sharedRadiusAndThreshold=dict(dependencyId='5FA0A10', provider='3A5E4E0',
        providerInterface='renderer component58 / vtable551F408', dependencyPointer='component60',
        thresholdGetter='inputF0-8 ->virtual30 ->1A3C960 ->renderer component280',
        currentRadiusPublisher='3AB5A60 ->interface228 / renderer component280',
        toolSource='current ordinary catalogue Descriptor.InnerRadius',
        zeroRange='Known zero takes the endpoint branch; neither strict-less-than comparison enters division.'),
    frameValue=dict(callbackTable='5529D98[0]', callback='3ABA020 ->3AF5D70',
        forwarding='XMM2 to XMM1 unchanged', elapsedSecondsProved=True,
        originalClampsProved=True, assumedFixedCadence=False,
        clockProof='original-frame-clock.json / research/Trace-OriginalGalaxyFrameClock.py'),
    productionApi=dict(type='GalaxyOriginalFrameFadeState',
        activate='Activate resets the tool-owned state to proved initial0',
        advance='TryAdvanceForCamera(actualCamera, currentInnerRadius, originalViewMode, explicitInputFrameValue)',
        prepare='TryCreateForCamera(actualCamera, currentInnerRadius, out fade); does not advance time',
        missingEvent='Invalidates history; remains unavailable until a new activation',
        missingClockDefault=None, hostElapsedSeconds='Use GalaxyOriginalFrameFadeClock to apply proved normal/fixed-step input policy'),
    unknown=['Current-version live client acceptance and original runtime phase comparison'], proofs=proofs)
OUT.mkdir(parents=True, exist_ok=True)
path = OUT / 'original-frame-fade-state.json'
path.write_text(json.dumps(result, indent=2)+'\n', encoding='utf-8')
print(json.dumps(dict(proofCount=len(proofs), initialTransition=0, sharedRadiusThreshold=True,
    inputFrameSecondsProved=True, output=str(path))))
