create-pixi-vn 2.1.3 → 2.1.5

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 (127) hide show
  1. package/package.json +1 -1
  2. package/template-lib/package.json +3 -0
  3. package/template-nqtr-react-vite-muijoy/.assetpack.ts +82 -2
  4. package/template-nqtr-react-vite-muijoy/package.json +2 -2
  5. package/template-nqtr-react-vite-muijoy/src/assets/index.ts +2 -11
  6. package/template-nqtr-react-vite-muijoy/src/assets/manifest.gen.json +2 -7
  7. package/template-nqtr-react-vite-muijoy/src/components/menus/settings/index.tsx +15 -6
  8. package/template-nqtr-react-vite-muijoy/src/components/menus/settings/menus/diagnostics.tsx +266 -0
  9. package/template-nqtr-react-vite-muijoy/src/components/menus/settings/quick-menus.tsx +25 -1
  10. package/template-nqtr-react-vite-muijoy/src/lib/hooks/device-diagnostics-hooks.ts +68 -0
  11. package/template-nqtr-react-vite-muijoy/src/lib/hooks/hotkeys-hooks.ts +16 -0
  12. package/template-nqtr-react-vite-muijoy/src/lib/utils/device-diagnostics-utility.ts +382 -0
  13. package/template-nqtr-react-vite-muijoy/src/locales/en.json +50 -0
  14. package/template-nqtr-react-vite-muijoy/src/pixi-vn.keys.gen.ts +4 -7
  15. package/template-nqtr-react-vite-muijoy-ink/.assetpack.ts +82 -2
  16. package/template-nqtr-react-vite-muijoy-ink/package.json +3 -3
  17. package/template-nqtr-react-vite-muijoy-ink/src/assets/index.ts +2 -11
  18. package/template-nqtr-react-vite-muijoy-ink/src/assets/manifest.gen.json +2 -7
  19. package/template-nqtr-react-vite-muijoy-ink/src/components/menus/settings/index.tsx +15 -6
  20. package/template-nqtr-react-vite-muijoy-ink/src/components/menus/settings/menus/diagnostics.tsx +266 -0
  21. package/template-nqtr-react-vite-muijoy-ink/src/components/menus/settings/quick-menus.tsx +25 -1
  22. package/template-nqtr-react-vite-muijoy-ink/src/lib/hooks/device-diagnostics-hooks.ts +68 -0
  23. package/template-nqtr-react-vite-muijoy-ink/src/lib/hooks/hotkeys-hooks.ts +16 -0
  24. package/template-nqtr-react-vite-muijoy-ink/src/lib/utils/device-diagnostics-utility.ts +382 -0
  25. package/template-nqtr-react-vite-muijoy-ink/src/locales/en.json +50 -0
  26. package/template-nqtr-react-vite-muijoy-ink/src/pixi-vn.keys.gen.ts +4 -7
  27. package/template-nqtr-react-vite-muijoy-ink-tauri/.assetpack.ts +82 -2
  28. package/template-nqtr-react-vite-muijoy-ink-tauri/.vscode/launch.json +1 -1
  29. package/template-nqtr-react-vite-muijoy-ink-tauri/.vscode/tasks.json +1 -35
  30. package/template-nqtr-react-vite-muijoy-ink-tauri/package.json +3 -3
  31. package/template-nqtr-react-vite-muijoy-ink-tauri/src/assets/index.ts +2 -11
  32. package/template-nqtr-react-vite-muijoy-ink-tauri/src/assets/manifest.gen.json +2 -7
  33. package/template-nqtr-react-vite-muijoy-ink-tauri/src/components/menus/settings/index.tsx +15 -6
  34. package/template-nqtr-react-vite-muijoy-ink-tauri/src/components/menus/settings/menus/diagnostics.tsx +332 -0
  35. package/template-nqtr-react-vite-muijoy-ink-tauri/src/components/menus/settings/quick-menus.tsx +32 -1
  36. package/template-nqtr-react-vite-muijoy-ink-tauri/src/content/ink/hashtag-commands.ts +17 -0
  37. package/template-nqtr-react-vite-muijoy-ink-tauri/src/lib/hooks/device-diagnostics-hooks.ts +96 -0
  38. package/template-nqtr-react-vite-muijoy-ink-tauri/src/lib/hooks/hotkeys-hooks.ts +16 -0
  39. package/template-nqtr-react-vite-muijoy-ink-tauri/src/lib/system-info.ts +54 -0
  40. package/template-nqtr-react-vite-muijoy-ink-tauri/src/lib/utils/device-diagnostics-utility.ts +511 -0
  41. package/template-nqtr-react-vite-muijoy-ink-tauri/src/locales/en.json +66 -0
  42. package/template-nqtr-react-vite-muijoy-ink-tauri/src/pixi-vn.keys.gen.ts +4 -7
  43. package/template-nqtr-react-vite-muijoy-ink-tauri/src-tauri/Cargo.toml +1 -0
  44. package/template-nqtr-react-vite-muijoy-ink-tauri/src-tauri/src/lib.rs +34 -22
  45. package/template-nqtr-react-vite-muijoy-ink-tauri/src-tauri/src/system_info.rs +33 -0
  46. package/template-nqtr-react-vite-muijoy-tauri/.assetpack.ts +82 -2
  47. package/template-nqtr-react-vite-muijoy-tauri/.vscode/launch.json +1 -1
  48. package/template-nqtr-react-vite-muijoy-tauri/.vscode/tasks.json +1 -35
  49. package/template-nqtr-react-vite-muijoy-tauri/package.json +2 -2
  50. package/template-nqtr-react-vite-muijoy-tauri/src/assets/index.ts +2 -11
  51. package/template-nqtr-react-vite-muijoy-tauri/src/assets/manifest.gen.json +2 -7
  52. package/template-nqtr-react-vite-muijoy-tauri/src/components/menus/settings/index.tsx +15 -6
  53. package/template-nqtr-react-vite-muijoy-tauri/src/components/menus/settings/menus/diagnostics.tsx +332 -0
  54. package/template-nqtr-react-vite-muijoy-tauri/src/components/menus/settings/quick-menus.tsx +32 -1
  55. package/template-nqtr-react-vite-muijoy-tauri/src/lib/hooks/device-diagnostics-hooks.ts +96 -0
  56. package/template-nqtr-react-vite-muijoy-tauri/src/lib/hooks/hotkeys-hooks.ts +16 -0
  57. package/template-nqtr-react-vite-muijoy-tauri/src/lib/system-info.ts +54 -0
  58. package/template-nqtr-react-vite-muijoy-tauri/src/lib/utils/device-diagnostics-utility.ts +511 -0
  59. package/template-nqtr-react-vite-muijoy-tauri/src/locales/en.json +66 -0
  60. package/template-nqtr-react-vite-muijoy-tauri/src/pixi-vn.keys.gen.ts +4 -7
  61. package/template-nqtr-react-vite-muijoy-tauri/src-tauri/Cargo.toml +1 -0
  62. package/template-nqtr-react-vite-muijoy-tauri/src-tauri/src/lib.rs +32 -20
  63. package/template-nqtr-react-vite-muijoy-tauri/src-tauri/src/system_info.rs +33 -0
  64. package/template-react-vite-muijoy/.assetpack.ts +82 -2
  65. package/template-react-vite-muijoy/package.json +1 -1
  66. package/template-react-vite-muijoy/src/assets/index.ts +55 -55
  67. package/template-react-vite-muijoy/src/assets/manifest.gen.json +2 -7
  68. package/template-react-vite-muijoy/src/components/menus/settings/index.tsx +15 -6
  69. package/template-react-vite-muijoy/src/components/menus/settings/menus/diagnostics.tsx +266 -0
  70. package/template-react-vite-muijoy/src/components/menus/settings/quick-menus.tsx +25 -1
  71. package/template-react-vite-muijoy/src/lib/hooks/device-diagnostics-hooks.ts +68 -0
  72. package/template-react-vite-muijoy/src/lib/hooks/hotkeys-hooks.ts +16 -0
  73. package/template-react-vite-muijoy/src/lib/utils/device-diagnostics-utility.ts +382 -0
  74. package/template-react-vite-muijoy/src/locales/en.json +50 -0
  75. package/template-react-vite-muijoy/src/pixi-vn.keys.gen.ts +2 -3
  76. package/template-react-vite-muijoy-ink/.assetpack.ts +82 -2
  77. package/template-react-vite-muijoy-ink/ink/second_part.ink +0 -1
  78. package/template-react-vite-muijoy-ink/ink/start.ink +4 -6
  79. package/template-react-vite-muijoy-ink/package.json +2 -2
  80. package/template-react-vite-muijoy-ink/src/assets/index.ts +55 -55
  81. package/template-react-vite-muijoy-ink/src/assets/manifest.gen.json +2 -7
  82. package/template-react-vite-muijoy-ink/src/components/menus/settings/index.tsx +15 -6
  83. package/template-react-vite-muijoy-ink/src/components/menus/settings/menus/diagnostics.tsx +266 -0
  84. package/template-react-vite-muijoy-ink/src/components/menus/settings/quick-menus.tsx +25 -1
  85. package/template-react-vite-muijoy-ink/src/lib/hooks/device-diagnostics-hooks.ts +68 -0
  86. package/template-react-vite-muijoy-ink/src/lib/hooks/hotkeys-hooks.ts +16 -0
  87. package/template-react-vite-muijoy-ink/src/lib/utils/device-diagnostics-utility.ts +382 -0
  88. package/template-react-vite-muijoy-ink/src/locales/en.json +50 -0
  89. package/template-react-vite-muijoy-ink/src/pixi-vn.keys.gen.ts +2 -3
  90. package/template-react-vite-muijoy-ink-tauri/.assetpack.ts +82 -2
  91. package/template-react-vite-muijoy-ink-tauri/.vscode/launch.json +1 -1
  92. package/template-react-vite-muijoy-ink-tauri/.vscode/tasks.json +1 -35
  93. package/template-react-vite-muijoy-ink-tauri/ink/second_part.ink +0 -1
  94. package/template-react-vite-muijoy-ink-tauri/ink/start.ink +4 -6
  95. package/template-react-vite-muijoy-ink-tauri/package.json +2 -2
  96. package/template-react-vite-muijoy-ink-tauri/src/assets/index.ts +55 -55
  97. package/template-react-vite-muijoy-ink-tauri/src/assets/manifest.gen.json +2 -7
  98. package/template-react-vite-muijoy-ink-tauri/src/components/menus/settings/index.tsx +15 -6
  99. package/template-react-vite-muijoy-ink-tauri/src/components/menus/settings/menus/diagnostics.tsx +332 -0
  100. package/template-react-vite-muijoy-ink-tauri/src/components/menus/settings/quick-menus.tsx +32 -1
  101. package/template-react-vite-muijoy-ink-tauri/src/lib/hooks/device-diagnostics-hooks.ts +96 -0
  102. package/template-react-vite-muijoy-ink-tauri/src/lib/hooks/hotkeys-hooks.ts +16 -0
  103. package/template-react-vite-muijoy-ink-tauri/src/lib/system-info.ts +54 -0
  104. package/template-react-vite-muijoy-ink-tauri/src/lib/utils/device-diagnostics-utility.ts +511 -0
  105. package/template-react-vite-muijoy-ink-tauri/src/locales/en.json +66 -0
  106. package/template-react-vite-muijoy-ink-tauri/src/pixi-vn.keys.gen.ts +2 -3
  107. package/template-react-vite-muijoy-ink-tauri/src-tauri/Cargo.toml +1 -0
  108. package/template-react-vite-muijoy-ink-tauri/src-tauri/src/lib.rs +32 -20
  109. package/template-react-vite-muijoy-ink-tauri/src-tauri/src/system_info.rs +33 -0
  110. package/template-react-vite-muijoy-tauri/.assetpack.ts +82 -2
  111. package/template-react-vite-muijoy-tauri/.vscode/launch.json +1 -1
  112. package/template-react-vite-muijoy-tauri/.vscode/tasks.json +1 -35
  113. package/template-react-vite-muijoy-tauri/package.json +1 -1
  114. package/template-react-vite-muijoy-tauri/src/assets/index.ts +55 -55
  115. package/template-react-vite-muijoy-tauri/src/assets/manifest.gen.json +2 -7
  116. package/template-react-vite-muijoy-tauri/src/components/menus/settings/index.tsx +15 -6
  117. package/template-react-vite-muijoy-tauri/src/components/menus/settings/menus/diagnostics.tsx +332 -0
  118. package/template-react-vite-muijoy-tauri/src/components/menus/settings/quick-menus.tsx +32 -1
  119. package/template-react-vite-muijoy-tauri/src/lib/hooks/device-diagnostics-hooks.ts +96 -0
  120. package/template-react-vite-muijoy-tauri/src/lib/hooks/hotkeys-hooks.ts +16 -0
  121. package/template-react-vite-muijoy-tauri/src/lib/system-info.ts +54 -0
  122. package/template-react-vite-muijoy-tauri/src/lib/utils/device-diagnostics-utility.ts +511 -0
  123. package/template-react-vite-muijoy-tauri/src/locales/en.json +66 -0
  124. package/template-react-vite-muijoy-tauri/src/pixi-vn.keys.gen.ts +2 -3
  125. package/template-react-vite-muijoy-tauri/src-tauri/Cargo.toml +1 -0
  126. package/template-react-vite-muijoy-tauri/src-tauri/src/lib.rs +32 -20
  127. package/template-react-vite-muijoy-tauri/src-tauri/src/system_info.rs +33 -0
