Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
# @suaveplan/bitpacker-delta - Bitpacker delta plugin
|
||||
|
||||
## Summary
|
||||
|
||||
Delta encoding utilities for efficient repeated state transmission. Detects changes between successive values and encodes differences rather than full values.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [delta-manager](./src/delta-manager/delta-manager.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [delta-manager](./src/delta-manager/delta-manager.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [delta-state](./src/delta-state/delta-state.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [delta-state](./src/delta-state/delta-state.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [index](./src/index.md) — **Path:** `bitpacker-delta/src/index.ts` **Export:** See source for details.
|
||||
- [string-diff](./src/string-diff.md) — **Path:** `src/string-diff.ts` **Export:** `calculateStringDiff`, `applyStringDiff`, `calculateDiffSavings`, `shouldUse…
|
||||
- [xor-encoder](./src/xor-encoder.md) — **Path:** `src/xor-encoder.ts` **Export:** `calculateXorDelta`, `applyXorDelta`, `calculateDeltaBitSize`, `isXorEfficie…
|
||||
<!-- end:auto-docs --> - [delta-manager](./src/delta-manager/delta-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [delta-state](./src/delta-state/delta-state.md) — <!-- begin:auto-docs --> _(no submodules)_- [delta-state](./src/delta-state/delta-state.md) — <!-- begin:auto-docs --> _(no submodules)_- [index](./src/index.md) — **Path:** `bitpacker-delta/src/index.ts` **Export:** See source for details.
|
||||
- [string-diff](./src/string-diff.md) — **Path:** `src/string-diff.ts` **Export:** `calculateStringDiff`, `applyStringDiff`, `calculateDiffSavings`, `shouldUse…
|
||||
- [xor-encoder](./src/xor-encoder.md) — **Path:** `src/xor-encoder.ts` **Export:** `calculateXorDelta`, `applyXorDelta`, `calculateDeltaBitSize`, `isXorEfficie…
|
||||
- [delta-manager](./src/delta-manager/delta-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [delta-state](./src/delta-state/delta-state.md) — <!-- begin:auto-docs --> _(no submodules)_- [delta-state](./src/delta-state/delta-state.md) — <!-- begin:auto-docs --> _(no submodules)_- [index](./src/index.md) — **Path:** `bitpacker-delta/src/index.ts` **Export:** See source for details.
|
||||
- [string-diff](./src/string-diff.md) — **Path:** `src/string-diff.ts` **Export:** `calculateStringDiff`, `applyStringDiff`, `calculateDiffSavings`, `shouldUse…
|
||||
- [xor-encoder](./src/xor-encoder.md) — **Path:** `src/xor-encoder.ts` **Export:** `calculateXorDelta`, `applyXorDelta`, `calculateDeltaBitSize`, `isXorEfficie…
|
||||
- [delta-manager](./src/delta-manager/delta-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [delta-state](./src/delta-state/delta-state.md) — <!-- begin:auto-docs --> _(no submodules)_- [delta-state](./src/delta-state/delta-state.md) — <!-- begin:auto-docs --> _(no submodules)_- [index](./src/index.md) — **Path:** `bitpacker-delta/src/index.ts` **Export:** See source for details.
|
||||
- [string-diff](./src/string-diff.md) — **Path:** `src/string-diff.ts` **Export:** `calculateStringDiff`, `applyStringDiff`, `calculateDiffSavings`, `shouldUse…
|
||||
- [xor-encoder](./src/xor-encoder.md) — **Path:** `src/xor-encoder.ts` **Export:** `calculateXorDelta`, `applyXorDelta`, `calculateDeltaBitSize`, `isXorEfficie…
|
||||
- [delta-manager](./src/delta-manager/delta-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [delta-state](./src/delta-state/delta-state.md) — <!-- begin:auto-docs --> _(no submodules)_ - [delta-state](./src/delta-state/delta-state.md) — <!-- begin:auto-docs --> _(no submodules)_- [index](./src/index.md) — **Path:** `bitpacker-delta/src/index.ts` **Export:** See source for details.
|
||||
- [string-diff](./src/string-diff.md) — **Path:** `src/string-diff.ts` **Export:** `calculateStringDiff`, `applyStringDiff`, `calculateDiffSavings`, `shouldUse…
|
||||
- [xor-encoder](./src/xor-encoder.md) — **Path:** `src/xor-encoder.ts` **Export:** `calculateXorDelta`, `applyXorDelta`, `calculateDeltaBitSize`, `isXorEfficie…
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/bitpacker-delta
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
import { DeltaManager, calculateStringDiff, applyStringDiff, numberXor } from "@suaveplan/bitpacker-delta";
|
||||
|
||||
// Change detection
|
||||
const dm = new DeltaManager();
|
||||
dm.detectChanges({ x: 1, y: 2 }); // returns { x: 1, y: 2 }
|
||||
dm.detectChanges({ x: 1, y: 2 }); // returns undefined (no change)
|
||||
dm.detectChanges({ x: 1, y: 3 }); // returns { x: 1, y: 3 }
|
||||
|
||||
// String diff
|
||||
const diff = calculateStringDiff("hello world", "hello there");
|
||||
// { prefixLen: 6, suffixLen: 0, middleLen: 5, middle: "there" }
|
||||
const reconstructed = applyStringDiff("hello world", diff);
|
||||
// "hello there"
|
||||
|
||||
// Numeric XOR delta
|
||||
const delta = numberXor(3.14159, 2.71828);
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
- `src/index.ts` — `DeltaManager`, change detection API
|
||||
- `src/xor-encoder.ts` — XOR-based numeric delta encoding
|
||||
- `src/string-diff.ts` — Common prefix/suffix string diff encoding
|
||||
@@ -0,0 +1,123 @@
|
||||
# DeltaManager
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`DeltaManager` is the field-level delta detector for bit-packed objects. Given a current state and a `BitPackerSchema` describing it, it returns the ordered list of fields whose value has changed since the previous snapshot stored under the same state key. Encoders use the returned `FieldChange[]` to build a fixed-width bitmask and send only the changed fields, dramatically reducing bandwidth for high-frequency state-sync workloads (game replication, multi-peer document sync, telemetry deltas).
|
||||
|
||||
This is the port of suaveplan's upstream `core/delta/delta-manager.ts`. hardening replaces upstream's `Date.now()` with injectable `Clock` (CLAUDE.md determinism rule), upstream's `JSON.stringify`-based equality with a structural walker that respects `BitPackerSchema` field order (avoiding subtle false-positive diffs from key-order changes in nested objects), upstream's plain `Error` throws with `BitpackerError` codes, and validates `DeltaConfig` via Zod with strict-mode key rejection.
|
||||
|
||||
## Features
|
||||
|
||||
- Field-indexed `FieldChange[]` output matching `Object.keys(schema)` order for stable bit offsets
|
||||
- Per-state-key snapshot store with LRU eviction (delegates to `DeltaStateManager`)
|
||||
- Configurable `maxFieldCount` cap with `BITPACKER.DELTA_FIELD_LIMIT_EXCEEDED` enforcement
|
||||
- Structural equality default (matches schema semantics) with opt-in `equalityMode: "json"` for upstream byte-compat
|
||||
- Zod-validated `DeltaConfig`; unknown keys rejected with `BITPACKER.DELTA_INVALID_CONFIG`
|
||||
- Threshold-driven `shouldUseDelta(originalBits, deltaBits)` for bandwidth-savings decisions
|
||||
- Zero `Math.random` / `Date.now` in the implementation — fully deterministic given identical inputs
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { DeltaManager } from "@suaveplan/bitpacker-delta";
|
||||
|
||||
const schema = { x: "u32", y: "u32" } as const;
|
||||
const dm = new DeltaManager({ mode: "field-level" });
|
||||
|
||||
// First call: no previous state, so no changes.
|
||||
dm.detectChanges({ x: 0, y: 0 }, schema); // → []
|
||||
dm.storePreviousState({ x: 0, y: 0 });
|
||||
|
||||
// Second call: x changed.
|
||||
dm.detectChanges({ x: 5, y: 0 }, schema);
|
||||
// → [{ index: 0, key: "x", previousValue: 0, currentValue: 5 }]
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { DeltaManager } from "@suaveplan/bitpacker-delta";
|
||||
|
||||
const schema = {
|
||||
pos: { x: "f32", y: "f32" },
|
||||
vel: { x: "f32", y: "f32" },
|
||||
health: "u8",
|
||||
} as const;
|
||||
|
||||
// Per-peer state streams: one DeltaManager, many state keys.
|
||||
const dm = new DeltaManager(
|
||||
{ mode: "field-level", threshold: 25, maxFieldCount: 16 },
|
||||
{ maxStates: 256 } // 256 simultaneous peers
|
||||
);
|
||||
|
||||
for (const peer of peers) {
|
||||
// Each peer is its own state stream.
|
||||
const previous = dm.getPreviousState(peer.id);
|
||||
const changes = dm.detectChanges(peer.state, schema, peer.id);
|
||||
|
||||
// Only send a delta if it saves at least 25% of bits.
|
||||
const fullSize = bitWidthOf(schema);
|
||||
const deltaSize = changes.length * 32 + Math.ceil(3 / 8) * 8;
|
||||
if (dm.shouldUseDelta(fullSize, deltaSize)) {
|
||||
sendDelta(peer.id, changes);
|
||||
} else {
|
||||
sendFull(peer.id, peer.state);
|
||||
}
|
||||
|
||||
dm.storePreviousState(peer.state, peer.id);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `new DeltaManager(config?: DeltaConfig, options?: DeltaManagerOptions)`
|
||||
|
||||
`config.mode` ∈ `"none" | "xor" | "string-diff" | "field-level"`. Default `"none"`.
|
||||
`config.threshold` — bandwidth-savings %, 0–100. Default `0`.
|
||||
`config.maxFieldCount` — max fields the bitmask can address. Default `64`.
|
||||
`config.stateKey` — default key for `detectChanges` calls without one. Default `"default"`.
|
||||
`config.equalityMode` — `"structural"` (default) or `"json"` (upstream-compat).
|
||||
`options.maxStates` — capacity of the underlying state store. Default `100`.
|
||||
|
||||
Throws `BitpackerError(BITPACKER.DELTA_INVALID_CONFIG)` on invalid config.
|
||||
|
||||
### `detectChanges(current, schema, stateKey?) → FieldChange[]`
|
||||
|
||||
Returns the ordered list of fields whose `current` value differs from the stored snapshot under `stateKey`. First call (no snapshot) returns `[]`. Schema with more keys than `maxFieldCount` throws `BITPACKER.DELTA_FIELD_LIMIT_EXCEEDED`.
|
||||
|
||||
### `storePreviousState(state, stateKey?) → void`
|
||||
|
||||
Stores a deep-cloned snapshot of `state` for the next diff.
|
||||
|
||||
### `getPreviousState(stateKey?) → unknown | undefined`
|
||||
|
||||
### `hasPreviousState(stateKey?) → boolean`
|
||||
|
||||
Snapshot inspection helpers.
|
||||
|
||||
### `calculateSavings(originalBits, deltaBits) → number`
|
||||
|
||||
### `shouldUseDelta(originalBits, deltaBits) → boolean`
|
||||
|
||||
Bandwidth-savings calculation against the configured `threshold`.
|
||||
|
||||
### `clear() → void`
|
||||
|
||||
### `clearState(stateKey) → void`
|
||||
|
||||
### `getConfig() → Readonly<Required<DeltaConfig>>`
|
||||
|
||||
Lifecycle and inspection helpers.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The structural-equality walker treats every `ArrayBuffer.isView` value (every typed-array variant) as a byte-by-byte comparison. `Date` instances compare via `getTime()`. Plain objects compare key-sets first (size + key membership), then recurse on values. Arrays compare length-then-elements. This matches `BitPackerSchema` semantics: two values that pack to identical bytes are equal.
|
||||
|
||||
The `"json"` equality mode is provided for upstream byte-compat but has known limitations: `JSON.stringify({ a: 1, b: 2 })` and `JSON.stringify({ b: 2, a: 1 })` produce different strings even though the values are semantically equal in a `BitPackerSchema` context. Consumers porting from upstream should switch to `"structural"` once they've verified no callers depend on key-order-sensitive equality.
|
||||
|
||||
The `maxFieldCount` cap is a defence: a hostile peer that sends a schema with thousands of fields could exhaust memory if the bitmask were unbounded. The default (64) accommodates typical state-sync workloads; consumers with larger schemas raise it explicitly.
|
||||
|
||||
Threshold semantics: `shouldUseDelta` returns `true` when `(originalBits - deltaBits) / originalBits * 100 >= threshold`. A `threshold` of `0` means "always prefer delta when smaller"; a `threshold` of `25` means "only prefer delta if it saves at least a quarter of the bits."
|
||||
@@ -0,0 +1,90 @@
|
||||
# DeltaStateManager
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`DeltaStateManager` keeps deep-cloned snapshots of previous values per state key, so that a delta encoder can diff the current value against its predecessor without worrying about caller-side mutations. It is the storage layer for `DeltaManager`; consumers usually go through `DeltaManager` rather than calling this class directly.
|
||||
|
||||
The store is bounded: an LRU eviction policy drops the oldest key once the configured capacity is reached. Default capacity is 100 keys, which is plenty for most workloads (one key per per-peer state stream). Re-storing under an existing key updates the value in place without advancing its LRU position — this matches upstream semantics so that frequently-updated keys do not unfairly evict rarely-updated ones.
|
||||
|
||||
## Features
|
||||
|
||||
- Deep-cloning on store: `Date`, all typed-array variants (`Uint8Array`, `Int8Array`, `Uint16Array`, `Int16Array`, `Uint32Array`, `Int32Array`, `Float32Array`, `Float64Array`), arrays, and plain objects are all isolated from caller mutations.
|
||||
- Insertion-order LRU eviction; `maxStates` configurable (default 100).
|
||||
- `clear()`, `clearState(key)`, `getStateCount()`, `getMaxStates()` accessors for inspection and reset.
|
||||
- Zero `Math.random` / `Date.now` calls — fully deterministic given identical inputs.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { DeltaStateManager } from "@suaveplan/bitpacker-delta";
|
||||
|
||||
const states = new DeltaStateManager(100);
|
||||
|
||||
states.storePreviousState("player-1", { x: 0, y: 0 });
|
||||
const previous = states.getPreviousState("player-1");
|
||||
// → { x: 0, y: 0 }
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { DeltaStateManager } from "@suaveplan/bitpacker-delta";
|
||||
|
||||
const states = new DeltaStateManager(3);
|
||||
states.storePreviousState("a", { v: 1 });
|
||||
states.storePreviousState("b", { v: 2 });
|
||||
states.storePreviousState("c", { v: 3 });
|
||||
states.storePreviousState("d", { v: 4 }); // evicts "a"
|
||||
|
||||
states.hasPreviousState("a"); // → false (evicted)
|
||||
states.hasPreviousState("d"); // → true
|
||||
|
||||
// Mutating the original after storing does NOT touch the snapshot:
|
||||
const original = { x: 0 };
|
||||
states.storePreviousState("e", original);
|
||||
original.x = 999;
|
||||
states.getPreviousState("e"); // → { x: 0 }
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `new DeltaStateManager(maxStates?: number)`
|
||||
|
||||
- `maxStates` — positive integer; default `100`. Throws `RangeError` if non-integer or `< 1`.
|
||||
|
||||
### `storePreviousState(key: string, state: unknown): void`
|
||||
|
||||
Stores a deep-cloned copy of `state` under `key`. If `key` already exists, the value is updated in place; LRU position is unchanged. If the store is at capacity and this is a new key, the oldest key is evicted.
|
||||
|
||||
### `getPreviousState(key: string): unknown | undefined`
|
||||
|
||||
Returns the stored snapshot, or `undefined` if the key has never been stored or has been evicted.
|
||||
|
||||
### `hasPreviousState(key: string): boolean`
|
||||
|
||||
True if a snapshot exists for `key`.
|
||||
|
||||
### `clear(): void`
|
||||
|
||||
Drops every snapshot.
|
||||
|
||||
### `clearState(key: string): void`
|
||||
|
||||
Drops the snapshot for one key. No-op if absent.
|
||||
|
||||
### `getStateCount(): number`
|
||||
|
||||
Number of stored snapshots.
|
||||
|
||||
### `getMaxStates(): number`
|
||||
|
||||
Configured maximum capacity.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The deep-clone walker handles the eight typed-array variants explicitly because the generic object-clone path (`Object.hasOwn` over keys) does not preserve `Uint8Array`-vs-plain-array distinction. Maps and Sets are intentionally NOT cloned: delta-state semantics assume keys are strings and values are plain data; if a consumer needs `Map`/`Set`, they should canonicalise to a plain object first.
|
||||
|
||||
Performance: insertion is O(1) amortised; eviction is O(1) (Array `.shift()` on a small bounded array). Lookup is O(1). The `stateKeys` array tracks insertion order without using a `Map`'s native iteration (some legacy environments don't preserve insertion order — though every modern runtime does — so the explicit array is defensive).
|
||||
@@ -0,0 +1,8 @@
|
||||
## index
|
||||
|
||||
**Path:** `bitpacker-delta/src/index.ts`
|
||||
**Export:** See source for details.
|
||||
|
||||
### What it does
|
||||
|
||||
Part of the @suaveplan/ package.
|
||||
@@ -0,0 +1,26 @@
|
||||
## String Diff
|
||||
|
||||
**Path:** `src/string-diff.ts`
|
||||
**Export:** `calculateStringDiff`, `applyStringDiff`, `calculateDiffSavings`, `shouldUseDiff`, `StringDiff`
|
||||
|
||||
### What it does
|
||||
|
||||
Efficiently encodes string changes by finding common prefix and suffix, only encoding the differing middle section.
|
||||
|
||||
### Key API
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `calculateStringDiff(previous, current)` | Compute `{ prefixLen, suffixLen, middleLen, middle }` |
|
||||
| `applyStringDiff(previous, diff)` | Reconstruct current string from diff |
|
||||
| `calculateDiffSavings(previous, current, diff)` | Percentage savings from diff encoding |
|
||||
| `shouldUseDiff(previous, current, threshold?)` | Decide whether diff encoding is worthwhile |
|
||||
|
||||
### Format
|
||||
|
||||
`[prefixLen][suffixLen][middleLen][middleChars...]` — each length field is 1 byte.
|
||||
|
||||
### Used by
|
||||
|
||||
- Delta compression for string fields in game state
|
||||
- Network protocols with repetitive string data
|
||||
@@ -0,0 +1,23 @@
|
||||
## XOR Encoder
|
||||
|
||||
**Path:** `src/xor-encoder.ts`
|
||||
**Export:** `calculateXorDelta`, `applyXorDelta`, `calculateDeltaBitSize`, `isXorEfficient`, `numberXor`
|
||||
|
||||
### What it does
|
||||
|
||||
XOR-based delta encoding for numeric values. Encodes the difference between values rather than absolute values, which can save space when consecutive values are similar.
|
||||
|
||||
### Key API
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `numberXor(a, b)` | XOR two float64 values byte-by-byte |
|
||||
| `calculateXorDelta(current, previous)` | Compute `current ^ previous` |
|
||||
| `applyXorDelta(previous, delta)` | Reconstruct `current` from `previous ^ delta` |
|
||||
| `calculateDeltaBitSize(delta)` | Count significant bits in delta |
|
||||
| `isXorEfficient(current, previous, maxBits)` | Determine if XOR saves space |
|
||||
|
||||
### Used by
|
||||
|
||||
- `@suaveplan/bitpacker-delta` `DeltaManager` for change detection
|
||||
- Downstream compression pipelines
|
||||
Reference in New Issue
Block a user