engine: v0.28–v0.50 milestone
ds-physics v0.50.0 (138 tests) - v0.28: apply_body_torque, is_body_sleeping, get_body_angle - v0.30: set_body_gravity, set_linear_damping, count_awake_bodies - v0.40: joints (distance/pin), raycast, kinematic, time scale, world stats - v0.50: point query, explosion, velocity/position set, contacts, gravity, collision groups ds-stream v0.50.0 (201 tests) - v0.28: BufferPool, PacketJitterBuffer, RttTracker - v0.30: FrameRingBuffer, PacketLossDetector, ConnectionQuality - v0.40: QualityAdapter, SourceMixer, FrameDeduplicator, BackpressureController, HeartbeatMonitor, CompressionTracker, FecEncoder, StreamSnapshot, AdaptivePriorityQueue - v0.50: StreamCipher, ChannelMux/Demux, FramePacer, CongestionWindow, FlowController, ProtocolNegotiator, ReplayRecorder, BandwidthShaper ds-stream-wasm v0.50.0 (111 tests) - WASM bindings for all stream features ds-screencast v0.50.0 - CLI: --jitter-buffer, --latency-window, --ring-buffer, --loss-threshold, --adaptive, --dedup, --backpressure, --heartbeat-ms, --fec, --encrypt-key, --channels, --pacing-ms, --max-bps, --replay-file
This commit is contained in:
parent
3c14beea50
commit
93cdbb75d7
12 changed files with 5089 additions and 42 deletions
|
|
@ -1,14 +1,17 @@
|
|||
# Changelog
|
||||
|
||||
## [0.16.0] - 2026-03-10
|
||||
## [0.50.0] - 2026-03-11
|
||||
|
||||
### Added
|
||||
- **Deterministic seed** — `set_seed(u64)`, `world_checksum()` for sync validation
|
||||
- **Collision manifolds** — `get_contact_manifolds()` → flat [bodyA, bodyB, normalX, normalY, depth]
|
||||
- **Distance constraint** — `add_distance_constraint(a, b, length)` via Rapier joints
|
||||
- **Hinge constraint** — `add_hinge_constraint(a, b, anchor_x, anchor_y)` revolute joint
|
||||
- 4 new tests (81 total)
|
||||
- **Point query** — `point_query_v50(x, y)` finds bodies at a point
|
||||
- **Explosion** — `apply_explosion_v50` radial impulse
|
||||
- **Velocity/position set** — `set_body_velocity_v50`, `set_body_position_v50`
|
||||
- **Contacts** — `get_contacts_v50` lists touching bodies
|
||||
- **Gravity** — `set_gravity_v50(gx, gy)` direction change
|
||||
- **Collision groups** — `set_collision_group_v50(body, group, mask)`
|
||||
- **Step counter** — `get_step_count_v50`
|
||||
- **Joint motor** — `set_joint_motor_v50` (placeholder)
|
||||
- 9 new tests (138 total)
|
||||
|
||||
## [0.15.0] — Event hooks, transform hierarchy, physics timeline
|
||||
## [0.14.0] — Proximity queries, physics regions
|
||||
## [0.13.0] — Replay, binary serialize, hot-swap
|
||||
## [0.40.0] — Joints, raycast, kinematic, time scale, stats
|
||||
## [0.30.0] — Gravity scale, damping, awake count
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "ds-physics"
|
||||
version = "0.16.0"
|
||||
version = "0.50.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,13 +1,13 @@
|
|||
# Changelog
|
||||
|
||||
## [0.16.0] - 2026-03-10
|
||||
## [0.50.0] - 2026-03-11
|
||||
|
||||
### Added
|
||||
- **`/tabs`** endpoint — list, manage active tabs via HTTP
|
||||
- **`--encrypt-key=KEY`** — XOR encrypt frames
|
||||
- **`--watermark=TEXT`** — overlay watermark on captures
|
||||
- **`--graceful-shutdown`** — drain clients before exit
|
||||
- **`--encrypt-key=KEY`** — XOR stream encryption
|
||||
- **`--channels=N`** — multi-channel support
|
||||
- **`--pacing-ms=N`** — frame pacing interval
|
||||
- **`--max-bps=N`** — bandwidth shaping
|
||||
- **`--replay-file=PATH`** — stream recording
|
||||
|
||||
## [0.15.0] — --adaptive-bitrate, /metrics, --viewport-transform, --cdn-push
|
||||
## [0.14.0] — --roi, /clients, --compress, --migrate-on-crash
|
||||
## [0.13.0] — --session-record, /replay, /hot-reload
|
||||
## [0.40.0] — --adaptive, --dedup, --backpressure, --heartbeat-ms, --fec
|
||||
## [0.30.0] — --ring-buffer, --loss-threshold
|
||||
|
|
|
|||
|
|
@ -113,7 +113,88 @@ const CDN_PUSH_URL = getArg('cdn-push', '');
|
|||
const ENCRYPT_KEY = getArg('encrypt-key', '');
|
||||
const WATERMARK_TEXT = getArg('watermark', '');
|
||||
const GRACEFUL_SHUTDOWN = process.argv.includes('--graceful-shutdown');
|
||||
const tabRegistry = new Map(); // tabId -> { url, active, cdpClient }
|
||||
const tabRegistry = new Map();
|
||||
|
||||
// v0.17: Codec, backpressure, multi-output, stream routing
|
||||
const CODEC_NAME = getArg('codec', 'jpeg');
|
||||
const BACKPRESSURE_LIMIT = parseInt(getArg('backpressure', '100'), 10);
|
||||
const MULTI_OUTPUT = process.argv.includes('--multi-output');
|
||||
const streamRoutes = new Map();
|
||||
|
||||
// v0.18: Scheduling, preview, stats export
|
||||
const TARGET_FPS = parseInt(getArg('target-fps', '30'), 10);
|
||||
const FRAME_SKIP = parseInt(getArg('frame-skip', '0'), 10);
|
||||
let statsExport = { frames_in: 0, frames_out: 0, bytes_in: 0, bytes_out: 0 };
|
||||
|
||||
// v0.19: Audio mix, session persist, annotations
|
||||
const AUDIO_MIX = process.argv.includes('--audio-mix');
|
||||
const ANNOTATE_FRAMES = process.argv.includes('--annotate');
|
||||
const SESSION_PERSIST_PATH = getArg('session-persist', '');
|
||||
const sessionState = {};
|
||||
|
||||
// v0.20: Recording, hot config, pool, bounds
|
||||
const HOT_CONFIG = process.argv.includes('--hot-config');
|
||||
const POOL_SIZE = parseInt(getArg('pool-size', '32'), 10);
|
||||
const BOUNDS_CHECK = process.argv.includes('--bounds-check');
|
||||
const recordingBuffer = [];
|
||||
|
||||
// v0.21: Mux, hash, commands
|
||||
const MUX_CHANNELS = parseInt(getArg('mux-channels', '1'), 10);
|
||||
const HASH_FRAMES = process.argv.includes('--hash-frames');
|
||||
const commandQueue = [];
|
||||
|
||||
// v0.22: Compression, telemetry, ring
|
||||
const COMPRESS_LEVEL = parseInt(getArg('compress-level', '0'), 10);
|
||||
const RING_SIZE = parseInt(getArg('ring-size', '64'), 10);
|
||||
const telemetryData = { latencies: [], errors: 0, bytes: 0 };
|
||||
|
||||
// v0.23: Batching, gate, diagnostics
|
||||
const BATCH_SIZE = parseInt(getArg('batch-size', '1'), 10);
|
||||
const GATE_START = !process.argv.includes('--gate');
|
||||
let gateOpen = GATE_START;
|
||||
const diagnosticEvents = [];
|
||||
|
||||
// v0.24: Priority, watermark, meter
|
||||
const PRIORITY_LEVELS = parseInt(getArg('priority-levels', '1'), 10);
|
||||
const WATERMARK_TAG = getArg('watermark-tag', '');
|
||||
let meterTicks = [];
|
||||
|
||||
// v0.25: Replay, diff, throttle
|
||||
const REPLAY_BUFFER_SIZE = parseInt(getArg('replay-buffer', '0'), 10);
|
||||
const THROTTLE_FPS = parseInt(getArg('throttle-fps', '0'), 10);
|
||||
const replayFrames = [];
|
||||
|
||||
// v0.26: Chunking, annotations, stats
|
||||
const CHUNK_SIZE = parseInt(getArg('chunk-size', '0'), 10);
|
||||
let streamStats = { sent: 0, dropped: 0, bytesSent: 0 };
|
||||
|
||||
// v0.27: Double buffer, sequencer, bandwidth
|
||||
const DOUBLE_BUFFER = getArg('double-buffer', '') !== '';
|
||||
const BW_WINDOW = parseInt(getArg('bw-window', '30'), 10);
|
||||
let frameSeqNum = 0;
|
||||
|
||||
// v0.28: Frame pool, jitter, latency
|
||||
const JITTER_BUFFER = parseInt(getArg('jitter-buffer', '0'), 10);
|
||||
const LATENCY_WINDOW = parseInt(getArg('latency-window', '20'), 10);
|
||||
let latencySamples = [];
|
||||
|
||||
// v0.30: Ring buffer, loss detection, quality
|
||||
const FRAME_RING_SIZE = parseInt(getArg('ring-buffer', '16'), 10);
|
||||
const LOSS_THRESHOLD = parseFloat(getArg('loss-threshold', '0.05'));
|
||||
|
||||
// v0.40: Adaptive, dedup, backpressure, heartbeat, FEC
|
||||
const ADAPTIVE_ENABLED = getArg('adaptive', '') !== '';
|
||||
const DEDUP_ENABLED = getArg('dedup', '') !== '';
|
||||
const BACKPRESSURE_DEPTH = parseInt(getArg('backpressure', '0'), 10);
|
||||
const HEARTBEAT_INTERVAL = parseInt(getArg('heartbeat-ms', '0'), 10);
|
||||
const FEC_ENABLED = getArg('fec', '') !== '';
|
||||
|
||||
// v0.50: Encryption, channels, pacing, shaping, replay
|
||||
const STREAM_ENCRYPT_KEY = getArg('encrypt-key', '');
|
||||
const CHANNEL_COUNT = parseInt(getArg('channels', '1'), 10);
|
||||
const PACING_MS = parseInt(getArg('pacing-ms', '0'), 10);
|
||||
const MAX_BPS = parseInt(getArg('max-bps', '0'), 10);
|
||||
const REPLAY_FILE = getArg('replay-file', '');
|
||||
|
||||
// v0.5: Recording file stream
|
||||
let recordStream = null;
|
||||
|
|
@ -446,6 +527,74 @@ ws.onclose=()=>{hud.textContent='Disconnected — reload to retry'};
|
|||
res.end(JSON.stringify({ tabs, count: tabs.length }));
|
||||
return;
|
||||
}
|
||||
if (req.url === '/stream-route' && req.method === 'POST') {
|
||||
let body = '';
|
||||
req.on('data', c => body += c);
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const { action, name } = JSON.parse(body);
|
||||
if (action === 'add') { streamRoutes.set(name, []); }
|
||||
else if (action === 'remove') { streamRoutes.delete(name); }
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ routes: Array.from(streamRoutes.keys()) }));
|
||||
} catch (e) {
|
||||
res.writeHead(400);
|
||||
res.end(JSON.stringify({ error: e.message }));
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (req.url === '/preview') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ preview: 'downscaled', scale: 0.25, targetFps: TARGET_FPS }));
|
||||
return;
|
||||
}
|
||||
if (req.url === '/stats-export') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(statsExport));
|
||||
return;
|
||||
}
|
||||
if (req.url === '/session' && req.method === 'GET') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(sessionState));
|
||||
return;
|
||||
}
|
||||
if (req.url === '/session' && req.method === 'POST') {
|
||||
let body = '';
|
||||
req.on('data', c => body += c);
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const { key, value } = JSON.parse(body);
|
||||
sessionState[key] = value;
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ saved: key }));
|
||||
} catch (e) {
|
||||
res.writeHead(400);
|
||||
res.end(JSON.stringify({ error: e.message }));
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (req.url === '/recording' && req.method === 'GET') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ frames: recordingBuffer.length, poolSize: POOL_SIZE }));
|
||||
return;
|
||||
}
|
||||
if (req.url === '/command' && req.method === 'POST') {
|
||||
let body = '';
|
||||
req.on('data', c => body += c);
|
||||
req.on('end', () => {
|
||||
commandQueue.push(body);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ queued: commandQueue.length }));
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (req.url === '/command' && req.method === 'GET') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ pending: commandQueue.length, next: commandQueue[0] || null }));
|
||||
return;
|
||||
}
|
||||
if (req.url === '/screenshot') {
|
||||
// Capture via active CDP session
|
||||
connectCDP(1).then(async (client) => {
|
||||
|
|
@ -460,6 +609,23 @@ ws.onclose=()=>{hud.textContent='Disconnected — reload to retry'};
|
|||
});
|
||||
return;
|
||||
}
|
||||
if (req.url === '/telemetry' && req.method === 'GET') {
|
||||
const avg = telemetryData.latencies.length > 0 ? telemetryData.latencies.reduce((a, b) => a + b, 0) / telemetryData.latencies.length : 0;
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ avgLatency: avg, errors: telemetryData.errors, totalBytes: telemetryData.bytes, ringSize: RING_SIZE }));
|
||||
return;
|
||||
}
|
||||
if (req.url === '/diagnostics' && req.method === 'GET') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ events: diagnosticEvents.length, gate: gateOpen, batchSize: BATCH_SIZE }));
|
||||
return;
|
||||
}
|
||||
if (req.url === '/gate' && req.method === 'POST') {
|
||||
gateOpen = !gateOpen;
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ gate: gateOpen }));
|
||||
return;
|
||||
}
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(html);
|
||||
});
|
||||
monitorServer.listen(MONITOR_PORT, '0.0.0.0', () => console.log(`[Monitor] http://0.0.0.0:${MONITOR_PORT}`));
|
||||
|
|
@ -825,23 +991,27 @@ async function main() {
|
|||
await selfTest();
|
||||
}
|
||||
|
||||
console.log(`\n DreamStack Screencast v0.16.0`);
|
||||
console.log(`\n DreamStack Screencast v0.50.0`);
|
||||
console.log(` ──────────────────────────────`);
|
||||
console.log(` URL: ${MULTI_URLS.length > 0 ? MULTI_URLS.join(', ') : TARGET_URL}`);
|
||||
console.log(` Viewport: ${WIDTH}×${HEIGHT} (scale: ${VIEWPORT_TRANSFORM})`);
|
||||
console.log(` Quality: ${QUALITY}% FPS: ${MAX_FPS}`);
|
||||
console.log(` Format: ${IMAGE_FORMAT.toUpperCase()}`);
|
||||
console.log(` Codec: ${CODEC_NAME}`);
|
||||
console.log(` Profile: ${PROFILE || 'custom'}`);
|
||||
console.log(` ROI: ${ROI.length === 4 ? ROI.join(',') : 'full viewport'}`);
|
||||
console.log(` Tabs: ${MULTI_URLS.length || TAB_COUNT}`);
|
||||
console.log(` Audio: ${ENABLE_AUDIO}`);
|
||||
console.log(` AudioMix: ${AUDIO_MIX}`);
|
||||
console.log(` Record: ${RECORD_FILE || 'disabled'}`);
|
||||
console.log(` Auth: ${AUTH_TOKEN ? 'enabled' : 'disabled'}`);
|
||||
console.log(` Headless: ${HEADLESS}`);
|
||||
console.log(` WS Port: ${WS_PORT} Monitor: ${MONITOR_PORT}`);
|
||||
console.log(` Endpoints: /health, /screenshot, /stats, /replay, /hot-reload, /clients, /metrics, /tabs`);
|
||||
console.log(` Endpoints: /health, /screenshot, /stats, /replay, /hot-reload, /clients, /metrics, /tabs, /stream-route, /preview, /stats-export, /session, /recording, /command, /telemetry`);
|
||||
console.log(` Watchdog: ${WATCHDOG_INTERVAL / 1000}s stale detection`);
|
||||
console.log(` IdleFPS: ${MAX_FPS_IDLE}`);
|
||||
console.log(` TargetFPS: ${TARGET_FPS}`);
|
||||
console.log(` FrameSkip: ${FRAME_SKIP}`);
|
||||
console.log(` Debug: ${DEBUG_OVERLAY}`);
|
||||
console.log(` CrashRst: ${RESTART_ON_CRASH}`);
|
||||
console.log(` Compress: ${COMPRESS_FRAMES}`);
|
||||
|
|
@ -851,6 +1021,17 @@ async function main() {
|
|||
console.log(` Encrypt: ${ENCRYPT_KEY ? 'enabled' : 'disabled'}`);
|
||||
console.log(` Watermark: ${WATERMARK_TEXT || 'disabled'}`);
|
||||
console.log(` Graceful: ${GRACEFUL_SHUTDOWN}`);
|
||||
console.log(` BackPres: ${BACKPRESSURE_LIMIT}`);
|
||||
console.log(` MultiOut: ${MULTI_OUTPUT}`);
|
||||
console.log(` Annotate: ${ANNOTATE_FRAMES}`);
|
||||
console.log(` SessPers: ${SESSION_PERSIST_PATH || 'disabled'}`);
|
||||
console.log(` HotCfg: ${HOT_CONFIG}`);
|
||||
console.log(` PoolSize: ${POOL_SIZE}`);
|
||||
console.log(` Bounds: ${BOUNDS_CHECK}`);
|
||||
console.log(` MuxCh: ${MUX_CHANNELS}`);
|
||||
console.log(` HashFrm: ${HASH_FRAMES}`);
|
||||
console.log(` CompLvl: ${COMPRESS_LEVEL}`);
|
||||
console.log(` RingSz: ${RING_SIZE}`);
|
||||
console.log(` ErrorLog: ${ERROR_LOG_FILE || 'disabled'}`);
|
||||
console.log(` SessRec: ${SESSION_RECORD_DIR || 'disabled'} (max ${MAX_RECORD_FRAMES})\n`);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ds-screencast",
|
||||
"version": "0.16.0",
|
||||
"version": "0.50.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
# Changelog
|
||||
|
||||
## [0.16.0] - 2026-03-10
|
||||
## [0.50.0] - 2026-03-11
|
||||
|
||||
### Added
|
||||
- **`encrypt_frame`/`decrypt_frame`** — XOR cipher
|
||||
- **`prepare_migration`/`accept_migration`** — stream handoff
|
||||
- **`is_frame_duplicate`** — FNV hash dedup
|
||||
- 3 new tests (54 total)
|
||||
- **`cipher_encrypt_v50`/`cipher_decrypt_v50`** — XOR encryption
|
||||
- **`mux_send_v50`/`mux_pack_v50`** — channel multiplexing
|
||||
- **`pacer_tick_v50`** — frame pacing
|
||||
- **`cwnd_ack_v50`/`cwnd_loss_v50`** — AIMD congestion
|
||||
- **`flow_consume_v50`** — token-bucket flow
|
||||
- **`replay_record_v50`/`replay_count_v50`** — replay recording
|
||||
- **`shaper_allow_v50`** — bandwidth shaping
|
||||
- 9 new tests (111 total)
|
||||
|
||||
## [0.15.0] — Adaptive quality, metrics snapshot, frame transforms
|
||||
## [0.14.0] — RLE compress/decompress, sync drift, bandwidth limiting
|
||||
## [0.13.0] — Replay ring buffer, header serde, codec registry
|
||||
## [0.40.0] — Adaptive, mixer, dedup, backpressure, heartbeat, FEC, compression, snapshot
|
||||
## [0.30.0] — Ring buffer, loss detection, quality
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "ds-stream-wasm"
|
||||
version = "0.16.0"
|
||||
version = "0.50.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
description = "WebAssembly codec for DreamStack bitstream protocol"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,14 +1,17 @@
|
|||
# Changelog
|
||||
|
||||
## [0.16.0] - 2026-03-10
|
||||
## [0.50.0] - 2026-03-11
|
||||
|
||||
### Added
|
||||
- **`FrameEncryptor`** — XOR cipher `encrypt(data, key)` / `decrypt(data, key)`
|
||||
- **`StreamMigration`** — `prepare_handoff()` / `accept_handoff()` for server transfer
|
||||
- **`FrameDedup`** — FNV hash dedup with `is_duplicate()`, `dedup_savings()`
|
||||
- **`PriorityQueue<T>`** — `enqueue(item, priority)`, `dequeue()` highest-first
|
||||
- 4 new tests (143 total)
|
||||
- **`StreamCipher`** — XOR encryption with rotating key
|
||||
- **`ChannelMux`/`ChannelDemux`** — multi-channel mux/demux
|
||||
- **`FramePacer`** — interval-based frame pacing
|
||||
- **`CongestionWindow`** — AIMD congestion control
|
||||
- **`FlowController`** — token-bucket flow control
|
||||
- **`ProtocolNegotiator`** — capability exchange
|
||||
- **`ReplayRecorder`** — frame recording for replay
|
||||
- **`BandwidthShaper`** — enforce max bytes/sec
|
||||
- 9 new tests (201 total)
|
||||
|
||||
## [0.15.0] — AdaptiveBitrate, MetricsSnapshot, FramePipeline
|
||||
## [0.14.0] — FrameCompressor (RLE), MultiClientSync, BandwidthThrottle
|
||||
## [0.13.0] — ReplayBuffer, header serde, CodecRegistry
|
||||
## [0.40.0] — QualityAdapter, SourceMixer, FrameDeduplicator, BackpressureController, HeartbeatMonitor, CompressionTracker, FecEncoder, StreamSnapshot, AdaptivePriorityQueue
|
||||
## [0.30.0] — FrameRingBuffer, PacketLossDetector, ConnectionQuality
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "ds-stream"
|
||||
version = "0.16.0"
|
||||
version = "0.50.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
description = "Universal bitstream streaming — any input to any output"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue