SplitScript reference / Language / state

state

declaration

state "game.exe" { ... } | state Provider { ... } | state { provider Name: Provider { ... }, ... }

Declares process attachment and persistent watched state.

A native string is an exact host process identity. The current Windows host reports executable filenames including .exe, so a Windows candidate must include that extension. An array tries alternate executable names in order; it does not attach to several processes at once. A named standard-library provider selects a typed memory model. Unity binds managed image schemas while GBA, PS1, PS2, SMS, Genesis, GCN, and Wii expose emulator-specific read roots and accept original console addresses in state fields. When one autosplitter supports genuinely different runtimes, a state { provider Name: Provider { ... }, ... } declaration cooperatively tries the alternatives that accept the attached process and selects the first one that completes. The read-only provider: StateProvider value identifies that choice. Fields with compatible declarations in every alternative remain directly available through current and old; a direct match on provider exposes alternative-only fields and provider roots. Process names shared by alternatives are attached only once. A provider alternative must read directly from an attached process; providers with a prepared schema or attachment context, such as Unity, use the concise single-provider form. Build-specific fields may use an if / else if / else chain over an enum global initialized during onAttach, or over an enum state field when the shape can change while attached. Later branches cover the exact variants left unmatched by earlier branches. Compatible fields declared in every possible branch remain directly available; other fields require the corresponding predicate or match refinement. Every state expression has one implicit fallible boundary (T!): internal postfix ? and a fallible final call propagate into that same boundary. Use an ordinary value block when address discovery or decoding needs several local steps; its final expression supplies the field value without requiring a helper function. A field may use another field from the same active branch by name, including as the base of an at path. Declaration order is irrelevant: the compiler evaluates dependencies first and rejects cycles. Initialization requires all required fields to succeed in one poll and seeds old and current equally without running lifecycle actions. Later, failed fields retain their accepted values while successful independent fields advance; a dependent field is not evaluated when one of its dependencies fails. Deliberately optional reads can discard their error into T? with discardError.

Examples

Read state from a native process

state "game.exe" {
    score = process.read<i32>(0x1000);
}

Compose fallible address discovery with a read

state "game.exe" {
    score: i32 = {
        let address = process.follow(0x1000, [0x20])?
        process.read(address)
    };
}

Try alternate executable names

state ["game.exe", "game-demo.exe"] {
    score: i32 at 0x1000;
}

Share one autosplitter across different runtimes

state {
    provider Windows: Native ["game.exe"] {
        level: u32 at 0x1000;
        checkpoint: u8 at 0x1100;
    },
    provider Advance: GBA {
        level: u32 at 0x03000010;
        room: u8 at 0x03000020;
    },
}

split {
    return match provider {
        StateProvider.Windows => current.checkpoint == 1,
        StateProvider.Advance => current.room == 5,
    }
}

Support multiple game builds

enum Build {
    Steam,
    GOG,
}

let build: Build

state "game.exe" {
    if build == Build.Steam {
        level: u32 at 0x1000;
        checkpoint: u8 at 0x1100;
    } else {
        level: u32 at 0x2000;
        checkpoint: u16 at 0x2100;
    }
}

onAttach {
    let module = await process.mainModule()
    if module.size == 10_000 {
        build = Build.Steam
        return
    }
    if module.size == 20_000 {
        build = Build.GOG
        return
    }
    await process.closed()
}

whileAttached {
    setVariable("Level", current.level)
    setVariable("Checkpoint", match build {
        Build.Steam => current.checkpoint as u16,
        Build.GOG => current.checkpoint,
    })
}

Read state from a GBA emulator

state GBA {
    room: u8 at 0x03000010;
}

Read state from a PlayStation emulator

state PS1 {
    health: u16 at 0x80012346;
}

Read state from a PlayStation 2 emulator

state PS2 {
    health: u16 at 0x00123456;
}

Read state from a Master System emulator

state SMS {
    lives: u8 at 0xc010;
}

Read state from a Sega Genesis emulator

state Genesis {
    score: u32 at 0x1200;
}

Read state from a GameCube emulator

state GCN {
    room: u16 at 0x80001000;
}

Read state from a Wii emulator

state Wii {
    room: u16 at 0x80001000;
}