@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.
- package/LICENSE +201 -0
- package/README.md +89 -0
- package/bridge/README.md +102 -0
- package/bridge/UptimizrGodot.cs +185 -0
- package/bridge/UptimizrGodot.gd +178 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/uptimizr-godot.global.js +58 -0
- package/dist/uptimizr-godot.js +58 -0
- package/package.json +63 -0
|
@@ -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)
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|