A functional toolkit for authoring resource-leveling algorithms — compose constraint and scoring blocks, enumerate feasible schedules
0

Configure Feed

Select the types of activity you want to include in your feed.

feat(mpp): fixed and variable-length table readers

The table structures MPP stores its records in: FixedMeta/FixedData for
fixed-width rows and VarMeta/Var2Data for variable-length values.
FixedData resolves a row offset to its index through a Map rather than a
linear scan, keeping lookups O(1) on large tables.

Russ T. Fugal (Mar 9, 2026, 1:30 PM -0600) 7f9237f3 31297d88

+304
+96
src/mpp/FixedData.ts
··· 1 + import type { FixedMeta } from "./FixedMeta.ts"; 2 + import { MppUtility } from "./MppUtility.ts"; 3 + 4 + export class FixedData { 5 + private readonly offsetIndex: Map<number, number>; 6 + 7 + constructor( 8 + public readonly raw: Uint8Array, 9 + private readonly records: Array<Uint8Array | null>, 10 + offsets: number[], 11 + ) { 12 + this.offsetIndex = new Map<number, number>(); 13 + for (let i = 0; i < offsets.length; i++) { 14 + const offset = offsets[i]!; 15 + if (!this.offsetIndex.has(offset)) { 16 + this.offsetIndex.set(offset, i); 17 + } 18 + } 19 + } 20 + 21 + get count(): number { 22 + return this.records.length; 23 + } 24 + 25 + getByteArrayValue(index: number): Uint8Array | null { 26 + return this.records[index] ?? null; 27 + } 28 + 29 + getIndexFromOffset(offset: number): number { 30 + return this.offsetIndex.get(offset) ?? -1; 31 + } 32 + 33 + static fromMeta(meta: FixedMeta, raw: Uint8Array, maxExpectedSize = 0, minSize = 0): FixedData { 34 + const records: Array<Uint8Array | null> = []; 35 + const offsets: number[] = []; 36 + 37 + for (let index = 0; index < meta.adjustedItemCount; index += 1) { 38 + const metaData = meta.getByteArrayValue(index); 39 + if (!metaData) { 40 + records.push(null); 41 + offsets.push(-1); 42 + continue; 43 + } 44 + 45 + const itemOffset = MppUtility.getInt(metaData, 4); 46 + offsets.push(itemOffset); 47 + if (itemOffset < 0 || itemOffset > raw.length) { 48 + records.push(null); 49 + continue; 50 + } 51 + 52 + let itemSize: number; 53 + if (index + 1 === meta.adjustedItemCount) { 54 + itemSize = raw.length - itemOffset; 55 + } else { 56 + const nextMetaData = meta.getByteArrayValue(index + 1); 57 + const nextOffset = nextMetaData ? MppUtility.getInt(nextMetaData, 4) : raw.length; 58 + itemSize = nextOffset - itemOffset; 59 + } 60 + 61 + if (itemSize === 0) { 62 + itemSize = minSize; 63 + } 64 + 65 + const available = raw.length - itemOffset; 66 + if (itemSize < 0 || itemSize > available) { 67 + itemSize = maxExpectedSize === 0 ? available : Math.min(maxExpectedSize, available); 68 + } 69 + 70 + if (maxExpectedSize !== 0 && itemSize > maxExpectedSize) { 71 + itemSize = maxExpectedSize; 72 + } 73 + 74 + records.push(itemSize > 0 ? raw.subarray(itemOffset, itemOffset + itemSize) : null); 75 + } 76 + 77 + return new FixedData(raw, records, offsets); 78 + } 79 + 80 + static fromFixedSize(raw: Uint8Array, itemSize: number, readRemainderBlock = false): FixedData { 81 + const count = 82 + Math.floor(raw.length / itemSize) + 83 + (readRemainderBlock && raw.length % itemSize !== 0 ? 1 : 0); 84 + const records: Array<Uint8Array | null> = []; 85 + const offsets: number[] = []; 86 + 87 + for (let index = 0; index < count; index += 1) { 88 + const start = index * itemSize; 89 + const end = Math.min(raw.length, start + itemSize); 90 + records.push(raw.subarray(start, end)); 91 + offsets.push(start); 92 + } 93 + 94 + return new FixedData(raw, records, offsets); 95 + } 96 + }
+94
src/mpp/FixedMeta.ts
··· 1 + import { MppUtility } from "./MppUtility.ts"; 2 + 3 + export interface FixedMetaSummary { 4 + signature: number; 5 + version: number; 6 + itemCount: number; 7 + adjustedItemCount: number; 8 + itemSize: number; 9 + rawLength: number; 10 + } 11 + 12 + export class FixedMeta { 13 + constructor( 14 + public readonly raw: Uint8Array, 15 + public readonly summary: FixedMetaSummary, 16 + private readonly items: Uint8Array[], 17 + ) {} 18 + 19 + get itemCount(): number { 20 + return this.summary.itemCount; 21 + } 22 + 23 + get adjustedItemCount(): number { 24 + return this.summary.adjustedItemCount; 25 + } 26 + 27 + getByteArrayValue(index: number): Uint8Array | null { 28 + return this.items[index] ?? null; 29 + } 30 + 31 + static fromBuffer(raw: Uint8Array, itemSize: number): FixedMeta { 32 + return this.fromBufferWithProvider(raw, () => itemSize); 33 + } 34 + 35 + static fromBufferWithHeuristic( 36 + raw: Uint8Array, 37 + otherFixedDataItemCount: number, 38 + itemSizes: number[], 39 + ): FixedMeta { 40 + return this.fromBufferWithProvider(raw, (fileSize, itemCount) => { 41 + const available = fileSize - HEADER_SIZE; 42 + let chosenSize = itemSizes[0] ?? 0; 43 + let distance = Number.NEGATIVE_INFINITY; 44 + 45 + for (const candidate of itemSizes) { 46 + if (available % candidate !== 0) { 47 + continue; 48 + } 49 + 50 + if (available / candidate === otherFixedDataItemCount) { 51 + chosenSize = candidate; 52 + break; 53 + } 54 + 55 + const candidateDistance = itemCount * candidate - available; 56 + if (candidateDistance <= 0 && candidateDistance > distance) { 57 + chosenSize = candidate; 58 + distance = candidateDistance; 59 + } 60 + } 61 + 62 + return chosenSize; 63 + }); 64 + } 65 + 66 + private static fromBufferWithProvider( 67 + raw: Uint8Array, 68 + itemSizeProvider: (fileSize: number, itemCount: number) => number, 69 + ): FixedMeta { 70 + const signature = raw.length >= 4 ? MppUtility.getUInt(raw, 0) : 0; 71 + const version = raw.length >= 8 ? MppUtility.getUInt(raw, 4) : 0; 72 + const itemCount = raw.length >= 12 ? MppUtility.getUInt(raw, 8) : 0; 73 + const itemSize = itemSizeProvider(raw.length, itemCount); 74 + const adjustedItemCount = itemSize > 0 ? Math.floor((raw.length - HEADER_SIZE) / itemSize) : 0; 75 + const items = Array.from({ length: adjustedItemCount }, (_, index) => 76 + raw.subarray(HEADER_SIZE + index * itemSize, HEADER_SIZE + (index + 1) * itemSize), 77 + ); 78 + 79 + return new FixedMeta( 80 + raw, 81 + { 82 + signature, 83 + version, 84 + itemCount, 85 + adjustedItemCount, 86 + itemSize, 87 + rawLength: raw.length, 88 + }, 89 + items, 90 + ); 91 + } 92 + } 93 + 94 + const HEADER_SIZE = 16;
+46
src/mpp/Var2Data.ts
··· 1 + import { MppUtility } from "./MppUtility.ts"; 2 + import type { VarMeta } from "./VarMeta.ts"; 3 + 4 + export class Var2Data { 5 + constructor( 6 + public readonly raw: Uint8Array, 7 + private readonly meta: VarMeta, 8 + private readonly blocks: Map<number, Uint8Array>, 9 + ) {} 10 + 11 + static fromMeta(meta: VarMeta, raw: Uint8Array): Var2Data { 12 + const blocks = new Map<number, Uint8Array>(); 13 + 14 + for (const offset of meta.offsets) { 15 + if (offset < 0 || offset + 4 > raw.length) { 16 + continue; 17 + } 18 + 19 + const size = MppUtility.getInt(raw, offset); 20 + if (size < 0 || offset + 4 + size > raw.length) { 21 + continue; 22 + } 23 + 24 + blocks.set(offset, raw.subarray(offset + 4, offset + 4 + size)); 25 + } 26 + 27 + return new Var2Data(raw, meta, blocks); 28 + } 29 + 30 + getByteArray(offset: number | null): Uint8Array | null { 31 + return offset === null ? null : (this.blocks.get(offset) ?? null); 32 + } 33 + 34 + getByteArrayById(id: number, type: number): Uint8Array | null { 35 + return this.getByteArray(this.meta.getOffset(id, type)); 36 + } 37 + 38 + getUnicodeStringById(id: number, type: number): string | null { 39 + const value = this.getByteArrayById(id, type); 40 + if (!value) { 41 + return null; 42 + } 43 + const text = MppUtility.getUnicodeStringToEnd(value, 0); 44 + return text.length > 0 ? text : null; 45 + } 46 + }
+68
src/mpp/VarMeta.ts
··· 1 + import { MppUtility } from "./MppUtility.ts"; 2 + 3 + export interface VarMetaSummary { 4 + signature: number; 5 + version: number; 6 + itemCount: number; 7 + dataSize: number; 8 + rawLength: number; 9 + } 10 + 11 + export class VarMeta { 12 + constructor( 13 + public readonly raw: Uint8Array, 14 + public readonly summary: VarMetaSummary, 15 + public readonly table: Map<number, Map<number, number>>, 16 + public readonly offsets: number[], 17 + ) {} 18 + 19 + get itemCount(): number { 20 + return this.summary.itemCount; 21 + } 22 + 23 + getOffset(id: number, type: number): number | null { 24 + return this.table.get(id)?.get(type) ?? null; 25 + } 26 + 27 + getTypes(id: number): Set<number> { 28 + return new Set(this.table.get(id)?.keys() ?? []); 29 + } 30 + 31 + static fromBuffer(raw: Uint8Array): VarMeta { 32 + const signature = raw.length >= 4 ? MppUtility.getUInt(raw, 0) : 0; 33 + const version = raw.length >= 8 ? MppUtility.getUInt(raw, 4) : 0; 34 + const itemCount = raw.length >= 12 ? MppUtility.getUInt(raw, 8) : 0; 35 + const dataSize = raw.length >= 24 ? MppUtility.getUInt(raw, 20) : 0; 36 + const table = new Map<number, Map<number, number>>(); 37 + const offsets: number[] = []; 38 + 39 + for ( 40 + let offset = 24, count = 0; 41 + offset + 12 <= raw.length && count < itemCount; 42 + offset += 12, count += 1 43 + ) { 44 + const uniqueId = MppUtility.getInt(raw, offset); 45 + const dataOffset = MppUtility.getInt(raw, offset + 4); 46 + const type = MppUtility.getUShort(raw, offset + 8); 47 + const entry = table.get(uniqueId) ?? new Map<number, number>(); 48 + entry.set(type, dataOffset); 49 + table.set(uniqueId, entry); 50 + offsets.push(dataOffset); 51 + } 52 + 53 + offsets.sort((left, right) => left - right); 54 + 55 + return new VarMeta( 56 + raw, 57 + { 58 + signature, 59 + version, 60 + itemCount, 61 + dataSize, 62 + rawLength: raw.length, 63 + }, 64 + table, 65 + offsets, 66 + ); 67 + } 68 + }