@uptimizr/godot 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.
@@ -0,0 +1,178 @@
1
+ # UptimizrGodot.gd — engine-side bridge for the @uptimizr/godot connector (ADR 0045).
2
+ #
3
+ # Copy-in asset (Apache-2.0). NOT an npm package. Add it as an Autoload singleton in a
4
+ # Godot 4 project to enable the connector's *bridged tier* (camera pose / world-space
5
+ # picks / FPS / scene proxy). The *JS-only tier* (pointer heatmaps + FPS + JS errors)
6
+ # already works with no engine code.
7
+ #
8
+ # Setup:
9
+ # 1. Load @uptimizr/godot in the Web export's host page and call `trackGodot(...)`,
10
+ # which exposes the bridge on `window.__uptimizr_godot__`.
11
+ # 2. Project > Project Settings > Globals > Autoload: add this script as `UptimizrGodot`.
12
+ #
13
+ # This shim carries NO analytics logic, IDs, or schema knowledge. It reads the engine's
14
+ # own camera / raycast / perf and hands **world-space values in Godot's NATIVE frame**
15
+ # (right-handed, y-up, meters) to the connector, which owns all normalization (negates Z)
16
+ # and schema mapping.
17
+ #
18
+ # Privacy (ADR 0003): only low-cardinality, non-PII telemetry crosses the bridge — poses,
19
+ # FPS, and developer-named objects. It MUST NOT invent identifiers or forward raw input.
20
+ extends Node
21
+
22
+ ## Expected bridge wire-protocol version (must match BRIDGE_PROTOCOL_VERSION in
23
+ ## @uptimizr/web-export). The shim refuses to push if the page's bridge disagrees.
24
+ const PROTOCOL_VERSION := 1
25
+
26
+ ## The `window` property the connector exposes the bridge on (the connector's
27
+ ## `bridgeGlobal`; defaults to `__uptimizr_<name>__`).
28
+ const BRIDGE_GLOBAL := "__uptimizr_godot__"
29
+
30
+ ## Push at most this many camera poses per second (the connector also throttles its
31
+ ## screen-space tier). Keeps per-frame JS-array allocation bounded. Set <= 0 to push
32
+ ## every frame.
33
+ @export var pose_samples_per_second: float = 30.0
34
+
35
+ ## When true, a left mouse click casts a ray from the camera through the pointer and
36
+ ## pushes the first named collider it hits as a pick.
37
+ @export var capture_picks: bool = true
38
+
39
+ ## Physics ray length (metres) used for click picks.
40
+ @export var pick_ray_length: float = 1000.0
41
+
42
+ ## Nodes in this group are included when `push_scene_proxy()` is called. Opt-in by
43
+ ## design so only developer-marked, named objects are described (privacy + low
44
+ ## cardinality). Add `VisualInstance3D` nodes to it via `add_to_group("uptimizr_tracked")`.
45
+ const SCENE_PROXY_GROUP := "uptimizr_tracked"
46
+
47
+ var _bridge: JavaScriptObject = null
48
+ var _ready_ok := false
49
+ var _accum := 0.0
50
+
51
+
52
+ func _ready() -> void:
53
+ # The bridge only exists in a Web export; no-op everywhere else.
54
+ if not OS.has_feature("web"):
55
+ set_process(false)
56
+ set_process_unhandled_input(false)
57
+ return
58
+
59
+ _bridge = JavaScriptBridge.get_interface(BRIDGE_GLOBAL)
60
+ if _bridge == null:
61
+ push_warning("[Uptimizr] bridge global '%s' not found — call trackGodot() in the host page before this autoload runs." % BRIDGE_GLOBAL)
62
+ set_process(false)
63
+ set_process_unhandled_input(false)
64
+ return
65
+
66
+ # Refuse to push against an incompatible bridge contract.
67
+ var version := int(_bridge.protocolVersion)
68
+ if version != PROTOCOL_VERSION:
69
+ push_warning("[Uptimizr] bridge protocol mismatch (page=%d, shim=%d) — disabling pose/pick capture." % [version, PROTOCOL_VERSION])
70
+ _bridge = null
71
+ set_process(false)
72
+ set_process_unhandled_input(false)
73
+ return
74
+
75
+ _ready_ok = true
76
+
77
+
78
+ func _process(delta: float) -> void:
79
+ if not _ready_ok:
80
+ return
81
+
82
+ if pose_samples_per_second > 0.0:
83
+ _accum += delta
84
+ var interval := 1.0 / pose_samples_per_second
85
+ if _accum < interval:
86
+ # Still report FPS every frame even when skipping a pose sample.
87
+ _push_perf()
88
+ return
89
+ _accum = 0.0
90
+
91
+ _push_pose()
92
+ _push_perf()
93
+
94
+
95
+ func _push_pose() -> void:
96
+ var cam := get_viewport().get_camera_3d()
97
+ if cam == null:
98
+ return
99
+ var xform := cam.global_transform
100
+ var p := xform.origin
101
+ # Godot cameras look down their local -Z; pass the WORLD-space forward vector.
102
+ var f := -xform.basis.z
103
+ var u := xform.basis.y
104
+ # Camera3D.fov is vertical degrees by default (KEEP_HEIGHT); the bridge wants radians.
105
+ _bridge.pushPose(_vec3(p), _vec3(f), _vec3(u), deg_to_rad(cam.fov))
106
+
107
+
108
+ func _push_perf() -> void:
109
+ _bridge.pushPerf(Engine.get_frames_per_second())
110
+
111
+
112
+ func _unhandled_input(event: InputEvent) -> void:
113
+ if not _ready_ok or not capture_picks:
114
+ return
115
+ if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
116
+ _push_pick_at(event.position)
117
+
118
+
119
+ func _push_pick_at(screen_pos: Vector2) -> void:
120
+ var cam := get_viewport().get_camera_3d()
121
+ if cam == null:
122
+ return
123
+ var from := cam.project_ray_origin(screen_pos)
124
+ var to := from + cam.project_ray_normal(screen_pos) * pick_ray_length
125
+ var space := cam.get_world_3d().direct_space_state
126
+ var query := PhysicsRayQueryParameters3D.create(from, to)
127
+ var hit := space.intersect_ray(query)
128
+ if hit.is_empty():
129
+ return
130
+ var collider: Object = hit.get("collider")
131
+ if collider == null or not (collider is Node):
132
+ return
133
+ # Only the developer-assigned node name and the world hit point are sent (ADR 0003).
134
+ var point: Vector3 = hit.get("position")
135
+ _bridge.pushPick(String((collider as Node).name), _vec3(point))
136
+
137
+
138
+ ## Push a spatial proxy of the named, developer-marked nodes in `SCENE_PROXY_GROUP`.
139
+ ## Call this once after your scene is built (it is NOT automatic — opt-in keeps the proxy
140
+ ## low-cardinality and free of incidental geometry). Sends world-space AABBs in Godot's
141
+ ## native frame; the connector normalizes them.
142
+ func push_scene_proxy() -> void:
143
+ if not _ready_ok:
144
+ return
145
+ # `create_object` is vararg, so GDScript types its result as Variant — declare the
146
+ # type explicitly or `inference_on_variant` (an error by default) rejects the script.
147
+ var nodes: JavaScriptObject = JavaScriptBridge.create_object("Array")
148
+ for node in get_tree().get_nodes_in_group(SCENE_PROXY_GROUP):
149
+ if not (node is VisualInstance3D):
150
+ continue
151
+ var vi := node as VisualInstance3D
152
+ var local := vi.get_aabb()
153
+ var world := vi.global_transform * local
154
+ var min_p := world.position
155
+ var max_p := world.position + world.size
156
+ var entry: JavaScriptObject = JavaScriptBridge.create_object("Object")
157
+ entry.name = String(vi.name)
158
+ entry.aabb = JavaScriptBridge.create_object(
159
+ "Array", min_p.x, min_p.y, min_p.z, max_p.x, max_p.y, max_p.z
160
+ )
161
+ nodes.push(entry)
162
+ _bridge.setSceneProxy(nodes)
163
+
164
+
165
+ ## Detach the bridge; subsequent pushes become no-ops on the JS side too.
166
+ func dispose() -> void:
167
+ if _bridge != null:
168
+ _bridge.dispose()
169
+ _bridge = null
170
+ _ready_ok = false
171
+ set_process(false)
172
+ set_process_unhandled_input(false)
173
+
174
+
175
+ # Build a JS `[x, y, z]` array the connector can read. Raw GDScript Arrays do not
176
+ # auto-marshal across the JS boundary, so we construct a real JS Array via the bridge.
177
+ func _vec3(v: Vector3) -> JavaScriptObject:
178
+ return JavaScriptBridge.create_object("Array", v.x, v.y, v.z)
@@ -0,0 +1,22 @@
1
+ import type { EngineBridge, NativeFrame, TrackWebExportOptions, WebExportCollectorOptions, WebExportSession } from "@uptimizr/web-export";
2
+ import type { Collector } from "@uptimizr/sdk-core";
3
+ /** Godot's native world coordinate frame: right-handed, y-up, meters. */
4
+ export declare const GODOT_FRAME: NativeFrame;
5
+ /** The engine id used for connector provenance and the collector name. */
6
+ export declare const GODOT_CONNECTOR_NAME = "godot";
7
+ export type GodotCollectorOptions = Omit<WebExportCollectorOptions, "name" | "frame">;
8
+ export type TrackGodotOptions = Omit<TrackWebExportOptions, "name" | "frame">;
9
+ /**
10
+ * The Godot collector — register it with an sdk-core client via `client.use(...)`.
11
+ * Wires the JS-only tier and exposes the engine bridge (default global
12
+ * `window.__uptimizr_godot__`) for the engine-side shim.
13
+ */
14
+ export declare function godotCollector(options?: GodotCollectorOptions): Collector;
15
+ /**
16
+ * One-call Godot integration: create a client, register {@link godotCollector}, and
17
+ * start the session with Godot's connector provenance (ADR 0018). Returns the client
18
+ * and the {@link EngineBridge} the Godot shim pushes through.
19
+ */
20
+ export declare function trackGodot(options: TrackGodotOptions): WebExportSession;
21
+ export type { EngineBridge };
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,yBAAyB,EACzB,gBAAgB,EACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,yEAAyE;AACzE,eAAO,MAAM,WAAW,EAAE,WAAgE,CAAC;AAE3F,0EAA0E;AAC1E,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAE5C,MAAM,MAAM,qBAAqB,GAAG,IAAI,CAAC,yBAAyB,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;AACtF,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,qBAAqB,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;AAE9E;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,SAAS,CAE7E;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAEvE;AAED,YAAY,EAAE,YAAY,EAAE,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * `@uptimizr/godot` — the Godot 4 (Web export) connector for Uptimizr (ADR 0045).
3
+ *
4
+ * Godot compiles to WebAssembly and renders into a `<canvas>`, so there is no live
5
+ * JS scene to read. This connector is **two-part**:
6
+ *
7
+ * - a **JS-only tier** (this package, no engine code) — pointer heatmaps, rAF FPS,
8
+ * and error capture straight from the canvas DOM; and
9
+ * - a **bridged tier** — a thin engine-side shim (a GDScript or C# autoload using
10
+ * `JavaScriptBridge`, see `bridge/`) pushes camera pose / picks / perf over the
11
+ * versioned {@link EngineBridge} for view-direction heatmaps, world-space gaze,
12
+ * and replay.
13
+ *
14
+ * Godot's native world frame is **right-handed, y-up, meters**, so world-space
15
+ * payloads are normalized to the canonical frame (left-handed, y-up) by negating Z
16
+ * at the emission boundary (ADR 0018).
17
+ */
18
+ import { trackWebExport, webExportCollector } from "@uptimizr/web-export";
19
+ /** Godot's native world coordinate frame: right-handed, y-up, meters. */
20
+ export const GODOT_FRAME = { handedness: "right", upAxis: "y", unitScale: 1 };
21
+ /** The engine id used for connector provenance and the collector name. */
22
+ export const GODOT_CONNECTOR_NAME = "godot";
23
+ /**
24
+ * The Godot collector — register it with an sdk-core client via `client.use(...)`.
25
+ * Wires the JS-only tier and exposes the engine bridge (default global
26
+ * `window.__uptimizr_godot__`) for the engine-side shim.
27
+ */
28
+ export function godotCollector(options = {}) {
29
+ return webExportCollector({ ...options, name: GODOT_CONNECTOR_NAME, frame: GODOT_FRAME });
30
+ }
31
+ /**
32
+ * One-call Godot integration: create a client, register {@link godotCollector}, and
33
+ * start the session with Godot's connector provenance (ADR 0018). Returns the client
34
+ * and the {@link EngineBridge} the Godot shim pushes through.
35
+ */
36
+ export function trackGodot(options) {
37
+ return trackWebExport({ ...options, name: GODOT_CONNECTOR_NAME, frame: GODOT_FRAME });
38
+ }
39
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAU1E,yEAAyE;AACzE,MAAM,CAAC,MAAM,WAAW,GAAgB,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;AAE3F,0EAA0E;AAC1E,MAAM,CAAC,MAAM,oBAAoB,GAAG,OAAO,CAAC;AAK5C;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,UAAiC,EAAE;IAChE,OAAO,kBAAkB,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;AAC5F,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,OAA0B;IACnD,OAAO,cAAc,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;AACxF,CAAC"}