@@ -0,0 +1,511 @@
1
+ import packageJson from "@/../package.json";
2
+ import { canvas, PIXIVN_VERSION } from "@drincs/pixi-vn";
3
+ import motionPackageJson from "motion/package.json";
4
+ import { VERSION as PIXIJS_VERSION } from "pixi.js";
5
+ import tonePackageJson from "tone/package.json";
6
+
7
+ export interface WebglExtensionCheck {
8
+ name: string;
9
+ supported: boolean;
10
+ }
11
+
12
+ export interface WebglDiagnostics {
13
+ webgl1Supported: boolean;
14
+ webgl2Supported: boolean;
15
+ vendor: string | null;
16
+ renderer: string | null;
17
+ unmaskedVendor: string | null;
18
+ unmaskedRenderer: string | null;
19
+ /** null when it cannot be determined (e.g. WEBGL_debug_renderer_info is blocked). */
20
+ hardwareAccelerated: boolean | null;
21
+ /** Coarse GPU brand bucket (NVIDIA/AMD/Intel/Apple/Qualcomm/ARM/Imagination/Software/Unknown), including mobile GPU families. */
22
+ gpuVendorBrand: string;
23
+ /** Mesa version parsed out of the renderer string, when running on a Linux Mesa driver stack. */
24
+ mesaVersion: string | null;
25
+ maxTextureSize: number | null;
26
+ floatTexturesSupported: boolean;
27
+ msaaSupported: boolean;
28
+ shadersCompile: boolean;
29
+ keyExtensions: WebglExtensionCheck[];
30
+ allExtensions: string[];
31
+ }
32
+
33
+ export interface MobileDiagnostics {
34
+ touchSupported: boolean;
35
+ maxTouchPoints: number;
36
+ /** Approximate device RAM in GB. Chromium-only; null elsewhere. */
37
+ deviceMemoryGb: number | null;
38
+ /** Coarse connection quality bucket (e.g. "4g", "3g"). Chromium-only; null elsewhere. */
39
+ networkEffectiveType: string | null;
40
+ /** e.g. "portrait-primary" / "landscape-primary". */
41
+ screenOrientation: string | null;
42
+ }
43
+
44
+ /**
45
+ * OS/distro and WebView engine details from the native Tauri backend.
46
+ * `null` when running in a plain browser (browsers deliberately don't expose this).
47
+ */
48
+ export interface NativeDiagnostics {
49
+ osType: string;
50
+ osVersion: string;
51
+ bitness: string;
52
+ architecture: string;
53
+ webviewVersion: string | null;
54
+ }
55
+
56
+ export interface BrowserCapabilities {
57
+ audio: boolean;
58
+ gamepad: boolean;
59
+ clipboard: boolean;
60
+ fullscreen: boolean;
61
+ localStorage: boolean;
62
+ indexedDb: boolean;
63
+ /** Best-effort heuristic only: Steam's overlay is compositor-level and generally invisible to page JS. */
64
+ steamOverlayDetected: boolean;
65
+ }
66
+
67
+ export interface ResolutionInfo {
68
+ innerWidth: number;
69
+ innerHeight: number;
70
+ screenWidth: number;
71
+ screenHeight: number;
72
+ devicePixelRatio: number;
73
+ }
74
+
75
+ export interface JsMemoryInfo {
76
+ usedJSHeapSize: number;
77
+ totalJSHeapSize: number;
78
+ jsHeapSizeLimit: number;
79
+ }
80
+
81
+ const KEY_WEBGL_EXTENSIONS = [
82
+ "WEBGL_debug_renderer_info",
83
+ "WEBGL_lose_context",
84
+ "WEBGL_compressed_texture_s3tc",
85
+ "WEBGL_compressed_texture_astc",
86
+ "WEBGL_compressed_texture_etc",
87
+ "OES_texture_float",
88
+ "OES_texture_float_linear",
89
+ "OES_texture_half_float",
90
+ "OES_element_index_uint",
91
+ "OES_standard_derivatives",
92
+ "ANGLE_instanced_arrays",
93
+ "EXT_texture_filter_anisotropic",
94
+ "EXT_color_buffer_float",
95
+ ];
96
+
97
+ const SOFTWARE_RENDERER_PATTERN =
98
+ /swiftshader|llvmpipe|software|microsoft basic render|d3d11 warp|apple software renderer/i;
99
+
100
+ /**
101
+ * Coarse GPU brand bucket from the (unmasked) vendor/renderer strings.
102
+ * Covers mobile GPU families (Adreno/Qualcomm, Mali/ARM, PowerVR/Imagination) too.
103
+ */
104
+ function classifyGpuVendorBrand(vendorAndRenderer: string): string {
105
+ if (SOFTWARE_RENDERER_PATTERN.test(vendorAndRenderer)) return "Software (no GPU)";
106
+ if (/nvidia|geforce|quadro|rtx\s?\d|gtx\s?\d/i.test(vendorAndRenderer)) return "NVIDIA";
107
+ if (/amd|ati\b|radeon/i.test(vendorAndRenderer)) return "AMD";
108
+ if (/intel/i.test(vendorAndRenderer)) return "Intel";
109
+ if (/apple/i.test(vendorAndRenderer)) return "Apple";
110
+ if (/qualcomm|adreno/i.test(vendorAndRenderer)) return "Qualcomm (Adreno)";
111
+ if (/arm\b|mali/i.test(vendorAndRenderer)) return "ARM (Mali)";
112
+ if (/imagination|powervr/i.test(vendorAndRenderer)) return "Imagination (PowerVR)";
113
+ return "Unknown";
114
+ }
115
+
116
+ /** Extracts the Mesa driver version from a Linux OpenGL/ANGLE renderer string, when present. */
117
+ function extractMesaVersion(rendererString: string): string | null {
118
+ return /Mesa\s+([\d.]+)/i.exec(rendererString)?.[1] ?? null;
119
+ }
120
+
121
+ function testShaderCompilation(gl: WebGLRenderingContext | WebGL2RenderingContext): boolean {
122
+ try {
123
+ const vertexShader = gl.createShader(gl.VERTEX_SHADER);
124
+ const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
125
+ if (!vertexShader || !fragmentShader) {
126
+ return false;
127
+ }
128
+
129
+ gl.shaderSource(
130
+ vertexShader,
131
+ "attribute vec2 p; void main() { gl_Position = vec4(p, 0.0, 1.0); }",
132
+ );
133
+ gl.compileShader(vertexShader);
134
+ gl.shaderSource(
135
+ fragmentShader,
136
+ "precision mediump float; void main() { gl_FragColor = vec4(1.0); }",
137
+ );
138
+ gl.compileShader(fragmentShader);
139
+ if (
140
+ !gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS) ||
141
+ !gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)
142
+ ) {
143
+ return false;
144
+ }
145
+
146
+ const program = gl.createProgram();
147
+ if (!program) {
148
+ return false;
149
+ }
150
+ gl.attachShader(program, vertexShader);
151
+ gl.attachShader(program, fragmentShader);
152
+ gl.linkProgram(program);
153
+ return !!gl.getProgramParameter(program, gl.LINK_STATUS);
154
+ } catch {
155
+ return false;
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Probes a throwaway canvas instead of the live Pixi-VN canvas so this works
161
+ * regardless of which renderer Pixi actually picked (or before Pixi has initialized).
162
+ */
163
+ export function getWebglDiagnostics(): WebglDiagnostics {
164
+ const probeCanvas = document.createElement("canvas");
165
+ const gl2 = probeCanvas.getContext("webgl2") as WebGL2RenderingContext | null;
166
+ const gl1 = (probeCanvas.getContext("webgl") ??
167
+ probeCanvas.getContext("experimental-webgl")) as WebGLRenderingContext | null;
168
+ const gl = gl2 ?? gl1;
169
+
170
+ if (!gl) {
171
+ return {
172
+ webgl1Supported: false,
173
+ webgl2Supported: false,
174
+ vendor: null,
175
+ renderer: null,
176
+ unmaskedVendor: null,
177
+ unmaskedRenderer: null,
178
+ hardwareAccelerated: null,
179
+ gpuVendorBrand: "Unknown",
180
+ mesaVersion: null,
181
+ maxTextureSize: null,
182
+ floatTexturesSupported: false,
183
+ msaaSupported: false,
184
+ shadersCompile: false,
185
+ keyExtensions: KEY_WEBGL_EXTENSIONS.map((name) => ({ name, supported: false })),
186
+ allExtensions: [],
187
+ };
188
+ }
189
+
190
+ const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
191
+ const vendor = (gl.getParameter(gl.VENDOR) as string) ?? null;
192
+ const renderer = (gl.getParameter(gl.RENDERER) as string) ?? null;
193
+ const unmaskedVendor = debugInfo
194
+ ? ((gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) as string) ?? null)
195
+ : null;
196
+ const unmaskedRenderer = debugInfo
197
+ ? ((gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) as string) ?? null)
198
+ : null;
199
+ const rendererForHeuristic = unmaskedRenderer ?? renderer;
200
+ const hardwareAccelerated = rendererForHeuristic
201
+ ? !SOFTWARE_RENDERER_PATTERN.test(rendererForHeuristic)
202
+ : null;
203
+ const gpuVendorBrand = classifyGpuVendorBrand(
204
+ `${unmaskedVendor ?? vendor ?? ""} ${rendererForHeuristic ?? ""}`,
205
+ );
206
+ const mesaVersion = rendererForHeuristic ? extractMesaVersion(rendererForHeuristic) : null;
207
+
208
+ const allExtensions = gl.getSupportedExtensions() ?? [];
209
+ const keyExtensions = KEY_WEBGL_EXTENSIONS.map((name) => ({
210
+ name,
211
+ supported: allExtensions.includes(name),
212
+ }));
213
+
214
+ const maxTextureSize = (gl.getParameter(gl.MAX_TEXTURE_SIZE) as number) ?? null;
215
+ const floatTexturesSupported = gl2 ? true : allExtensions.includes("OES_texture_float");
216
+ const msaaSupported = gl2
217
+ ? ((gl2.getParameter(gl2.MAX_SAMPLES) as number) ?? 0) > 1
218
+ : (gl.getContextAttributes()?.antialias ?? false);
219
+ const shadersCompile = testShaderCompilation(gl);
220
+
221
+ // This is a throwaway probe context; release it instead of leaving it around.
222
+ gl.getExtension("WEBGL_lose_context")?.loseContext();
223
+
224
+ return {
225
+ webgl1Supported: !!gl1 || !!gl2,
226
+ webgl2Supported: !!gl2,
227
+ vendor,
228
+ renderer,
229
+ unmaskedVendor,
230
+ unmaskedRenderer,
231
+ hardwareAccelerated,
232
+ gpuVendorBrand,
233
+ mesaVersion,
234
+ maxTextureSize,
235
+ floatTexturesSupported,
236
+ msaaSupported,
237
+ shadersCompile,
238
+ keyExtensions,
239
+ allExtensions,
240
+ };
241
+ }
242
+
243
+ interface UserAgentDataBrand {
244
+ brand: string;
245
+ version: string;
246
+ }
247
+
248
+ interface NavigatorUAData {
249
+ platform?: string;
250
+ brands?: UserAgentDataBrand[];
251
+ }
252
+
253
+ function getUserAgentData(): NavigatorUAData | undefined {
254
+ return (navigator as Navigator & { userAgentData?: NavigatorUAData }).userAgentData;
255
+ }
256
+
257
+ export function detectPlatform(): string {
258
+ const ua = navigator.userAgent;
259
+ const platformHint = getUserAgentData()?.platform ?? navigator.platform ?? "";
260
+
261
+ if (/android/i.test(ua)) return "Android";
262
+ if (/iphone|ipod/i.test(ua)) return "iOS";
263
+ // iPadOS reports as "MacIntel" but exposes multi-touch, unlike a real Mac.
264
+ if (/ipad/i.test(ua) || (/mac/i.test(platformHint) && navigator.maxTouchPoints > 1))
265
+ return "iOS";
266
+ if (/mac/i.test(platformHint)) return "macOS";
267
+ if (/win/i.test(platformHint)) return "Windows";
268
+ if (/linux/i.test(platformHint)) return "Linux";
269
+ return "Unknown";
270
+ }
271
+
272
+ export function detectBrowserEngine(): string {
273
+ const ua = navigator.userAgent;
274
+ const brands = getUserAgentData()?.brands ?? [];
275
+
276
+ // User-Agent Client Hints: WebView2 (Windows) reliably advertises this brand.
277
+ if (brands.some((brand) => /webview2/i.test(brand.brand))) return "WebView2 (Chromium)";
278
+ // Chromium's Android WebView appends a distinct "; wv)" token to the UA string.
279
+ if (/; wv\)/i.test(ua)) return "Android WebView (Chromium)";
280
+
281
+ if (/edg\//i.test(ua)) return "Chromium (Edge)";
282
+ if (/firefox|fxios/i.test(ua)) return "Gecko (Firefox)";
283
+ if (/crios/i.test(ua)) return "Chromium (Chrome iOS)";
284
+ if (/chrome|chromium/i.test(ua)) return "Chromium";
285
+ if (/safari/i.test(ua)) {
286
+ // Real desktop/mobile Safari doesn't exist on Linux, so a WebKit UA there is
287
+ // almost certainly a WebKitGTK-based webview (e.g. Tauri's Linux WebView).
288
+ return detectPlatform() === "Linux" ? "WebKitGTK (Linux WebView)" : "WebKit (Safari)";
289
+ }
290
+ return "Unknown";
291
+ }
292
+
293
+ /** Numeric engine/browser version parsed from the UA string (Chromium/Firefox build, or Safari/WebKit version). */
294
+ export function getEngineVersion(): string | null {
295
+ const ua = navigator.userAgent;
296
+ const firefoxMatch = /Firefox\/([\d.]+)/.exec(ua);
297
+ if (firefoxMatch) return firefoxMatch[1];
298
+ const chromeMatch = /Chrome\/([\d.]+)/.exec(ua);
299
+ if (chromeMatch) return chromeMatch[1];
300
+ // Safari's real version lives in "Version/", not the AppleWebKit build number.
301
+ const safariVersionMatch = /Version\/([\d.]+)/.exec(ua);
302
+ if (safariVersionMatch) return safariVersionMatch[1];
303
+ const webkitMatch = /AppleWebKit\/([\d.]+)/.exec(ua);
304
+ if (webkitMatch) return webkitMatch[1];
305
+ return null;
306
+ }
307
+
308
+ export function getBrowserCapabilities(): BrowserCapabilities {
309
+ const win = window as Window & {
310
+ webkitAudioContext?: typeof AudioContext;
311
+ cefQuery?: unknown;
312
+ };
313
+
314
+ let localStorageAvailable = false;
315
+ try {
316
+ const testKey = "__diagnostics_test__";
317
+ window.localStorage.setItem(testKey, "1");
318
+ window.localStorage.removeItem(testKey);
319
+ localStorageAvailable = true;
320
+ } catch {
321
+ localStorageAvailable = false;
322
+ }
323
+
324
+ return {
325
+ audio: typeof AudioContext !== "undefined" || typeof win.webkitAudioContext !== "undefined",
326
+ gamepad: typeof navigator.getGamepads === "function",
327
+ clipboard: typeof navigator.clipboard?.writeText === "function",
328
+ fullscreen: document.fullscreenEnabled === true,
329
+ localStorage: localStorageAvailable,
330
+ indexedDb: typeof window.indexedDB !== "undefined",
331
+ steamOverlayDetected:
332
+ typeof win.cefQuery === "function" ||
333
+ /Valve Steam|SteamOverlay/i.test(navigator.userAgent),
334
+ };
335
+ }
336
+
337
+ export function getMobileDiagnostics(): MobileDiagnostics {
338
+ const nav = navigator as Navigator & {
339
+ deviceMemory?: number;
340
+ connection?: { effectiveType?: string };
341
+ };
342
+
343
+ return {
344
+ touchSupported: navigator.maxTouchPoints > 0,
345
+ maxTouchPoints: navigator.maxTouchPoints,
346
+ deviceMemoryGb: nav.deviceMemory ?? null,
347
+ networkEffectiveType: nav.connection?.effectiveType ?? null,
348
+ screenOrientation: window.screen?.orientation?.type ?? null,
349
+ };
350
+ }
351
+
352
+ export function getResolutionInfo(): ResolutionInfo {
353
+ return {
354
+ innerWidth: window.innerWidth,
355
+ innerHeight: window.innerHeight,
356
+ screenWidth: window.screen?.width ?? 0,
357
+ screenHeight: window.screen?.height ?? 0,
358
+ devicePixelRatio: window.devicePixelRatio || 1,
359
+ };
360
+ }
361
+
362
+ export function getJsMemoryInfo(): JsMemoryInfo | null {
363
+ const memory = (performance as Performance & { memory?: JsMemoryInfo }).memory;
364
+ if (!memory) {
365
+ return null;
366
+ }
367
+ return {
368
+ usedJSHeapSize: memory.usedJSHeapSize,
369
+ totalJSHeapSize: memory.totalJSHeapSize,
370
+ jsHeapSizeLimit: memory.jsHeapSizeLimit,
371
+ };
372
+ }
373
+
374
+ export function getCurrentFps(): number | null {
375
+ try {
376
+ return Math.round(canvas.app.ticker.FPS);
377
+ } catch {
378
+ return null;
379
+ }
380
+ }
381
+
382
+ export function getGameVersion(): string {
383
+ return packageJson.version;
384
+ }
385
+
386
+ export function getPixiJsVersion(): string {
387
+ return PIXIJS_VERSION;
388
+ }
389
+
390
+ export function getPixiVnVersion(): string {
391
+ return PIXIVN_VERSION;
392
+ }
393
+
394
+ export function getToneJsVersion(): string {
395
+ return tonePackageJson.version;
396
+ }
397
+
398
+ export function getMotionVersion(): string {
399
+ return motionPackageJson.version;
400
+ }
401
+
402
+ function formatBytes(bytes: number): string {
403
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
404
+ }
405
+
406
+ export interface DeviceDiagnosticsSnapshot {
407
+ webgl: WebglDiagnostics;
408
+ capabilities: BrowserCapabilities;
409
+ mobile: MobileDiagnostics;
410
+ native: NativeDiagnostics | null;
411
+ resolution: ResolutionInfo;
412
+ memory: JsMemoryInfo | null;
413
+ fps: number | null;
414
+ userAgent: string;
415
+ platform: string;
416
+ browserEngine: string;
417
+ engineVersion: string | null;
418
+ gameVersion: string;
419
+ pixiJsVersion: string;
420
+ pixiVnVersion: string;
421
+ toneJsVersion: string;
422
+ motionVersion: string;
423
+ }
424
+
425
+ export function buildDiagnosticsReportText(snapshot: DeviceDiagnosticsSnapshot): string {
426
+ const { webgl, capabilities, mobile, native, resolution, memory, fps } = snapshot;
427
+ const yn = (value: boolean | null) => (value === null ? "Unknown" : value ? "Yes" : "No");
428
+
429
+ const lines = [
430
+ "=== Device Diagnostics Report ===",
431
+ `Game version: ${snapshot.gameVersion}`,
432
+ `PixiJS version: ${snapshot.pixiJsVersion}`,
433
+ `Pixi'VN version: ${snapshot.pixiVnVersion}`,
434
+ `Tone.js version: ${snapshot.toneJsVersion}`,
435
+ `Motion version: ${snapshot.motionVersion}`,
436
+ `User Agent: ${snapshot.userAgent}`,
437
+ `Platform: ${snapshot.platform}`,
438
+ `Browser/WebView: ${snapshot.browserEngine}`,
439
+ `Engine version: ${snapshot.engineVersion ?? "Unknown"}`,
440
+ "",
441
+ "-- Native / OS (Tauri only) --",
442
+ native
443
+ ? [
444
+ `OS: ${native.osType} ${native.osVersion} (${native.bitness}, ${native.architecture})`,
445
+ `WebView engine version: ${native.webviewVersion ?? "Unknown"}`,
446
+ ].join("\n")
447
+ : "Not available (running in a standard browser)",
448
+ "",
449
+ "-- Graphics --",
450
+ `WebGL1 available: ${yn(webgl.webgl1Supported)}`,
451
+ `WebGL2 available: ${yn(webgl.webgl2Supported)}`,
452
+ `Hardware acceleration: ${yn(webgl.hardwareAccelerated)}`,
453
+ `GPU vendor: ${webgl.unmaskedVendor ?? webgl.vendor ?? "Unknown"}`,
454
+ `GPU renderer: ${webgl.unmaskedRenderer ?? webgl.renderer ?? "Unknown"}`,
455
+ `GPU brand: ${webgl.gpuVendorBrand}`,
456
+ `Mesa driver version: ${webgl.mesaVersion ?? "Not detected"}`,
457
+ `Max texture size: ${webgl.maxTextureSize ?? "Unknown"}`,
458
+ `Float textures: ${yn(webgl.floatTexturesSupported)}`,
459
+ `MSAA: ${yn(webgl.msaaSupported)}`,
460
+ `Shaders compile: ${yn(webgl.shadersCompile)}`,
461
+ `WebGL extensions: ${webgl.keyExtensions
462
+ .filter((extension) => extension.supported)
463
+ .map((extension) => extension.name)
464
+ .join(", ")}`,
465
+ "",
466
+ "-- Display --",
467
+ `Resolution: ${resolution.innerWidth}x${resolution.innerHeight} (screen ${resolution.screenWidth}x${resolution.screenHeight})`,
468
+ `Device pixel ratio: ${resolution.devicePixelRatio}`,
469
+ `FPS: ${fps ?? "Unknown"}`,
470
+ "",
471
+ "-- Memory --",
472
+ memory
473
+ ? `JS heap: ${formatBytes(memory.usedJSHeapSize)} / ${formatBytes(memory.totalJSHeapSize)} (limit ${formatBytes(memory.jsHeapSizeLimit)})`
474
+ : "JS heap: Not available in this browser",
475
+ "",
476
+ "-- Mobile & sensors --",
477
+ `Touch support: ${yn(mobile.touchSupported)} (${mobile.maxTouchPoints} points)`,
478
+ `Device memory: ${mobile.deviceMemoryGb !== null ? `${mobile.deviceMemoryGb} GB` : "Not available"}`,
479
+ `Network type: ${mobile.networkEffectiveType ?? "Not available"}`,
480
+ `Screen orientation: ${mobile.screenOrientation ?? "Unknown"}`,
481
+ "",
482
+ "-- Browser capabilities --",
483
+ `Audio (Web Audio API): ${yn(capabilities.audio)}`,
484
+ `Gamepad API: ${yn(capabilities.gamepad)}`,
485
+ `Clipboard API: ${yn(capabilities.clipboard)}`,
486
+ `Fullscreen API: ${yn(capabilities.fullscreen)}`,
487
+ `LocalStorage: ${yn(capabilities.localStorage)}`,
488
+ `IndexedDB: ${yn(capabilities.indexedDb)}`,
489
+ `Steam Overlay detected (best-effort): ${yn(capabilities.steamOverlayDetected)}`,
490
+ ];
491
+
492
+ return lines.join("\n");
493
+ }
494
+
495
+ export function buildDiagnosticsReportJson(snapshot: DeviceDiagnosticsSnapshot): object {
496
+ return {
497
+ generatedAt: new Date().toISOString(),
498
+ ...snapshot,
499
+ };
500
+ }
501
+
502
+ export function downloadDiagnosticsReportJson(snapshot: DeviceDiagnosticsSnapshot) {
503
+ const jsonString = JSON.stringify(buildDiagnosticsReportJson(snapshot), null, 2);
504
+ const blob = new Blob([jsonString], { type: "application/json" });
505
+ const url = URL.createObjectURL(blob);
506
+ const a = document.createElement("a");
507
+ a.href = url;
508
+ a.download = `${__APP_NAME__}-${__APP_VERSION__}-diagnostics-${Date.now()}.json`;
509
+ a.click();
510
+ URL.revokeObjectURL(url);
511
+ }
@@ -63,6 +63,72 @@
63
63
  "name": "Name",
