@plasius/gpu-demo-viewer 0.1.11 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -18,6 +18,20 @@ The format is based on Keep a Changelog and this project adheres to SemVer.
18
18
  - **Security**
19
19
  - (placeholder)
20
20
 
21
+ ## [0.2.0] - 2026-10-21
22
+
23
+ - **Added**
24
+ - Furnished-interior and workshop composite studies in the local model workshop, with shared inspection controls and individual source credits.
25
+
26
+ - **Changed**
27
+ - (placeholder)
28
+
29
+ - **Fixed**
30
+ - (placeholder)
31
+
32
+ - **Security**
33
+ - Update the workshop's Vite development dependency to the patched 8.3.5 release.
34
+
21
35
  ## [0.1.11] - 2026-10-21
22
36
 
23
37
  - Add the nine qualified furniture/workshop source models and their integrity inventories, using released shared1.1.12 and renderer0.2.54; reconcile the prior 31-model production records.
@@ -278,3 +292,4 @@ The format is based on Keep a Changelog and this project adheres to SemVer.
278
292
  [0.1.9]: https://github.com/Plasius-LTD/gpu-demo-viewer/releases/tag/v0.1.9
279
293
  [0.1.10]: https://github.com/Plasius-LTD/gpu-demo-viewer/releases/tag/v0.1.10
280
294
  [0.1.11]: https://github.com/Plasius-LTD/gpu-demo-viewer/releases/tag/v0.1.11
295
+ [0.2.0]: https://github.com/Plasius-LTD/gpu-demo-viewer/releases/tag/v0.2.0
package/README.md CHANGED
@@ -54,6 +54,16 @@ or enabled on the production site. The download helper admits only configured
54
54
  resources and keeps two workers, a 100 MiB per-file limit and a 200 MiB batch limit.
55
55
  Source, creator, licence and review notes appear beside each model.
56
56
 
