@series-inc/rundot-3d-engine 0.6.9 → 0.6.11
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/SKILL.md +366 -0
- package/dist/{chunk-UDJVZHS6.js → chunk-WLXQBO3A.js} +181 -11
- package/dist/chunk-WLXQBO3A.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1 -1
- package/dist/systems/index.d.ts +8 -0
- package/dist/systems/index.js +1 -1
- package/package.json +3 -2
- package/scripts/postinstall.mjs +25 -6
- package/dist/chunk-UDJVZHS6.js.map +0 -1
package/SKILL.md
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# Rundot 3D Engine
|
|
2
|
+
|
|
3
|
+
The Rundot 3D Engine (`@series-inc/rundot-3d-engine`) is a Three.js-based game engine with ECS architecture, Rapier physics, and StowKit asset integration.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { VenusGame, GameObject, Component } from "@series-inc/rundot-3d-engine"
|
|
9
|
+
import { MeshRenderer, RigidBodyComponentThree, RigidBodyType, ColliderShape } from "@series-inc/rundot-3d-engine/systems"
|
|
10
|
+
|
|
11
|
+
class MyGame extends VenusGame {
|
|
12
|
+
protected async onStart(): Promise<void> {
|
|
13
|
+
// Load assets
|
|
14
|
+
const stowkit = StowKitSystem.getInstance()
|
|
15
|
+
const buildJson = (await import("../prefabs/build.json")).default
|
|
16
|
+
await stowkit.loadFromBuildJson(buildJson, {
|
|
17
|
+
fetchBlob: (path) => fetch(path).then(r => r.blob()),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
// Create a player
|
|
21
|
+
const player = new GameObject("Player")
|
|
22
|
+
player.position.set(0, 1, 0)
|
|
23
|
+
|
|
24
|
+
// Add mesh
|
|
25
|
+
const meshObj = new GameObject("PlayerMesh")
|
|
26
|
+
meshObj.addComponent(new MeshRenderer("player_mesh"))
|
|
27
|
+
player.add(meshObj)
|
|
28
|
+
|
|
29
|
+
// Add physics
|
|
30
|
+
player.addComponent(new RigidBodyComponentThree({
|
|
31
|
+
type: RigidBodyType.DYNAMIC,
|
|
32
|
+
shape: ColliderShape.CAPSULE,
|
|
33
|
+
radius: 0.5,
|
|
34
|
+
height: 2,
|
|
35
|
+
lockRotationX: true,
|
|
36
|
+
lockRotationY: true,
|
|
37
|
+
lockRotationZ: true,
|
|
38
|
+
}))
|
|
39
|
+
|
|
40
|
+
// Add lighting
|
|
41
|
+
const light = new THREE.DirectionalLight(0xffffff, 1)
|
|
42
|
+
light.position.set(5, 10, 5)
|
|
43
|
+
this.scene.add(light)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
protected preRender(deltaTime: number): void {
|
|
47
|
+
// Per-frame logic
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
protected async onDispose(): Promise<void> {
|
|
51
|
+
// Cleanup
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
MyGame.create()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Core Architecture
|
|
59
|
+
|
|
60
|
+
### VenusGame — Game Base Class
|
|
61
|
+
|
|
62
|
+
Manages renderer, scene, camera, and all engine systems.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
class MyGame extends VenusGame {
|
|
66
|
+
protected getConfig(): VenusGameConfig {
|
|
67
|
+
return {
|
|
68
|
+
backgroundColor: 0x87CEEB,
|
|
69
|
+
antialias: true,
|
|
70
|
+
shadowMapEnabled: true,
|
|
71
|
+
shadowMapType: "vsm", // or "pcf_soft"
|
|
72
|
+
toneMapping: "aces", // "aces" | "linear" | "none"
|
|
73
|
+
toneMappingExposure: 1.0,
|
|
74
|
+
audioEnabled: true,
|
|
75
|
+
cameraType: "perspective", // or "orthographic"
|
|
76
|
+
orthoSize: 10, // half-height for ortho camera
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
protected async onStart(): Promise<void> { /* init game */ }
|
|
81
|
+
protected preRender(deltaTime: number): void { /* per-frame logic */ }
|
|
82
|
+
protected async onDispose(): Promise<void> { /* cleanup */ }
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Static access** (after initialization):
|
|
87
|
+
- `VenusGame.scene` — Three.js scene
|
|
88
|
+
- `VenusGame.camera` — active camera
|
|
89
|
+
- `VenusGame.renderer` — WebGL renderer
|
|
90
|
+
- `VenusGame.instance` — game instance
|
|
91
|
+
|
|
92
|
+
**Lifecycle order:** Physics → Tweens → Components → `preRender()` → `render()`
|
|
93
|
+
|
|
94
|
+
**IMPORTANT:** Use the `deltaTime` parameter in `preRender()`. Never call `this.clock.getDelta()`.
|
|
95
|
+
|
|
96
|
+
### GameObject — Entity Class
|
|
97
|
+
|
|
98
|
+
Extends `THREE.Object3D`. Auto-added to scene on creation.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
const obj = new GameObject("MyObject")
|
|
102
|
+
obj.position.set(0, 1, 0)
|
|
103
|
+
obj.addComponent(new MyComponent())
|
|
104
|
+
|
|
105
|
+
// Hierarchy
|
|
106
|
+
const child = new GameObject("Child")
|
|
107
|
+
obj.add(child)
|
|
108
|
+
|
|
109
|
+
// Component access
|
|
110
|
+
const comp = obj.getComponent(MyComponent)
|
|
111
|
+
obj.hasComponent(MyComponent)
|
|
112
|
+
obj.removeComponent(MyComponent)
|
|
113
|
+
|
|
114
|
+
// Enable/disable
|
|
115
|
+
obj.setEnabled(false) // triggers onDisabled() on all components
|
|
116
|
+
obj.setEnabled(true) // triggers onEnabled()
|
|
117
|
+
|
|
118
|
+
// Cleanup
|
|
119
|
+
obj.dispose() // disposes all components and children
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Component — Behavior Base Class
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
class MyComponent extends Component {
|
|
126
|
+
protected onCreate(): void {
|
|
127
|
+
// Called when added to GameObject — init here
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public update(deltaTime: number): void {
|
|
131
|
+
// Called every frame
|
|
132
|
+
this.gameObject.position.x += deltaTime
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public lateUpdate(deltaTime: number): void {
|
|
136
|
+
// Called after all update() calls — camera follow, UI updates
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public onEnabled(): void { /* GameObject enabled */ }
|
|
140
|
+
public onDisabled(): void { /* GameObject disabled */ }
|
|
141
|
+
|
|
142
|
+
protected onCleanup(): void {
|
|
143
|
+
// Called on removal or dispose — cleanup listeners, resources
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Access from Component:**
|
|
149
|
+
- `this.gameObject` — the attached GameObject
|
|
150
|
+
- `this.scene` — the Three.js scene
|
|
151
|
+
- `this.getComponent(Type)` — get sibling component
|
|
152
|
+
|
|
153
|
+
## Systems
|
|
154
|
+
|
|
155
|
+
### MeshRenderer — Static Mesh Display
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { MeshRenderer } from "@series-inc/rundot-3d-engine/systems"
|
|
159
|
+
|
|
160
|
+
// ALWAYS use child GameObject pattern
|
|
161
|
+
const renderer = new MeshRenderer("asset_name", castShadow?, receiveShadow?, isStatic?, materialOverride?)
|
|
162
|
+
const meshObj = new GameObject("Mesh")
|
|
163
|
+
meshObj.addComponent(renderer)
|
|
164
|
+
parentGameObject.add(meshObj)
|
|
165
|
+
|
|
166
|
+
// Check if loaded
|
|
167
|
+
renderer.isLoaded()
|
|
168
|
+
renderer.getBounds()
|
|
169
|
+
renderer.setVisible(false)
|
|
170
|
+
renderer.setMaterial(customMaterial)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Static meshes** (`isStatic: true`) skip per-frame matrix updates — use for non-moving objects.
|
|
174
|
+
|
|
175
|
+
### SkeletalRenderer — Animated Characters
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { SkeletalRenderer } from "@series-inc/rundot-3d-engine/systems"
|
|
179
|
+
|
|
180
|
+
const skelRenderer = new SkeletalRenderer("character_mesh")
|
|
181
|
+
const meshObj = new GameObject("CharacterMesh")
|
|
182
|
+
meshObj.addComponent(skelRenderer)
|
|
183
|
+
character.add(meshObj)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### RigidBodyComponentThree — Physics
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { RigidBodyComponentThree, RigidBodyType, ColliderShape } from "@series-inc/rundot-3d-engine/systems"
|
|
190
|
+
|
|
191
|
+
// Dynamic (affected by physics)
|
|
192
|
+
new RigidBodyComponentThree({
|
|
193
|
+
type: RigidBodyType.DYNAMIC,
|
|
194
|
+
shape: ColliderShape.BOX, // BOX, SPHERE, CAPSULE
|
|
195
|
+
size: new THREE.Vector3(1, 1, 1), // box dimensions
|
|
196
|
+
radius: 0.5, // sphere/capsule radius
|
|
197
|
+
height: 2, // capsule height
|
|
198
|
+
mass: 1.0,
|
|
199
|
+
friction: 0.5,
|
|
200
|
+
restitution: 0.8, // bounciness
|
|
201
|
+
linearDamping: 0.5,
|
|
202
|
+
angularDamping: 0.5,
|
|
203
|
+
lockRotationX/Y/Z: true, // lock rotation axes
|
|
204
|
+
fitToMesh: true, // auto-size from mesh bounds
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
// Static (immovable)
|
|
208
|
+
new RigidBodyComponentThree({ type: RigidBodyType.STATIC, shape: ColliderShape.BOX, size: ... })
|
|
209
|
+
|
|
210
|
+
// Kinematic (script-controlled)
|
|
211
|
+
new RigidBodyComponentThree({ type: RigidBodyType.KINEMATIC, ... })
|
|
212
|
+
|
|
213
|
+
// Trigger (sensor, no physics response)
|
|
214
|
+
new RigidBodyComponentThree({ type: RigidBodyType.STATIC, shape: ColliderShape.BOX, isSensor: true })
|
|
215
|
+
rb.registerOnTriggerEnter((other) => console.log("entered", other.name))
|
|
216
|
+
rb.registerOnTriggerExit((other) => console.log("exited", other.name))
|
|
217
|
+
|
|
218
|
+
// Velocity & forces (dynamic only)
|
|
219
|
+
rb.setVelocity(new THREE.Vector3(0, 5, 0))
|
|
220
|
+
rb.applyImpulse(new THREE.Vector3(10, 0, 0))
|
|
221
|
+
rb.applyForce(new THREE.Vector3(0, -9.8, 0))
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### AnimationGraphComponent — State Machine Animations
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { AnimationGraphComponent } from "@series-inc/rundot-3d-engine/systems"
|
|
228
|
+
|
|
229
|
+
const config = {
|
|
230
|
+
parameters: {
|
|
231
|
+
speed: { type: "float", default: 0 },
|
|
232
|
+
isGrounded: { type: "bool", default: true },
|
|
233
|
+
},
|
|
234
|
+
states: {
|
|
235
|
+
idle: { animation: "idle" },
|
|
236
|
+
walk: { animation: "walk" },
|
|
237
|
+
run: { animation: "run" },
|
|
238
|
+
locomotion: {
|
|
239
|
+
tree: {
|
|
240
|
+
parameter: "speed",
|
|
241
|
+
children: [
|
|
242
|
+
{ animation: "idle", threshold: 0 },
|
|
243
|
+
{ animation: "walk", threshold: 1 },
|
|
244
|
+
{ animation: "run", threshold: 2 },
|
|
245
|
+
],
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
transitions: [
|
|
250
|
+
{ from: "idle", to: "walk", when: { speed: 1 } },
|
|
251
|
+
{ from: "walk", to: "run", when: { speed: 2 } },
|
|
252
|
+
{ from: "attack", to: "idle", exitTime: 1.0 }, // after anim finishes
|
|
253
|
+
],
|
|
254
|
+
initialState: "idle",
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const animGraph = new AnimationGraphComponent(model, config)
|
|
258
|
+
character.addComponent(animGraph)
|
|
259
|
+
|
|
260
|
+
// Drive transitions via parameters
|
|
261
|
+
animGraph.setParameter("speed", 2)
|
|
262
|
+
animGraph.setState("attack") // direct state change
|
|
263
|
+
animGraph.getCurrentState()
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### StowKitSystem — Asset Loading
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
import { StowKitSystem } from "@series-inc/rundot-3d-engine/systems"
|
|
270
|
+
|
|
271
|
+
const stowkit = StowKitSystem.getInstance()
|
|
272
|
+
|
|
273
|
+
// Load from build.json
|
|
274
|
+
await stowkit.loadFromBuildJson(buildJson, {
|
|
275
|
+
fetchBlob: (path) => fetch(path).then(r => r.blob()),
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
// Access assets
|
|
279
|
+
const mesh = await stowkit.getMesh("name") // async
|
|
280
|
+
const mesh = stowkit.getMeshSync("name") // sync (null if not loaded)
|
|
281
|
+
const tex = await stowkit.getTexture("name")
|
|
282
|
+
const clip = await stowkit.getAnimation("walk", "character_mesh")
|
|
283
|
+
const audio = await stowkit.getAudio("sfx_click")
|
|
284
|
+
const skinned = await stowkit.getSkinnedMesh("character", 1.0)
|
|
285
|
+
|
|
286
|
+
// Clone with shadow settings
|
|
287
|
+
const clone = await stowkit.cloneMesh("name", castShadow, receiveShadow)
|
|
288
|
+
|
|
289
|
+
// GPU instancing
|
|
290
|
+
await stowkit.registerMeshForInstancing("coin_batch", "coin_mesh", true, true, 500)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Other Systems
|
|
294
|
+
|
|
295
|
+
- **AudioSystem** — 2D/3D positional audio with AudioListener management
|
|
296
|
+
- **InputManager** — keyboard, mouse, touch, and gamepad input
|
|
297
|
+
- **LightingSystem** — directional, point, and spot lights with shadow management
|
|
298
|
+
- **NavigationSystem** — A* pathfinding on navigation meshes
|
|
299
|
+
- **ParticleSystem** — GPU particle effects
|
|
300
|
+
- **TweenSystem** — property animation and easing
|
|
301
|
+
- **UISystem** — HTML-based UI overlay system
|
|
302
|
+
- **PrefabSystem** — load and instantiate prefab hierarchies from JSON
|
|
303
|
+
- **SplineSystem** — Catmull-Rom spline paths for cameras, movement, etc.
|
|
304
|
+
|
|
305
|
+
## Patterns
|
|
306
|
+
|
|
307
|
+
### Separation of Concerns
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
// Logic/physics on parent
|
|
311
|
+
const character = new GameObject("Character")
|
|
312
|
+
character.addComponent(new CharacterController())
|
|
313
|
+
character.addComponent(new RigidBodyComponentThree({ type: RigidBodyType.DYNAMIC, shape: ColliderShape.CAPSULE }))
|
|
314
|
+
|
|
315
|
+
// Visual as child (can offset independently)
|
|
316
|
+
const visual = new GameObject("Visual")
|
|
317
|
+
visual.addComponent(new SkeletalRenderer("character_mesh"))
|
|
318
|
+
character.add(visual)
|
|
319
|
+
visual.position.y = -1
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Cache Component References
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
class MyComponent extends Component {
|
|
326
|
+
private meshRenderer?: MeshRenderer
|
|
327
|
+
|
|
328
|
+
protected onCreate(): void {
|
|
329
|
+
this.meshRenderer = this.getComponent(MeshRenderer) // cache in onCreate
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
public update(deltaTime: number): void {
|
|
333
|
+
// use cached ref — don't search every frame
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Factory Pattern
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
class EnemyFactory {
|
|
342
|
+
static create(type: string): GameObject {
|
|
343
|
+
const enemy = new GameObject(`Enemy_${type}`)
|
|
344
|
+
enemy.addComponent(new EnemyAI())
|
|
345
|
+
const meshObj = new GameObject("Mesh")
|
|
346
|
+
meshObj.addComponent(new MeshRenderer(`enemy_${type}`))
|
|
347
|
+
enemy.add(meshObj)
|
|
348
|
+
return enemy
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Common Mistakes
|
|
354
|
+
|
|
355
|
+
- **Don't create GameObjects in `update()`** — causes memory leaks. Create once, reuse or pool.
|
|
356
|
+
- **Don't access `this.gameObject` in constructor** — use `onCreate()` instead.
|
|
357
|
+
- **Don't forget `dispose()`** — always dispose GameObjects when done.
|
|
358
|
+
- **Don't add multiple components of same type** to one GameObject — use child GameObjects.
|
|
359
|
+
- **Don't call `this.clock.getDelta()`** — use the `deltaTime` parameter.
|
|
360
|
+
- **Don't bypass MeshRenderer** by loading meshes directly from StowKitSystem — use the component pattern.
|
|
361
|
+
|
|
362
|
+
## Dependencies
|
|
363
|
+
|
|
364
|
+
- `three` (peer, >=0.180.0)
|
|
365
|
+
- `@dimforge/rapier3d` — Rapier physics engine
|
|
366
|
+
- `@series-inc/stowkit-reader` + `@series-inc/stowkit-three-loader` — asset loading
|
|
@@ -1081,6 +1081,124 @@ import { Preferences } from "@capacitor/preferences";
|
|
|
1081
1081
|
import { LocalNotifications } from "@capacitor/local-notifications";
|
|
1082
1082
|
import { App } from "@capacitor/app";
|
|
1083
1083
|
import { SplashScreen } from "@capacitor/splash-screen";
|
|
1084
|
+
|
|
1085
|
+
// src/platform/datadog-analytics/index.ts
|
|
1086
|
+
var OTEL_ENDPOINT = "https://otel.run.game";
|
|
1087
|
+
var LOGS_PATH = "/v1/logs";
|
|
1088
|
+
function toOtlpValue(v) {
|
|
1089
|
+
if (typeof v === "string") return { stringValue: v };
|
|
1090
|
+
if (typeof v === "number") return Number.isInteger(v) ? { intValue: String(v) } : { doubleValue: v };
|
|
1091
|
+
if (typeof v === "boolean") return { boolValue: v };
|
|
1092
|
+
return { stringValue: String(v) };
|
|
1093
|
+
}
|
|
1094
|
+
function toOtlpAttributes(data) {
|
|
1095
|
+
return Object.entries(data).map(([key, value]) => ({ key, value: toOtlpValue(value) }));
|
|
1096
|
+
}
|
|
1097
|
+
function generateSessionId() {
|
|
1098
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
1099
|
+
return crypto.randomUUID();
|
|
1100
|
+
}
|
|
1101
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
|
|
1102
|
+
}
|
|
1103
|
+
var config = {
|
|
1104
|
+
endpoint: OTEL_ENDPOINT.replace(/\/$/, ""),
|
|
1105
|
+
serviceName: "unknown",
|
|
1106
|
+
serviceVersion: "0.0.0",
|
|
1107
|
+
platform: "web",
|
|
1108
|
+
sessionId: ""
|
|
1109
|
+
};
|
|
1110
|
+
function init(options) {
|
|
1111
|
+
config.sessionId = generateSessionId();
|
|
1112
|
+
if (options) {
|
|
1113
|
+
if (options.serviceName !== void 0) config.serviceName = options.serviceName;
|
|
1114
|
+
if (options.serviceVersion !== void 0) config.serviceVersion = options.serviceVersion;
|
|
1115
|
+
if (options.platform !== void 0) config.platform = options.platform;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
function getEndpoint() {
|
|
1119
|
+
if (config.platform === "android" || config.platform === "ios") {
|
|
1120
|
+
return config.endpoint;
|
|
1121
|
+
}
|
|
1122
|
+
if (typeof window !== "undefined") {
|
|
1123
|
+
const host = window.location?.hostname ?? "";
|
|
1124
|
+
if (host === "localhost" || host === "127.0.0.1") {
|
|
1125
|
+
return `${window.location.origin}/api/otel`;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
return config.endpoint;
|
|
1129
|
+
}
|
|
1130
|
+
function send(attributes, body) {
|
|
1131
|
+
const payload = {
|
|
1132
|
+
resourceLogs: [
|
|
1133
|
+
{
|
|
1134
|
+
resource: {
|
|
1135
|
+
attributes: [
|
|
1136
|
+
{ key: "service.name", value: { stringValue: config.serviceName } },
|
|
1137
|
+
{ key: "service.version", value: { stringValue: config.serviceVersion } },
|
|
1138
|
+
{ key: "log.type", value: { stringValue: "hermes" } }
|
|
1139
|
+
]
|
|
1140
|
+
},
|
|
1141
|
+
scopeLogs: [
|
|
1142
|
+
{
|
|
1143
|
+
scope: { name: "datadog-analytics", version: "1.0.0" },
|
|
1144
|
+
logRecords: [
|
|
1145
|
+
{
|
|
1146
|
+
timeUnixNano: String(Date.now() * 1e6),
|
|
1147
|
+
severityText: "INFO",
|
|
1148
|
+
severityNumber: 9,
|
|
1149
|
+
body: { stringValue: body },
|
|
1150
|
+
attributes: toOtlpAttributes({
|
|
1151
|
+
timestamp: Date.now(),
|
|
1152
|
+
platform: config.platform,
|
|
1153
|
+
service_name: config.serviceName,
|
|
1154
|
+
session_id: config.sessionId,
|
|
1155
|
+
...attributes
|
|
1156
|
+
})
|
|
1157
|
+
}
|
|
1158
|
+
]
|
|
1159
|
+
}
|
|
1160
|
+
]
|
|
1161
|
+
}
|
|
1162
|
+
]
|
|
1163
|
+
};
|
|
1164
|
+
const url = `${getEndpoint()}${LOGS_PATH}`;
|
|
1165
|
+
return fetch(url, {
|
|
1166
|
+
method: "POST",
|
|
1167
|
+
headers: { "Content-Type": "application/json" },
|
|
1168
|
+
body: JSON.stringify(payload)
|
|
1169
|
+
}).then((res) => {
|
|
1170
|
+
if (!res.ok && typeof console !== "undefined" && console.warn) {
|
|
1171
|
+
console.warn("[DatadogAnalytics] send failed:", res.status, url);
|
|
1172
|
+
}
|
|
1173
|
+
return res.ok;
|
|
1174
|
+
}).catch((err) => {
|
|
1175
|
+
if (typeof console !== "undefined" && console.warn) {
|
|
1176
|
+
console.warn("[DatadogAnalytics] send error:", err);
|
|
1177
|
+
}
|
|
1178
|
+
return false;
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
function trackCustom(params) {
|
|
1182
|
+
const { name, screen, desc, ...rest } = params;
|
|
1183
|
+
return send(
|
|
1184
|
+
{ event_type: name, screen_name: screen ?? "", ...desc !== void 0 && { description: desc }, ...rest },
|
|
1185
|
+
name
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
function trackFunnel(params) {
|
|
1189
|
+
const { step, screenName, stepNumber = 0, funnelName = "studio_funnel", ...context } = params;
|
|
1190
|
+
return send(
|
|
1191
|
+
{ event_type: "step_funnel", screen_name: screenName, funnel_name: funnelName, step_name: step, step_number: stepNumber, ...context },
|
|
1192
|
+
`step_funnel:${step}`
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
var DatadogAnalytics = {
|
|
1196
|
+
init,
|
|
1197
|
+
trackCustom,
|
|
1198
|
+
trackFunnel
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
// src/platform/CapacitorPlatform.ts
|
|
1084
1202
|
var LOG_PREFIX = "[Capacitor]";
|
|
1085
1203
|
function stringToNotificationId(str) {
|
|
1086
1204
|
let hash = 0;
|
|
@@ -1140,14 +1258,16 @@ var CapacitorPlatform = class {
|
|
|
1140
1258
|
};
|
|
1141
1259
|
// AppsFlyer module (lazy-loaded, only on native)
|
|
1142
1260
|
appsFlyerModule = null;
|
|
1143
|
-
// Analytics implementation - AppsFlyer on native, console fallback otherwise
|
|
1261
|
+
// Analytics implementation - AppsFlyer + Datadog on native (Capacitor builds), console fallback otherwise
|
|
1144
1262
|
analytics = {
|
|
1145
1263
|
trackFunnelStep: (step, name) => {
|
|
1146
1264
|
console.log(`${LOG_PREFIX} analytics.trackFunnelStep(${step}, "${name}")`);
|
|
1265
|
+
this.sendDatadogFunnel(step, name);
|
|
1147
1266
|
this.sendAppsFlyerEvent(`funnel_step_${step}`, { af_content: name });
|
|
1148
1267
|
},
|
|
1149
1268
|
recordCustomEvent: (eventName, params) => {
|
|
1150
1269
|
console.log(`${LOG_PREFIX} analytics.recordCustomEvent("${eventName}")`, params);
|
|
1270
|
+
this.sendDatadogCustomEvent(eventName, params ?? {});
|
|
1151
1271
|
this.sendAppsFlyerEvent(eventName, params ?? {});
|
|
1152
1272
|
}
|
|
1153
1273
|
};
|
|
@@ -1156,6 +1276,17 @@ var CapacitorPlatform = class {
|
|
|
1156
1276
|
this.appsFlyerModule.AppsFlyer.logEvent({ eventName, eventValue }).catch(() => {
|
|
1157
1277
|
});
|
|
1158
1278
|
}
|
|
1279
|
+
datadogInitialized = false;
|
|
1280
|
+
sendDatadogFunnel(step, name) {
|
|
1281
|
+
if (!this.datadogInitialized) return;
|
|
1282
|
+
DatadogAnalytics.trackFunnel({ step: name, screenName: name, stepNumber: step }).catch(() => {
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
sendDatadogCustomEvent(eventName, params) {
|
|
1286
|
+
if (!this.datadogInitialized) return;
|
|
1287
|
+
DatadogAnalytics.trackCustom({ name: eventName, ...params }).catch(() => {
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1159
1290
|
// Ads implementation
|
|
1160
1291
|
// TODO: Add @capacitor-community/admob when ready for monetization
|
|
1161
1292
|
ads = {
|
|
@@ -1250,13 +1381,23 @@ var CapacitorPlatform = class {
|
|
|
1250
1381
|
// Notifications implementation using @capacitor/local-notifications
|
|
1251
1382
|
notifications = {
|
|
1252
1383
|
scheduleAsync: async (title, body, delaySeconds, notificationId) => {
|
|
1384
|
+
if (!Capacitor.isNativePlatform()) {
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1253
1387
|
const id = notificationId ? stringToNotificationId(notificationId) : Date.now();
|
|
1254
1388
|
console.log(`${LOG_PREFIX} notifications.scheduleAsync("${title}", "${body}", ${delaySeconds}s, id=${id})`);
|
|
1255
1389
|
try {
|
|
1256
|
-
const
|
|
1257
|
-
|
|
1390
|
+
const existing = await LocalNotifications.checkPermissions();
|
|
1391
|
+
let permission = existing;
|
|
1392
|
+
if (existing.display !== "granted") {
|
|
1393
|
+
if (existing.display === "denied") {
|
|
1394
|
+
console.log(`${LOG_PREFIX} notifications: permission denied, skipping`);
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
permission = await LocalNotifications.requestPermissions();
|
|
1398
|
+
}
|
|
1258
1399
|
if (permission.display !== "granted") {
|
|
1259
|
-
console.
|
|
1400
|
+
console.log(`${LOG_PREFIX} notifications: permission not granted (${permission.display})`);
|
|
1260
1401
|
return;
|
|
1261
1402
|
}
|
|
1262
1403
|
await this.ensureNotificationChannel();
|
|
@@ -1310,6 +1451,12 @@ var CapacitorPlatform = class {
|
|
|
1310
1451
|
this.pauseCallbacks.push(callback);
|
|
1311
1452
|
}
|
|
1312
1453
|
};
|
|
1454
|
+
initDatadogAsync() {
|
|
1455
|
+
const platform = Capacitor.getPlatform();
|
|
1456
|
+
DatadogAnalytics.init({ serviceName: "burgertime-capacitor", serviceVersion: "0.0.0", platform });
|
|
1457
|
+
this.datadogInitialized = true;
|
|
1458
|
+
console.log(`${LOG_PREFIX} Datadog analytics: initialized (${platform})`);
|
|
1459
|
+
}
|
|
1313
1460
|
async initAppsFlyerAsync() {
|
|
1314
1461
|
console.log(`${LOG_PREFIX} AppsFlyer: initAppsFlyerAsync() called (native platform)`);
|
|
1315
1462
|
const devKey = import.meta.env?.VITE_APPSFLYER_DEV_KEY;
|
|
@@ -1359,6 +1506,7 @@ var CapacitorPlatform = class {
|
|
|
1359
1506
|
// Initialize
|
|
1360
1507
|
async initializeAsync(options) {
|
|
1361
1508
|
console.log(`${LOG_PREFIX} initializeAsync()`, options);
|
|
1509
|
+
this.initDatadogAsync();
|
|
1362
1510
|
if (Capacitor.isNativePlatform()) {
|
|
1363
1511
|
await this.initAppsFlyerAsync();
|
|
1364
1512
|
}
|
|
@@ -5533,6 +5681,8 @@ var StowKitSystem = class _StowKitSystem {
|
|
|
5533
5681
|
materialConverter;
|
|
5534
5682
|
decoderPaths = { ...DEFAULT_DECODER_PATHS };
|
|
5535
5683
|
fetchBlob;
|
|
5684
|
+
onTimingStart;
|
|
5685
|
+
onTimingEnd;
|
|
5536
5686
|
// Loaded packs by alias
|
|
5537
5687
|
packs = /* @__PURE__ */ new Map();
|
|
5538
5688
|
// Asset caches
|
|
@@ -5572,11 +5722,13 @@ var StowKitSystem = class _StowKitSystem {
|
|
|
5572
5722
|
* @param config Configuration including material converter and CDN fetch function
|
|
5573
5723
|
* @returns The loaded PrefabCollection
|
|
5574
5724
|
*/
|
|
5575
|
-
async loadFromBuildJson(buildJson,
|
|
5576
|
-
this.materialConverter =
|
|
5577
|
-
this.fetchBlob =
|
|
5578
|
-
|
|
5579
|
-
|
|
5725
|
+
async loadFromBuildJson(buildJson, config2) {
|
|
5726
|
+
this.materialConverter = config2.materialConverter;
|
|
5727
|
+
this.fetchBlob = config2.fetchBlob;
|
|
5728
|
+
this.onTimingStart = config2.onTimingStart;
|
|
5729
|
+
this.onTimingEnd = config2.onTimingEnd;
|
|
5730
|
+
if (config2.decoderPaths) {
|
|
5731
|
+
this.decoderPaths = { ...DEFAULT_DECODER_PATHS, ...config2.decoderPaths };
|
|
5580
5732
|
}
|
|
5581
5733
|
const prefabCollection = PrefabCollection.createFromJSON(
|
|
5582
5734
|
buildJson
|
|
@@ -5589,13 +5741,22 @@ var StowKitSystem = class _StowKitSystem {
|
|
|
5589
5741
|
await Promise.all(
|
|
5590
5742
|
mounts.filter((mount) => !this.packs.has(mount.alias)).map(async (mount) => {
|
|
5591
5743
|
console.log(`[StowKitSystem] Loading pack "${mount.alias}" from ${mount.path}`);
|
|
5592
|
-
const
|
|
5744
|
+
const fetchLabel = `StowKit: fetch - ${mount.alias}`;
|
|
5745
|
+
this.onTimingStart?.(fetchLabel);
|
|
5746
|
+
const blob = await config2.fetchBlob(mount.path);
|
|
5747
|
+
this.onTimingEnd?.(fetchLabel);
|
|
5748
|
+
const arrayBufferLabel = `StowKit: arrayBuffer - ${mount.alias}`;
|
|
5749
|
+
this.onTimingStart?.(arrayBufferLabel);
|
|
5593
5750
|
const arrayBuffer = await blob.arrayBuffer();
|
|
5751
|
+
this.onTimingEnd?.(arrayBufferLabel);
|
|
5752
|
+
const unpackLabel = `StowKit: unpack - ${mount.alias}`;
|
|
5753
|
+
this.onTimingStart?.(unpackLabel);
|
|
5594
5754
|
const pack = await StowKitLoader2.loadFromMemory(arrayBuffer, {
|
|
5595
5755
|
basisPath: this.decoderPaths.basis,
|
|
5596
5756
|
dracoPath: this.decoderPaths.draco,
|
|
5597
5757
|
wasmPath: this.decoderPaths.wasm
|
|
5598
5758
|
});
|
|
5759
|
+
this.onTimingEnd?.(unpackLabel);
|
|
5599
5760
|
this.packs.set(mount.alias, pack);
|
|
5600
5761
|
})
|
|
5601
5762
|
);
|
|
@@ -5620,13 +5781,22 @@ var StowKitSystem = class _StowKitSystem {
|
|
|
5620
5781
|
throw new Error("StowKitSystem: fetchBlob not configured. Call loadFromBuildJson first.");
|
|
5621
5782
|
}
|
|
5622
5783
|
console.log(`[StowKitSystem] Loading pack "${alias}" from ${path}`);
|
|
5784
|
+
const fetchLabel = `StowKit: fetch - ${alias}`;
|
|
5785
|
+
this.onTimingStart?.(fetchLabel);
|
|
5623
5786
|
const blob = await this.fetchBlob(path);
|
|
5787
|
+
this.onTimingEnd?.(fetchLabel);
|
|
5788
|
+
const arrayBufferLabel = `StowKit: arrayBuffer - ${alias}`;
|
|
5789
|
+
this.onTimingStart?.(arrayBufferLabel);
|
|
5624
5790
|
const arrayBuffer = await blob.arrayBuffer();
|
|
5791
|
+
this.onTimingEnd?.(arrayBufferLabel);
|
|
5792
|
+
const unpackLabel = `StowKit: unpack - ${alias}`;
|
|
5793
|
+
this.onTimingStart?.(unpackLabel);
|
|
5625
5794
|
const pack = await StowKitLoader2.loadFromMemory(arrayBuffer, {
|
|
5626
5795
|
basisPath: this.decoderPaths.basis,
|
|
5627
5796
|
dracoPath: this.decoderPaths.draco,
|
|
5628
5797
|
wasmPath: this.decoderPaths.wasm
|
|
5629
5798
|
});
|
|
5799
|
+
this.onTimingEnd?.(unpackLabel);
|
|
5630
5800
|
this.packs.set(alias, pack);
|
|
5631
5801
|
console.log(`[StowKitSystem] Pack "${alias}" loaded`);
|
|
5632
5802
|
}
|
|
@@ -6242,4 +6412,4 @@ export {
|
|
|
6242
6412
|
PrefabLoader,
|
|
6243
6413
|
StowKitSystem
|
|
6244
6414
|
};
|
|
6245
|
-
//# sourceMappingURL=chunk-
|
|
6415
|
+
//# sourceMappingURL=chunk-WLXQBO3A.js.map
|