64
64
  "version": "Version",
65
65
  "engine": "Engine",
66
+ "diagnostics": "Diagnostics",
67
+ "diagnostics_yes": "Yes",
68
+ "diagnostics_no": "No",
69
+ "diagnostics_unknown": "Unknown",
70
+ "diagnostics_not_detected": "Not detected",
71
+ "diagnostics_not_available": "Not available",
72
+ "diagnostics_webgl2_missing_message": "This game requires WebGL2. Please update your browser, operating system, or GPU drivers.",
73
+ "diagnostics_section_engine": "Engine",
74
+ "diagnostics_section_device": "Device",
75
+ "diagnostics_section_native": "Native / OS",
76
+ "diagnostics_section_graphics": "Graphics",
77
+ "diagnostics_section_display": "Display",
78
+ "diagnostics_section_mobile": "Mobile & Sensors",
79
+ "diagnostics_section_memory": "Memory",
80
+ "diagnostics_section_capabilities": "Browser Capabilities",
81
+ "diagnostics_section_extensions": "WebGL Extensions",
82
+ "diagnostics_game_version": "Game version",
83
+ "diagnostics_pixijs_version": "PixiJS version",
84
+ "diagnostics_pixivn_version": "Pixi'VN version",
85
+ "diagnostics_tonejs_version": "Tone.js version",
86
+ "diagnostics_motion_version": "Motion version",
87
+ "diagnostics_platform": "Platform",
88
+ "diagnostics_browser_engine": "Browser/WebView",
89
+ "diagnostics_engine_version": "Engine version",
90
+ "diagnostics_user_agent": "User Agent",
91
+ "diagnostics_os": "Operating system",
92
+ "diagnostics_os_bitness": "OS bitness",
93
+ "diagnostics_os_architecture": "OS architecture",
94
+ "diagnostics_webview_version": "WebView engine version",
95
+ "diagnostics_native_unavailable": "Not available — running in a standard browser. This is populated automatically in the Tauri build.",
96
+ "diagnostics_webgl1": "WebGL1 available",
97
+ "diagnostics_webgl2": "WebGL2 available",
98
+ "diagnostics_hardware_acceleration": "Hardware acceleration",
99
+ "diagnostics_gpu_vendor": "GPU vendor",
100
+ "diagnostics_gpu_renderer": "GPU renderer",
101
+ "diagnostics_gpu_vendor_brand": "GPU brand",
102
+ "diagnostics_mesa_driver": "Mesa driver version",
103
+ "diagnostics_max_texture_size": "Max texture size",
104
+ "diagnostics_max_texture_size_check": "Max texture size >= 8192",
105
+ "diagnostics_gpu_supported": "GPU supported",
106
+ "diagnostics_float_textures": "Float textures",
107
+ "diagnostics_msaa": "MSAA",
108
+ "diagnostics_shaders_compile": "Shaders compile",
109
+ "diagnostics_resolution": "Resolution",
110
+ "diagnostics_device_pixel_ratio": "Device pixel ratio",
111
+ "diagnostics_fps": "FPS",
112
+ "diagnostics_touch_support": "Touch support",
113
+ "diagnostics_device_memory": "Device memory",
114
+ "diagnostics_network_type": "Network type",
115
+ "diagnostics_screen_orientation": "Screen orientation",
116
+ "diagnostics_js_heap": "JS heap used / total",
117
+ "diagnostics_js_heap_unavailable": "Not available in this browser.",
118
+ "diagnostics_audio": "Audio (Web Audio API)",
119
+ "diagnostics_gamepad": "Gamepad API",
120
+ "diagnostics_clipboard": "Clipboard API",
121
+ "diagnostics_fullscreen": "Fullscreen API",
122
+ "diagnostics_local_storage": "LocalStorage",
123
+ "diagnostics_indexed_db": "IndexedDB",
124
+ "diagnostics_steam_overlay": "Steam Overlay",
125
+ "diagnostics_steam_overlay_hint": "Best-effort detection only; not reliable in a standard browser.",
126
+ "diagnostics_copy_report": "Copy report",
127
+ "diagnostics_report_copied": "Report copied to clipboard",
128
+ "diagnostics_report_copy_failed": "Could not copy the report",
129
+ "diagnostics_download_report": "Download JSON",
130
+ "diagnostics_report_downloaded": "Report downloaded",
131
+ "diagnostics_hotkey_description": "Open settings directly on the diagnostics page.",
66
132
  "quick_actions": "Quick Actions",
