@threejs-doctor/core 0.1.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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/dist/ab-compare.d.ts +25 -0
  3. package/dist/ab-compare.d.ts.map +1 -0
  4. package/dist/ab-compare.js +95 -0
  5. package/dist/ab-compare.js.map +1 -0
  6. package/dist/device-probe.d.ts +24 -0
  7. package/dist/device-probe.d.ts.map +1 -0
  8. package/dist/device-probe.js +80 -0
  9. package/dist/device-probe.js.map +1 -0
  10. package/dist/hysteresis.d.ts +26 -0
  11. package/dist/hysteresis.d.ts.map +1 -0
  12. package/dist/hysteresis.js +134 -0
  13. package/dist/hysteresis.js.map +1 -0
  14. package/dist/index.d.ts +14 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +14 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/measure-validity.d.ts +13 -0
  19. package/dist/measure-validity.d.ts.map +1 -0
  20. package/dist/measure-validity.js +13 -0
  21. package/dist/measure-validity.js.map +1 -0
  22. package/dist/metrics-collector.d.ts +23 -0
  23. package/dist/metrics-collector.d.ts.map +1 -0
  24. package/dist/metrics-collector.js +47 -0
  25. package/dist/metrics-collector.js.map +1 -0
  26. package/dist/pixel-diff.d.ts +10 -0
  27. package/dist/pixel-diff.d.ts.map +1 -0
  28. package/dist/pixel-diff.js +26 -0
  29. package/dist/pixel-diff.js.map +1 -0
  30. package/dist/quality-caps.d.ts +66 -0
  31. package/dist/quality-caps.d.ts.map +1 -0
  32. package/dist/quality-caps.js +111 -0
  33. package/dist/quality-caps.js.map +1 -0
  34. package/dist/quality-types.d.ts +31 -0
  35. package/dist/quality-types.d.ts.map +1 -0
  36. package/dist/quality-types.js +2 -0
  37. package/dist/quality-types.js.map +1 -0
  38. package/dist/scene-snapshot.d.ts +29 -0
  39. package/dist/scene-snapshot.d.ts.map +1 -0
  40. package/dist/scene-snapshot.js +29 -0
  41. package/dist/scene-snapshot.js.map +1 -0
  42. package/dist/start-tier.d.ts +18 -0
  43. package/dist/start-tier.d.ts.map +1 -0
  44. package/dist/start-tier.js +58 -0
  45. package/dist/start-tier.js.map +1 -0
  46. package/dist/stats-math.d.ts +3 -0
  47. package/dist/stats-math.d.ts.map +1 -0
  48. package/dist/stats-math.js +11 -0
  49. package/dist/stats-math.js.map +1 -0
  50. package/dist/types.d.ts +121 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +26 -0
  53. package/dist/types.js.map +1 -0
  54. package/package.json +48 -0
