/** * Detect whether a buffer is a known Granny2 file by inspecting its * first 16 bytes. Tries LE first, then BE-with-swap. Returns the * endianness + pointer width baked into the file so the caller can wire * its u32 reader accordingly. * * @param {GR2Input} buffer — the candidate .gr2 bytes. * @returns {GR2DetectResult} */ export function detectGR2(buffer: GR2Input): GR2DetectResult; /** * Parse a GR2 buffer into `{ header, sections, data, sectionBytes(...) }`. * * Reads the 32-byte magic, the post-magic header (60 or 72 bytes depending * on `version`), and the section array. Does NOT decompress section * payloads — call `decompressSection(section, file.sectionBytes(section))` * from `./Granny.js` for that. * * @param {GR2Input} buffer — the .gr2 bytes. * @returns {GR2File} * @throws {Error} on non-Granny input. * @throws {RangeError} when the declared section array escapes the buffer. */ export function parseGR2File(buffer: GR2Input): GR2File; /** Acceptable input shapes for the parser. * @typedef {ArrayBuffer | Uint8Array | DataView | ArrayBufferView} GR2Input */ /** Quad of u32 magic words (4 × 4 bytes at the file start). * @typedef {readonly [number, number, number, number]} GR2Magic */ /** Compression tag, see {@link COMPRESSION_NAMES}. * @typedef {0 | 1 | 2 | 3 | 4} CompressionTag */ /** Granny section-slot index, see {@link SECTION_NAMES}. * @typedef {0 | 1 | 2 | 3 | 4 | 5 | 6 | 7} SectionIndex */ /** * One entry in the GR2 section table (44 bytes on disk — see * `docs/gr2-format.md` § Section record). `compression_name` and * `semantic_name` are computed accessors ; the rest map 1-to-1 to the * on-disk u32s. * * @typedef {object} GR2Section * @property {number} index — 0-based position in the section table. * @property {number} compression — compression algorithm tag (0=none, 1=Oodle0, 2=Oodle1, 3=BitKnit, 4=BitKnit2). * @property {number} data_offset — offset of this section's compressed bytes, relative to the file start. * @property {number} data_size — length of this section's compressed bytes on disk. * @property {number} expanded_size — target length of the section once decompressed. * @property {number} internal_alignment — required alignment for this section's data buffer (4 / 8 / …). * @property {number} first_16bit — Oodle0 block-stop 1 : decoded-byte offset where the 16-bit length context ends. * @property {number} first_8bit — Oodle0 block-stop 2 : decoded-byte offset where the 8-bit length context ends. * @property {number} pointer_fixup_offset — pointer-fixup table offset (S5+). * @property {number} pointer_fixup_count — pointer-fixup entry count, 12 bytes each. * @property {number} mixed_marshalling_offset — mixed-marshalling table offset (S5+). * @property {number} mixed_marshalling_count — mixed-marshalling entry count, 12 bytes each. * @property {string} compression_name — computed : human name for {@link GR2Section.compression}. * @property {string} semantic_name — computed : Granny semantic name for {@link GR2Section.index}. */ /** * Top-level GR2 file header (~72 bytes for version ≥ 7, ~60 bytes otherwise). * * @typedef {object} GR2Header * @property {number} version — Granny file format version — ≥ 7 across our iRO ver12 corpus. * @property {number} total_size — total file size as declared by the writer. * @property {number} crc — CRC32 of the file's contents. * @property {number} section_array_offset — offset of the section array relative to the end of the magic. * @property {number} section_count — number of entries in the section array. * @property {readonly [number, number]} root_type — `[section_index, offset_within_section]`. * @property {readonly [number, number]} root_object — `[section_index, offset_within_section]`. * @property {number} type_tag — type-tag identifying the .gr2 schema generation. * @property {readonly number[]} extra_tags — 4 user / auxiliary tag values. * @property {number} string_db_crc — string-database CRC (version ≥ 7). * @property {readonly number[]} reserved — 3 reserved u32 (version ≥ 7). * @property {32 | 64} pointer_size — pointer width baked into the file's serialized references. * @property {boolean} byte_reversed — true if u32s are stored byte-reversed. */ /** * Parsed GR2 file, ready for section decompression. * * @typedef {object} GR2File * @property {GR2Header} header * @property {readonly GR2Section[]} sections * @property {Uint8Array} data — raw input bytes — kept for sliced reads via {@link GR2File.sectionBytes}. * @property {(section: GR2Section) => Uint8Array} sectionBytes — slice of `data` * carrying `section`'s on-disk compressed bytes. */ /** * Result of magic detection (precedes a full parse). * * @typedef {object} GR2DetectResult * @property {boolean} ok — true if the buffer's first 16 bytes match one of the known magics. * @property {boolean} byteReversed — true if u32s should be read big-endian. * @property {0 | 32 | 64} pointerSize — pointer width baked into the file (`0` when `ok === false`). */ /** `MAGIC_OLD` — earliest Granny 2.x ; LE u32s, 32-bit pointers. * @type {GR2Magic} */ export const MAGIC_OLD: GR2Magic; /** `MAGIC_32LE` — standard Granny 2.x, LE u32s, 32-bit pointers. **All iRO ver12 .gr2 use this**. * @type {GR2Magic} */ export const MAGIC_32LE: GR2Magic; /** `MAGIC_32BE` — `MAGIC_32LE`'s u32s each byte-reversed (big-endian on disk). * @type {GR2Magic} */ export const MAGIC_32BE: GR2Magic; /** `MAGIC_64LE` — 64-bit-pointer Granny 2.x, LE u32s. * @type {GR2Magic} */ export const MAGIC_64LE: GR2Magic; /** `MAGIC_64BE` — `MAGIC_64LE`'s u32s each byte-reversed. * @type {GR2Magic} */ export const MAGIC_64BE: GR2Magic; /** Number of bytes the magic quad occupies at the file start (16 used + 16 reserved). */ export const MAGIC_SIZE: 32; /** Bytes per entry in the section array (= 11 × u32). */ export const SECTION_RECORD_SIZE: 44; /** Number of `extra_tags` u32s in the header. */ export const EXTRA_TAG_COUNT: 4; /** Compression tag — section bytes are stored raw (no decompression). */ export const COMPRESSION_NONE: 0; /** Compression tag — RAD Oodle0 classic LZ + arithmetic codec. */ export const COMPRESSION_OODLE0: 1; /** Compression tag — RAD Oodle1 (not implemented ; no iRO ver12 asset uses it). */ export const COMPRESSION_OODLE1: 2; /** Compression tag — RAD BitKnit (not implemented). */ export const COMPRESSION_BITKNIT: 3; /** Compression tag — RAD BitKnit2 (not implemented). */ export const COMPRESSION_BITKNIT2: 4; /** Compression tag → human name. Used by `GR2Section.compression_name`. * @type {Readonly>} */ export const COMPRESSION_NAMES: Readonly>; /** Section-slot index → Granny semantic name. See `docs/gr2-format.md` § Section slots. * @type {Readonly>} */ export const SECTION_NAMES: Readonly>; /** * Acceptable input shapes for the parser. */ export type GR2Input = ArrayBuffer | Uint8Array | DataView | ArrayBufferView; /** * Quad of u32 magic words (4 × 4 bytes at the file start). */ export type GR2Magic = readonly [ number, number, number, number ]; /** * Compression tag, see {@link COMPRESSION_NAMES}. */ export type CompressionTag = 0 | 1 | 2 | 3 | 4; /** * Granny section-slot index, see {@link SECTION_NAMES}. */ export type SectionIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; /** * One entry in the GR2 section table (44 bytes on disk — see * `docs/gr2-format.md` § Section record). `compression_name` and * `semantic_name` are computed accessors ; the rest map 1-to-1 to the * on-disk u32s. */ export type GR2Section = { /** * — 0-based position in the section table. */ index: number; /** * — compression algorithm tag (0=none, 1=Oodle0, 2=Oodle1, 3=BitKnit, 4=BitKnit2). */ compression: number; /** * — offset of this section's compressed bytes, relative to the file start. */ data_offset: number; /** * — length of this section's compressed bytes on disk. */ data_size: number; /** * — target length of the section once decompressed. */ expanded_size: number; /** * — required alignment for this section's data buffer (4 / 8 / …). */ internal_alignment: number; /** * — Oodle0 block-stop 1 : decoded-byte offset where the 16-bit length context ends. */ first_16bit: number; /** * — Oodle0 block-stop 2 : decoded-byte offset where the 8-bit length context ends. */ first_8bit: number; /** * — pointer-fixup table offset (S5+). */ pointer_fixup_offset: number; /** * — pointer-fixup entry count, 12 bytes each. */ pointer_fixup_count: number; /** * — mixed-marshalling table offset (S5+). */ mixed_marshalling_offset: number; /** * — mixed-marshalling entry count, 12 bytes each. */ mixed_marshalling_count: number; /** * — computed : human name for {@link GR2Section.compression}. */ compression_name: string; /** * — computed : Granny semantic name for {@link GR2Section.index}. */ semantic_name: string; }; /** * Top-level GR2 file header (~72 bytes for version ≥ 7, ~60 bytes otherwise). */ export type GR2Header = { /** * — Granny file format version — ≥ 7 across our iRO ver12 corpus. */ version: number; /** * — total file size as declared by the writer. */ total_size: number; /** * — CRC32 of the file's contents. */ crc: number; /** * — offset of the section array relative to the end of the magic. */ section_array_offset: number; /** * — number of entries in the section array. */ section_count: number; /** * — `[section_index, offset_within_section]`. */ root_type: readonly [ number, number ]; /** * — `[section_index, offset_within_section]`. */ root_object: readonly [ number, number ]; /** * — type-tag identifying the .gr2 schema generation. */ type_tag: number; /** * — 4 user / auxiliary tag values. */ extra_tags: readonly number[]; /** * — string-database CRC (version ≥ 7). */ string_db_crc: number; /** * — 3 reserved u32 (version ≥ 7). */ reserved: readonly number[]; /** * — pointer width baked into the file's serialized references. */ pointer_size: 32 | 64; /** * — true if u32s are stored byte-reversed. */ byte_reversed: boolean; }; /** * Parsed GR2 file, ready for section decompression. */ export type GR2File = { header: GR2Header; sections: readonly GR2Section[]; /** * — raw input bytes — kept for sliced reads via {@link GR2File.sectionBytes}. */ data: Uint8Array; /** * — slice of `data` * carrying `section`'s on-disk compressed bytes. */ sectionBytes: (section: GR2Section) => Uint8Array; }; /** * Result of magic detection (precedes a full parse). */ export type GR2DetectResult = { /** * — true if the buffer's first 16 bytes match one of the known magics. */ ok: boolean; /** * — true if u32s should be read big-endian. */ byteReversed: boolean; /** * — pointer width baked into the file (`0` when `ok === false`). */ pointerSize: 0 | 32 | 64; }; /** * Encode a `[section, offset]` ref as a single u32 fake pointer. * Round-trips with {@link decodeFakePointer}. * * @param {number} section * @param {number} offset * @returns {number} */ export function makeFakePointer(section: number, offset: number): number; /** * Decode a fake pointer back into `[section, offset]`. Returns `null` * for pointers outside the fake-pointer range or pointing at a section * index that doesn't exist. * * @param {number} pointer * @param {number} sectionCount * @returns {SectionRef | null} */ export function decodeFakePointer(pointer: number, sectionCount: number): SectionRef | null; /** * Decompress every section, apply the pointer-fixup table, and return a * `LoadedGR2` ready for the type-tree walker. See module header for the * fake-pointer encoding rationale. * * @throws Error when the file declares `byte_reversed` AND has non-empty * mixed-marshalling tables — we'd need to actually flip endianness for * in-section scalars, which the iRO corpus never requires. (LE assets * carry empty mixed-marshalling tables ; the throw catches accidental * silent corruption on a future BE asset.) * * @param {import('./GrannyFile.js').GR2File} file * @returns {LoadedGR2} */ export function loadGR2(file: GR2File): LoadedGR2; /** * Walk a `DataTypeDefinition` chain starting at `ref` (a * `[section, offset]` pair, typically `file.header.root_type` or a * member's `referenceType`). Returns an array of member descriptors * terminating at the MT_END sentinel. * * Each member is read from a 32-byte record : * u32 member_type * u32 name_ptr (fake pointer → string) * u32 type_ptr (fake pointer → sub-type tree, or 0) * u32 array_width (default 1 if 0) * u32 extra[3] * u32 _unused * * Note : the on-disk pointer slots are pointer-sized (4 or 8 bytes), but * for the all-32-bit iRO corpus they fit exactly in 32 bits. * * @param {LoadedGR2} loaded * @param {SectionRef} ref — `[section, offset]` of the type-definition chain. * @param {ParseTypeTreeOptions} [options] * @returns {readonly TypeMember[]} member descriptors in source order. */ export function parseTypeTree(loaded: LoadedGR2, ref: SectionRef, options?: ParseTypeTreeOptions): readonly TypeMember[]; /** * Recursive total of `members`' storage sizes. INLINE members recurse * into their sub-type. The `seen` set guards against cyclic schemas * (Granny's schema is acyclic in practice but the guard is cheap). * * Cycle protection is per-recursion-stack : each sibling member starts * with a *fresh copy* of the caller's `seen` set. Without the copy, * structs with multiple inline siblings of the same sub-type (e.g. * TransformTrack's three identical CurveData inline members) collapse * to the size of one — a stride bug invisible to S5/S6 but fatal to * S7 animation walks, where `objectStorageSize` drives array stride. * * A shared sub-type in a DAG of inline members is sized once and cached in * `memo` (keyed by the sub-type ref), collapsing the otherwise-`B^D` re-walk * a crafted file could force. The memo-hit path **adds** the cached size (it * does not skip like the cycle guard), so sibling members of the same type * still sum correctly — the stride correctness above is preserved. For any * acyclic schema (all real assets) a type's size is context-independent, so * the memoized value equals the full recursion result → byte-exact. * * @param {LoadedGR2} loaded * @param {readonly TypeMember[]} members * @param {number} pointerSize * @param {Set} seen — cycle guard ; pass a fresh `Set` at the call site. * @param {Record} [memo] - per-call size cache keyed by sub-type ref ; defaults to a fresh `{}`. * @returns {number} total storage size in bytes. */ export function objectStorageSize(loaded: LoadedGR2, members: readonly TypeMember[], pointerSize: number, seen: Set, memo?: Record): number; /** * Materialize one instance against its `typeTree`, walking * `loaded.sectionsFixed[ref[0]]` from `ref[1]`. Returns a plain JS * object keyed by member name : * * { * Meshes: { type: 'array_of_references', count, target, * element_refs: [{section, offset}, ...] }, * Skeletons: { ... }, * Animations: { ... }, * ... * } * * Scalars are read from `sectionsOriginal` (unmodified bytes) ; pointers * are read from `sectionsFixed` (rewritten with fake pointers by * `loadGR2`). * * @param {LoadedGR2} loaded * @param {readonly TypeMember[]} typeTree * @param {SectionRef} ref — `[section, offset]` of the instance. * @param {ParseObjectOptions} [options] * @param {number} [depth] - internal: current INLINE recursion depth (caps at {@link MAX_INLINE_DEPTH}). * @param {Set | null} [seen] - internal: INLINE type-refs on the recursion stack (cycle guard); allocated lazily. * @returns {ParsedObject} field map keyed by member name. */ export function parseObject(loaded: LoadedGR2, typeTree: readonly TypeMember[], ref: SectionRef, options?: ParseObjectOptions, depth?: number, seen?: Set | null): ParsedObject; /** * Walk an inline-struct array : `count` objects of `typeRef`-shaped struct, * packed back-to-back starting at `arrayRef`. Returns each as * `{ref, fields}` where `ref` is the per-object `{section, offset}` (needed * by callers to read raw bytes — e.g. SkeletonBone's 68-byte Transform — * located at field-offsets inside the object's section) and `fields` is the * `parseObject` materialization. * * Port of blendergranny `types.read_reference_array_objects`. Null-safe : * returns `[]` when either ref is null or count is non-positive. * * @param {LoadedGR2} loaded * @param {RefDict | null} arrayRef * @param {number} count * @param {RefDict | null} typeRef * @param {ReadReferenceArrayObjectsOptions} [options] * @returns {readonly ReferenceArrayObject[]} */ export function readReferenceArrayObjects(loaded: LoadedGR2, arrayRef: RefDict | null, count: number, typeRef: RefDict | null, options?: ReadReferenceArrayObjectsOptions): readonly ReferenceArrayObject[]; /** * Typed error for malformed / hostile type-tree input (recursion breach, * cycle). Catch-friendly ; mirrors `DecompressionError` so callers can * distinguish a parser rejection from a bare `RangeError`. */ export class GrannyParseError extends Error { constructor(message: any); } /** * `[section_index, offset_within_section]` pair used everywhere as a ref. * @typedef {readonly [number, number]} SectionRef */ /** * `{section, offset}` dict shape returned for caller convenience. * @typedef {object} RefDict * @property {number} section * @property {number} offset */ /** * One MEMBER_TYPE constant value (0–22). See {@link MEMBER_TYPE_NAMES}. * @typedef {number} MemberTypeConstant */ /** * Member descriptor read from one 32-byte record of a DataTypeDefinition chain. * * @typedef {object} TypeMember * @property {MemberTypeConstant} memberType — raw `MT_*` enum value (0–22). * @property {string} memberTypeName — human name for {@link TypeMember.memberType}. * @property {string} name — member name (ASCII, decoded from the string-pointer slot). * @property {SectionRef | null} referenceType — decoded sub-type ref, or `null` for scalar / terminal members. * @property {number} arrayWidth — array width (default 1 for non-array members). * @property {readonly [number, number, number]} extra — three extra u32 slots in the on-disk record. * @property {number} offset — byte offset of this record within its containing section. */ /** * One pointer-fixup entry : rebases a writer-side pointer to a `[section, offset]` ref. * * @typedef {object} PointerFixup * @property {number} source_section * @property {number} source_offset * @property {SectionRef} target */ /** * One mixed-marshalling entry : describes an endian-flip the writer expects. * * @typedef {object} MixedMarshallingFixup * @property {number} source_section * @property {number} count * @property {number} offset * @property {SectionRef} type_ref */ /** * Bundle of decompressed sections + applied pointer fixups ready for walking. * * @typedef {object} LoadedGR2 * @property {import('./GrannyFile.js').GR2File} file — the originating GR2 file (header + section table). * @property {readonly Uint8Array[]} sectionsOriginal — one `Uint8Array` per section, decompressed, untouched (scalar reads). * @property {readonly Uint8Array[]} sectionsFixed — one `Uint8Array` per section, with pointer-fixup slots overwritten by fake pointers. * @property {readonly PointerFixup[]} pointerFixups — all pointer fixups parsed from the file (flattened across sections). * @property {readonly MixedMarshallingFixup[]} mixedFixups — all mixed-marshalling fixups (typically empty for LE corpus). * @property {4 | 8} pointerSize — pointer width in bytes (4 for 32-bit files, 8 for 64-bit). */ /** * Common shape of a materialized field returned by {@link parseObject}. * * @typedef {object} ParsedField * @property {string} type — `memberTypeName` from the source {@link TypeMember}. * @property {number} offset — byte offset of the field inside its parent struct. * @property {RefDict} [reference_type] - sub-type ref, if the source member had one. * @property {number | string} [value] - decoded scalar value (`int*` / `uint*` / `real32` / `string`). * @property {RefDict | null} [target] - resolved target ref (`reference` / `*_to_array` / `*_of_references`). * @property {number} [count] - element count (`*_to_array` / `*_of_references` / `*_variant_array`). * @property {boolean} [truncated] - true when {@link ParsedField.count} exceeded `maxArrayRefs`. * @property {readonly RefDict[]} [element_refs] - decoded per-element refs (only `array_of_references`). * @property {RefDict | null} [variant_type] - resolved variant-type ref (`variant_reference` / `reference_to_variant_array`). * @property {ParsedObject} [inline] - materialized sub-object (only `inline` members). */ /** * Plain JS object materialized from one `[ref, typeTree]` pair, keyed by member name. * @typedef {{ readonly [memberName: string]: ParsedField }} ParsedObject */ /** * Options for {@link parseTypeTree}. * @typedef {object} ParseTypeTreeOptions * @property {number} [maxMembers] - cap on the number of member records walked (default 512). */ /** * Options for {@link parseObject}. * @typedef {object} ParseObjectOptions * @property {number} [maxArrayRefs] - cap on the number of `element_refs` returned per array (default 256). */ /** * Options for {@link readReferenceArrayObjects}. * @typedef {object} ReadReferenceArrayObjectsOptions * @property {number} [maxCount] - cap on the number of array elements walked (default 64). * @property {number} [maxArrayRefs] - cap forwarded to {@link parseObject} per element (default `maxCount`). */ /** * One element of an inline-struct array as returned by {@link readReferenceArrayObjects}. * * @typedef {object} ReferenceArrayObject * @property {RefDict} ref — per-object `{section, offset}` within the array's section. * @property {ParsedObject} fields — materialized field map for this element. */ /** Marker terminating a type-definition chain (no payload). */ export const MT_END: 0; /** Inline sub-struct (no pointer, walks sub-type tree in place). */ export const MT_INLINE: 1; /** Pointer to a sub-object (single instance). */ export const MT_REFERENCE: 2; /** `(count:u32, pointer)` pair → array of `count` structs of `reference_type`. */ export const MT_REFERENCE_TO_ARRAY: 3; /** `(count:u32, pointer)` pair → array of `count` pointers to structs of `reference_type`. */ export const MT_ARRAY_OF_REFERENCES: 4; /** `(type_ptr, object_ptr)` pair → reference with runtime-resolved type. */ export const MT_VARIANT_REFERENCE: 5; /** Placeholder for a type the file uses but the SDK doesn't expose. */ export const MT_UNSUPPORTED: 6; /** `(type_ptr, count:u32, object_ptr)` triple → variant-typed array. */ export const MT_REFERENCE_TO_VARIANT_ARRAY: 7; /** Pointer to a NUL-terminated (or length-prefixed) string. */ export const MT_STRING: 8; /** Inline 4×4 + translation transform (68 bytes). */ export const MT_TRANSFORM: 9; /** 32-bit IEEE float. */ export const MT_REAL32: 10; /** Signed 8-bit integer. */ export const MT_INT8: 11; /** Unsigned 8-bit integer. */ export const MT_UINT8: 12; /** Signed 8-bit, intended as a normalized binormal component. */ export const MT_BINORMAL_INT8: 13; /** Unsigned 8-bit, intended as a normalized normal component. */ export const MT_NORMAL_UINT8: 14; /** Signed 16-bit integer. */ export const MT_INT16: 15; /** Unsigned 16-bit integer. */ export const MT_UINT16: 16; /** Signed 16-bit, intended as a normalized binormal component. */ export const MT_BINORMAL_INT16: 17; /** Unsigned 16-bit, intended as a normalized normal component. */ export const MT_NORMAL_UINT16: 18; /** Signed 32-bit integer. */ export const MT_INT32: 19; /** Unsigned 32-bit integer. */ export const MT_UINT32: 20; /** 16-bit half-float (rare). */ export const MT_REAL16: 21; /** Null reference marker (occupies one pointer slot, never resolves). */ export const MT_EMPTY_REFERENCE: 22; /** MEMBER_TYPE constant → human name. * @type {Readonly>} */ export const MEMBER_TYPE_NAMES: Readonly>; /** Base value distinguishing fake pointers from raw u32 noise. */ export const FAKE_POINTER_BASE: 268435456; /** Per-section stride inside the fake-pointer encoding. */ export const FAKE_SECTION_STRIDE: 1048576; export const MAX_INLINE_DEPTH: 64; /** * `[section_index, offset_within_section]` pair used everywhere as a ref. */ export type SectionRef = readonly [ number, number ]; /** * `{section, offset}` dict shape returned for caller convenience. */ export type RefDict = { section: number; offset: number; }; /** * One MEMBER_TYPE constant value (0–22). See {@link MEMBER_TYPE_NAMES}. */ export type MemberTypeConstant = number; /** * Member descriptor read from one 32-byte record of a DataTypeDefinition chain. */ export type TypeMember = { /** * — raw `MT_*` enum value (0–22). */ memberType: MemberTypeConstant; /** * — human name for {@link TypeMember.memberType}. */ memberTypeName: string; /** * — member name (ASCII, decoded from the string-pointer slot). */ name: string; /** * — decoded sub-type ref, or `null` for scalar / terminal members. */ referenceType: SectionRef | null; /** * — array width (default 1 for non-array members). */ arrayWidth: number; /** * — three extra u32 slots in the on-disk record. */ extra: readonly [ number, number, number ]; /** * — byte offset of this record within its containing section. */ offset: number; }; /** * One pointer-fixup entry : rebases a writer-side pointer to a `[section, offset]` ref. */ export type PointerFixup = { source_section: number; source_offset: number; target: SectionRef; }; /** * One mixed-marshalling entry : describes an endian-flip the writer expects. */ export type MixedMarshallingFixup = { source_section: number; count: number; offset: number; type_ref: SectionRef; }; /** * Bundle of decompressed sections + applied pointer fixups ready for walking. */ export type LoadedGR2 = { /** * — the originating GR2 file (header + section table). */ file: GR2File; /** * — one `Uint8Array` per section, decompressed, untouched (scalar reads). */ sectionsOriginal: readonly Uint8Array[]; /** * — one `Uint8Array` per section, with pointer-fixup slots overwritten by fake pointers. */ sectionsFixed: readonly Uint8Array[]; /** * — all pointer fixups parsed from the file (flattened across sections). */ pointerFixups: readonly PointerFixup[]; /** * — all mixed-marshalling fixups (typically empty for LE corpus). */ mixedFixups: readonly MixedMarshallingFixup[]; /** * — pointer width in bytes (4 for 32-bit files, 8 for 64-bit). */ pointerSize: 4 | 8; }; /** * Common shape of a materialized field returned by {@link parseObject}. */ export type ParsedField = { /** * — `memberTypeName` from the source {@link TypeMember}. */ type: string; /** * — byte offset of the field inside its parent struct. */ offset: number; /** * - sub-type ref, if the source member had one. */ reference_type?: RefDict; /** * - decoded scalar value (`int*` / `uint*` / `real32` / `string`). */ value?: number | string; /** * - resolved target ref (`reference` / `*_to_array` / `*_of_references`). */ target?: RefDict | null; /** * - element count (`*_to_array` / `*_of_references` / `*_variant_array`). */ count?: number; /** * - true when {@link ParsedField.count} exceeded `maxArrayRefs`. */ truncated?: boolean; /** * - decoded per-element refs (only `array_of_references`). */ element_refs?: readonly RefDict[]; /** * - resolved variant-type ref (`variant_reference` / `reference_to_variant_array`). */ variant_type?: RefDict | null; /** * - materialized sub-object (only `inline` members). */ inline?: ParsedObject; }; /** * Plain JS object materialized from one `[ref, typeTree]` pair, keyed by member name. */ export type ParsedObject = { readonly [memberName: string]: ParsedField; }; /** * Options for {@link parseTypeTree}. */ export type ParseTypeTreeOptions = { /** * - cap on the number of member records walked (default 512). */ maxMembers?: number; }; /** * Options for {@link parseObject}. */ export type ParseObjectOptions = { /** * - cap on the number of `element_refs` returned per array (default 256). */ maxArrayRefs?: number; }; /** * Options for {@link readReferenceArrayObjects}. */ export type ReadReferenceArrayObjectsOptions = { /** * - cap on the number of array elements walked (default 64). */ maxCount?: number; /** * - cap forwarded to {@link parseObject} per element (default `maxCount`). */ maxArrayRefs?: number; }; /** * One element of an inline-struct array as returned by {@link readReferenceArrayObjects}. */ export type ReferenceArrayObject = { /** * — per-object `{section, offset}` within the array's section. */ ref: RefDict; /** * — materialized field map for this element. */ fields: ParsedObject; }; /** * Read the 68-byte fixed Transform struct from `loaded.sectionsOriginal` * at `(section, offset)`. Returns the identity transform if the address * falls outside the section. * * @param {import('./GrannyTypeTree.js').LoadedGR2} loaded — output of `loadGR2(file)` * @param {number} section — section index into `loaded.sectionsOriginal` * @param {number} offset — byte offset into the section * @returns {Transform} */ export function readTransform(loaded: LoadedGR2, section: number, offset: number): Transform; /** * Public type for the 68-byte fixed Granny Transform struct — the single * canonical `Transform` shape shared by `GrannySkeleton`, `GrannyModel`, * and `GrannyPose` (they all import this one via * `@typedef {import('./GrannyTransform.js').Transform}`). * * The arrays are mutable `number[]` because that is exactly what the shared * {@link readTransform} decoder hands back (fresh `[…]` literals). An earlier * `GrannySkeleton` copy typed them as `readonly` fixed-length tuples ; that * copy is superseded here so the one decoder's output matches the type * without casts. * * @typedef {object} Transform * @property {number} flags — Granny TRANSFORM_FLAGS bitmask (HAS_POSITION / * HAS_ORIENTATION / HAS_SCALESHEAR). * @property {number[]} position — xyz translation. * @property {number[]} orientation — xyzw rotation quaternion. * @property {number[]} scaleShear — row-major 3×3 scale + shear matrix (9 floats). */ /** * Frozen identity transform — returned when the address falls outside * the section's byte range. Safe to share across callers (immutable). * * @type {Readonly} */ export const IDENTITY_TRANSFORM: Readonly; /** * Public type for the 68-byte fixed Granny Transform struct — the single * canonical `Transform` shape shared by `GrannySkeleton`, `GrannyModel`, * and `GrannyPose` (they all import this one via * `@typedef {import('./GrannyTransform.js').Transform}`). * * The arrays are mutable `number[]` because that is exactly what the shared * {@link readTransform} decoder hands back (fresh `[…]` literals). An earlier * `GrannySkeleton` copy typed them as `readonly` fixed-length tuples ; that * copy is superseded here so the one decoder's output matches the type * without casts. */ export type Transform = { /** * — Granny TRANSFORM_FLAGS bitmask (HAS_POSITION / * HAS_ORIENTATION / HAS_SCALESHEAR). */ flags: number; /** * — xyz translation. */ position: number[]; /** * — xyzw rotation quaternion. */ orientation: number[]; /** * — row-major 3×3 scale + shear matrix (9 floats). */ scaleShear: number[]; }; /** * One bone in a skeleton (parent-index linked into the same skeleton). * * @typedef {object} SkeletonBone * @property {number} index — 0-based index within the parent skeleton's `bones` array. * @property {string} name — ASCII bone name from the GR2 file (defaults to `Bone_`). * @property {number} parentIndex — index of the parent bone, or a negative value for root bones. * @property {import('./GrannyTransform.js').Transform} transform — local-space * Transform applied at this bone. * @property {number[]} inverseWorldTransform — inverse bind-pose 4×4 matrix * (16 floats, row-major) used for skinning. */ /** * A skeleton : ordered bones + LOD type. * * @typedef {object} Skeleton * @property {string} name — skeleton name from the GR2 file (defaults to `Skeleton_`). * @property {SkeletonBone[]} bones — bones in source order ; `parentIndex` * references entries within this array. * @property {number} lodType — Granny LOD type field (0 for standard skeletons). */ /** * Options for {@link extractSkeletons}. * * @typedef {object} ExtractSkeletonsOptions * @property {number} [maxSkeletons] - cap on the number of skeletons extracted (default 16). * @property {number} [maxBones] - cap on the number of bones extracted per skeleton (default 4096). */ /** * Walk `root.Skeletons` and return every skeleton with its bones, local * Transforms, and InverseWorldTransform 4×4. Returns `[]` for fixtures * that don't carry any skeleton (animation-only files in the iRO corpus). * * @param {import('./GrannyTypeTree.js').LoadedGR2} loaded — output of `loadGR2(file)`. * @param {ExtractSkeletonsOptions} [options] * @returns {Skeleton[]} */ export function extractSkeletons(loaded: LoadedGR2, options?: ExtractSkeletonsOptions): Skeleton[]; /** * One bone in a skeleton (parent-index linked into the same skeleton). */ export type SkeletonBone = { /** * — 0-based index within the parent skeleton's `bones` array. */ index: number; /** * — ASCII bone name from the GR2 file (defaults to `Bone_`). */ name: string; /** * — index of the parent bone, or a negative value for root bones. */ parentIndex: number; /** * — local-space * Transform applied at this bone. */ transform: Transform; /** * — inverse bind-pose 4×4 matrix * (16 floats, row-major) used for skinning. */ inverseWorldTransform: number[]; }; /** * A skeleton : ordered bones + LOD type. */ export type Skeleton = { /** * — skeleton name from the GR2 file (defaults to `Skeleton_`). */ name: string; /** * — bones in source order ; `parentIndex` * references entries within this array. */ bones: SkeletonBone[]; /** * — Granny LOD type field (0 for standard skeletons). */ lodType: number; }; /** * Options for {@link extractSkeletons}. */ export type ExtractSkeletonsOptions = { /** * - cap on the number of skeletons extracted (default 16). */ maxSkeletons?: number; /** * - cap on the number of bones extracted per skeleton (default 4096). */ maxBones?: number; }; /** * One declared component of a vertex row (Position, Normal, BoneWeights, …). * * @typedef {object} VertexComponent * @property {string} name — component name from the Granny vertex type tree. * @property {number} offset — byte offset within one vertex row at which this component starts. * @property {number} width — declared element width (typically 3 for Position, 2 for UV, 4 for weights). * @property {import('./GrannyTypeTree.js').MemberTypeConstant} memberType — raw `MT_*` enum value the component is stored as. */ /** * A skeleton-bone reference a mesh declares it depends on (skinning slot). * * @typedef {object} BoneBinding * @property {number} index — 0-based slot index within the mesh's `boneBindings` array. * @property {string} name — bone name (matches a `SkeletonBone.name` in the model's skeleton). */ /** * One per-vertex (bone, weight) entry for skinning. * * @typedef {object} VertexBoneWeight * @property {number} boneIndex — index into the mesh's `boneBindings` array (NOT a global bone index). * @property {number} weight — skinning weight in `[0, 1]` (already normalized for normal-uint encodings). */ /** * One material-bucketed triangle range within the mesh. * * @typedef {object} MeshTriangleGroup * @property {number} materialIndex — index into the mesh's `materials` array. * @property {number} triFirst — first triangle of the batch (index into the mesh's triangle list). * @property {number} triCount — number of triangles in the batch. */ /** * Material metadata + the first associated texture (file name + size). * * @typedef {object} MaterialInfo * @property {number} index — 0-based index in the GR2 file's Materials array. * @property {string} name — material name (defaults to `Material_` if missing). * @property {string} textureFile — texture file name as authored (empty when no Texture / Maps). * @property {readonly [number, number] | null} textureSize — `[width, height]` in pixels when the * Texture sub-object declares them ; `null` otherwise. */ /** * A fully decoded mesh ready for the renderer. * * @typedef {object} MeshGeometry * @property {string} name — mesh name (defaults to `Mesh_` if missing). * @property {number} vertexCount — number of vertices in the vertex buffer. * @property {number} indexCount — number of indices (always divisible by 3 for triangle meshes). * @property {number} vertexStride — bytes per vertex row in the source vertex buffer. * @property {readonly VertexComponent[]} components — declared vertex layout components in source order. * @property {ReadonlyArray} positions — per-vertex `[x, y, z]` positions. * @property {ReadonlyArray} normals — per-vertex `[x, y, z]` normals. * @property {ReadonlyArray} uvs — per-vertex `[u, v]` texture coordinates. * @property {readonly number[]} indices — index buffer (16- or 32-bit indices, decoded to JS numbers). * @property {readonly BoneBinding[]} boneBindings — mesh-local bone binding table. * @property {ReadonlyArray} vertexWeights — per-vertex bone-weight list. * @property {readonly MaterialInfo[]} materials — material references the mesh declares it uses. * @property {readonly MeshTriangleGroup[]} triangleGroups — triangle batches grouped by material. */ /** * Options for {@link extractMeshes}. * * @typedef {object} ExtractMeshesOptions * @property {number} [maxMeshes] - cap on the number of meshes extracted (default 32). * @property {number} [maxMaterials] - cap on the number of materials looked up (default 4096). * @property {number} [maxBones] - cap on the number of bone / material bindings per mesh (default 4096). */ /** * Options for {@link extractMaterials}. * * @typedef {object} ExtractMaterialsOptions * @property {number} [maxMaterials] - cap on the number of materials extracted (default 4096). */ /** * Walk `root.Meshes` and decode every static mesh into a renderable * `MeshGeometry`. Returns `[]` for fixtures without any mesh (animation- * only files in the iRO corpus) and skips meshes whose vertex buffer is * unparseable (null ref / zero count / vertex type missing). * * @param {import('./GrannyTypeTree.js').LoadedGR2} loaded * @param {ExtractMeshesOptions} [options] * @returns {readonly MeshGeometry[]} */ export function extractMeshes(loaded: LoadedGR2, options?: ExtractMeshesOptions): readonly MeshGeometry[]; /** * Public top-level Materials extractor. Returns one {@link MaterialInfo} * per `root.Materials` entry, in source order (so `materials[i].index === i` * for fixtures with no null refs). Mirrors `extractMeshes` / `extractTextures` * surface : pure function of `loaded`, idempotent, no side effects. * * For consumers that want material binding per mesh, use `extractMeshes` — * it already resolves materials per face-group via the shared cache. * * @param {import('./GrannyTypeTree.js').LoadedGR2} loaded * @param {ExtractMaterialsOptions} [options] * @returns {readonly MaterialInfo[]} */ export function extractMaterials(loaded: LoadedGR2, options?: ExtractMaterialsOptions): readonly MaterialInfo[]; /** * One declared component of a vertex row (Position, Normal, BoneWeights, …). */ export type VertexComponent = { /** * — component name from the Granny vertex type tree. */ name: string; /** * — byte offset within one vertex row at which this component starts. */ offset: number; /** * — declared element width (typically 3 for Position, 2 for UV, 4 for weights). */ width: number; /** * — raw `MT_*` enum value the component is stored as. */ memberType: MemberTypeConstant; }; /** * A skeleton-bone reference a mesh declares it depends on (skinning slot). */ export type BoneBinding = { /** * — 0-based slot index within the mesh's `boneBindings` array. */ index: number; /** * — bone name (matches a `SkeletonBone.name` in the model's skeleton). */ name: string; }; /** * One per-vertex (bone, weight) entry for skinning. */ export type VertexBoneWeight = { /** * — index into the mesh's `boneBindings` array (NOT a global bone index). */ boneIndex: number; /** * — skinning weight in `[0, 1]` (already normalized for normal-uint encodings). */ weight: number; }; /** * One material-bucketed triangle range within the mesh. */ export type MeshTriangleGroup = { /** * — index into the mesh's `materials` array. */ materialIndex: number; /** * — first triangle of the batch (index into the mesh's triangle list). */ triFirst: number; /** * — number of triangles in the batch. */ triCount: number; }; /** * Material metadata + the first associated texture (file name + size). */ export type MaterialInfo = { /** * — 0-based index in the GR2 file's Materials array. */ index: number; /** * — material name (defaults to `Material_` if missing). */ name: string; /** * — texture file name as authored (empty when no Texture / Maps). */ textureFile: string; /** * — `[width, height]` in pixels when the * Texture sub-object declares them ; `null` otherwise. */ textureSize: readonly [ number, number ] | null; }; /** * A fully decoded mesh ready for the renderer. */ export type MeshGeometry = { /** * — mesh name (defaults to `Mesh_` if missing). */ name: string; /** * — number of vertices in the vertex buffer. */ vertexCount: number; /** * — number of indices (always divisible by 3 for triangle meshes). */ indexCount: number; /** * — bytes per vertex row in the source vertex buffer. */ vertexStride: number; /** * — declared vertex layout components in source order. */ components: readonly VertexComponent[]; /** * — per-vertex `[x, y, z]` positions. */ positions: ReadonlyArray; /** * — per-vertex `[x, y, z]` normals. */ normals: ReadonlyArray; /** * — per-vertex `[u, v]` texture coordinates. */ uvs: ReadonlyArray; /** * — index buffer (16- or 32-bit indices, decoded to JS numbers). */ indices: readonly number[]; /** * — mesh-local bone binding table. */ boneBindings: readonly BoneBinding[]; /** * — per-vertex bone-weight list. */ vertexWeights: ReadonlyArray; /** * — material references the mesh declares it uses. */ materials: readonly MaterialInfo[]; /** * — triangle batches grouped by material. */ triangleGroups: readonly MeshTriangleGroup[]; }; /** * Options for {@link extractMeshes}. */ export type ExtractMeshesOptions = { /** * - cap on the number of meshes extracted (default 32). */ maxMeshes?: number; /** * - cap on the number of materials looked up (default 4096). */ maxMaterials?: number; /** * - cap on the number of bone / material bindings per mesh (default 4096). */ maxBones?: number; }; /** * Options for {@link extractMaterials}. */ export type ExtractMaterialsOptions = { /** * - cap on the number of materials extracted (default 4096). */ maxMaterials?: number; }; /** * Walk `root.Models` and return every model with its skeleton index, its * initial placement Transform, and its mesh-binding list. Returns `[]` * for fixtures that don't carry any model (animation-only files). * * @param {import('./GrannyTypeTree.js').LoadedGR2} loaded — output of `loadGR2(file)`. * @param {{ maxModels?: number, maxBindings?: number }} [options] * @returns {ModelInfo[]} */ export function extractModels(loaded: LoadedGR2, options?: { maxModels?: number; maxBindings?: number; }): ModelInfo[]; /** * One binding of a Mesh to a Model. Indices into the array returned by * `extractMeshes(loaded)`. The skinning bone-table is inherited from the * Model's `skeletonIdx` ; per-binding `ToBoneIndices` is supported by * newer Granny SDK versions but absent in the iRO ver12 corpus and * therefore not surfaced here. */ export type ModelMeshBinding = { /** * — index into `extractMeshes(loaded)` ; -1 if the * on-disk pointer doesn't resolve to any extracted mesh (defensive fallback). */ meshIdx: number; }; /** * Materialized `GrannyModel` — the canonical instance struct that binds * a `GrannySkeleton` to a list of `GrannyMesh` with an initial world * placement Transform. Returned in order matching `root.Models.element_refs`. */ export type ModelInfo = { /** * — Model.Name, or `Model_` when the name string is empty. */ name: string; /** * — index into `extractSkeletons(loaded)` ; -1 when * the on-disk pointer doesn't resolve. */ skeletonIdx: number; /** * — 68-byte * Granny Transform decoded into `{ flags, position, orientation, scaleShear }`. * Identity when the address falls outside the section. */ initialPlacement: Transform; /** * — one entry per * `GrannyModelMeshBinding` in the model. */ meshBindings: ModelMeshBinding[]; }; /** * Ensure the IGC texture codec is loaded. In the default (static) build the * decoder is already bundled, so this resolves immediately — call it anyway * for forward-compat with the code-split build, where it dynamic-imports the * IGC chunk before the first IGC `parseTextured` / `extractTextures`. * * Idempotent ; safe to await repeatedly. * * @returns {Promise} */ export function loadTextureCodec(): Promise; /** * One decoded (texture, image, MIP) entry returned by {@link extractTextures}. * * @typedef {object} TextureRecord * @property {number} texIdx — 0-based texture index within `root.Textures`. * @property {number} imgIdx — 0-based image index within the texture's `Images` array (iRO uses 0). * @property {number} mipIdx — 0-based MIP-level index within the image's `MIPLevels` array (iRO uses 0). * @property {string} name — texture authored name (defaults to `tex` when `FromFileName` is empty). * @property {string} fromFileName — original `FromFileName` from the Granny struct (may include Windows paths). * @property {number} width — MIP width in pixels. * @property {number} height — MIP height in pixels. * @property {1 | 2 | 3} encoding — encoding tag (`1` = Raw, `2` = S3TC, `3` = IGC). * @property {number} subFormat — S3TC subformat (`0..3` = DXT-N) ; meaningful only for `encoding === 2`. * @property {0 | 1} alpha — `1` when the texture carries an alpha channel ; `0` for opaque. * @property {Uint8Array} pixels — decoded RGBA8888 bytes, length = `width * height * 4`. */ /** * One pre-decode record yielded by {@link walkTextureImages}. * * @typedef {object} TextureWalkRecord * @property {number} texIdx * @property {number} imgIdx * @property {number} mipIdx * @property {number} width * @property {number} height * @property {number} encoding * @property {number} subFormat * @property {0 | 1} alpha * @property {string} fromFileName * @property {Uint8Array | null} pixelBytes — raw `Pixels` bytes from the .gr2 ; * `null` when the pixel array can't be resolved. * @property {number} pixelCount — length declared in the Granny `Pixels` / * `PixelBytes` reference (may differ from `pixelBytes?.length` on broken assets). */ /** * Options for {@link extractTextures} and {@link walkTextureImages}. * * @typedef {object} ExtractTexturesOptions * @property {number} [maxTextures] - cap on the number of textures walked (default 256). * @property {number} [maxImages] - cap on the number of images per texture (default 8). * @property {number} [maxMips] - cap on the number of MIP levels per image (default 32 ; iRO corpus uses 1). */ /** * Walk `root.Textures` and decode every (texture, image, MIP) triple to * RGBA8888. Returns a flat array — one entry per MIP — matching the * `tests/fixtures/baked/textures/textures.json` manifest shape so byte- * exact parity tests can join by `(texIdx, imgIdx, mipIdx)`. * * Texture-less fixtures (animation-only files in the iRO corpus) resolve * to `[]`. IGC textures decode synchronously in the default build (the * code-split build needs `await loadTextureCodec()` first) ; S3TC throws * (no iRO asset uses it). * * @param {import('./GrannyTypeTree.js').LoadedGR2} loaded — output of `loadGR2(file)`. * @param {ExtractTexturesOptions} [options] * @returns {TextureRecord[]} * @throws {Error} on S3TC textures (encoding=2) — no iRO asset uses them. */ export function extractTextures(loaded: LoadedGR2, options?: ExtractTexturesOptions): TextureRecord[]; /** * Walk `root.Textures → Images → MIPLevels` and emit one record per * (texture, image, MIP) triple, carrying the raw `Pixels` bytes * pre-decode. This is the shared traversal used by both * `extractTextures` (decode path) and `scripts/bake-textures.mjs` (Wine * shim driver). * * Animation-only fixtures (or any fixture where `root.Textures` is * empty / absent) resolve to `[]`. * * @param {import('./GrannyTypeTree.js').LoadedGR2} loaded — output of `loadGR2(file)`. * @param {ExtractTexturesOptions} [options] * @returns {TextureWalkRecord[]} */ export function walkTextureImages(loaded: LoadedGR2, options?: ExtractTexturesOptions): TextureWalkRecord[]; /** * One decoded (texture, image, MIP) entry returned by {@link extractTextures}. */ export type TextureRecord = { /** * — 0-based texture index within `root.Textures`. */ texIdx: number; /** * — 0-based image index within the texture's `Images` array (iRO uses 0). */ imgIdx: number; /** * — 0-based MIP-level index within the image's `MIPLevels` array (iRO uses 0). */ mipIdx: number; /** * — texture authored name (defaults to `tex` when `FromFileName` is empty). */ name: string; /** * — original `FromFileName` from the Granny struct (may include Windows paths). */ fromFileName: string; /** * — MIP width in pixels. */ width: number; /** * — MIP height in pixels. */ height: number; /** * — encoding tag (`1` = Raw, `2` = S3TC, `3` = IGC). */ encoding: 1 | 2 | 3; /** * — S3TC subformat (`0..3` = DXT-N) ; meaningful only for `encoding === 2`. */ subFormat: number; /** * — `1` when the texture carries an alpha channel ; `0` for opaque. */ alpha: 0 | 1; /** * — decoded RGBA8888 bytes, length = `width * height * 4`. */ pixels: Uint8Array; }; /** * One pre-decode record yielded by {@link walkTextureImages}. */ export type TextureWalkRecord = { texIdx: number; imgIdx: number; mipIdx: number; width: number; height: number; encoding: number; subFormat: number; alpha: 0 | 1; fromFileName: string; /** * — raw `Pixels` bytes from the .gr2 ; * `null` when the pixel array can't be resolved. */ pixelBytes: Uint8Array | null; /** * — length declared in the Granny `Pixels` / * `PixelBytes` reference (may differ from `pixelBytes?.length` on broken assets). */ pixelCount: number; }; /** * Options for {@link extractTextures} and {@link walkTextureImages}. */ export type ExtractTexturesOptions = { /** * - cap on the number of textures walked (default 256). */ maxTextures?: number; /** * - cap on the number of images per texture (default 8). */ maxImages?: number; /** * - cap on the number of MIP levels per image (default 32 ; iRO corpus uses 1). */ maxMips?: number; }; /** * Walk `root.TrackGroups` and `root.Animations` and return the * `Animation[]` array. Each animation carries its referenced * `TrackGroup[]` (resolved by name), and each track group carries its * `TransformTrack[]` with decoded position / orientation / scale-shear * curves ready for `evaluateTransformTrack`. * * Returns `[]` for fixtures that don't expose any animation (typical of * pure model files — though some iRO model files bundle a single rest * animation). * * @param {import('./GrannyTypeTree.js').LoadedGR2} loaded * @param {ExtractAnimationsOptions} [options] * @returns {readonly Animation[]} */ export function extractAnimations(loaded: LoadedGR2, options?: ExtractAnimationsOptions): readonly Animation$1[]; /** * Evaluate all three curves of a `TransformTrack` at time `t` and * return the local Transform `{ position[3], orientation[4], * scaleShear[9] }`. Curves that are null or empty fall back to identity * values (zero position, identity quat, identity 3×3 scale-shear) so * the return shape is always populated and ready for the renderer. * * Uniform-scale tracks (dim 3) are expanded to a diagonal 3×3 matrix * so callers don't need to branch on the original codec dimension. * * @param {TransformTrack | null | undefined} track * @param {number} t — sample time. * @returns {EvaluatedTransform} */ export function evaluateTransformTrack(track: TransformTrack | null | undefined, t: number): EvaluatedTransform; /** * Evaluate every TransformTrack of every TrackGroup in `animation` at * time `t`. Returns a map keyed by `transformTrack.name` (the name S8 * will use to join against `skeleton.bones[i].name`). Track-group * boundaries are flattened — caller doesn't typically care which group * a track came from once the names are resolved. * * @param {Animation | null | undefined} animation * @param {number} t — sample time. * @returns {{ readonly [trackName: string]: EvaluatedTransform }} */ export function evaluateAnimation(animation: Animation$1 | null | undefined, t: number): { readonly [trackName: string]: EvaluatedTransform; }; /** * Supported curve format names ; mirrors blendergranny + LegacyCurve32f. * The `(string & {})` member keeps the literal-completion list while still * accepting `*Constant32f` and future codec names. */ export type CurveCodec = "D3K16uC16u" | "D3I1K16uC16u" | "D4nK8uC7u" | "D4nK16uC15u" | "LegacyCurve32f" | "DaIdentity" | (string & {}); /** * Compressed B-spline curve. Decoded knots + controls live in `Float32Array`s ; * `controls` is flattened so consumers read it as * `controls[knotIndex * dimension + dim]`. For constant codecs (`*Constant32f`) * and identity codecs (`DaIdentity`), `knots` and `controls` are both empty and * `sampleValue` carries the fixed value (length = dimension). */ export type Curve = { /** * — codec name decoded from the curve type's first member. */ codec: CurveCodec; /** * — Granny-encoded format byte (≥0 for modern codecs, -1 for legacy). */ format: number; /** * — B-spline degree (0 = step, 1 = linear, 2 = quadratic, higher = nearest). */ degree: number; /** * — per-knot value width (3 position, 4 orientation, 9 scale-shear). */ dimension: number; /** * — combined knot + control count from the on-disk header. */ knotControlCount: number; /** * — fallback value for constant / identity curves (length = dimension). */ sampleValue: Float32Array; /** * — time-axis values in source order. Empty for constant / identity codecs. */ knots: Float32Array; /** * — flattened controls in `[knot * dimension + dim]` order. */ controls: Float32Array; }; /** * One bone's local-Transform timeline. The three curves drive position (dim 3), * orientation (dim 4 quaternion), and scale-shear (dim 9 3×3 matrix) * independently. A curve is `null` only when the file omits it — * `evaluateTransformTrack` substitutes identity in that case. */ export type TransformTrack = { /** * — index within the parent TrackGroup's `transformTracks` array. */ index: number; /** * — ASCII name from the GR2 file (joins to `skeleton.bones[i].name`). */ name: string; /** * — Granny SDK flags (typically 0 — interpretation deferred). */ flags: number; /** * — quaternion orientation curve at this bone, or `null` if absent. */ orientationCurve: Curve | null; /** * — position curve at this bone, or `null` if absent. */ positionCurve: Curve | null; /** * — scale + shear curve at this bone, or `null` if absent. */ scaleShearCurve: Curve | null; }; /** * A coherent set of TransformTracks. Multiple animations may share a single * TrackGroup (e.g. character idle + walk both reference the same skeleton's tracks). */ export type TrackGroup = { /** * — index within the top-level `track_groups` array. */ index: number; /** * — track group name — animations reference it via this name. */ name: string; /** * — reported count of vector tracks (values not decoded). */ vectorTrackCount: number; /** * — reported count of transform tracks (must equal `transformTracks.length`). */ transformTrackCount: number; /** * — reported count of text tracks (not decoded). */ textTrackCount: number; /** * — Granny SDK accumulation flags. */ accumulationFlags: number; /** * — per-loop translation offset along the dominant axis. */ loopTranslation: number; /** * — vector-track names (decoded — values not yet). */ vectorTrackNames: readonly string[]; /** * — per-bone Transform timelines decoded for this group. */ transformTracks: readonly TransformTrack[]; }; /** * One playable animation : duration + references to TrackGroups by name. */ type Animation$1 = { /** * — index within the top-level `animations` array. */ index: number; /** * — animation name (e.g. `attack`, `dead`, `move` for iRO assets). */ name: string; /** * — total duration in seconds. */ duration: number; /** * — suggested per-frame time step (`0` when the file doesn't carry one). */ timeStep: number; /** * — curve-sampling oversampling factor (typically `1.0`). */ oversampling: number; /** * — default loop count (`0` one-shot, `-1` infinite by convention). */ defaultLoopCount: number; /** * — Granny SDK flags. */ flags: number; /** * — TrackGroup names referenced by this animation. */ trackGroupNames: readonly string[]; /** * — TrackGroup objects resolved by name lookup (may be `[]`). */ trackGroups: readonly TrackGroup[]; }; /** * Options for {@link extractAnimations}. */ export type ExtractAnimationsOptions = { /** * - cap on the number of TrackGroups extracted (default 64). */ maxTrackGroups?: number; /** * - cap on the number of TransformTracks per TrackGroup (default 512). */ maxTracksPerGroup?: number; /** * - cap on the number of Animations extracted (default 64). */ maxAnimations?: number; }; /** * Local Transform produced by {@link evaluateTransformTrack} at a given time. */ export type EvaluatedTransform = { /** * — bone position in parent space (x, y, z). */ position: readonly [ number, number, number ]; /** * — bone orientation quaternion * (x, y, z, w) ; re-normalized after blend. */ orientation: readonly [ number, number, number, number ]; /** * — bone scale + shear matrix (3×3 row-major, 9 floats). */ scaleShear: readonly number[]; }; /** * Convert one Transform `{position, orientation, scaleShear}` into a * column-major 4×4 matrix `Mlocal = T × R × S`. The quaternion is used * as-is (granny2.dll does not renormalize at matrix-build — see below) ; * the 9-float scale-shear (row-major 3×3) is lifted into the upper-left * 3×3 of the column-major 4×4. Output is a fresh Float32Array(16). * * @param {SampledTransform | null | undefined} transform * @returns {Float32Array} fresh column-major 4×4. */ export function composeLocalMatrix(transform: SampledTransform | null | undefined): Float32Array; /** * Multiply two column-major 4×4 matrices : `out = a × b`. The result is * written into the supplied `out` array (caller may pre-allocate for * hot-path reuse) ; pass `null` for `out` to allocate a fresh Float32Array. * `a` and `b` may alias `out`. * * @param {ArrayLike} a — column-major 4×4. * @param {ArrayLike} b — column-major 4×4. * @param {Float32Array | number[] | null} [out] - destination ; a plain * `number[]` is accepted for the internal f64 cascade. Fresh Float32Array(16) * when null/omitted. * @returns {Float32Array | number[]} `out` (or the freshly allocated result). */ export function multiplyMat4(a: ArrayLike, b: ArrayLike, out?: Float32Array | number[] | null): Float32Array | number[]; /** * Walk the bone hierarchy parent-first and produce per-bone world * matrices via forward kinematics : `Mworld[i] = Mworld[parent] × * Mlocal[i]` (root bones use just `Mlocal[i]`). S6 guarantees * `parentIndex < boneIndex` so a single forward pass suffices. * * `localMatrices` must be an array of column-major 16-float matrices * (Float32Array or plain `Array`), one per bone, in the same * order as `skeleton.bones`. The cascade itself runs in plain `Array`s * to keep f64 precision through deep skeletons (10+ level chains * accumulate ~5e-4 of f32 ULP otherwise — well above the 1e-4 parity * target). Returns a fresh array of Float32Array(16) (GPU-ready). * * @param {import('./GrannySkeleton.js').Skeleton} skeleton * @param {readonly ArrayLike[]} localMatrices — one column-major 4×4 * per bone, in `skeleton.bones` order. * @returns {Float32Array[]} fresh per-bone world matrices. */ export function composeWorldPose(skeleton: Skeleton, localMatrices: readonly ArrayLike[]): Float32Array[]; /** * Post-multiply each world matrix by the bone's bind-pose inverse : * `Mskin[i] = Mworld[i] × IWT[i]`. The result is the matrix the GPU * vertex shader uses to push a bind-pose vertex into the current frame's * world space. * * The on-disk IWT is 16 floats in Granny's row-major byte layout ; * `IWT_TRANSPOSE_ON_LOAD` controls whether they're transposed during the * row-major → column-major conversion. The bind-pose smoke test * (`composeSkinningMatrices(skeleton, composeWorldPose(skeleton, bind))` * returns identity per bone) is the canonical check. * * @param {import('./GrannySkeleton.js').Skeleton} skeleton * @param {readonly ArrayLike[]} worldMatrices — per-bone world matrices * from {@link composeWorldPose}. * @returns {Float32Array[]} fresh per-bone skinning matrices. */ export function composeSkinningMatrices(skeleton: Skeleton, worldMatrices: readonly ArrayLike[]): Float32Array[]; /** * Top-level entry : sample one animation at time `t` and produce all * three pose arrays at once. The per-bone local Transform is read from * `evaluateAnimation(animation, t)[bone.name]` ; bones not driven by * the animation fall back to their bind-pose Transform (so animations * that omit fingers / tail bones still produce a coherent pose). * * `animation` may be `null` to request the bind pose (no animation * driver, every bone uses its bind-pose Transform). * * @param {import('./GrannySkeleton.js').Skeleton} skeleton * @param {import('./GrannyAnimation.js').Animation | null | undefined} animation * @param {number} t — sample time. * @returns {PoseSnapshot} */ export function poseSkeletonAt(skeleton: Skeleton, animation: Animation$1 | null | undefined, t: number): PoseSnapshot; /** * One bone's local Transform at a sampled instant : either a `Transform` * read from the bind pose, an `EvaluatedTransform` produced by * `evaluateAnimation`, or any structurally-compatible shape (length-3 * position, length-4 quaternion, length-9 scale-shear — checked at * runtime ; the type only constrains the shape, not tuple length). */ export type LooseTransform = { flags?: number; position?: ArrayLike; orientation?: ArrayLike; scaleShear?: ArrayLike; }; export type SampledTransform = Transform | EvaluatedTransform | LooseTransform; /** * Output of {@link poseSkeletonAt} : per-bone snapshots ready for the GPU. */ export type PoseSnapshot = { /** * — per-bone local Transform : * evaluated from the animation when the bone has a matching track, bind-pose * fallback otherwise. Indexed by `bone.index`. */ localTransforms: SampledTransform[]; /** * — per-bone world matrix * (`Mworld[i] = Mworld[parent] × Mlocal[i]`), column-major Float32Array(16). * Indexed by `bone.index`. */ worldMatrices: Float32Array[]; /** * — per-bone skinning matrix * (`Mskin[i] = Mworld[i] × IWT[i]`), column-major Float32Array(16). The GPU * vertex shader uses these directly to push bind-pose vertices into the * frame's world space. Indexed by `bone.index`. */ skinningMatrices: Float32Array[]; }; /** * Parse Oodle0's 36-byte LZ header into a 3-block decode plan : per-block * arith-model sizing + the decoded-byte ranges each block must emit. * * Used internally by {@link decompressOodle0} ; exposed for unit testing. * * @param {Oodle0SectionInput} section * @param {Uint8Array} compressed — the 36-byte LZ header + bitstream. * @returns {Oodle0Plan} */ export function parseOodle0Plan(section: Oodle0SectionInput, compressed: Uint8Array): Oodle0Plan; /** * Reverse the low `nbits` of `value`. Used at multiple points in the arith * decoder where byte / nibble groups need to be read MSB-first : * `code = bitReverse(get(31), 31)` on init, plus byte / nibble swaps * inside `ArithBits.remove()`. Missing one bit-reverse = silent mismatch. * * @param {number} value * @param {number} nbits — 0..31. * @returns {number} */ export function bitReverse(value: number, nbits: number): number; /** * Decompress one Oodle0-tagged section. * * Walks `parseOodle0Plan(section, compressed)`'s 3 blocks back-to-back, * each with its own {@link LZState}, into a pre-allocated * `Uint8Array(section.expanded_size)`. Throws if the decoded length * doesn't match `section.expanded_size` — full byte-exact check, no * "close enough" per [`feedback_no_empirical_closure_re`]. * * @param {Oodle0SectionInput} section — section header from `parseGR2File(...).sections[i]`. * @param {Uint8Array} compressed — raw section bytes (`file.sectionBytes(section)`). * @returns {Uint8Array} of length `section.expanded_size`. * @throws {DecompressionError} on malformed input or length mismatch. */ export function decompressOodle0(section: Oodle0SectionInput, compressed: Uint8Array): Uint8Array; /** * Structural subset of {@link import('./GrannyFile.js').GR2Section} actually * consumed by the Oodle0 codec. A full `GR2Section` is assignable wherever * this is expected — but a hand-built object with just these 4 fields works * too (useful for unit tests that don't want to mock the full section table). * * @typedef {object} Oodle0SectionInput * @property {number} index * @property {number} expanded_size * @property {number} first_16bit — decoded-byte offset where the 16-bit length context block ends. * @property {number} first_8bit — decoded-byte offset where the 8-bit length context block ends. */ /** * One of the three blocks an Oodle0 section is split into. * * @typedef {object} Oodle0Block * @property {0 | 1 | 2} index * @property {number} output_start — decoded-byte offset where this block starts emitting. * @property {number} output_end — decoded-byte offset where this block stops. * @property {number} output_size — `max(0, output_end - output_start)`. * @property {boolean} is_empty * @property {Oodle0LZHeader} header */ /** * Decode plan for one Oodle0 section — 3 blocks back-to-back. * * @typedef {object} Oodle0Plan * @property {number} section_index * @property {number} expanded_size * @property {readonly Oodle0Block[]} blocks — always {@link OODLE0_BLOCK_COUNT} (3) entries. * @property {36} bitstream_offset — constant 36 ; bitstream begins right after * the 3 × 12-byte block headers. */ /** Size of the Oodle0 LZ header block (3 × 12 bytes = 9 × u32). */ export const OODLE0_HEADER_SIZE: 36; /** Number of LZ blocks an Oodle0 section is split into. */ export const OODLE0_BLOCK_COUNT: 3; /** Bit width of the low-offset alphabet ; back-distance = `low + 1 + (high << OFFSET_SPLIT_SHIFT)`. */ export const OFFSET_SPLIT_SHIFT: 2; /** Mask covering the `OFFSET_SPLIT_SHIFT` low bits of the low-offset alphabet. */ export const LOW_OFFSET_MASK: number; /** Largest LZ77 length-context symbol. */ export const MAX_LENS: 64; /** Special-case length lookup for symbols ≥ `MAX_LENS - 3` (= 61, 62, 63, 64). */ export const LONG_LENGTHS: number[]; /** Absolute ceiling on a section's decompressed size. 256 MiB. */ export const OODLE0_MAX_EXPANDED_SIZE: number; /** Max `expanded_size` as a multiple of the compressed input length. */ export const OODLE0_MAX_EXPAND_RATIO: 1024; /** Ceiling on an arith model's alphabet size (blocks the 23-bit 8.4M field). */ export const OODLE0_MAX_ALPHABET: number; /** Raised by the Oodle0 decoder on malformed or out-of-spec input. */ export class DecompressionError extends Error { constructor(message: any); } /** * Per-block LZ header — 12 bytes laid out as 3 × u32. Three of these are * packed at the start of every Oodle0 section. See `docs/gr2-format.md` § * Oodle0 bitstream for the field-by-field bit split. */ export class Oodle0LZHeader { /** * @param {number} maxOffsetAndByte — raw u32 ; low 9 bits = max literal value, high 23 = max back-distance. * @param {number} uniqOffsetAndByte — raw u32 ; low 9 bits = literal alphabet size, high 23 = offset alphabet size. * @param {number} uniqLens — raw u32 ; 4 × u8 unique-symbol count, one per length-context group. */ constructor(maxOffsetAndByte: number, uniqOffsetAndByte: number, uniqLens: number); max_offset_and_byte: number; uniq_offset_and_byte: number; uniq_lens: number; /** Max literal value the block emits — low 9 bits of `max_offset_and_byte`. */ get max_byte_value(): number; /** Max LZ77 back-distance — high 23 bits of `max_offset_and_byte`. */ get max_offset(): number; /** Literal-alphabet size for the block's `bytes` arith model. */ get unique_byte_values(): number; /** Offset-alphabet size for the block's `offset_high` arith model. */ get unique_offsets(): number; /** * Per-length-context unique-symbol count. The 65 length symbols * (`0..MAX_LENS`) split into 4 groups of 16 ; each group gets its * own arith-model sizing taken from one of the 4 bytes of `uniq_lens` * (MSB-first per group). * * @param {number} index — 0-based length symbol (`0..MAX_LENS`). * @returns {number} unique-symbol count for the containing group. */ length_unique(index: number): number; } export namespace __test__ { export { VarBits }; export { ArithBits }; export { ArithModel }; export { EscapeSymbol }; export { LZState }; export { u32lePadded }; export { blockStops }; export { clampStop }; export { alignedCount }; export { bestShift }; } /** * Structural subset of {@link import ('./GrannyFile.js').GR2Section} actually * consumed by the Oodle0 codec. A full `GR2Section` is assignable wherever * this is expected — but a hand-built object with just these 4 fields works * too (useful for unit tests that don't want to mock the full section table). */ export type Oodle0SectionInput = { index: number; expanded_size: number; /** * — decoded-byte offset where the 16-bit length context block ends. */ first_16bit: number; /** * — decoded-byte offset where the 8-bit length context block ends. */ first_8bit: number; }; /** * One of the three blocks an Oodle0 section is split into. */ export type Oodle0Block = { index: 0 | 1 | 2; /** * — decoded-byte offset where this block starts emitting. */ output_start: number; /** * — decoded-byte offset where this block stops. */ output_end: number; /** * — `max(0, output_end - output_start)`. */ output_size: number; is_empty: boolean; header: Oodle0LZHeader; }; /** * Decode plan for one Oodle0 section — 3 blocks back-to-back. */ export type Oodle0Plan = { section_index: number; expanded_size: number; /** * — always {@link OODLE0_BLOCK_COUNT} (3) entries. */ blocks: readonly Oodle0Block[]; /** * — constant 36 ; bitstream begins right after * the 3 × 12-byte block headers. */ bitstream_offset: 36; }; declare class VarBits { constructor(data: any, offset: any); data: any; cur: any; bits: number; bitlen: number; /** Consume `nbits` bits ; return them as an unsigned int (`0..2^nbits - 1`). */ get(nbits: any): number; /** Consume exactly 1 bit (`0` or `1`). Fast path for the inner decode loop. */ get1(): number; } declare class ArithBits { constructor(data: any, offset: any); vbits: VarBits; high: number; low: number; code: number; /** Compute the cumulative count `c` such that the current code falls in `[start, start+c]` for a given `scale`. */ getCount(scale: any): number; /** Decode an integer value in `[0, scale)` directly (used for escape symbols). */ getValue(scale: any): number; /** * Advance the decoder by removing the `[start, start+count)` interval * from the current `[low, high]` range. Performs the standard arith- * coding renormalization (8-bit + 4-bit + 1-bit unscaled rounds, then * the underflow loop) — see RAD's leaked source for asm-cite parity. */ remove(start: any, count: any, scale: any): void; } declare class ArithModel { constructor(uniqueValues: any); unique_values: any; totals: any[]; counts: any[]; values: any[]; number: number; bin_size: number; bin_shift: number; last_bin_start: number; /** * Decode one symbol from `bits`. Returns the symbol value as a plain * number, or an {@link EscapeSymbol} marker when a new alphabet * entry is being introduced. * * @throws DecompressionError if the model overflows its capacity */ decompress(bits: any): any; /** Register the value behind an escape marker so subsequent reads find it. */ setEscaped(marker: any, value: any): void; /** * Find the position whose cumulative count contains `count`. * * Two-stage : first a 4-compare binary search over the 16-entry * cumulative `totals` to find the right bin, then a bounded linear * scan within that bin's `counts` slice. The bin layout is what * {@link bestShift} was designed for — bin sizes are picked so the * within-bin scan stays small. */ _findPos(count: any): any[]; /** * Increment `counts[value]` AND `totals[bin(value)..15]` by `delta`. * * The original RAD code packed two parallel u16 increments into a * single u32 add ; in our codepaths the packed amount always had * identical hi and lo halves (0x10001, 0x20002, 0x30003), so a * straight per-bin u16 add gives the same result without the * packing dance. {@link _decrementCounts} handles the asymmetric * negative-pair case directly. */ _quickIncrement(value: any, delta: any): void; /** Add `delta` (u16) to `totals[bin(value)..15]` cumulatively. */ _incrementTotals(value: any, delta: any): void; /** Decrement `counts[value]` AND `totals[bin(value)..15]` by `amount`. */ _decrementCounts(value: any, amount: any): void; /** * Halve every `counts` entry and rebuild `totals`. Triggered when the * cumulative tally hits 16384. Also drops entries that fall to ≤ 1 * and re-bins the survivors based on the updated alphabet size. */ _rescale(): void; } declare class EscapeSymbol { constructor(index: any); index: any; } declare class LZState { constructor(header: any); max_bytes: any; max_offsets: any; max_offset_low: number; bytes: ArithModel; lengths: any[]; offset_low: ArithModel; offset_high: ArithModel; bytes_decompressed: number; last_length: number; } declare function u32lePadded(data: any, offset: any): number; declare function blockStops(section: any): any[]; declare function clampStop(value: any, expandedSize: any): any; declare function alignedCount(uniqueValues: any): number; declare function bestShift(value: any): number[]; /** * Idempotent async init seam. In the default JS-only build `initKernels()` * resolves immediately — there is nothing to instantiate. The opt-in WASM * build (`./wasm`) swaps the kernel seam so `initKernels()` here awaits * `WebAssembly.instantiate` (browser main-thread instantiate is async and * capped at 4 KB synchronously). Instantiation is lazy — deferred to the first * `ready()` call and cached — so importing `Granny` never compiles wasm. Await * it once at startup ; decode calls stay synchronous afterward. * * @returns {Promise} */ export function ready(): Promise; /** * Decompress a single section, dispatching by `section.compression`. * * Supported : `COMPRESSION_NONE` (0) and `COMPRESSION_OODLE0` (1). * `COMPRESSION_OODLE1` (2), `COMPRESSION_BITKNIT` (3), `COMPRESSION_BITKNIT2` (4) * throw — none of the iRO ver12 corpus uses them ; codec ports will land * in a later session if an asset ever needs them. * * Empty sections (`expanded_size === 0`) short-circuit to an empty * Uint8Array without dispatching. * * @param {DispatchSectionInput} section — codec inputs + `compression` tag. * @param {Uint8Array} compressed — bytes from `file.sectionBytes(section)`. * @returns {Uint8Array} decompressed bytes of length `section.expanded_size`. * @throws {RangeError} when NoCompression input is shorter than `expanded_size`. * @throws {Error} on an unsupported compression tag. */ export function decompressSection(section: DispatchSectionInput, compressed: Uint8Array): Uint8Array; /** * Full pipeline : parse the GR2 file, decompress + fixup all sections, * walk the root type tree, materialize the root object. * * After this returns, `result.root` is a plain JS graph keyed by member * name (`root.Meshes`, `root.Skeletons`, …) ready for downstream * skeleton / mesh extraction to navigate without touching binary again. * * @param {import('./GrannyFile.js').GR2Input} buffer — the .gr2 bytes. * @returns {ParseResult} * @throws {Error} on non-Granny input, unsupported compression, or * cross-endian asset with non-empty mixed-marshalling table. * @throws {RangeError} when a declared section / fixup table escapes the buffer. */ export function parse(buffer: GR2Input): ParseResult; /** * Full model pipeline : `parse(buffer)` + skeleton + mesh extraction. * On any of the 6 model fixtures, `result.skeletons` carries the bind- * pose bones (with local Transforms + InverseWorldTransform) and * `result.meshes` carries decoded vertex / index / weight / material * buffers — ready for the renderer without any further binary parsing. * * Re-runs the pipeline locally (does not call {@link parse}) so the * lean `parse()` return shape stays unchanged for callers that don't * need the multi-MB `loaded` buffers. * * Animation-only fixtures resolve to empty arrays for both `skeletons` * and `meshes` — same throw / error surface as {@link parse}. * * @param {import('./GrannyFile.js').GR2Input} buffer — the .gr2 bytes. * @param {ParseModelOptions} [options] * @returns {ParseModelResult} */ export function parseModel(buffer: GR2Input, options?: ParseModelOptions): ParseModelResult; /** * Full animated-asset pipeline : `parseModel(buffer)` + animation * extraction. On the 15 animation-only fixtures, `result.animations` * carries every `Animation` with its resolved `TrackGroup`s + decoded * `TransformTrack`s ; call `evaluateTransformTrack(track, t)` (or * `evaluateAnimation(anim, t)`) to sample local-Transform values at any * point in time, ready for S8 pose composition. * * Pure model fixtures resolve to `animations = []` (they carry skeleton * + mesh but no curve data). Mirrors {@link parseModel}'s contract : * re-runs the lower-level pipeline locally so {@link parse} stays lean. * * @param {import('./GrannyFile.js').GR2Input} buffer — the .gr2 bytes. * @param {ParseAnimatedOptions} [options] * @returns {ParseAnimatedResult} */ export function parseAnimated(buffer: GR2Input, options?: ParseAnimatedOptions): ParseAnimatedResult; /** * Full textured-model pipeline : `parseModel(buffer)` + texture * extraction. On any model fixture with textures, `result.textures` * carries each decoded `(texture, image, MIP)` as RGBA8888 ready for * upload to the GPU. Animation-only fixtures resolve `result.textures` * to `[]`. * * Re-runs the lower-level pipeline locally (does not call * {@link parseModel}) so the lean shape stays consistent with the other * pipeline entries. * * @param {import('./GrannyFile.js').GR2Input} buffer — the .gr2 bytes. * @param {ParseTexturedOptions} [options] * @returns {ParseTexturedResult} * @throws {Error} on textures with `encoding=2` (S3TC, not in iRO corpus). * @throws {Error} on an IGC (`encoding=3`) decode in the code-split (`./split`) * build when `await loadTextureCodec()` has not resolved yet — the default * build decodes IGC synchronously with no warmup. */ export function parseTextured(buffer: GR2Input, options?: ParseTexturedOptions): ParseTexturedResult; /** * Single-pass pipeline : one `parseGR2File`→`loadGR2`, then every extractor on * that single decompressed graph — skeleton + mesh + texture + animation + * model. Returns the superset of {@link parseTextured} and {@link parseAnimated} * plus `models` (each with `initialPlacement`), so a consumer that needs * textured + animated + model(InitialPlacement) pays the expensive Oodle0 * decompress (`loadGR2`) **once** instead of three times. * * Additive — {@link parseTextured} / {@link parseAnimated} are unchanged. On an * animation-only fixture, `meshes` / `skeletons` / `textures` / `models` resolve * to empty arrays and `animations` carries the curves ; on a pure model fixture * the reverse. Same throw / error surface as {@link parse}. * * @param {import('./GrannyFile.js').GR2Input} buffer — the .gr2 bytes. * @param {ParseAllOptions} [options] * @returns {ParseAllResult} */ export function parseAll(buffer: GR2Input, options?: ParseAllOptions): ParseAllResult; /** * Sample the first skeleton of `parsed` at time `t` against * `parsed.animations[animationIndex]`. Returns a {@link PoseSnapshot} * with per-bone local Transforms + world matrices + skinning matrices * (column-major Float32Array(16), GPU-ready). * * The iRO ver12 layout keeps model + animations in separate `.gr2` * files ; callers typically graft an animation array onto a parsed * model before calling `poseAt`. Pure model fixtures (no animation * available) accept `animationIndex` outside the available range — the * pose collapses to the bind pose. The `animations` array can also be * grafted from a sibling `parseAnimated` call when the animation lives * in a separate `.gr2` (model + N animations keyed by mob ID). * * @param {ParseModelResult | ParseAnimatedResult} parsed — a `parseModel` / * `parseAnimated` result (optionally with a grafted `animations` array). * @param {number} animationIndex — index into `parsed.animations`. * @param {number} t — sample time. * @returns {import('./GrannyPose.js').PoseSnapshot} */ export function poseAt(parsed: ParseModelResult | ParseAnimatedResult, animationIndex: number, t: number): PoseSnapshot; /** * Namespace facade — intentionally minimal (`ready` only) so that importing * `Granny` never pulls the texture / IGC graph into a bundle, preserving * tree-shaking and the `./split` code-split. Use the flat named exports * (`parseModel`, `parseTextured`, …) for the decode API. * * @type {{ readonly ready: typeof ready }} */ export const Granny: { readonly ready: typeof ready; }; /** * Structural subset of {@link import ('./GrannyFile.js').GR2Section} the * dispatcher needs : the codec inputs + the `compression` tag for routing. */ export type DispatchSectionInput = Oodle0SectionInput & { compression: number; }; /** * Result of the top-level {@link parse} entry. */ export type ParseResult = { /** * — header + section table for the input buffer. */ file: GR2File; /** * — members * of the root type tree (terminates at MT_END). */ typeTree: ReadonlyArray; /** * — materialized root * object, keyed by member name. */ root: ParsedObject; }; /** * Result of {@link parseModel} : {@link ParseResult} + skeleton + mesh extraction. */ export type ParseModelResult = ParseResult & { skeletons: ReadonlyArray; meshes: ReadonlyArray; }; /** * Result of {@link parseTextured} : {@link ParseModelResult} + texture extraction. */ export type ParseTexturedResult = ParseModelResult & { textures: ReadonlyArray; }; /** * Result of {@link parseAnimated} : {@link ParseModelResult} + animation extraction. */ export type ParseAnimatedResult = ParseModelResult & { animations: ReadonlyArray; }; /** * Result of {@link parseAll} : {@link ParseTexturedResult} + animation + model * extraction — the union every high-level consumer needs from a single * `loadGR2`. `models[0].initialPlacement` is the InitialPlacement Transform a * caller previously reached only via the low-level graph. */ export type ParseAllResult = ParseTexturedResult & { animations: ReadonlyArray; models: ModelInfo[]; }; /** * Combined options for {@link parseModel} (forwarded to skeleton + mesh extractors). */ export type ParseModelOptions = ExtractSkeletonsOptions & ExtractMeshesOptions; /** * Combined options for {@link parseTextured} (skeleton + mesh + texture extractors). */ export type ParseTexturedOptions = ExtractSkeletonsOptions & ExtractMeshesOptions & ExtractTexturesOptions; /** * Combined options for {@link parseAnimated} (skeleton + mesh + animation extractors). */ export type ParseAnimatedOptions = ExtractSkeletonsOptions & ExtractMeshesOptions & ExtractAnimationsOptions; /** * Combined options for {@link parseAll} (skeleton + mesh + texture + animation + * model extractors). The model extractor's options are inline (`maxModels`, * `maxBindings`) — it has no exported Options typedef. */ export type ParseAllOptions = ExtractSkeletonsOptions & ExtractMeshesOptions & ExtractTexturesOptions & ExtractAnimationsOptions & { maxModels?: number; maxBindings?: number; }; export { Animation$1 as Animation, }; export {};