57
+ Two composite studies appear after the individual models in **Choose a model or
58
+ study**: a furnished interior and a workshop still life. They arrange four and
59
+ five credited originals under shared lighting and one camera. They are additional
60
+ views of existing models, so the catalogue still contains forty models. Each
61
+ study lists its sources, licences and chosen dimensions; these are static review
62
+ arrangements, not verified physical-scale scenes. See the
63
+ [composite study guide](https://github.com/Plasius-LTD/gpu-demo-viewer/blob/main/docs/composite-model-studies.md).
64
+ Keep `?review=first-twelve` in the local URL and append
65
+ `&model=study-furnished-interior` or `&model=study-workshop` to bookmark a study.
66
+
57
67
  ## Camera and render quality
58
68
 
59
69
  Drag or use one finger to orbit; Shift/right-drag trucks, Alt-drag pans, and the
@@ -0,0 +1,21 @@
1
+ # ADR 0006: Consume shared composite inspection
2
+
3
+ Status: accepted. Date: 2026-10-21.
4
+ Feature: site#1170; Story site#2313; viewer Task #73.
5
+
6
+ The workshop needs to compare several originals under one camera and light.
7
+ Define small authored arrangements using installed catalogue keys, dimensions,
8
+ anchors and Y rotation. Expose them as composite studies alongside individual
9
+ model selection. Keep the count of acquired models distinct from the number of
10
+ views. Resolve complete integrity manifests through the existing fixture helper
11
+ and show every source's attribution and licence.
12
+
13
+ Consume `createSceneInspection` from the released shared package. Rendering,
14
+ loading, budgets, cancellation, camera and refinement remain shared-runtime
15
+ responsibilities. The viewer owns selection, composition data, credits and UI
16
+ lifecycle. Keep source overrides explicit and local to development; repeat
17
+ browser review with the registry package before release.
18
+
19
+ The existing scene-fidelity feature flag and route capability continue to govern
20
+ hosted product access. This local review does not add public-site navigation or
21
+ change the existing localhost review gate.
@@ -8,3 +8,4 @@
8
8
  - [ADR 0003: Reviewed model library](adr-0003-reviewed-model-library.md)
9
9
 
10
10
  - [ADR 0005: Build-time model acquisition subpath](adr-0005-build-time-model-acquisition-subpath.md)
11
+ - [ADR 0006: Composite study consumer](adr-0006-composite-study-consumer.md)
@@ -0,0 +1,65 @@
1
+ # Furnished interior and workshop studies
2
+
3
+ Feature: site#1170; Story site#2313; viewer Task #73; shared Task #161.
4
+ Inherited remote rollout: `gpu-demo.scene-fidelity.enabled`.
5
+
6
+ After qualifying and releasing the forty-model batch, add two local workshop
7
+ studies using the shared `createSceneInspection` API. The furnished interior
8
+ compares the sofa, silk pouf, cabinet and basket. The workshop compares the
9
+ spinning wheel, barrel, crate, vice and blowtorch. All are existing credited,
10
+ integrity-checked originals. Scale and placement are authored review choices.
11
+
12
+ Keep individual models available and clearly distinguish composite studies from
13
+ the model count. Show every source/author/licence and chosen dimension. Reuse the
14
+ existing quality controls, camera help, progressive status, cancellation and
15
+ visibility lifecycle. Load only the selected model or study. Public-site scene
16
+ navigation is separate qualified consumer work.
17
+
18
+ Before implementation, test scene selection, complete source requests, missing
19
+ source rejection, credits, shared quality/lifecycle controls and cancellation.
20
+ Review both arrangements in actual WebGPU with >=3 sources together, camera
21
+ movement and native UHD. Release the shared package first, then consume that
22
+ registry version and repeat the review before viewer release. Keep source aliases
23
+ explicit and local to development. Record limits instead of claiming untested
24
+ animation, adaptive optical quality, physical touch or screen-reader speech.
25
+
26
+ ## Initial browser review
27
+
28
+ The four-model interior (211,146 triangles including ground) and five-model
29
+ workshop (82,635 triangles) completed fixed 64 samples at 800 × 282 with
30
+ renderer 0.2.54 and a local source override for the new shared API. Both also
31
+ completed native 3840 × 2160 at one sample; camera changes and denoising-off
32
+ refinement completed again. The interior additionally passed mouse orbit and
33
+ a UHD-to-balanced round trip using native browser menus. All source credits
34
+ and licence links were inspected. At a 320-pixel viewport the interior completed
35
+ 64 samples at 800 × 601, all four models remained in frame, and the page and
36
+ expanded credits had no horizontal overflow. The temporary viewport override was
37
+ reset. No renderer console warnings/errors appeared.
38
+
39
+ Preparing a heavy scene after quality changes takes several seconds. Direct DOM
40
+ automation hit its three-second input deadline and left two transitions incomplete;
41
+ a clean page with native menu input completed the same transition. This does
42
+ not establish real-time responsiveness. The retained evidence records this
43
+ limitation alongside the completed frames. Physical touch, screen-reader speech
44
+ and reference optical fidelity remain untested. The original versus voxel
45
+ distinction is unchanged.
46
+
47
+ ## Registry-consumer qualification
48
+
49
+ Both studies were reviewed again with the published `gpu-shared` 1.2.0 and
50
+ `gpu-renderer` 0.2.54 packages, with no source aliases. Both completed 64 samples
51
+ at 800 × 282, refined after keyboard camera movement and denoising changes, and
52
+ completed native 3840 × 2160 at one sample. The interior also completed the return
53
+ to balanced resolution. No renderer console warnings/errors appeared. A native
54
+ checkbox action exceeded the automation input deadline but the requested quality
55
+ change and subsequent 64-sample refinement completed.
56
+
57
+ The repeat mobile check could not be completed: the high-level viewport override
58
+ did not affect the review tab, and browser approval rejected tab-scoped emulation.
59
+ No workaround was attempted. The earlier source-version 320-pixel evidence still
60
+ applies to the same layout; this is not a claim of a second mobile test.
61
+
62
+ The viewer suite passes all 56 tests with 100% line coverage, including all changed
63
+ JavaScript sources in LCOV. Syntax, build, package-content and full dependency
64
+ audit checks pass. These are functional render checks; one-sample UHD output is
65
+ not an optical-quality or performance qualification.
@@ -20,7 +20,7 @@ export function createReviewController({ load, onState }) {
20
20
  const id = ++generation;
21
21
  abort?.abort(); active?.destroy(); active = undefined;
22
22
  abort = new AbortController();
23
- if (!asset.entry) { onState({kind:'missing', asset, message:'Download deferred. This model is not installed.'}); return; }
23
+ if (!asset.entry && !asset.models?.length) { onState({kind:'missing', asset, message:'Download deferred. This model is not installed.'}); return; }
24
24
  onState({kind:'loading',asset});
25
25
  try {
26
26
  const runtime = await load(asset, abort.signal);
@@ -16,8 +16,8 @@
16
16
  </script></head>
17
17
  <body><header><a href="https://plasius.co.uk/" target="_blank" rel="noopener">PLASIUS</a><span>Renderer workshop · Local review</span></header>
18
18
  <main><div class="intro"><p class="eyebrow">From source to scene</p><h1>Model studies</h1><p>Original assets. One renderer. Explore the surfaces, silhouettes and details that will shape our next improvements.</p></div>
19
- <div class="workspace"><aside><label for="model">Choose a model</label><select id="model" disabled></select><p id="category" class="muted"></p><h2 id="name"></h2><p id="target"></p><p id="note"></p><div id="metrics" class="metrics"></div>
20
- <details><summary>Source and licence</summary><p id="credits"></p><p><a id="source" target="_blank" rel="noopener">Original model</a> · <a id="licence" target="_blank" rel="noopener"></a></p><p>Original files retained. Framing is normalized for inspection. Powered by Poly Haven for its asset metadata.</p></details>
19
+ <div class="workspace"><aside><label for="model">Choose a model or study</label><select id="model" disabled></select><p id="category" class="muted"></p><h2 id="name"></h2><p id="target"></p><p id="note"></p><div id="metrics" class="metrics"></div>
20
+ <details><summary>Source and licence</summary><p id="credits"></p><p id="source-line"><a id="source" target="_blank" rel="noopener">Original model</a> · <a id="licence" target="_blank" rel="noopener"></a></p><ul id="composite-credits" hidden></ul><p>Original files retained. Framing is normalized for inspection. Powered by Poly Haven for its asset metadata.</p></details>
21
21
  <details><summary>Camera help</summary><p id="help">Drag to orbit. Shift-drag or right-drag to truck. Alt-drag to pan. Scroll to move closer; Shift-scroll changes the lens. Touch: one finger orbits; two fingers move and pinch. Keyboard: arrows orbit, Shift-arrows truck, + / − move closer or further, Home resets. Keys 1–5 choose orbit, truck, pan, dolly or zoom. Press ? for a brief reminder.</p></details>
22
22
  <details open><summary>Review focus</summary><p id="review"></p><p>This is an inspection pass, not a visual-quality approval. Source animations are retained on disk; this view displays static geometry.</p></details></aside>
23
23
  <section class="stage" aria-label="Model inspection">
@@ -1,4 +1,5 @@
1
1
  import { createReviewController, fixtureRequest, reviewEnabled } from './controller.js';
2
+ import { compositeStudies, compositeRequest, renderCompositeCredits } from './scenes.js';
2
3
 
3
4
  export async function mountLibrary({document:doc=globalThis.document,window:win=globalThis.window,
4
5
  fetch:request=globalThis.fetch,importRuntime=()=>import('@plasius/gpu-shared/model-inspection')}={}) {
@@ -10,11 +11,12 @@ export async function mountLibrary({document:doc=globalThis.document,window:win=
10
11
  const response=await request(new URL('./catalogue.json',win.location.href));
11
12
  if (!response.ok) throw new Error('Model catalogue could not be loaded.');
12
13
  const {assets}=await response.json();
13
- for(const asset of assets) {
14
+ const studies=compositeStudies(assets), selections=[...assets,...studies];
15
+ for(const asset of selections) {
14
16
  const option=doc.createElement('option');option.value=asset.key;
15
- option.textContent=asset.name+(asset.entry?'':' — deferred');el('model').append(option);
17
+ option.textContent=asset.name+(asset.models?' — composite study':asset.entry?'':' — deferred');el('model').append(option);
16
18
  }
17
- const {createModelInspection}=await importRuntime();
19
+ const {createModelInspection,createSceneInspection}=await importRuntime();
18
20
  el('samples').value='64';el('bounces').value='6';el('resolution').value='800';el('denoise').checked=true;
19
21
  const quality=()=>{
20
22
  const [width,fixedHeight]=el('resolution').value.split('x').map(Number);
@@ -28,9 +30,11 @@ export async function mountLibrary({document:doc=globalThis.document,window:win=
28
30
  };
29
31
  const controller=createReviewController({
30
32
  load:async(asset,signal)=>{
31
- const request=fixtureRequest(asset,new URL('./',win.location.href));
33
+ const base=new URL('./',win.location.href);
34
+ const request=asset.models?compositeRequest(asset,base):fixtureRequest(asset,base);
32
35
  el('canvas').hidden=false;
33
- return createModelInspection({canvas:el('canvas'),...request,signal,enabled:true,
36
+ const inspect=asset.models?createSceneInspection:createModelInspection;
37
+ return inspect({canvas:el('canvas'),...request,signal,enabled:true,
34
38
  ...quality(),quality:quality(),
35
39
  onProgress:p=>{if(!signal.aborted)refinement(p);},
36
40
  onHintChange:visible=>{el('hint').hidden=!visible;},
@@ -42,23 +46,27 @@ export async function mountLibrary({document:doc=globalThis.document,window:win=
42
46
  if(state.kind==='loading')status.textContent=`Loading ${state.asset.name} and checking its files…`;
43
47
  else if(state.kind==='ready') {
44
48
  status.textContent=`${state.asset.review_status==='needs-fix'?'Rendered — visual defect recorded':'Ready'} · ${state.asset.name} · ${state.runtime.triangleCount.toLocaleString()} triangles · ${state.runtime.primitiveCount} mesh primitives`;
45
- el('metrics').textContent=`${state.runtime.triangleCount.toLocaleString()} triangles · ${state.asset.inspection.materials} source materials`;
49
+ el('metrics').textContent=`${state.runtime.triangleCount.toLocaleString()} triangles · ${state.asset.inspection.materials} source materials${state.asset.models?` · ${state.asset.models.length} models + shared ground`:''}`;
46
50
  } else {status.textContent=state.message;el('canvas').hidden=true;el('refinement').textContent='Refinement unavailable';}
47
51
  },
48
52
  });
49
53
  const change=()=>{
50
- const asset=assets.find(a=>a.key===el('model').value);
54
+ const asset=selections.find(a=>a.key===el('model').value);
51
55
  el('category').textContent=asset.category;el('name').textContent=asset.name;
52
56
  el('target').textContent=asset.renderer_target;el('review').textContent=asset.observed_issue??asset.preparation;
53
- el('credits').textContent=asset.authors;el('source').href=asset.source_url;
54
- el('licence').href=asset.licence_url;el('licence').textContent=asset.licence;
57
+ el('source-line').hidden=!!asset.models;el('composite-credits').hidden=!asset.models;
58
+ el('credits').textContent=asset.models?'Sources in this study':asset.authors;
59
+ renderCompositeCredits(doc,el('composite-credits'),asset.models??[]);
60
+ if(!asset.models){el('source').href=asset.source_url;el('licence').href=asset.licence_url;el('licence').textContent=asset.licence;}
55
61
  el('metrics').textContent=asset.entry?`${(asset.files.reduce((n,f)=>n+f.bytes,0)/1048576).toFixed(1)} MiB on disk`:'';
56
62
  const clips=asset.inspection?.animations??[];
57
- el('note').textContent=clips.length?`Static pose only. ${clips.length} source clips are present: ${clips.join(', ')}. Animation playback needs importer work.`:
63
+ el('note').textContent=asset.models?`${asset.models.length} original models share one camera, ground and lighting. Geometry counts include the ground.`:
64
+ clips.length?`Static pose only. ${clips.length} source clips are present: ${clips.join(', ')}. Animation playback needs importer work.`:
58
65
  asset.inspection?.extensions.length?`Material features to inspect: ${asset.inspection.extensions.join(', ')}.`:'Source materials under the same inspection lighting.';
59
66
  return controller.select(asset);
60
67
  };
61
- el('model').value=assets.find(a=>a.entry).key;
68
+ const requested=new URL(win.location.href).searchParams.get('model');
69
+ el('model').value=selections.find(a=>a.key===requested&&(a.entry||a.models))?.key??assets.find(a=>a.entry).key;
62
70
  el('model').addEventListener('change',change);
63
71
  el('model').disabled=false;
64
72
  const qualityChange=async()=>{
@@ -0,0 +1,54 @@
1
+ import { fixtureRequest } from './controller.js';
2
+
3
+ const definitions = [
4
+ { key: 'study-furnished-interior', name: 'Furnished interior',
5
+ renderer_target: 'Compare woven fabric, silk, leather, wicker and aged wood in one arrangement.',
6
+ preparation: 'Inspect relative scale, fabric response and contact shadows. Sizes are chosen scene units, not verified physical dimensions.',
7
+ placements: [
8
+ ['SheenWoodLeatherSofa', [0, 0, -.8], 2.4, 0],
9
+ ['vintage_cabinet_01', [-2.15, 0, -.8], 1.7, 0],
10
+ ['SpecularSilkPouf', [.55, 0, .85], .7, 0],
11
+ ['wicker_basket_01', [1.6, 0, -.3], .6, 0],
12
+ ] },
13
+ { key: 'study-workshop', name: 'Workshop still life',
14
+ renderer_target: 'Compare worked metal, rough timber and worn surfaces under shared light.',
15
+ preparation: 'Inspect object contact and the contrast between brass, painted metal and timber. These are static review arrangements.',
16
+ placements: [
17
+ ['spinning_wheel_01', [-1.1, 0, -.65], 1.8, 0],
18
+ ['wine_barrel_01', [1.15, 0, -.7], 1.25, 0],
19
+ ['wooden_crate_02', [0, 0, .7], 1.2, 0],
20
+ ['bench_vice_01', [-1.3, 0, 1.05], .55, .3],
21
+ // Crate lid height from the original accessor bounds and lid translation,
22
+ // uniformly scaled to its chosen 1.2-unit longest dimension.
23
+ ['brass_blowtorch', [0, .47735, .7], .55, 0],
24
+ ] },
25
+ ];
26
+
27
+ export function compositeStudies(assets) {
28
+ return definitions.flatMap(({ placements, ...definition }) => {
29
+ const models = placements.map(([key, anchor, size, yaw]) => ({
30
+ asset: assets.find(a => a.key === key), anchor: [...anchor], size, yaw,
31
+ }));
32
+ if (models.some(model => !model.asset?.entry)) return [];
33
+ return [{ ...definition, category: 'Composite study', models,
34
+ inspection: { materials: models.reduce((sum, model) => sum + model.asset.inspection.materials, 0) } }];
35
+ });
36
+ }
37
+
38
+ export function compositeRequest(study, base) {
39
+ return { models: study.models.map(({ asset, anchor, size, yaw }) => ({
40
+ ...fixtureRequest(asset, base), anchor, size, yaw,
41
+ })) };
42
+ }
43
+
44
+ export function renderCompositeCredits(doc, container, models) {
45
+ container.replaceChildren();
46
+ for (const { asset, size } of models) {
47
+ const item = doc.createElement('li'), source = doc.createElement('a'), licence = doc.createElement('a');
48
+ source.textContent = `${asset.name} — ${asset.authors}`; source.href = asset.source_url;
49
+ licence.textContent = asset.licence; licence.href = asset.licence_url;
50
+ for (const link of [source, licence]) { link.target = '_blank'; link.rel = 'noopener'; }
51
+ item.append(source, ' · ', licence, ` · longest dimension ${size} scene units`);
52
+ container.append(item);
53
+ }
54
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plasius/gpu-demo-viewer",
3
- "version": "0.1.11",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "Root validation viewer for Plasius gpu-* demos.",
6
6
  "license": "Apache-2.0",
@@ -47,7 +47,7 @@
47
47
  "lint": "npm run typecheck",
48
48
  "pack:check": "node scripts/verify-public-package.cjs",
49
49
  "prepublishOnly": "npm run build && npm run pack:check",
50
- "typecheck": "node --check index.js && node --check main.js && node --check viewer-manifest.js && node --check showcase/index.js && node --check showcase/main.js && node --check wavefront/main.js && node --check model-library/main.js && node --check model-library/controller.js && node --check model-library/acquire.js && node --check model-library/rollout.js",
50
+ "typecheck": "node --check index.js && node --check main.js && node --check viewer-manifest.js && node --check showcase/index.js && node --check showcase/main.js && node --check wavefront/main.js && node --check model-library/main.js && node --check model-library/controller.js && node --check model-library/scenes.js && node --check model-library/acquire.js && node --check model-library/rollout.js",
51
51
  "test": "node --test",
52
52
  "models:download": "node scripts/acquire-model-library.mjs",
53
53
  "models:dev": "vite --config model-library/vite.config.js",
@@ -55,10 +55,10 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "@plasius/gpu-renderer": "^0.2.54",
58
- "@plasius/gpu-shared": "^1.1.12"
58
+ "@plasius/gpu-shared": "^1.2.0"
59
59
  },
60
60
  "devDependencies": {
61
61
  "c8": "^11.0.0",
62
- "vite": "^8.1.4"
62
+ "vite": "^8.3.5"
63
63
  }
64
64
  }