@@ -0,0 +1,26 @@
1
+ export function pixelChangedRatio(a, b, channelThreshold = 8) {
2
+ const len = Math.min(a.length, b.length);
3
+ if (len < 4)
4
+ return a.length === b.length ? 0 : 1;
5
+ const pixels = Math.floor(len / 4);
6
+ let changed = 0;
7
+ for (let i = 0; i < pixels; i++) {
8
+ const o = i * 4;
9
+ const dr = Math.abs(Number(a[o]) - Number(b[o]));
10
+ const dg = Math.abs(Number(a[o + 1]) - Number(b[o + 1]));
11
+ const db = Math.abs(Number(a[o + 2]) - Number(b[o + 2]));
12
+ const da = Math.abs(Number(a[o + 3]) - Number(b[o + 3]));
13
+ if (Math.max(dr, dg, db, da) > channelThreshold)
14
+ changed += 1;
15
+ }
16
+ if (a.length !== b.length)
17
+ return 1;
18
+ return changed / pixels;
19
+ }
20
+ export function classifyVisualSafety(opts) {
21
+ const maxChangedRatio = opts.maxChangedRatio ?? 0.005;
22
+ const floor = Math.max(maxChangedRatio, opts.controlChangedRatio);
23
+ const visualDelta = opts.candidateChangedRatio > floor;
24
+ return { safe: !visualDelta, visualDelta };
25
+ }
26
+ //# sourceMappingURL=pixel-diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pixel-diff.js","sourceRoot":"","sources":["../src/pixel-diff.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB,CAC/B,CAAoB,EACpB,CAAoB,EACpB,gBAAgB,GAAG,CAAC;IAEpB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAA;IACxC,IAAI,GAAG,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACjD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAA;IAClC,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACf,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAChD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;QACxD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;QACxD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;QACxD,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,gBAAgB;YAAE,OAAO,IAAI,CAAC,CAAA;IAC/D,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,CAAC,CAAA;IACnC,OAAO,OAAO,GAAG,MAAM,CAAA;AACzB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,IAIpC;IACC,MAAM,eAAe,GAAG,IAAI,CAAC,eAAe,IAAI,KAAK,CAAA;IACrD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAA;IACjE,MAAM,WAAW,GAAG,IAAI,CAAC,qBAAqB,GAAG,KAAK,CAAA;IACtD,OAAO,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,CAAA;AAC5C,CAAC"}
@@ -0,0 +1,66 @@
1
+ import type { QualityKnobSet, QualityTier } from './quality-types.js';
2
+ export declare const TIER_ORDER: readonly QualityTier[];
3
+ export declare const HYSTERESIS: {
4
+ readonly emergencyP95Ms: 50;
5
+ readonly dropP95Ms: 33.4;
6
+ readonly climbP95Ms: 22;
7
+ readonly dropWindows: 2;
8
+ readonly climbWindows: 3;
9
+ readonly cooldownWindows: 2;
10
+ readonly windowFrames: 90;
11
+ readonly ttfiBudgetMs: 3000;
12
+ readonly targetFps: 30;
13
+ };
14
+ export interface GenericTierCaps {
15
+ pixelRatio: number;
16
+ drawingBufferPixels?: number;
17
+ shadowCasters?: number;
18
+ postfxOff: boolean;
19
+ toneMapping?: 0 | 1;
20
+ anisotropy?: number;
21
+ }
22
+ export declare const GENERIC_CAPS: Record<QualityTier, GenericTierCaps>;
23
+ /** Extra generic caps applied once when potato still misses 30 FPS. */
24
+ export declare const POTATO_FLOOR_CAPS: {
25
+ readonly pixelRatio: 0.5;
26
+ readonly drawingBufferPixels: 600000;
27
+ readonly shadowCasters: 0;
28
+ readonly postfxOff: true;
29
+ };
30
+ /**
31
+ * One more generic-cap nudge when the potato floor still misses 30 FPS
32
+ * but avgFps is already in the high 20s. Do not apply meshLod/rtScale here.
33
+ */
34
+ export declare const POTATO_NEAR_MISS_CAPS: {
35
+ readonly pixelRatio: 0.4;
36
+ readonly drawingBufferPixels: 500000;
37
+ readonly shadowCasters: 0;
38
+ readonly postfxOff: true;
39
+ };
40
+ /** Inclusive lower bound for the high-20s near-miss floor nudge. */
41
+ export declare const POTATO_NEAR_MISS_MIN_AVG_FPS = 24;
42
+ /**
43
+ * Extra generic caps when the potato floor still misses and avgFps stays
44
+ * below 10. Do not apply meshLod/rtScale here.
45
+ */
46
+ export declare const POTATO_HOPELESS_CAPS: {
47
+ readonly pixelRatio: 0.35;
48
+ readonly drawingBufferPixels: 400000;
49
+ readonly shadowCasters: 0;
50
+ readonly postfxOff: true;
51
+ };
52
+ /** Exclusive upper bound: hopeless floor only when avgFps is below this. */
53
+ export declare const POTATO_HOPELESS_MAX_AVG_FPS = 10;
54
+ /**
55
+ * After the second-stage potato floor, if avgFps is still below this,
56
+ * pause spectrum and no-op every cascade update. Prefer pause over destroy.
57
+ */
58
+ export declare const POTATO_OCEAN_FREEZE_MAX_AVG_FPS = 5;
59
+ /** fftSize that no-ops `cascade.update` on every cascade (pause, do not dispose). */
60
+ export declare const POTATO_OCEAN_FREEZE_FFT_SIZE: readonly [0, 0, 0];
61
+ /** Lowest pelagic `effectQuality` when the handle exposes that knob. */
62
+ export declare const POTATO_OCEAN_FREEZE_EFFECT_QUALITY = 0;
63
+ /** `spectrumEveryNFrames` value that pauses updateSpectrum/runPass entirely. */
64
+ export declare const SPECTRUM_PAUSE_EVERY_N = 0;
65
+ export declare const ADAPTER_KNOBS: Record<QualityTier, QualityKnobSet>;
66
+ //# sourceMappingURL=quality-caps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quality-caps.d.ts","sourceRoot":"","sources":["../src/quality-caps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAErE,eAAO,MAAM,UAAU,EAAE,SAAS,WAAW,EAAqC,CAAA;AAElF,eAAO,MAAM,UAAU;;;;;;;;;;CAUb,CAAA;AAEV,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,OAAO,CAAA;IAClB,WAAW,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,WAAW,EAAE,eAAe,CA2B7D,CAAA;AAED,uEAAuE;AACvE,eAAO,MAAM,iBAAiB;;;;;CAKpB,CAAA;AAEV;;;GAGG;AACH,eAAO,MAAM,qBAAqB;;;;;CAKxB,CAAA;AAEV,oEAAoE;AACpE,eAAO,MAAM,4BAA4B,KAAK,CAAA;AAE9C;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;;CAKvB,CAAA;AAEV,4EAA4E;AAC5E,eAAO,MAAM,2BAA2B,KAAK,CAAA;AAE7C;;;GAGG;AACH,eAAO,MAAM,+BAA+B,IAAI,CAAA;AAEhD,qFAAqF;AACrF,eAAO,MAAM,4BAA4B,oBAAqB,CAAA;AAE9D,wEAAwE;AACxE,eAAO,MAAM,kCAAkC,IAAI,CAAA;AAEnD,gFAAgF;AAChF,eAAO,MAAM,sBAAsB,IAAI,CAAA;AAEvC,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,WAAW,EAAE,cAAc,CA2B7D,CAAA"}
@@ -0,0 +1,111 @@
1
+ export const TIER_ORDER = ['potato', 'low', 'mid', 'high'];
2
+ export const HYSTERESIS = {
3
+ emergencyP95Ms: 50,
4
+ dropP95Ms: 33.4,
5
+ climbP95Ms: 22,
6
+ dropWindows: 2,
7
+ climbWindows: 3,
8
+ cooldownWindows: 2,
9
+ windowFrames: 90,
10
+ ttfiBudgetMs: 3000,
11
+ targetFps: 30,
12
+ };
13
+ export const GENERIC_CAPS = {
14
+ potato: {
15
+ pixelRatio: 1.0,
16
+ drawingBufferPixels: 1.2e6,
17
+ shadowCasters: 0,
18
+ postfxOff: true,
19
+ toneMapping: 0,
20
+ anisotropy: 1,
21
+ },
22
+ low: {
23
+ pixelRatio: 1.25,
24
+ drawingBufferPixels: 2.0e6,
25
+ shadowCasters: 1,
26
+ postfxOff: true,
27
+ toneMapping: 1,
28
+ anisotropy: 1,
29
+ },
30
+ mid: {
31
+ pixelRatio: 1.5,
32
+ drawingBufferPixels: 2.7e6,
33
+ postfxOff: false,
34
+ anisotropy: 4,
35
+ },
36
+ high: {
37
+ pixelRatio: 2,
38
+ postfxOff: false,
39
+ },
40
+ };
41
+ /** Extra generic caps applied once when potato still misses 30 FPS. */
42
+ export const POTATO_FLOOR_CAPS = {
43
+ pixelRatio: 0.5,
44
+ drawingBufferPixels: 6e5,
45
+ shadowCasters: 0,
46
+ postfxOff: true,
47
+ };
48
+ /**
49
+ * One more generic-cap nudge when the potato floor still misses 30 FPS
50
+ * but avgFps is already in the high 20s. Do not apply meshLod/rtScale here.
51
+ */
52
+ export const POTATO_NEAR_MISS_CAPS = {
53
+ pixelRatio: 0.4,
54
+ drawingBufferPixels: 5e5,
55
+ shadowCasters: 0,
56
+ postfxOff: true,
57
+ };
58
+ /** Inclusive lower bound for the high-20s near-miss floor nudge. */
59
+ export const POTATO_NEAR_MISS_MIN_AVG_FPS = 24;
60
+ /**
61
+ * Extra generic caps when the potato floor still misses and avgFps stays
62
+ * below 10. Do not apply meshLod/rtScale here.
63
+ */
64
+ export const POTATO_HOPELESS_CAPS = {
65
+ pixelRatio: 0.35,
66
+ drawingBufferPixels: 4e5,
67
+ shadowCasters: 0,
68
+ postfxOff: true,
69
+ };
70
+ /** Exclusive upper bound: hopeless floor only when avgFps is below this. */
71
+ export const POTATO_HOPELESS_MAX_AVG_FPS = 10;
72
+ /**
73
+ * After the second-stage potato floor, if avgFps is still below this,
74
+ * pause spectrum and no-op every cascade update. Prefer pause over destroy.
75
+ */
76
+ export const POTATO_OCEAN_FREEZE_MAX_AVG_FPS = 5;
77
+ /** fftSize that no-ops `cascade.update` on every cascade (pause, do not dispose). */
78
+ export const POTATO_OCEAN_FREEZE_FFT_SIZE = [0, 0, 0];
79
+ /** Lowest pelagic `effectQuality` when the handle exposes that knob. */
80
+ export const POTATO_OCEAN_FREEZE_EFFECT_QUALITY = 0;
81
+ /** `spectrumEveryNFrames` value that pauses updateSpectrum/runPass entirely. */
82
+ export const SPECTRUM_PAUSE_EVERY_N = 0;
83
+ export const ADAPTER_KNOBS = {
84
+ potato: {
85
+ fftSize: [64, 0, 0],
86
+ spectrumEveryNFrames: 8,
87
+ deferredHdr: true,
88
+ },
89
+ low: {
90
+ fftSize: [128, 128, 128],
91
+ spectrumEveryNFrames: 1,
92
+ rtScale: 0.5,
93
+ meshLod: 1,
94
+ deferredHdr: true,
95
+ },
96
+ mid: {
97
+ fftSize: [128, 256, 128],
98
+ spectrumEveryNFrames: 1,
99
+ rtScale: 0.7,
100
+ meshLod: 2,
101
+ deferredHdr: false,
102
+ },
103
+ high: {
104
+ fftSize: [128, 256, 128],
105
+ spectrumEveryNFrames: 1,
106
+ rtScale: 1.0,
107
+ meshLod: 2,
108
+ deferredHdr: false,
109
+ },
110
+ };
111
+ //# sourceMappingURL=quality-caps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quality-caps.js","sourceRoot":"","sources":["../src/quality-caps.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ export type QualityTier = 'potato' | 'low' | 'mid' | 'high';
2
+ export type QualityMode = 'advise' | 'safe-auto' | 'takeover';
3
+ export type LadderPhase = 'boot' | 'runtime';
4
+ export type AdapterCapability = 'fftSize' | 'rtScale' | 'meshLod' | 'deferredHdr' | 'simPassCount';
5
+ export interface QualityKnobSet {
6
+ fftSize?: number[];
7
+ spectrumEveryNFrames?: number;
8
+ rtScale?: number;
9
+ meshLod?: 0 | 1 | 2;
10
+ deferredHdr?: boolean;
11
+ effectQuality?: number;
12
+ }
13
+ export interface AdapterExtras {
14
+ simPassCount?: number;
15
+ bytesLoaded?: number;
16
+ compileMs?: number;
17
+ }
18
+ export interface KnobHandle {
19
+ rollback(): void;
20
+ }
21
+ export interface QualityAdapter {
22
+ readonly id: string;
23
+ capabilities(): AdapterCapability[];
24
+ snapshot(): QualityKnobSet;
25
+ apply(tier: QualityTier, knobs: QualityKnobSet): KnobHandle;
26
+ readExtras?(): AdapterExtras;
27
+ takeExclusiveControl?(): {
28
+ release(): void;
29
+ };
30
+ }
31
+ //# sourceMappingURL=quality-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quality-types.d.ts","sourceRoot":"","sources":["../src/quality-types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAA;AAC3D,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,CAAA;AAC7D,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,CAAA;AAE5C,MAAM,MAAM,iBAAiB,GACzB,SAAS,GACT,SAAS,GACT,SAAS,GACT,aAAa,GACb,cAAc,CAAA;AAElB,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACnB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,IAAI,IAAI,CAAA;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,YAAY,IAAI,iBAAiB,EAAE,CAAA;IACnC,QAAQ,IAAI,cAAc,CAAA;IAC1B,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,cAAc,GAAG,UAAU,CAAA;IAC3D,UAAU,CAAC,IAAI,aAAa,CAAA;IAC5B,oBAAoB,CAAC,IAAI;QAAE,OAAO,IAAI,IAAI,CAAA;KAAE,CAAA;CAC7C"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=quality-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quality-types.js","sourceRoot":"","sources":["../src/quality-types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,29 @@
1
+ import type { SceneSnapshot } from './types.js';
2
+ export interface SnapshotTextureInput {
3
+ uuid: string;
4
+ width: number;
5
+ height: number;
6
+ bytesPerPixel: number;
7
+ }
8
+ export interface SnapshotInput {
9
+ objectCount: number;
10
+ meshCount: number;
11
+ geometries: ReadonlyArray<{
12
+ uuid: string;
13
+ }>;
14
+ materials: ReadonlyArray<{
15
+ uuid: string;
16
+ }>;
17
+ textures: ReadonlyArray<SnapshotTextureInput>;
18
+ lights: ReadonlyArray<{
19
+ castShadow: boolean;
20
+ }>;
21
+ drawCalls: number;
22
+ triangles: number;
23
+ continuousFrameloop: boolean;
24
+ matrixAutoUpdateCount: number;
25
+ rendererPixelRatio?: number | undefined;
26
+ antialias?: boolean | undefined;
27
+ }
28
+ export declare function snapshotScene(input: SnapshotInput): SceneSnapshot;
29
+ //# sourceMappingURL=scene-snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scene-snapshot.d.ts","sourceRoot":"","sources":["../src/scene-snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC3C,SAAS,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC1C,QAAQ,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAA;IAC7C,MAAM,EAAE,aAAa,CAAC;QAAE,UAAU,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IAC9C,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,mBAAmB,EAAE,OAAO,CAAA;IAC5B,qBAAqB,EAAE,MAAM,CAAA;IAC7B,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAChC;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,aAAa,CA2BjE"}
@@ -0,0 +1,29 @@
1
+ export function snapshotScene(input) {
2
+ const geometryIds = new Set(input.geometries.map((g) => g.uuid));
3
+ const materialIds = new Set(input.materials.map((m) => m.uuid));
4
+ const textureIds = new Set(input.textures.map((t) => t.uuid));
5
+ let estimatedVramBytes = 0;
6
+ let maxTextureDimension = 0;
7
+ for (const tex of input.textures) {
8
+ estimatedVramBytes += tex.width * tex.height * tex.bytesPerPixel;
9
+ maxTextureDimension = Math.max(maxTextureDimension, tex.width, tex.height);
10
+ }
11
+ return {
12
+ objectCount: input.objectCount,
13
+ meshCount: input.meshCount,
14
+ geometryCount: geometryIds.size,
15
+ materialCount: materialIds.size,
16
+ textureCount: textureIds.size,
17
+ estimatedVramBytes,
18
+ lightCount: input.lights.length,
19
+ shadowCastingLightCount: input.lights.filter((l) => l.castShadow).length,
20
+ drawCalls: input.drawCalls,
21
+ triangles: input.triangles,
22
+ maxTextureDimension,
23
+ continuousFrameloop: input.continuousFrameloop,
24
+ matrixAutoUpdateCount: input.matrixAutoUpdateCount,
25
+ rendererPixelRatio: input.rendererPixelRatio,
26
+ antialias: input.antialias,
27
+ };
28
+ }
29
+ //# sourceMappingURL=scene-snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scene-snapshot.js","sourceRoot":"","sources":["../src/scene-snapshot.ts"],"names":[],"mappings":"AAwBA,MAAM,UAAU,aAAa,CAAC,KAAoB;IAChD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IAChE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IAC/D,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IAC7D,IAAI,kBAAkB,GAAG,CAAC,CAAA;IAC1B,IAAI,mBAAmB,GAAG,CAAC,CAAA;IAC3B,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACjC,kBAAkB,IAAI,GAAG,CAAC,KAAK,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,aAAa,CAAA;QAChE,mBAAmB,GAAG,IAAI,CAAC,GAAG,CAAC,mBAAmB,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC5E,CAAC;IACD,OAAO;QACL,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,aAAa,EAAE,WAAW,CAAC,IAAI;QAC/B,aAAa,EAAE,WAAW,CAAC,IAAI;QAC/B,YAAY,EAAE,UAAU,CAAC,IAAI;QAC7B,kBAAkB;QAClB,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;QAC/B,uBAAuB,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,MAAM;QACxE,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,mBAAmB;QACnB,mBAAmB,EAAE,KAAK,CAAC,mBAAmB;QAC9C,qBAAqB,EAAE,KAAK,CAAC,qBAAqB;QAClD,kBAAkB,EAAE,KAAK,CAAC,kBAAkB;QAC5C,SAAS,EAAE,KAAK,CAAC,SAAS;KAC3B,CAAA;AACH,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { DeviceCapabilities } from './types.js';
2
+ import type { DeviceProbeInput } from './device-probe.js';
3
+ import type { QualityTier } from './quality-types.js';
4
+ export interface StartTierResult {
5
+ startTier: QualityTier;
6
+ maxTier: QualityTier;
7
+ mobile: boolean;
8
+ noFloatRt: boolean;
9
+ }
10
+ export declare function isMobileSignal(input: {
11
+ maxTouchPoints?: number;
12
+ coarsePointer?: boolean;
13
+ deviceMemory?: number;
14
+ devicePixelRatio: number;
15
+ hardwareConcurrency: number;
16
+ }): boolean;
17
+ export declare function resolveStartTier(caps: DeviceCapabilities, opts?: Partial<DeviceProbeInput>): StartTierResult;
18
+ //# sourceMappingURL=start-tier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start-tier.d.ts","sourceRoot":"","sources":["../src/start-tier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAErD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,WAAW,CAAA;IACtB,OAAO,EAAE,WAAW,CAAA;IACpB,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,OAAO,CAAA;CACnB;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE;IACpC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gBAAgB,EAAE,MAAM,CAAA;IACxB,mBAAmB,EAAE,MAAM,CAAA;CAC5B,GAAG,OAAO,CAYV;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,kBAAkB,EACxB,IAAI,GAAE,OAAO,CAAC,gBAAgB,CAAM,GACnC,eAAe,CA8CjB"}
@@ -0,0 +1,58 @@
1
+ export function isMobileSignal(input) {
2
+ if ((input.maxTouchPoints ?? 0) >= 1)
3
+ return true;
4
+ if (input.coarsePointer === true)
5
+ return true;
6
+ if (input.deviceMemory !== undefined &&
7
+ input.deviceMemory <= 8 &&
8
+ input.devicePixelRatio >= 2 &&
9
+ input.hardwareConcurrency <= 8) {
10
+ return true;
11
+ }
12
+ return false;
13
+ }
14
+ export function resolveStartTier(caps, opts = {}) {
15
+ const deviceMemory = opts.deviceMemory ?? caps.deviceMemory;
16
+ const maxTouchPoints = opts.maxTouchPoints ?? caps.maxTouchPoints;
17
+ const coarsePointer = opts.coarsePointer ?? caps.coarsePointer;
18
+ const colorBufferFloat = opts.colorBufferFloat ?? caps.colorBufferFloat;
19
+ const floatLinear = opts.floatLinear ?? caps.floatLinear;
20
+ const devicePixelRatio = opts.devicePixelRatio ?? caps.devicePixelRatio;
21
+ const hardwareConcurrency = opts.hardwareConcurrency ?? caps.hardwareConcurrency;
22
+ const webgl = opts.webgl ?? caps.webgl;
23
+ if (!webgl) {
24
+ throw new Error('webgl required for quality ladder');
25
+ }
26
+ const mobileInput = {
27
+ devicePixelRatio,
28
+ hardwareConcurrency,
29
+ };
30
+ if (maxTouchPoints !== undefined)
31
+ mobileInput.maxTouchPoints = maxTouchPoints;
32
+ if (coarsePointer !== undefined)
33
+ mobileInput.coarsePointer = coarsePointer;
34
+ if (deviceMemory !== undefined)
35
+ mobileInput.deviceMemory = deviceMemory;
36
+ const mobile = isMobileSignal(mobileInput);
37
+ if (colorBufferFloat === false) {
38
+ return { startTier: 'potato', maxTier: 'potato', mobile, noFloatRt: true };
39
+ }
40
+ if (mobile) {
41
+ const potatoClass = deviceMemory === undefined ||
42
+ deviceMemory <= 4 ||
43
+ hardwareConcurrency <= 4 ||
44
+ floatLinear === false;
45
+ if (potatoClass) {
46
+ return { startTier: 'potato', maxTier: 'mid', mobile: true, noFloatRt: false };
47
+ }
48
+ return { startTier: 'low', maxTier: 'mid', mobile: true, noFloatRt: false };
49
+ }
50
+ if (caps.tier === 'low') {
51
+ return { startTier: 'low', maxTier: 'high', mobile: false, noFloatRt: false };
52
+ }
53
+ if (caps.tier === 'mid') {
54
+ return { startTier: 'mid', maxTier: 'high', mobile: false, noFloatRt: false };
55
+ }
56
+ return { startTier: 'high', maxTier: 'high', mobile: false, noFloatRt: false };
57
+ }
58
+ //# sourceMappingURL=start-tier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start-tier.js","sourceRoot":"","sources":["../src/start-tier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** Median of a non-empty numeric series. Empty input is 0 (callers must not invent). */
2
+ export declare function median(values: readonly number[]): number;
3
+ //# sourceMappingURL=stats-math.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats-math.d.ts","sourceRoot":"","sources":["../src/stats-math.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,wBAAgB,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAMxD"}
@@ -0,0 +1,11 @@
1
+ /** Median of a non-empty numeric series. Empty input is 0 (callers must not invent). */
2
+ export function median(values) {
3
+ if (values.length === 0)
4
+ return 0;
5
+ const sorted = [...values].sort((a, b) => a - b);
6
+ const mid = Math.floor(sorted.length / 2);
7
+ if (sorted.length % 2 === 1)
8
+ return sorted[mid];
9
+ return (sorted[mid - 1] + sorted[mid]) / 2;
10
+ }
11
+ //# sourceMappingURL=stats-math.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats-math.js","sourceRoot":"","sources":["../src/stats-math.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,MAAM,UAAU,MAAM,CAAC,MAAyB;IAC9C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IACjC,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAChD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IACzC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,GAAG,CAAE,CAAA;IAChD,OAAO,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAE,GAAG,MAAM,CAAC,GAAG,CAAE,CAAC,GAAG,CAAC,CAAA;AAC9C,CAAC"}
@@ -0,0 +1,121 @@
1
+ export type Profile = 'marketing' | 'product' | 'game' | 'cad' | 'auto';
2
+ export type Mode = 'diagnose' | 'optimize' | 'benchmark';
3
+ export type DeviceTier = 'low' | 'mid' | 'high';
4
+ export type Severity = 'info' | 'warn' | 'error';
5
+ export type PassId = 'dpr-cap' | 'pixel-budget' | 'shadow-budget' | 'postfx-budget' | 'tone-map-lite' | 'anisotropy-cap' | 'frameloop-demand' | 'distance-cull' | 'material-downgrade';
6
+ export declare const SAFE_PASSES: readonly PassId[];
7
+ /** One-shot passes that are not safe by default (world-space distance-cull hides permanently). */
8
+ export declare const AGGRESSIVE_PASSES: readonly PassId[];
9
+ /** Marketing/product (static) scenes may demand-loop; games and CAD keep the host RAF. */
10
+ export declare function isStaticDemandProfile(profile: Profile): boolean;
11
+ /**
12
+ * Default Quality Ladder / Doctor `apply: ['safe']` set.
13
+ * `frameloop-demand` is omitted unless the profile is clearly marketing/product,
14
+ * or the host opts in via `optimize({ apply: ['frameloop-demand'] })`.
15
+ */
16
+ export declare function safePassesFor(profile: Profile): PassId[];
17
+ export interface DeviceCapabilities {
18
+ tier: DeviceTier;
19
+ maxTextureSize: number;
20
+ webgl: boolean;
21
+ webgpu: boolean;
22
+ devicePixelRatio: number;
23
+ hardwareConcurrency: number;
24
+ deviceMemory?: number;
25
+ maxTouchPoints?: number;
26
+ coarsePointer?: boolean;
27
+ prefersReducedData?: boolean;
28
+ colorBufferFloat?: boolean;
29
+ floatLinear?: boolean;
30
+ maxRenderbufferSize?: number;
31
+ }
32
+ export interface MetricsSample {
33
+ avgFps: number;
34
+ p95FrameTimeMs: number;
35
+ drawCalls: number;
36
+ /** Drawn triangles from renderer.info.render.triangles (never invented). */
37
+ triangles: number;
38
+ textureCount: number;
39
+ estimatedVramBytes?: number | undefined;
40
+ geometryCount: number;
41
+ lightCount: number;
42
+ shadowCastingLightCount: number;
43
+ simPassCount?: number;
44
+ bytesLoaded?: number;
45
+ compileMs?: number;
46
+ drawingBufferPixels?: number;
47
+ /** GPU elapsed ms from EXT_disjoint_timer_query_webgl2 when a result is actually available. */
48
+ gpuFrameTimeMs?: number | undefined;
49
+ /**
50
+ * Sampler existed but this measure produced no GPU times (tight loop without a
51
+ * macrotask, leftovers discarded, or QUERY_RESULT never became available).
52
+ * Never invent `gpuFrameTimeMs` when this is set.
53
+ */
54
+ gpuTimingSkipped?: boolean | undefined;
55
+ /** Live run was hidden or rAF-throttled; do not treat this sample as a score win. */
56
+ invalid?: boolean | undefined;
57
+ invalidReason?: string | undefined;
58
+ }
59
+ export interface SceneSnapshot {
60
+ objectCount: number;
61
+ meshCount: number;
62
+ geometryCount: number;
63
+ materialCount: number;
64
+ textureCount: number;
65
+ estimatedVramBytes: number;
66
+ lightCount: number;
67
+ shadowCastingLightCount: number;
68
+ drawCalls: number;
69
+ triangles: number;
70
+ maxTextureDimension: number;
71
+ continuousFrameloop: boolean;
72
+ matrixAutoUpdateCount: number;
73
+ /** Finite renderer DPR when known; omitted/undefined means do not flag uncapped-dpr. */
74
+ rendererPixelRatio?: number | undefined;
75
+ /** MSAA flag from GL context attributes when known. */
76
+ antialias?: boolean | undefined;
77
+ /**
78
+ * Scene-graph triangle attribution: indexed/non-indexed geometry × InstancedMesh.count.
79
+ * Used to name top contributors. Cost / score / triangles/too-many use `triangles` (drawn).
80
+ * Omitted when no mesh geometry could be counted.
81
+ */
82
+ geometryTriangleCount?: number | undefined;
83
+ /** Top contributor as `name:triangles` (first of top-N). Omitted when none counted. */
84
+ triangleContributorSummary?: string | undefined;
85
+ topContributorShare?: number | undefined;
86
+ frustumCulledDisabledCount?: number | undefined;
87
+ /**
88
+ * Meshes whose world bounding-sphere radius is greater than `camera.far`.
89
+ * InstancedMesh uses computeBoundingSphere() (instance-aware), not the prototype sphere.
90
+ * Omitted when camera.far or a bounding sphere cannot be read.
91
+ */
92
+ oversizedBoundCount?: number | undefined;
93
+ shadowTriangleCount?: number | undefined;
94
+ /** Casters fully outside every detectable shadow camera. Omitted when no shadow camera is testable. */
95
+ shadowCastersOutsideFrustum?: number | undefined;
96
+ zeroIntensityLightCount?: number | undefined;
97
+ instancedBufferBytes?: number | undefined;
98
+ /** InstancedMesh removed from the graph without dispose(). */
99
+ removedUndisposedInstancedCount?: number | undefined;
100
+ composerPixelRatio?: number | undefined;
101
+ composerWidth?: number | undefined;
102
+ composerHeight?: number | undefined;
103
+ drawingBufferWidth?: number | undefined;
104
+ drawingBufferHeight?: number | undefined;
105
+ /** Copied from a measured sample only when EXT_disjoint_timer_query_webgl2 returned a result. */
106
+ gpuFrameTimeMs?: number | undefined;
107
+ }
108
+ export interface RendererInfoLike {
109
+ render: {
110
+ calls: number;
111
+ triangles: number;
112
+ points?: number | undefined;
113
+ };
114
+ memory: {
115
+ geometries: number;
116
+ textures: number;
117
+ };
118
+ autoReset?: boolean | undefined;
119
+ reset?: () => void;
120
+ }
121
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,26 @@
1
+ export const SAFE_PASSES = [
2
+ 'dpr-cap',
3
+ 'pixel-budget',
4
+ 'shadow-budget',
5
+ 'postfx-budget',
6
+ 'tone-map-lite',
7
+ 'anisotropy-cap',
8
+ ];
9
+ /** One-shot passes that are not safe by default (world-space distance-cull hides permanently). */
10
+ export const AGGRESSIVE_PASSES = ['distance-cull'];
11
+ /** Marketing/product (static) scenes may demand-loop; games and CAD keep the host RAF. */
12
+ export function isStaticDemandProfile(profile) {
13
+ return profile === 'marketing' || profile === 'product';
14
+ }
15
+ /**
16
+ * Default Quality Ladder / Doctor `apply: ['safe']` set.
17
+ * `frameloop-demand` is omitted unless the profile is clearly marketing/product,
18
+ * or the host opts in via `optimize({ apply: ['frameloop-demand'] })`.
19
+ */
20
+ export function safePassesFor(profile) {
21
+ if (isStaticDemandProfile(profile)) {
22
+ return [...SAFE_PASSES, 'frameloop-demand'];
23
+ }
24
+ return [...SAFE_PASSES];
25
+ }
26
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAeA,MAAM,CAAC,MAAM,WAAW,GAAsB;IAC5C,SAAS;IACT,cAAc;IACd,eAAe;IACf,eAAe;IACf,eAAe;IACf,gBAAgB;CACR,CAAA;AAEV,kGAAkG;AAClG,MAAM,CAAC,MAAM,iBAAiB,GAAsB,CAAC,eAAe,CAAU,CAAA;AAE9E,0FAA0F;AAC1F,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,KAAK,SAAS,CAAA;AACzD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,IAAI,qBAAqB,CAAC,OAAO,CAAC,EAAE,CAAC;QACnC,OAAO,CAAC,GAAG,WAAW,EAAE,kBAAkB,CAAC,CAAA;IAC7C,CAAC;IACD,OAAO,CAAC,GAAG,WAAW,CAAC,CAAA;AACzB,CAAC"}