67
133
  "quick_actions_open_description": "Press Tab to open or close the quick actions wheel.",
68
134
  "quick_actions_click_hint": "Click an action to execute",
@@ -20,7 +20,6 @@ declare module "@drincs/pixi-vn/narration" {
20
20
  }
21
21
  declare module "@drincs/pixi-vn/canvas" {
22
22
  interface PixivnBundleIds {
23
- "default": never;
24
23
  "audio": never;
25
24
  "/": never;
26
25
  "start": never;
@@ -91,7 +90,7 @@ export const characterIds = ["mc","james","steph","sly"] as const;
91
90
  export const characterIdsEnum = {"mc":"mc","james":"james","steph":"steph","sly":"sly"} as const;
92
91
  export const labelIds = ["animation_01","second_part","start","start_|_c-0","start_|_c-1"] as const;
93
92
  export const labelIdsEnum = {"animation_01":"animation_01","second_part":"second_part","start":"start","start_|_c-0":"start_|_c-0","start_|_c-1":"start_|_c-1"} as const;
94
- export const bundleIds = ["default","audio","/","start","second_part","fm01","fm02","m01"] as const;
95
- export const bundleIdsEnum = {"default":"default","audio":"audio","/":"/","start":"start","second_part":"second_part","fm01":"fm01","fm02":"fm02","m01":"m01"} as const;
93
+ export const bundleIds = ["audio","/","start","second_part","fm01","fm02","m01"] as const;
94
+ export const bundleIdsEnum = {"audio":"audio","/":"/","start":"start","second_part":"second_part","fm01":"fm01","fm02":"fm02","m01":"m01"} as const;
96
95
  export const assetAliasIds = ["bgm_cheerful","sfx_whoosh","background_main_menu","bg01-hallway","bg02-dorm","fm01-body","fm01-eyes-grin","fm01-eyes-smile","fm01-eyes-soft","fm01-eyes-upset","fm01-eyes-wow","fm01-mouth-grin00","fm01-mouth-serious00","fm01-mouth-serious01","fm01-mouth-smile00","fm01-mouth-smile01","fm01-mouth-soft00","fm01-mouth-soft01","fm01-mouth-upset00","fm01-mouth-upset01","fm01-mouth-wow01","fm02-body","fm02-eyes-bawl","fm02-eyes-joy","fm02-eyes-nervous","fm02-eyes-smile","fm02-eyes-upset","fm02-eyes-wow","fm02-mouth-cry01","fm02-mouth-nervous00","fm02-mouth-nervous01","fm02-mouth-smile00","fm02-mouth-smile01","fm02-mouth-upset00","fm02-mouth-upset01","fm02-mouth-wow01","m01-body","m01-eyes-annoy","m01-eyes-concern","m01-eyes-cry","m01-eyes-grin","m01-eyes-smile","m01-eyes-wow","m01-mouth-annoy00","m01-mouth-annoy01","m01-mouth-concern00","m01-mouth-concern01","m01-mouth-cry00","m01-mouth-cry01","m01-mouth-grin00","m01-mouth-neutral00","m01-mouth-neutral01","m01-mouth-smile00","m01-mouth-smile01","m01-mouth-wow01"] as const;
97
96
  export const assetAliasIdsEnum = {"bgm_cheerful":"bgm_cheerful","sfx_whoosh":"sfx_whoosh","background_main_menu":"background_main_menu","bg01-hallway":"bg01-hallway","bg02-dorm":"bg02-dorm","fm01-body":"fm01-body","fm01-eyes-grin":"fm01-eyes-grin","fm01-eyes-smile":"fm01-eyes-smile","fm01-eyes-soft":"fm01-eyes-soft","fm01-eyes-upset":"fm01-eyes-upset","fm01-eyes-wow":"fm01-eyes-wow","fm01-mouth-grin00":"fm01-mouth-grin00","fm01-mouth-serious00":"fm01-mouth-serious00","fm01-mouth-serious01":"fm01-mouth-serious01","fm01-mouth-smile00":"fm01-mouth-smile00","fm01-mouth-smile01":"fm01-mouth-smile01","fm01-mouth-soft00":"fm01-mouth-soft00","fm01-mouth-soft01":"fm01-mouth-soft01","fm01-mouth-upset00":"fm01-mouth-upset00","fm01-mouth-upset01":"fm01-mouth-upset01","fm01-mouth-wow01":"fm01-mouth-wow01","fm02-body":"fm02-body","fm02-eyes-bawl":"fm02-eyes-bawl","fm02-eyes-joy":"fm02-eyes-joy","fm02-eyes-nervous":"fm02-eyes-nervous","fm02-eyes-smile":"fm02-eyes-smile","fm02-eyes-upset":"fm02-eyes-upset","fm02-eyes-wow":"fm02-eyes-wow","fm02-mouth-cry01":"fm02-mouth-cry01","fm02-mouth-nervous00":"fm02-mouth-nervous00","fm02-mouth-nervous01":"fm02-mouth-nervous01","fm02-mouth-smile00":"fm02-mouth-smile00","fm02-mouth-smile01":"fm02-mouth-smile01","fm02-mouth-upset00":"fm02-mouth-upset00","fm02-mouth-upset01":"fm02-mouth-upset01","fm02-mouth-wow01":"fm02-mouth-wow01","m01-body":"m01-body","m01-eyes-annoy":"m01-eyes-annoy","m01-eyes-concern":"m01-eyes-concern","m01-eyes-cry":"m01-eyes-cry","m01-eyes-grin":"m01-eyes-grin","m01-eyes-smile":"m01-eyes-smile","m01-eyes-wow":"m01-eyes-wow","m01-mouth-annoy00":"m01-mouth-annoy00","m01-mouth-annoy01":"m01-mouth-annoy01","m01-mouth-concern00":"m01-mouth-concern00","m01-mouth-concern01":"m01-mouth-concern01","m01-mouth-cry00":"m01-mouth-cry00","m01-mouth-cry01":"m01-mouth-cry01","m01-mouth-grin00":"m01-mouth-grin00","m01-mouth-neutral00":"m01-mouth-neutral00","m01-mouth-neutral01":"m01-mouth-neutral01","m01-mouth-smile00":"m01-mouth-smile00","m01-mouth-smile01":"m01-mouth-smile01","m01-mouth-wow01":"m01-mouth-wow01"} as const;
@@ -39,6 +39,7 @@ tauri-plugin-process = "2"
39
39
  tauri-plugin-window-state = "2"
40
40
  serde = { version = "1", features = ["derive"] }
41
41
  serde_json = "1"
42
+ os_info = "3"
42
43
  steamworks = { version = "0.13", optional = true }
43
44
 
44
45
  [profile.release]