From d91e5da9341ec58565e25689bd16a7aa17676161 Mon Sep 17 00:00:00 2001 From: David-Alexandre Chanel Date: Tue, 6 Oct 2026 16:02:15 +0200 Subject: [PATCH 1/6] feat: add compression mode control --- index.html | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index e375875..ac86c2b 100644 --- a/index.html +++ b/index.html @@ -142,7 +142,7 @@

Augmenta × Three.js

Advanced - 6060 · Auto · ×1 + 6060 · Auto · Compressed · ×1
@@ -158,6 +158,13 @@

Augmenta × Three.js

+
+ + +
From 0efafd2a6c1e44b6fab5c35cd68cecbbbe97724b Mon Sep 17 00:00:00 2001 From: David-Alexandre Chanel Date: Tue, 6 Oct 2026 16:02:18 +0200 Subject: [PATCH 2/6] feat: wire compression mode into connection settings --- src/main.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main.js b/src/main.js index b8a26f4..ebaf062 100644 --- a/src/main.js +++ b/src/main.js @@ -37,7 +37,7 @@ function refreshFavicon() { refreshFavicon(); const ui = { - app: $('#app'), sidebar: $('#sidebar'), sidebarResizer: $('#sidebar-resizer'), serverAddress: $('#server-address'), port: $('#port'), protocol: $('#protocol'), downsample: $('#downsample'), connect: $('#connect'), + app: $('#app'), sidebar: $('#sidebar'), sidebarResizer: $('#sidebar-resizer'), serverAddress: $('#server-address'), port: $('#port'), protocol: $('#protocol'), compression: $('#compression'), downsample: $('#downsample'), connect: $('#connect'), demo: $('#demo'), status: $('#status'), note: $('#connection-note'), summary: $('#summary'), debug: $('#debug-content'), clear: $('#clear'), resetCamera: $('#reset-camera'), scenes: $('#scenes'), sidebarToggle: $('#sidebar-toggle'), viewerTitle: $('.viewer-title'), connectionQrVisibility: $('.connection-qr-visibility'), @@ -182,6 +182,7 @@ function applyConnectionSettings(settings) { if (typeof settings.address === 'string') ui.serverAddress.value = settings.address; if (typeof settings.port === 'string') ui.port.value = settings.port; if (typeof settings.protocol === 'string') ui.protocol.value = settings.protocol; + if (typeof settings.compression === 'string') ui.compression.value = settings.compression; if (typeof settings.downsample === 'string') ui.downsample.value = settings.downsample; } @@ -418,8 +419,9 @@ function updateDisplaySectionSummary() { function updateConnectionAdvancedSummary() { const protocolLabel = ui.protocol.selectedOptions[0]?.textContent?.split(' — ')[0] ?? ui.protocol.value; + const compressionLabel = ui.compression.selectedOptions[0]?.textContent ?? ui.compression.value; ui.connectionAdvancedSummary.textContent = - `${ui.port.value || '—'} · ${protocolLabel} · ×${ui.downsample.value || '—'}`; + `${ui.port.value || '—'} · ${protocolLabel} · ${compressionLabel} · ×${ui.downsample.value || '—'}`; } function syncSceneSelector() { @@ -478,7 +480,9 @@ function getConnectionSettings() { address: ui.serverAddress.value, port: ui.port.value, protocol: ui.protocol.value, + compression: ui.compression.value, downsample: ui.downsample.value, + useCompression: ui.compression.value !== 'uncompressed', streamClusters: ui.requestClusters.checked, streamClusterPoints: ui.requestClusterPoints.checked, streamClouds: ui.requestGeneralPoints.checked @@ -747,6 +751,10 @@ ui.protocol.addEventListener('change', () => { saveConnectionField('protocol', ui.protocol.value); restartConnection('Protocol changed'); }); +ui.compression.addEventListener('change', () => { + saveConnectionField('compression', ui.compression.value); + restartConnection('Compression changed'); +}); ui.downsample.addEventListener('change', () => { restartConnection('Downsample changed'); }); From f40923a8166e821b872a57d2c75f8e7e0a62ac7d Mon Sep 17 00:00:00 2001 From: David-Alexandre Chanel Date: Tue, 6 Oct 2026 16:02:21 +0200 Subject: [PATCH 3/6] feat: request compressed or uncompressed websocket stream --- src/connection.js | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/connection.js b/src/connection.js index e378ef1..434c141 100644 --- a/src/connection.js +++ b/src/connection.js @@ -157,6 +157,7 @@ export function createConnectionController({ let streamClouds; let streamClusters; let streamClusterPoints; + let useCompression; try { const settings = getSettings(); const targets = ensureTargetPlan(settings.address, settings.port); @@ -166,6 +167,7 @@ export function createConnectionController({ streamClouds = settings.streamClouds !== false; streamClusters = settings.streamClusters !== false; streamClusterPoints = settings.streamClusterPoints !== false; + useCompression = settings.useCompression !== false; } catch (error) { wantsConnection = false; retrying = false; @@ -184,15 +186,17 @@ export function createConnectionController({ } let decompressor; - try { - decompressor = await getZstdDecompressor(); - } catch (error) { - if (attemptGeneration !== connectionAttemptGeneration || !wantsConnection) return; - wantsConnection = false; - retrying = false; - const message = error instanceof Error ? error.message : String(error); - publish('error', `Could not initialize Zstd decompression: ${message}`); - return; + if (useCompression) { + try { + decompressor = await getZstdDecompressor(); + } catch (error) { + if (attemptGeneration !== connectionAttemptGeneration || !wantsConnection) return; + wantsConnection = false; + retrying = false; + const message = error instanceof Error ? error.message : String(error); + publish('error', `Could not initialize Zstd decompression: ${message}`); + return; + } } if (attemptGeneration !== connectionAttemptGeneration || !wantsConnection) return; @@ -209,11 +213,10 @@ export function createConnectionController({ streamClusters, streamClusterPoints, streamZonePoints: true, - // Keep the realtime stream compressed by default, matching the Unity, - // TouchDesigner and VVVV clients. This cuts WebSocket bandwidth and - // backlog pressure; zstddec is initialized before the connection and - // the JS SDK invokes it synchronously before parsing each binary frame. - useCompression: true, + // Compression stays enabled by default, matching the Unity, + // TouchDesigner and VVVV clients, but Advanced can disable it for + // diagnostics or compatibility checks. + useCompression, displayPointIntensity: true, // Quaternions preserve Pleiades' exact OBB orientation. The viewer // performs the left-handed -> right-handed basis reflection explicitly. @@ -238,7 +241,7 @@ export function createConnectionController({ socketOpen = true; publish( 'connected', - `Connected to ${target.label}. Protocol V${activeVersion}; Zstd-compressed debug stream.` + `Connected to ${target.label}. Protocol V${activeVersion}; ${useCompression ? 'Zstd-compressed' : 'uncompressed'} debug stream.` ); }); From 0ee413ba23ac99c8731ae2e58607a68f24e0864a Mon Sep 17 00:00:00 2001 From: David-Alexandre Chanel Date: Tue, 6 Oct 2026 16:02:23 +0200 Subject: [PATCH 4/6] feat: share compression preference in connection URLs --- src/share-link.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/share-link.js b/src/share-link.js index 606756e..2304f3d 100644 --- a/src/share-link.js +++ b/src/share-link.js @@ -1,4 +1,4 @@ -const CONNECTION_QUERY_KEYS = ['address', 'port', 'protocol', 'downsample']; +const CONNECTION_QUERY_KEYS = ['address', 'port', 'protocol', 'compression', 'downsample']; export function normalizeConnectionOptions(settings) { const options = {}; @@ -16,6 +16,11 @@ export function normalizeConnectionOptions(settings) { options.protocol = protocol; } + const compression = String(settings?.compression ?? ''); + if (['compressed', 'uncompressed'].includes(compression)) { + options.compression = compression; + } + const downsample = Number(settings?.downsample); if (Number.isInteger(downsample) && downsample >= 1) { options.downsample = String(downsample); @@ -51,6 +56,7 @@ export function readConnectionOptionsFromUrl(urlValue) { address: url.searchParams.get('address'), port: url.searchParams.get('port'), protocol: url.searchParams.get('protocol'), + compression: url.searchParams.get('compression'), downsample: url.searchParams.get('downsample') }); } From 955165b48045d67d3b3df22d2cf55583e2a7597a Mon Sep 17 00:00:00 2001 From: David-Alexandre Chanel Date: Tue, 6 Oct 2026 16:03:00 +0200 Subject: [PATCH 5/6] test: cover selectable websocket compression --- tests/websocket-compression.test.mjs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/websocket-compression.test.mjs b/tests/websocket-compression.test.mjs index aab059e..b649fe7 100644 --- a/tests/websocket-compression.test.mjs +++ b/tests/websocket-compression.test.mjs @@ -37,12 +37,13 @@ test('bundled zstd decoder can decode Augmenta binary payloads synchronously aft ); }); -test('Three.js runtime requests compression and ships the decoder in the Pages artifact', () => { +test('Three.js runtime supports selectable compression and ships the decoder in the Pages artifact', () => { const connection = readFileSync(new URL('../src/connection.js', import.meta.url), 'utf8'); const index = readFileSync(new URL('../index.html', import.meta.url), 'utf8'); const assemble = readFileSync(new URL('../scripts/assemble-site.mjs', import.meta.url), 'utf8'); - assert.match(connection, /useCompression:\s*true/); + assert.match(connection, /useCompression\s*=\s*settings\.useCompression\s*!==\s*false/); + assert.match(connection, /useCompression,/); assert.match(connection, /decompressor,/); assert.match(connection, /import\('zstddec'\)/); assert.match(index, /zstddec@0\.3\.1\/\+esm/); From 8d7a41e22c3e125ba6cff5730141bf9ff172d87f Mon Sep 17 00:00:00 2001 From: David-Alexandre Chanel Date: Tue, 6 Oct 2026 16:03:34 +0200 Subject: [PATCH 6/6] test: preserve compression mode in share links --- tests/share-link.test.mjs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/share-link.test.mjs b/tests/share-link.test.mjs index 6dc2cbf..31aa2d2 100644 --- a/tests/share-link.test.mjs +++ b/tests/share-link.test.mjs @@ -14,6 +14,7 @@ test('connection share URL preserves the page and embeds current connection opti address: '192.168.1.42', port: '6060', protocol: '3', + compression: 'uncompressed', downsample: '2' } ); @@ -24,6 +25,7 @@ test('connection share URL preserves the page and embeds current connection opti assert.equal(parsed.searchParams.get('address'), '192.168.1.42'); assert.equal(parsed.searchParams.get('port'), '6060'); assert.equal(parsed.searchParams.get('protocol'), '3'); + assert.equal(parsed.searchParams.get('compression'), 'uncompressed'); assert.equal(parsed.searchParams.get('downsample'), '2'); assert.equal(parsed.hash, ''); }); @@ -31,19 +33,20 @@ test('connection share URL preserves the page and embeds current connection opti test('connection options in a shared URL are validated', () => { assert.deepEqual( readConnectionOptionsFromUrl( - 'https://example.test/?address=augmenta-WA12031.local&port=6060&protocol=auto&downsample=4' + 'https://example.test/?address=augmenta-WA12031.local&port=6060&protocol=auto&compression=compressed&downsample=4' ), { address: 'augmenta-WA12031.local', port: '6060', protocol: 'auto', + compression: 'compressed', downsample: '4' } ); assert.deepEqual( readConnectionOptionsFromUrl( - 'https://example.test/?address=%20&port=70000&protocol=9&downsample=0' + 'https://example.test/?address=%20&port=70000&protocol=9&compression=invalid&downsample=0' ), {} ); @@ -51,11 +54,12 @@ test('connection options in a shared URL are validated', () => { test('regenerating a share URL replaces stale connection query values', () => { const url = buildConnectionShareUrl( - 'https://example.test/?address=old&port=1&protocol=2&downsample=99', + 'https://example.test/?address=old&port=1&protocol=2&compression=uncompressed&downsample=99', { address: '10.0.0.5', port: 6060, protocol: 'auto', + compression: 'compressed', downsample: 1 } ); @@ -64,6 +68,7 @@ test('regenerating a share URL replaces stale connection query values', () => { address: '10.0.0.5', port: '6060', protocol: 'auto', + compression: 'compressed', downsample: '1' }); }); @@ -74,23 +79,27 @@ test('shared connection options override a local view without mutating local pre address: 'augmenta-local.local', port: '6060', protocol: 'auto', + compression: 'compressed', downsample: '1' }; const shared = { address: '192.168.1.42', - protocol: '3' + protocol: '3', + compression: 'uncompressed' }; assert.deepEqual(resolveConnectionOptions(local, shared), { address: '192.168.1.42', port: '6060', protocol: '3', + compression: 'uncompressed', downsample: '1' }); assert.deepEqual(local, { address: 'augmenta-local.local', port: '6060', protocol: 'auto', + compression: 'compressed', downsample: '1' }); }); @@ -100,11 +109,13 @@ test('connection option normalization keeps only valid local values', () => { address: ' augmenta-server ', port: 6060, protocol: '2', + compression: 'uncompressed', downsample: 4 }), { address: 'augmenta-server', port: '6060', protocol: '2', + compression: 'uncompressed', downsample: '4' }); @@ -112,6 +123,7 @@ test('connection option normalization keeps only valid local values', () => { address: ' ', port: 0, protocol: '7', + compression: 'invalid', downsample: -1 }), {}); });