@plasius/gpu-shared 1.0.0 → 1.0.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plasius/gpu-shared",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Shared browser-safe demo runtime and asset helpers for the Plasius gpu-* package family.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -1640,40 +1640,74 @@ function buildTrianglesFromMesh(
1640
1640
  }
1641
1641
  }
1642
1642
 
1643
- async function loadShowcaseAssetCatalog() {
1644
- const [brigantine, cutter, lighthouse, harborDock, shoreline] = await Promise.all([
1643
+ function createShowcaseAssetCatalog({
1644
+ mode,
1645
+ ships,
1646
+ environment,
1647
+ primaryShipKey = "brigantine",
1648
+ fallbackReason = null,
1649
+ }) {
1650
+ return Object.freeze({
1651
+ mode,
1652
+ primaryShipKey,
1653
+ ships: Object.freeze(ships),
1654
+ environment: Object.freeze(environment),
1655
+ fallbackReason,
1656
+ });
1657
+ }
1658
+
1659
+ function normalizeAssetCatalogFailureReason(error) {
1660
+ if (typeof error?.message === "string" && error.message.trim().length > 0) {
1661
+ return error.message;
1662
+ }
1663
+
1664
+ return "showcase asset loading failed";
1665
+ }
1666
+
1667
+ async function loadShowcaseAssetCatalog({ includeSecondaryShip = true } = {}) {
1668
+ const [brigantine, lighthouse, harborDock, shoreline] = await Promise.all([
1645
1669
  loadGltfModel(resolveShowcaseAssetUrl("brigantine")),
1646
- loadGltfModel(resolveShowcaseAssetUrl("cutter")),
1647
1670
  loadGltfModel(resolveShowcaseAssetUrl("lighthouse")),
1648
1671
  loadGltfModel(resolveShowcaseAssetUrl("harbor-dock")),
1649
1672
  loadGltfModel(resolveShowcaseAssetUrl("shoreline")),
1650
1673
  ]);
1674
+ const ships = {
1675
+ brigantine,
1676
+ };
1651
1677
 
1652
- return Object.freeze({
1653
- primaryShipKey: "brigantine",
1654
- ships: Object.freeze({
1655
- brigantine,
1656
- cutter,
1657
- }),
1658
- environment: Object.freeze({
1678
+ if (includeSecondaryShip) {
1679
+ ships.cutter = await loadGltfModel(resolveShowcaseAssetUrl("cutter"));
1680
+ }
1681
+
1682
+ return createShowcaseAssetCatalog({
1683
+ mode: includeSecondaryShip ? "modeled-rich" : "modeled-baseline",
1684
+ ships,
1685
+ environment: {
1659
1686
  lighthouse,
1660
1687
  "harbor-dock": harborDock,
1661
1688
  shoreline,
1662
- }),
1689
+ },
1663
1690
  });
1664
1691
  }
1665
1692
 
1666
- function createLegacyShowcaseAssetCatalog() {
1667
- const brigantine = loadGltfModel(resolveShowcaseAssetUrl("brigantine"));
1668
- return Promise.resolve(brigantine).then((primary) =>
1669
- Object.freeze({
1670
- primaryShipKey: "brigantine",
1671
- ships: Object.freeze({
1672
- brigantine: primary,
1673
- }),
1674
- environment: Object.freeze({}),
1675
- })
1676
- );
1693
+ async function createLegacyShowcaseAssetCatalog(error = null) {
1694
+ const brigantine = await loadGltfModel(resolveShowcaseAssetUrl("brigantine"));
1695
+ return createShowcaseAssetCatalog({
1696
+ mode: "legacy-fallback",
1697
+ ships: {
1698
+ brigantine,
1699
+ },
1700
+ environment: {},
1701
+ fallbackReason: normalizeAssetCatalogFailureReason(error),
1702
+ });
1703
+ }
1704
+
1705
+ async function loadShowcaseAssetCatalogWithFallback({ includeSecondaryShip = true } = {}) {
1706
+ try {
1707
+ return await loadShowcaseAssetCatalog({ includeSecondaryShip });
1708
+ } catch (error) {
1709
+ return createLegacyShowcaseAssetCatalog(error);
1710
+ }
1677
1711
  }
1678
1712
 
1679
1713
  function resolveShipModel(state, ship, fallbackModel = null) {
@@ -1684,6 +1718,10 @@ function resolveShipModel(state, ship, fallbackModel = null) {
1684
1718
  );
1685
1719
  }
1686
1720
 
1721
+ function hasModeledHarborEnvironment(state) {
1722
+ return Object.keys(state.assetCatalog?.environment ?? {}).length > 0;
1723
+ }
1724
+
1687
1725
  function createPerformanceGovernor(performanceFeatures) {
1688
1726
  const createQualityLadderAdapter = assertRequiredFunction(
1689
1727
  performanceFeatures,
@@ -3036,7 +3074,7 @@ function renderProjectedShadow(ctx, worldPoints, camera, viewport, lightDir, opt
3036
3074
  }
3037
3075
 
3038
3076
  function pushHarborGeometry(camera, viewport, triangles, state) {
3039
- if (!state.showcaseRealisticModelsEnabled) {
3077
+ if (!hasModeledHarborEnvironment(state)) {
3040
3078
  for (const object of LEGACY_HARBOR_LAYOUT) {
3041
3079
  buildTrianglesFromMesh(
3042
3080
  { positions: [object], indices: [0], normals: null, colors: null, material: createLegacyMeshPrimitive({})?.material, bounds: null, name: "legacy-structure" },
@@ -3905,7 +3943,11 @@ function renderLighthouseBeam(ctx, state, camera, viewport, visuals) {
3905
3943
  const lighthousePlacement = SHOWCASE_ENVIRONMENT_LAYOUT.find(
3906
3944
  (placement) => placement.assetKey === "lighthouse"
3907
3945
  );
3908
- if (!lighthousePlacement || !state.showcaseRealisticModelsEnabled) {
3946
+ if (
3947
+ !lighthousePlacement ||
3948
+ !state.showcaseRealisticModelsEnabled ||
3949
+ !hasModeledHarborEnvironment(state)
3950
+ ) {
3909
3951
  return;
3910
3952
  }
3911
3953
 
@@ -4280,7 +4322,7 @@ function renderScene(
4280
4322
 
4281
4323
  const sceneMetrics = [
4282
4324
  `focus: ${state.focus}`,
4283
- `ships: ${state.ships.length} active GLTF hulls across ${new Set(state.ships.map((ship) => ship.modelKey)).size} model families`,
4325
+ `ships: ${state.ships.length} active GLTF hulls across ${new Set(state.ships.map((ship) => resolveShipModel(state, ship, shipModel)?.name ?? ship.modelKey)).size} model families`,
4284
4326
  `moonlight: cold overhead key + ${HARBOR_TORCHES.length + state.ships.reduce((total, ship) => total + (Array.isArray(ship.lanterns) ? ship.lanterns.length : 0), 0)} warm deck and harbor lights`,
4285
4327
  `physics snapshot: ${state.physics.snapshot.stage} (${state.physics.snapshot.stability})`,
4286
4328
  `physics contacts: ${state.contactCount}`,
@@ -4375,17 +4417,28 @@ function syncTextState(state, shipModel, featureAdapters) {
4375
4417
  coordinateSystem: "right-handed world; +x right, +y up, +z forward from the shore",
4376
4418
  focus: state.focus,
4377
4419
  stress: state.stress,
4378
- ships: state.ships.map((ship) => ({
4379
- id: ship.id,
4380
- modelKey: ship.modelKey ?? "brigantine",
4381
- x: Number(ship.position.x.toFixed(2)),
4382
- y: Number(ship.position.y.toFixed(2)),
4383
- z: Number(ship.position.z.toFixed(2)),
4384
- vx: Number(ship.velocity.x.toFixed(2)),
4385
- vz: Number(ship.velocity.z.toFixed(2)),
4386
- massKg: Math.round(getShipMass(ship, resolveShipModel(state, ship, shipModel))),
4387
- lanterns: Array.isArray(ship.lanterns) ? ship.lanterns.length : 0,
4388
- })),
4420
+ ships: state.ships.map((ship) => {
4421
+ const resolvedShipModel = resolveShipModel(state, ship, shipModel);
4422
+ return {
4423
+ id: ship.id,
4424
+ modelKey: ship.modelKey ?? "brigantine",
4425
+ resolvedModelKey: resolvedShipModel?.name ?? ship.modelKey ?? "brigantine",
4426
+ x: Number(ship.position.x.toFixed(2)),
4427
+ y: Number(ship.position.y.toFixed(2)),
4428
+ z: Number(ship.position.z.toFixed(2)),
4429
+ vx: Number(ship.velocity.x.toFixed(2)),
4430
+ vz: Number(ship.velocity.z.toFixed(2)),
4431
+ massKg: Math.round(getShipMass(ship, resolvedShipModel)),
4432
+ lanterns: Array.isArray(ship.lanterns) ? ship.lanterns.length : 0,
4433
+ };
4434
+ }),
4435
+ assetCatalog: {
4436
+ mode: state.assetCatalog?.mode ?? "unknown",
4437
+ shipKeys: Object.keys(state.assetCatalog?.ships ?? {}).sort(),
4438
+ environmentKeys: Object.keys(state.assetCatalog?.environment ?? {}).sort(),
4439
+ fallbackReason: state.assetCatalog?.fallbackReason ?? null,
4440
+ requestedRealisticModels: state.showcaseRealisticModelsEnabled,
4441
+ },
4389
4442
  shipPhysics: Object.fromEntries(
4390
4443
  state.ships.map((ship) => [ship.id, resolveShipModel(state, ship, shipModel)?.physics ?? null])
4391
4444
  ),
@@ -4443,9 +4496,9 @@ export async function mountGpuShowcase(options = {}, featureFlags = null) {
4443
4496
  },
4444
4497
  featureAdapters
4445
4498
  );
4446
- const assetCatalog = await (state.showcaseRealisticModelsEnabled
4447
- ? loadShowcaseAssetCatalog()
4448
- : createLegacyShowcaseAssetCatalog());
4499
+ const assetCatalog = await loadShowcaseAssetCatalogWithFallback({
4500
+ includeSecondaryShip: state.showcaseRealisticModelsEnabled,
4501
+ });
4449
4502
  const shipModel = assetCatalog.ships[assetCatalog.primaryShipKey];
4450
4503
 
4451
4504
  state.assetCatalog = assetCatalog;
@@ -11,7 +11,7 @@ export const gpuSharedEnGbTranslations = Object.freeze({
11
11
  "gpuShared.showcase.details.realistic":
12
12
  "Moonlit GLTF ships now mix a brigantine and a cutter against modeled harbor assets; cloth, fluid, and ship-local lighting stay continuous while the governor pressure is {pressureLevel}.",
13
13
  "gpuShared.showcase.details.legacy":
14
- "Moonlit GLTF ships use the legacy brigantine and placeholder harbor blocks while cloth, fluid, and ship-local lighting stay continuous while the governor pressure is {pressureLevel}.",
14
+ "Showcase fallback keeps the brigantine-only harbor path active while cloth, fluid, and ship-local lighting stay continuous under {pressureLevel} governor pressure.",
15
15
  "gpuShared.showcase.action.pause": "Pause",
16
16
  "gpuShared.showcase.action.resume": "Resume",
17
17
  "gpuShared.showcase.control.stressMode": "Stress mode",
@@ -52,4 +52,3 @@ export const gpuSharedEnGbTranslations = Object.freeze({
52
52
  "gpuShared.debug.allocation.mainColorBuffer": "Main color buffer",
53
53
  "gpuShared.debug.allocation.shadowImpressionAtlas": "Shadow impression atlas",
54
54
  });
55
-
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/feature-flags.js","../src/translations/en-GB.js","../src/i18n.js"],"sourcesContent":["export const GPU_SHOWCASE_REALISTIC_MODELS_FEATURE = \"gpu_showcase_realistic_models_v1\";\nexport const GPU_SHOWCASE_PRODUCT_STUDIO_FEATURE = \"gpu_showcase_product_studio_wavefront_v1\";\n","export const gpuSharedEnGbTranslations = Object.freeze({\n \"gpuShared.showcase.title\": \"Flag by the Sea\",\n \"gpuShared.showcase.subtitle\":\n \"Shared 3D validation scene using GLTF ships, cloth, fluid continuity, adaptive performance, and telemetry.\",\n \"gpuShared.showcase.status.booting\": \"Booting 3D scene...\",\n \"gpuShared.showcase.status.live\": \"3D scene live - {fps} FPS\",\n \"gpuShared.showcase.details.booting\":\n \"Preparing a moonlit harbor scene, GLTF hull data, cloth and fluid continuity plans, and adaptive quality metadata.\",\n \"gpuShared.showcase.details.physics\":\n \"Stable world snapshots are emitted from {snapshotStageId} after the authoritative solver; the heavier hull now carries momentum through mass-aware collision impulses while cloth and fluid remain downstream.\",\n \"gpuShared.showcase.details.realistic\":\n \"Moonlit GLTF ships now mix a brigantine and a cutter against modeled harbor assets; cloth, fluid, and ship-local lighting stay continuous while the governor pressure is {pressureLevel}.\",\n \"gpuShared.showcase.details.legacy\":\n \"Moonlit GLTF ships use the legacy brigantine and placeholder harbor blocks while cloth, fluid, and ship-local lighting stay continuous while the governor pressure is {pressureLevel}.\",\n \"gpuShared.showcase.action.pause\": \"Pause\",\n \"gpuShared.showcase.action.resume\": \"Resume\",\n \"gpuShared.showcase.control.stressMode\": \"Stress mode\",\n \"gpuShared.showcase.control.focus\": \"Focus\",\n \"gpuShared.showcase.focus.integrated\": \"integrated\",\n \"gpuShared.showcase.focus.lighting\": \"lighting\",\n \"gpuShared.showcase.focus.cloth\": \"cloth\",\n \"gpuShared.showcase.focus.fluid\": \"fluid\",\n \"gpuShared.showcase.focus.physics\": \"physics\",\n \"gpuShared.showcase.focus.performance\": \"performance\",\n \"gpuShared.showcase.focus.debug\": \"debug\",\n \"gpuShared.showcase.legend.title\": \"Scene\",\n \"gpuShared.showcase.legend.shipMetadata\":\n \"GLTF ships carry hull mass and damping metadata.\",\n \"gpuShared.showcase.legend.lighting\":\n \"Lanterns and torches warm the moonlit harbor.\",\n \"gpuShared.showcase.legend.collisions\":\n \"Mass-aware collisions stay authoritative near the camera.\",\n \"gpuShared.showcase.section.sceneState\": \"Scene State\",\n \"gpuShared.showcase.section.qualityBudgets\": \"Quality + Budgets\",\n \"gpuShared.showcase.section.debugTelemetry\": \"Debug Telemetry\",\n \"gpuShared.showcase.section.notes\": \"Notes\",\n \"gpuShared.showcase.note.assetLoading\":\n \"Ships are loaded from a GLTF asset and carry mass, damping, restitution, and hull extents from node extras.\",\n \"gpuShared.showcase.note.moonlight\":\n \"Moonlight sets the cold ambient read while deck lanterns and harbor torches provide warm local contrast.\",\n \"gpuShared.showcase.note.continuity\":\n \"Cloth and fluid continuity stay coherent across near, mid, far, and horizon bands even in the darker night palette.\",\n \"gpuShared.showcase.note.performance\":\n \"Performance pressure reduces visual detail before mass-weighted authoritative collision motion is touched.\",\n \"gpuShared.showcase.note.physicsSnapshots\":\n \"Stable world snapshots are taken after the authoritative rigid-body commit and before visual follow-up work.\",\n \"gpuShared.showcase.note.physicsCollisions\":\n \"The ships collide with mass-weighted impulses and positional correction, so the heavier hull keeps more of its line.\",\n \"gpuShared.showcase.note.physicsLighting\":\n \"Moonlight keeps the overall read legible while lanterns and torches make collision moments easy to track against the water.\",\n \"gpuShared.debug.adapter.showcase\": \"3D showcase\",\n \"gpuShared.debug.allocation.mainColorBuffer\": \"Main color buffer\",\n \"gpuShared.debug.allocation.shadowImpressionAtlas\": \"Shadow impression atlas\",\n});\n\n","import { gpuSharedEnGbTranslations } from \"./translations/en-GB.js\";\n\nexport const gpuSharedTranslationKeys = Object.freeze({\n showcaseTitle: \"gpuShared.showcase.title\",\n showcaseSubtitle: \"gpuShared.showcase.subtitle\",\n statusBooting: \"gpuShared.showcase.status.booting\",\n statusLive: \"gpuShared.showcase.status.live\",\n detailsBooting: \"gpuShared.showcase.details.booting\",\n detailsPhysics: \"gpuShared.showcase.details.physics\",\n detailsRealistic: \"gpuShared.showcase.details.realistic\",\n detailsLegacy: \"gpuShared.showcase.details.legacy\",\n pause: \"gpuShared.showcase.action.pause\",\n resume: \"gpuShared.showcase.action.resume\",\n stressMode: \"gpuShared.showcase.control.stressMode\",\n focus: \"gpuShared.showcase.control.focus\",\n focusIntegrated: \"gpuShared.showcase.focus.integrated\",\n focusLighting: \"gpuShared.showcase.focus.lighting\",\n focusCloth: \"gpuShared.showcase.focus.cloth\",\n focusFluid: \"gpuShared.showcase.focus.fluid\",\n focusPhysics: \"gpuShared.showcase.focus.physics\",\n focusPerformance: \"gpuShared.showcase.focus.performance\",\n focusDebug: \"gpuShared.showcase.focus.debug\",\n legendTitle: \"gpuShared.showcase.legend.title\",\n legendShipMetadata: \"gpuShared.showcase.legend.shipMetadata\",\n legendLighting: \"gpuShared.showcase.legend.lighting\",\n legendCollisions: \"gpuShared.showcase.legend.collisions\",\n sceneState: \"gpuShared.showcase.section.sceneState\",\n qualityBudgets: \"gpuShared.showcase.section.qualityBudgets\",\n debugTelemetry: \"gpuShared.showcase.section.debugTelemetry\",\n notes: \"gpuShared.showcase.section.notes\",\n noteAssetLoading: \"gpuShared.showcase.note.assetLoading\",\n noteMoonlight: \"gpuShared.showcase.note.moonlight\",\n noteContinuity: \"gpuShared.showcase.note.continuity\",\n notePerformance: \"gpuShared.showcase.note.performance\",\n notePhysicsSnapshots: \"gpuShared.showcase.note.physicsSnapshots\",\n notePhysicsCollisions: \"gpuShared.showcase.note.physicsCollisions\",\n notePhysicsLighting: \"gpuShared.showcase.note.physicsLighting\",\n debugAdapterShowcase: \"gpuShared.debug.adapter.showcase\",\n debugMainColorBuffer: \"gpuShared.debug.allocation.mainColorBuffer\",\n debugShadowImpressionAtlas: \"gpuShared.debug.allocation.shadowImpressionAtlas\",\n});\n\nexport const gpuSharedTranslations = Object.freeze({\n \"en-GB\": gpuSharedEnGbTranslations,\n});\n\nfunction formatTranslation(template, args = {}) {\n return template.replace(/\\{([A-Za-z0-9_]+)\\}/g, (match, name) => {\n if (!Object.prototype.hasOwnProperty.call(args, name)) {\n return match;\n }\n\n const value = args[name];\n return value == null ? \"\" : String(value);\n });\n}\n\nexport function translateGpuSharedText(key, args, translate) {\n const translated = translate?.(key, args);\n if (translated && translated !== key) {\n return translated;\n }\n\n const fallback = gpuSharedEnGbTranslations[key];\n return fallback ? formatTranslation(fallback, args) : key;\n}\n\nexport function createGpuSharedTranslator(translate) {\n return (key, args) => translateGpuSharedText(key, args, translate);\n}\n\n"],"mappings":";AAAO,IAAM,wCAAwC;AAC9C,IAAM,sCAAsC;;;ACD5C,IAAM,4BAA4B,OAAO,OAAO;AAAA,EACrD,4BAA4B;AAAA,EAC5B,+BACE;AAAA,EACF,qCAAqC;AAAA,EACrC,kCAAkC;AAAA,EAClC,sCACE;AAAA,EACF,sCACE;AAAA,EACF,wCACE;AAAA,EACF,qCACE;AAAA,EACF,mCAAmC;AAAA,EACnC,oCAAoC;AAAA,EACpC,yCAAyC;AAAA,EACzC,oCAAoC;AAAA,EACpC,uCAAuC;AAAA,EACvC,qCAAqC;AAAA,EACrC,kCAAkC;AAAA,EAClC,kCAAkC;AAAA,EAClC,oCAAoC;AAAA,EACpC,wCAAwC;AAAA,EACxC,kCAAkC;AAAA,EAClC,mCAAmC;AAAA,EACnC,0CACE;AAAA,EACF,sCACE;AAAA,EACF,wCACE;AAAA,EACF,yCAAyC;AAAA,EACzC,6CAA6C;AAAA,EAC7C,6CAA6C;AAAA,EAC7C,oCAAoC;AAAA,EACpC,wCACE;AAAA,EACF,qCACE;AAAA,EACF,sCACE;AAAA,EACF,uCACE;AAAA,EACF,4CACE;AAAA,EACF,6CACE;AAAA,EACF,2CACE;AAAA,EACF,oCAAoC;AAAA,EACpC,8CAA8C;AAAA,EAC9C,oDAAoD;AACtD,CAAC;;;ACnDM,IAAM,2BAA2B,OAAO,OAAO;AAAA,EACpD,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,OAAO;AAAA,EACP,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,sBAAsB;AAAA,EACtB,uBAAuB;AAAA,EACvB,qBAAqB;AAAA,EACrB,sBAAsB;AAAA,EACtB,sBAAsB;AAAA,EACtB,4BAA4B;AAC9B,CAAC;AAEM,IAAM,wBAAwB,OAAO,OAAO;AAAA,EACjD,SAAS;AACX,CAAC;AAED,SAAS,kBAAkB,UAAU,OAAO,CAAC,GAAG;AAC9C,SAAO,SAAS,QAAQ,wBAAwB,CAAC,OAAO,SAAS;AAC/D,QAAI,CAAC,OAAO,UAAU,eAAe,KAAK,MAAM,IAAI,GAAG;AACrD,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,KAAK,IAAI;AACvB,WAAO,SAAS,OAAO,KAAK,OAAO,KAAK;AAAA,EAC1C,CAAC;AACH;AAEO,SAAS,uBAAuB,KAAK,MAAM,WAAW;AAC3D,QAAM,aAAa,YAAY,KAAK,IAAI;AACxC,MAAI,cAAc,eAAe,KAAK;AACpC,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,0BAA0B,GAAG;AAC9C,SAAO,WAAW,kBAAkB,UAAU,IAAI,IAAI;AACxD;AAEO,SAAS,0BAA0B,WAAW;AACnD,SAAO,CAAC,KAAK,SAAS,uBAAuB,KAAK,MAAM,SAAS;AACnE;","names":[]}