@aura3d/scripting 1.4.0 → 1.4.2

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.
@@ -1,22 +0,0 @@
1
- import type { Behavior } from "./Behavior";
2
-
3
- export type BehaviorFactory<T extends Behavior = Behavior> = () => T;
4
-
5
- export class BehaviorRegistry {
6
- private readonly factories = new Map<string, BehaviorFactory>();
7
-
8
- register(type: string, factory: BehaviorFactory): void {
9
- if (this.factories.has(type)) {
10
- throw new Error(`Behavior type already registered: ${type}`);
11
- }
12
- this.factories.set(type, factory);
13
- }
14
-
15
- create(type: string): Behavior {
16
- const factory = this.factories.get(type);
17
- if (!factory) {
18
- throw new Error(`Unknown behavior type: ${type}`);
19
- }
20
- return factory();
21
- }
22
- }
@@ -1,103 +0,0 @@
1
- import type { Behavior, BehaviorPhase } from "./Behavior";
2
- import type { BehaviorHost } from "./BehaviorHost";
3
- import { ScriptContext } from "./ScriptContext";
4
-
5
- export interface BehaviorError {
6
- readonly phase: BehaviorPhase;
7
- readonly behavior: Behavior;
8
- readonly error: unknown;
9
- }
10
-
11
- export interface BehaviorSystemUpdateOptions {
12
- readonly deltaSeconds?: number;
13
- readonly fixedDeltaSeconds?: number;
14
- }
15
-
16
- export class BehaviorSystem {
17
- readonly errors: BehaviorError[] = [];
18
-
19
- private readonly hosts = new Set<BehaviorHost>();
20
- private readonly started = new WeakSet<Behavior>();
21
- private readonly services = new Map<string, unknown>();
22
-
23
- registerHost(host: BehaviorHost): void {
24
- this.hosts.add(host);
25
- }
26
-
27
- unregisterHost(host: BehaviorHost): void {
28
- this.hosts.delete(host);
29
- }
30
-
31
- setService(name: string, value: unknown): void {
32
- this.services.set(name, value);
33
- }
34
-
35
- async fixedUpdate(options: BehaviorSystemUpdateOptions = {}): Promise<void> {
36
- await this.runPhase("fixed", options);
37
- }
38
-
39
- async update(options: BehaviorSystemUpdateOptions = {}): Promise<void> {
40
- await this.runStart(options);
41
- await this.runPhase("update", options);
42
- }
43
-
44
- private async runStart(options: BehaviorSystemUpdateOptions): Promise<void> {
45
- for (const host of [...this.hosts]) {
46
- if (host.isDestroyed) {
47
- this.hosts.delete(host);
48
- continue;
49
- }
50
-
51
- for (const behavior of host.list()) {
52
- if (behavior.enabled === false || this.started.has(behavior)) {
53
- continue;
54
- }
55
- this.started.add(behavior);
56
- await this.invoke("start", behavior, host, options);
57
- }
58
- }
59
- }
60
-
61
- private async runPhase(phase: "fixed" | "update", options: BehaviorSystemUpdateOptions): Promise<void> {
62
- for (const host of [...this.hosts]) {
63
- if (host.isDestroyed) {
64
- this.hosts.delete(host);
65
- continue;
66
- }
67
-
68
- for (const behavior of host.list()) {
69
- if (behavior.enabled === false) {
70
- continue;
71
- }
72
- await this.invoke(phase, behavior, host, options);
73
- }
74
- }
75
- }
76
-
77
- private async invoke(
78
- phase: BehaviorPhase,
79
- behavior: Behavior,
80
- host: BehaviorHost,
81
- options: BehaviorSystemUpdateOptions
82
- ): Promise<void> {
83
- const context = new ScriptContext({
84
- phase,
85
- deltaSeconds: options.deltaSeconds,
86
- fixedDeltaSeconds: options.fixedDeltaSeconds,
87
- target: host.target,
88
- services: this.services
89
- });
90
-
91
- try {
92
- if (phase === "start") {
93
- await behavior.onStart?.(context);
94
- } else if (phase === "fixed") {
95
- await behavior.onFixedUpdate?.(context);
96
- } else if (phase === "update") {
97
- await behavior.onUpdate?.(context);
98
- }
99
- } catch (error) {
100
- this.errors.push({ phase, behavior, error });
101
- }
102
- }
103
- }
@@ -1,234 +0,0 @@
1
- export type BehaviorTreeStatus = "success" | "failure" | "running";
2
- export type BlackboardValue = number | string | boolean | readonly unknown[] | Record<string, unknown> | null | undefined;
3
-
4
- export interface BlackboardChange {
5
- readonly key: string;
6
- readonly value: BlackboardValue;
7
- readonly previous: BlackboardValue;
8
- readonly version: number;
9
- }
10
-
11
- export interface BehaviorTreeContext {
12
- readonly blackboard: Blackboard;
13
- readonly deltaSeconds: number;
14
- readonly depth: number;
15
- }
16
-
17
- export interface BehaviorTreeTickResult {
18
- readonly status: BehaviorTreeStatus;
19
- readonly tickCount: number;
20
- readonly trace: readonly string[];
21
- readonly blackboardVersion: number;
22
- }
23
-
24
- export abstract class BehaviorTreeNode {
25
- public status: BehaviorTreeStatus = "failure";
26
- public running = false;
27
-
28
- constructor(public readonly name: string) {}
29
-
30
- abstract tick(context: BehaviorTreeContext): BehaviorTreeStatus;
31
-
32
- reset(): void {
33
- this.status = "failure";
34
- this.running = false;
35
- }
36
- }
37
-
38
- export class Blackboard {
39
- private readonly values = new Map<string, { readonly value: BlackboardValue; readonly version: number }>();
40
- private readonly changes: BlackboardChange[] = [];
41
- private localVersion = 0;
42
-
43
- constructor(public readonly parent?: Blackboard) {}
44
-
45
- get version(): number {
46
- return this.localVersion;
47
- }
48
-
49
- set(key: string, value: BlackboardValue): void {
50
- const previous = this.get(key);
51
- this.localVersion += 1;
52
- this.values.set(key, { value, version: this.localVersion });
53
- this.changes.push({ key, value, previous, version: this.localVersion });
54
- }
55
-
56
- get<T extends BlackboardValue = BlackboardValue>(key: string, fallback?: T): T {
57
- const local = this.values.get(key);
58
- if (local) return local.value as T;
59
- if (this.parent?.has(key)) return this.parent.get<T>(key, fallback);
60
- return fallback as T;
61
- }
62
-
63
- has(key: string): boolean {
64
- return this.values.has(key) || this.parent?.has(key) === true;
65
- }
66
-
67
- snapshot(): Record<string, BlackboardValue> {
68
- const inherited = this.parent?.snapshot() ?? {};
69
- return { ...inherited, ...Object.fromEntries([...this.values].map(([key, entry]) => [key, entry.value])) };
70
- }
71
-
72
- changeLog(): readonly BlackboardChange[] {
73
- return [...this.changes];
74
- }
75
- }
76
-
77
- export class BehaviorAction extends BehaviorTreeNode {
78
- constructor(name: string, private readonly action: (context: BehaviorTreeContext) => BehaviorTreeStatus) {
79
- super(name);
80
- }
81
-
82
- tick(context: BehaviorTreeContext): BehaviorTreeStatus {
83
- this.running = true;
84
- this.status = this.action(context);
85
- if (this.status !== "running") this.running = false;
86
- return this.status;
87
- }
88
- }
89
-
90
- export class BehaviorCondition extends BehaviorTreeNode {
91
- constructor(name: string, private readonly condition: (context: BehaviorTreeContext) => boolean) {
92
- super(name);
93
- }
94
-
95
- tick(context: BehaviorTreeContext): BehaviorTreeStatus {
96
- this.status = this.condition(context) ? "success" : "failure";
97
- return this.status;
98
- }
99
- }
100
-
101
- export class BehaviorSequence extends BehaviorTreeNode {
102
- private currentIndex = 0;
103
-
104
- constructor(name: string, public readonly children: readonly BehaviorTreeNode[]) {
105
- super(name);
106
- }
107
-
108
- tick(context: BehaviorTreeContext): BehaviorTreeStatus {
109
- this.running = true;
110
- while (this.currentIndex < this.children.length) {
111
- const child = this.children[this.currentIndex]!;
112
- const status = child.tick({ ...context, depth: context.depth + 1 });
113
- if (status === "failure") {
114
- this.reset();
115
- return "failure";
116
- }
117
- if (status === "running") {
118
- this.status = "running";
119
- return "running";
120
- }
121
- this.currentIndex += 1;
122
- }
123
- this.reset();
124
- this.status = "success";
125
- return "success";
126
- }
127
-
128
- override reset(): void {
129
- super.reset();
130
- this.currentIndex = 0;
131
- for (const child of this.children) child.reset();
132
- }
133
- }
134
-
135
- export class BehaviorSelector extends BehaviorTreeNode {
136
- private currentIndex = 0;
137
-
138
- constructor(name: string, public readonly children: readonly BehaviorTreeNode[]) {
139
- super(name);
140
- }
141
-
142
- tick(context: BehaviorTreeContext): BehaviorTreeStatus {
143
- this.running = true;
144
- while (this.currentIndex < this.children.length) {
145
- const child = this.children[this.currentIndex]!;
146
- const status = child.tick({ ...context, depth: context.depth + 1 });
147
- if (status === "success") {
148
- this.reset();
149
- return "success";
150
- }
151
- if (status === "running") {
152
- this.status = "running";
153
- return "running";
154
- }
155
- this.currentIndex += 1;
156
- }
157
- this.reset();
158
- this.status = "failure";
159
- return "failure";
160
- }
161
-
162
- override reset(): void {
163
- super.reset();
164
- this.currentIndex = 0;
165
- for (const child of this.children) child.reset();
166
- }
167
- }
168
-
169
- export class BehaviorTree {
170
- private tickCounter = 0;
171
- private lastTrace: string[] = [];
172
-
173
- constructor(public readonly root: BehaviorTreeNode, public readonly blackboard = new Blackboard()) {}
174
-
175
- tick(deltaSeconds: number): BehaviorTreeTickResult {
176
- this.tickCounter += 1;
177
- this.lastTrace = [];
178
- const status = this.tickNode(this.root, { blackboard: this.blackboard, deltaSeconds, depth: 0 });
179
- return {
180
- status,
181
- tickCount: this.tickCounter,
182
- trace: [...this.lastTrace],
183
- blackboardVersion: this.blackboard.version
184
- };
185
- }
186
-
187
- reset(): void {
188
- this.root.reset();
189
- this.lastTrace = [];
190
- this.tickCounter = 0;
191
- }
192
-
193
- private tickNode(node: BehaviorTreeNode, context: BehaviorTreeContext): BehaviorTreeStatus {
194
- const status = node.tick({
195
- ...context,
196
- blackboard: new TracingBlackboard(context.blackboard, (key, value) => {
197
- this.lastTrace.push(`${node.name}:${key}=${String(value)}`);
198
- })
199
- });
200
- this.lastTrace.push(`${node.name}:${status}`);
201
- return status;
202
- }
203
- }
204
-
205
- class TracingBlackboard extends Blackboard {
206
- constructor(private readonly target: Blackboard, private readonly onSet: (key: string, value: BlackboardValue) => void) {
207
- super();
208
- }
209
-
210
- override get version(): number {
211
- return this.target.version;
212
- }
213
-
214
- override set(key: string, value: BlackboardValue): void {
215
- this.target.set(key, value);
216
- this.onSet(key, value);
217
- }
218
-
219
- override get<T extends BlackboardValue = BlackboardValue>(key: string, fallback?: T): T {
220
- return this.target.get(key, fallback);
221
- }
222
-
223
- override has(key: string): boolean {
224
- return this.target.has(key);
225
- }
226
-
227
- override snapshot(): Record<string, BlackboardValue> {
228
- return this.target.snapshot();
229
- }
230
-
231
- override changeLog(): readonly BlackboardChange[] {
232
- return this.target.changeLog();
233
- }
234
- }
@@ -1,225 +0,0 @@
1
- export type CloudFixtureServiceStatus = "offline-cache" | "queued" | "synced" | "simulated-match" | "blocked";
2
-
3
- export interface CloudServiceFixtureOptions {
4
- readonly seed?: number;
5
- readonly playerId?: string;
6
- readonly score?: number;
7
- readonly offlineMode?: boolean;
8
- readonly sessionSeconds?: number;
9
- }
10
-
11
- export interface CloudServiceFixture {
12
- readonly source: "origin-master-cloud-services-adapted";
13
- readonly services: {
14
- readonly authentication: {
15
- readonly provider: "guest";
16
- readonly authenticated: false;
17
- readonly offlineUserId: string;
18
- readonly tokenIssued: false;
19
- readonly status: CloudFixtureServiceStatus;
20
- };
21
- readonly cloudSave: {
22
- readonly saveId: "runtime-progress";
23
- readonly version: number;
24
- readonly checksum: string;
25
- readonly queuedUploads: number;
26
- readonly conflictResolution: "last-write-wins" | "merge" | "manual";
27
- readonly status: CloudFixtureServiceStatus;
28
- };
29
- readonly achievements: {
30
- readonly unlocked: readonly string[];
31
- readonly inProgress: readonly { readonly id: string; readonly progress: number }[];
32
- readonly totalPoints: number;
33
- readonly status: CloudFixtureServiceStatus;
34
- };
35
- readonly leaderboard: {
36
- readonly id: "runtime-score";
37
- readonly score: number;
38
- readonly rank: number;
39
- readonly timeFrame: "daily" | "weekly" | "all-time";
40
- readonly cachedEntries: number;
41
- readonly status: CloudFixtureServiceStatus;
42
- };
43
- readonly remoteConfig: {
44
- readonly activated: true;
45
- readonly abTestGroup: "control" | "variant-a" | "variant-b";
46
- readonly parameterCount: number;
47
- readonly difficultyScale: number;
48
- readonly eventMultiplier: number;
49
- readonly status: CloudFixtureServiceStatus;
50
- };
51
- readonly matchmaking: {
52
- readonly queue: "casual-runtime";
53
- readonly region: "local";
54
- readonly ticketId: string;
55
- readonly estimatedWaitMs: number;
56
- readonly matchedPlayers: number;
57
- readonly status: CloudFixtureServiceStatus;
58
- };
59
- readonly contentDelivery: {
60
- readonly manifestId: "runtime-content";
61
- readonly assetCount: number;
62
- readonly cacheHits: number;
63
- readonly integrityHashes: readonly string[];
64
- readonly status: CloudFixtureServiceStatus;
65
- };
66
- };
67
- readonly productionReadiness: {
68
- readonly cloudServiceTelemetry: true;
69
- readonly offlineQueueTelemetry: true;
70
- readonly remoteConfigTelemetry: true;
71
- readonly leaderboardTelemetry: true;
72
- readonly matchmakingTelemetry: true;
73
- readonly contentDeliveryTelemetry: true;
74
- };
75
- readonly blockedClaims: readonly string[];
76
- readonly claimBoundary: string;
77
- readonly hash: string;
78
- }
79
-
80
- export function sampleCloudServiceFixture(options: CloudServiceFixtureOptions = {}): CloudServiceFixture {
81
- const seed = normalizeSeed(options.seed ?? 0xc10d);
82
- const playerId = normalizePlayerId(options.playerId ?? "external-parity-runtime-player");
83
- const score = positiveInteger(options.score ?? 12_400, "score");
84
- const sessionSeconds = positiveInteger(options.sessionSeconds ?? 180, "sessionSeconds");
85
- const offlineMode = options.offlineMode ?? true;
86
- const abTestGroup = seededChoice(seed, ["control", "variant-a", "variant-b"] as const);
87
- const difficultyScale = Number((0.86 + seededUnit(seed ^ 0x55a5) * 0.24).toFixed(3));
88
- const eventMultiplier = Number((1 + Math.min(0.5, sessionSeconds / 1800)).toFixed(3));
89
- const version = 3 + Math.floor(seededUnit(seed ^ 0x911) * 6);
90
- const savePayload = JSON.stringify({ playerId, score, sessionSeconds, version });
91
- const cloudSaveChecksum = stableHash(savePayload);
92
- const achievements = [
93
- { id: "first-run", points: 10, progress: 1 },
94
- { id: "collector", points: 25, progress: Math.min(1, score / 20_000) },
95
- { id: "speed-clear", points: 50, progress: Math.min(1, sessionSeconds <= 240 ? 1 : 240 / sessionSeconds) }
96
- ] as const;
97
- const unlocked = achievements.filter((achievement) => achievement.progress >= 1).map((achievement) => achievement.id);
98
- const inProgress = achievements.filter((achievement) => achievement.progress < 1).map((achievement) => ({
99
- id: achievement.id,
100
- progress: Number(achievement.progress.toFixed(3))
101
- }));
102
- const rank = Math.max(1, Math.round(5000 / Math.max(1, score / 500)));
103
- const integrityHashes = [
104
- stableHash(`runtime-content:${seed}:arena`),
105
- stableHash(`runtime-content:${seed}:hero`),
106
- stableHash(`runtime-content:${seed}:config`)
107
- ] as const;
108
- const status: CloudFixtureServiceStatus = offlineMode ? "offline-cache" : "queued";
109
- const fixture: Omit<CloudServiceFixture, "hash"> = {
110
- source: "origin-master-cloud-services-adapted",
111
- services: {
112
- authentication: {
113
- provider: "guest",
114
- authenticated: false,
115
- offlineUserId: `guest-${stableHash(playerId).slice(0, 6)}`,
116
- tokenIssued: false,
117
- status
118
- },
119
- cloudSave: {
120
- saveId: "runtime-progress",
121
- version,
122
- checksum: cloudSaveChecksum,
123
- queuedUploads: offlineMode ? 1 : 0,
124
- conflictResolution: "last-write-wins",
125
- status: offlineMode ? "queued" : "synced"
126
- },
127
- achievements: {
128
- unlocked,
129
- inProgress,
130
- totalPoints: achievements.filter((achievement) => achievement.progress >= 1).reduce((sum, achievement) => sum + achievement.points, 0),
131
- status
132
- },
133
- leaderboard: {
134
- id: "runtime-score",
135
- score,
136
- rank,
137
- timeFrame: "weekly",
138
- cachedEntries: 5 + Math.floor(seededUnit(seed ^ 0x7717) * 8),
139
- status
140
- },
141
- remoteConfig: {
142
- activated: true,
143
- abTestGroup,
144
- parameterCount: 4,
145
- difficultyScale,
146
- eventMultiplier,
147
- status
148
- },
149
- matchmaking: {
150
- queue: "casual-runtime",
151
- region: "local",
152
- ticketId: `ticket-${stableHash(`${playerId}:${score}`).slice(0, 8)}`,
153
- estimatedWaitMs: Math.round(1500 + seededUnit(seed ^ 0x4d47) * 3000),
154
- matchedPlayers: 2,
155
- status: "simulated-match"
156
- },
157
- contentDelivery: {
158
- manifestId: "runtime-content",
159
- assetCount: integrityHashes.length,
160
- cacheHits: offlineMode ? integrityHashes.length : Math.max(1, integrityHashes.length - 1),
161
- integrityHashes,
162
- status
163
- }
164
- },
165
- productionReadiness: {
166
- cloudServiceTelemetry: true,
167
- offlineQueueTelemetry: true,
168
- remoteConfigTelemetry: true,
169
- leaderboardTelemetry: true,
170
- matchmakingTelemetry: true,
171
- contentDeliveryTelemetry: true
172
- },
173
- blockedClaims: [
174
- "real cloud authentication backend",
175
- "production cloud-save conflict resolution",
176
- "hosted CDN/content-delivery parity",
177
- "real multiplayer matchmaking service",
178
- "Unity Gaming Services parity",
179
- "Unreal Online Services parity",
180
- "privacy/compliance/security certification"
181
- ],
182
- claimBoundary: "This fixture ports old cloud-service concepts into deterministic local telemetry. It does not contact a backend, issue real auth tokens, upload saves, submit leaderboards, run CDN delivery, create multiplayer matches, or claim Unity Gaming Services or Unreal Online Services parity."
183
- };
184
- return {
185
- ...fixture,
186
- hash: stableHash(JSON.stringify(fixture))
187
- };
188
- }
189
-
190
- function normalizeSeed(seed: number): number {
191
- if (!Number.isInteger(seed) || seed < 0) throw new Error("seed must be a non-negative integer.");
192
- return seed >>> 0;
193
- }
194
-
195
- function normalizePlayerId(playerId: string): string {
196
- const trimmed = playerId.trim();
197
- if (trimmed.length === 0) throw new Error("playerId must not be empty.");
198
- return trimmed;
199
- }
200
-
201
- function positiveInteger(value: number, name: string): number {
202
- if (!Number.isInteger(value) || value <= 0) throw new Error(`${name} must be a positive integer.`);
203
- return value;
204
- }
205
-
206
- function seededChoice<T>(seed: number, values: readonly T[]): T {
207
- return values[Math.min(values.length - 1, Math.floor(seededUnit(seed) * values.length))] as T;
208
- }
209
-
210
- function seededUnit(seed: number): number {
211
- let value = seed >>> 0;
212
- value ^= value << 13;
213
- value ^= value >>> 17;
214
- value ^= value << 5;
215
- return (value >>> 0) / 0xffffffff;
216
- }
217
-
218
- function stableHash(value: string): string {
219
- let hash = 2166136261;
220
- for (let index = 0; index < value.length; index += 1) {
221
- hash ^= value.charCodeAt(index);
222
- hash = Math.imul(hash, 16777619);
223
- }
224
- return (hash >>> 0).toString(16).padStart(8, "0");
225
- }