readKeyframeDeltaChain function

The same chain, fetched by byte range instead of from a resident file.

For the caller that has a FourdgsReadable and no reason to hold the file: a chain is a handful of records, and reading each one as the walk reaches it keeps a chunk resident rather than a scene (AGENTS.md §1). The states this composes are identical to composeKeyframeDeltaChain's — same records, same order, same refusals — so the two are interchangeable and a caller picks by what it already holds.

Implementation

Future<KeyframeDeltaState> readKeyframeDeltaChain(
  FourdgsReadable source,
  List<FourdgsChunkIndexEntry> index,
  FourdgsChunkIndexEntry entry, {
  Map<int, FourdgsChunkIndexEntry>? byOffset,
}) async {
  final chain = chainFrom(index, entry, byOffset: byOffset);
  final int size = await source.size();
  KeyframeDeltaState? state;
  int? referenceLevel;
  for (final link in chain) {
    final int expectedOpcode = _stateOpcode(link);
    if (link.chunkOffset < 0 ||
        link.chunkLength < recordHeaderBytes ||
        link.chunkOffset + link.chunkLength > size) {
      throw FourdgsMalformedFile(
        'the chunk at ${link.chunkOffset} declares a ${link.chunkLength}-byte '
        'range outside the $size-byte resource',
      );
    }
    final Uint8List header;
    try {
      header = await source.read(link.chunkOffset, recordHeaderBytes);
    } on RangeError catch (error) {
      throw FourdgsMalformedFile(
        'the state record at ${link.chunkOffset} could not be read as a '
        '$recordHeaderBytes-byte framing header: $error',
      );
    }
    // Price the indexed range only after its fixed-size framing proves it is
    // exactly one state record. Otherwise a forged chunk_length can make this
    // public range API allocate nearly the whole file before noticing that the
    // first record in it was small.
    _checkStateRecordHeader(
      header,
      fileOffset: link.chunkOffset,
      rangeLength: link.chunkLength,
      expectedOpcode: expectedOpcode,
    );
    final Uint8List blob;
    try {
      blob = await source.read(link.chunkOffset, link.chunkLength);
    } on RangeError catch (error) {
      throw FourdgsMalformedFile(
        'the chunk at ${link.chunkOffset} could not be read as its declared '
        '${link.chunkLength}-byte range: $error',
      );
    }
    final composed = _composeLink(
      state,
      _recordContent(
        blob,
        0,
        link.chunkLength,
        expectedOpcode: expectedOpcode,
        fileOffset: link.chunkOffset,
      ),
      link,
      referenceLevel: referenceLevel,
    );
    state = composed.state;
    referenceLevel = composed.level;
  }
  return _composed(state);
}