@aura3d/scripting 1.4.0 → 1.4.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aura3d/scripting",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -9,5 +9,35 @@
9
9
  }
10
10
  },
11
11
  "main": "./dist/index.js",
12
- "types": "./dist/index.d.ts"
12
+ "types": "./dist/index.d.ts",
13
+ "description": "Behavior attachment, runtime phases, script context data, behavior registries, visual graph validation, graph serialization, and deterministic visual graph execution.",
14
+ "keywords": [
15
+ "aura3d",
16
+ "3d",
17
+ "browser-3d",
18
+ "web-3d",
19
+ "typescript",
20
+ "scripting",
21
+ "visual-scripting",
22
+ "behavior-tree",
23
+ "runtime-scripting",
24
+ "node-graph"
25
+ ],
26
+ "homepage": "https://aura3d.auraone.ai",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/auraoneai/aura3d.git",
30
+ "directory": "packages/scripting"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/auraoneai/aura3d/issues"
34
+ },
35
+ "license": "MIT",
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "files": [
40
+ "README.md",
41
+ "dist"
42
+ ]
13
43
  }
@@ -1,324 +0,0 @@
1
- export type AdaptiveDifficultyMetricType =
2
- | "win-rate"
3
- | "death-rate"
4
- | "completion-time"
5
- | "damage-taken"
6
- | "damage-dealt"
7
- | "accuracy"
8
- | "resource-efficiency"
9
- | "progression-rate"
10
- | "retry-count";
11
-
12
- export type AdaptiveDifficultyStrategy = "gradual" | "immediate" | "predictive" | "manual";
13
-
14
- export type AdaptiveDifficultyChangeType =
15
- | "enemy-health"
16
- | "enemy-damage"
17
- | "enemy-count"
18
- | "resource-drop-rate"
19
- | "experience-multiplier"
20
- | "timer-multiplier"
21
- | "checkpoint-frequency";
22
-
23
- export interface AdaptiveDifficultyFixtureOptions {
24
- readonly strategy?: AdaptiveDifficultyStrategy;
25
- readonly recentDeaths?: number;
26
- readonly completionTimeSeconds?: number;
27
- readonly accuracy?: number;
28
- readonly resourceEfficiency?: number;
29
- readonly progressionRate?: number;
30
- readonly playerSkill?: number;
31
- readonly seed?: number;
32
- }
33
-
34
- export interface AdaptiveDifficultyMetricSummary {
35
- readonly type: AdaptiveDifficultyMetricType;
36
- readonly count: number;
37
- readonly mean: number;
38
- readonly median: number;
39
- readonly standardDeviation: number;
40
- readonly min: number;
41
- readonly max: number;
42
- readonly recentTrend: "improving" | "stable" | "declining";
43
- }
44
-
45
- export interface AdaptiveDifficultyTriggeredRule {
46
- readonly id: string;
47
- readonly metric: AdaptiveDifficultyMetricType;
48
- readonly strategy: AdaptiveDifficultyStrategy;
49
- readonly changeType: AdaptiveDifficultyChangeType;
50
- readonly target: number;
51
- readonly threshold: number;
52
- readonly rawMultiplier: number;
53
- readonly appliedMultiplier: number;
54
- readonly cooldownSeconds: number;
55
- readonly reason: string;
56
- }
57
-
58
- export interface AdaptiveDifficultyAdjustment {
59
- readonly enemyHealth: number;
60
- readonly enemyDamage: number;
61
- readonly enemyCount: number;
62
- readonly resourceDropRate: number;
63
- readonly experienceMultiplier: number;
64
- readonly timerMultiplier: number;
65
- readonly checkpointMultiplier: number;
66
- }
67
-
68
- export interface AdaptiveDifficultyFixture {
69
- readonly id: "external-parity-old-branch-adaptive-difficulty-fixture";
70
- readonly source: "origin-master-ai-balancing-smart-difficulty-adapted";
71
- readonly strategy: AdaptiveDifficultyStrategy;
72
- readonly metrics: readonly AdaptiveDifficultyMetricSummary[];
73
- readonly triggeredRules: readonly AdaptiveDifficultyTriggeredRule[];
74
- readonly adjustment: AdaptiveDifficultyAdjustment;
75
- readonly appliedChangeCount: number;
76
- readonly blockedClaims: readonly string[];
77
- readonly hash: string;
78
- readonly claimBoundary: string;
79
- }
80
-
81
- type RuleDefinition = {
82
- readonly id: string;
83
- readonly metric: AdaptiveDifficultyMetricType;
84
- readonly changeType: AdaptiveDifficultyChangeType;
85
- readonly direction: "above" | "below";
86
- readonly threshold: number;
87
- readonly multiplier: number;
88
- readonly target: number;
89
- readonly reason: string;
90
- };
91
-
92
- const rules: readonly RuleDefinition[] = [
93
- {
94
- id: "death-rate-relief",
95
- metric: "death-rate",
96
- changeType: "enemy-damage",
97
- direction: "above",
98
- threshold: 0.34,
99
- multiplier: 0.82,
100
- target: 0.18,
101
- reason: "Recent deaths exceed the bounded comfort threshold, so incoming damage is softened."
102
- },
103
- {
104
- id: "slow-completion-timer",
105
- metric: "completion-time",
106
- changeType: "timer-multiplier",
107
- direction: "above",
108
- threshold: 92,
109
- multiplier: 1.18,
110
- target: 72,
111
- reason: "Completion time is above target, so objective timers are relaxed."
112
- },
113
- {
114
- id: "low-accuracy-resource-support",
115
- metric: "accuracy",
116
- changeType: "resource-drop-rate",
117
- direction: "below",
118
- threshold: 0.46,
119
- multiplier: 1.22,
120
- target: 0.58,
121
- reason: "Accuracy is below target, so health/ammo resource availability is raised."
122
- },
123
- {
124
- id: "high-skill-enemy-presence",
125
- metric: "win-rate",
126
- changeType: "enemy-count",
127
- direction: "above",
128
- threshold: 0.72,
129
- multiplier: 1.1,
130
- target: 0.58,
131
- reason: "Win rate is above target, so bounded encounter density can increase."
132
- },
133
- {
134
- id: "resource-efficiency-reward",
135
- metric: "resource-efficiency",
136
- changeType: "experience-multiplier",
137
- direction: "above",
138
- threshold: 0.68,
139
- multiplier: 1.08,
140
- target: 0.56,
141
- reason: "Resource efficiency is above target, so reward pacing is slightly increased."
142
- },
143
- {
144
- id: "retry-checkpoint-support",
145
- metric: "retry-count",
146
- changeType: "checkpoint-frequency",
147
- direction: "above",
148
- threshold: 2.4,
149
- multiplier: 1.2,
150
- target: 1.4,
151
- reason: "Retry count is high, so checkpoint spacing is tightened."
152
- }
153
- ];
154
-
155
- export function sampleAdaptiveDifficultyFixture(options: AdaptiveDifficultyFixtureOptions = {}): AdaptiveDifficultyFixture {
156
- const strategy = options.strategy ?? "gradual";
157
- const seed = integer(options.seed ?? 0x3d2025, "seed");
158
- const playerSkill = clamp01(finite(options.playerSkill ?? 0.42, "playerSkill"));
159
- const recentDeaths = clamp(finite(options.recentDeaths ?? 3, "recentDeaths"), 0, 12);
160
- const completionTimeSeconds = clamp(finite(options.completionTimeSeconds ?? 108, "completionTimeSeconds"), 15, 420);
161
- const accuracy = clamp01(finite(options.accuracy ?? 0.41, "accuracy"));
162
- const resourceEfficiency = clamp01(finite(options.resourceEfficiency ?? 0.74, "resourceEfficiency"));
163
- const progressionRate = clamp01(finite(options.progressionRate ?? 0.52, "progressionRate"));
164
-
165
- const metricSamples = {
166
- "win-rate": sampleSeries(0.34 + playerSkill * 0.44, 0.06, seed, 0),
167
- "death-rate": sampleSeries(recentDeaths / 8, 0.09, seed, 11),
168
- "completion-time": sampleSeries(completionTimeSeconds, 8.5, seed, 23),
169
- "damage-taken": sampleSeries(0.56 + recentDeaths * 0.045, 0.08, seed, 37),
170
- "damage-dealt": sampleSeries(0.42 + accuracy * 0.45, 0.06, seed, 41),
171
- accuracy: sampleSeries(accuracy, 0.045, seed, 53),
172
- "resource-efficiency": sampleSeries(resourceEfficiency, 0.05, seed, 61),
173
- "progression-rate": sampleSeries(progressionRate, 0.07, seed, 71),
174
- "retry-count": sampleSeries(recentDeaths * 0.75, 0.28, seed, 83)
175
- } satisfies Record<AdaptiveDifficultyMetricType, readonly number[]>;
176
-
177
- const metrics = (Object.keys(metricSamples) as AdaptiveDifficultyMetricType[]).map((type) => summarizeMetric(type, metricSamples[type]));
178
- const byType = new Map(metrics.map((metric) => [metric.type, metric]));
179
- const triggeredRules = rules
180
- .filter((rule) => ruleTriggered(byType.get(rule.metric), rule))
181
- .map((rule, index) => triggeredRule(rule, strategy, index));
182
- const adjustment = applyRules(triggeredRules);
183
-
184
- return {
185
- id: "external-parity-old-branch-adaptive-difficulty-fixture",
186
- source: "origin-master-ai-balancing-smart-difficulty-adapted",
187
- strategy,
188
- metrics,
189
- triggeredRules,
190
- adjustment,
191
- appliedChangeCount: triggeredRules.length,
192
- blockedClaims: [
193
- "production dynamic difficulty adjustment service",
194
- "machine-learning or reinforcement-learning personalization",
195
- "player-profile telemetry backend",
196
- "live economy balancing",
197
- "Unity/Unreal AI middleware parity"
198
- ],
199
- hash: stableHash([
200
- strategy,
201
- ...metrics.map((metric) => `${metric.type}:${metric.mean}:${metric.recentTrend}`),
202
- ...triggeredRules.map((rule) => `${rule.id}:${rule.appliedMultiplier}`),
203
- Object.values(adjustment).join(":")
204
- ].join("|")),
205
- claimBoundary: "Deterministic adaptive-difficulty metric/rule telemetry adapted from old balancing and smart difficulty concepts; this is bounded runtime evidence, not production DDA, ML personalization, telemetry backend, live economy balancing, or Unity/Unreal AI middleware parity."
206
- };
207
- }
208
-
209
- function summarizeMetric(type: AdaptiveDifficultyMetricType, values: readonly number[]): AdaptiveDifficultyMetricSummary {
210
- const sorted = [...values].sort((left, right) => left - right);
211
- const mean = values.reduce((sum, value) => sum + value, 0) / values.length;
212
- const variance = values.reduce((sum, value) => sum + (value - mean) ** 2, 0) / values.length;
213
- const firstHalf = values.slice(0, Math.max(1, Math.floor(values.length / 2)));
214
- const secondHalf = values.slice(Math.floor(values.length / 2));
215
- const firstMean = firstHalf.reduce((sum, value) => sum + value, 0) / firstHalf.length;
216
- const secondMean = secondHalf.reduce((sum, value) => sum + value, 0) / secondHalf.length;
217
- const trendDelta = secondMean - firstMean;
218
- return {
219
- type,
220
- count: values.length,
221
- mean: round(mean),
222
- median: round(sorted[Math.floor(sorted.length / 2)] ?? mean),
223
- standardDeviation: round(Math.sqrt(variance)),
224
- min: round(sorted[0] ?? mean),
225
- max: round(sorted[sorted.length - 1] ?? mean),
226
- recentTrend: Math.abs(trendDelta) < 0.025 ? "stable" : isLowerBetter(type) ? trendDelta < 0 ? "improving" : "declining" : trendDelta > 0 ? "improving" : "declining"
227
- };
228
- }
229
-
230
- function triggeredRule(rule: RuleDefinition, strategy: AdaptiveDifficultyStrategy, index: number): AdaptiveDifficultyTriggeredRule {
231
- const smoothing = strategy === "immediate" ? 1 : strategy === "predictive" ? 0.72 : strategy === "manual" ? 0 : 0.55;
232
- const appliedMultiplier = strategy === "manual" ? 1 : 1 + (rule.multiplier - 1) * smoothing;
233
- return {
234
- id: rule.id,
235
- metric: rule.metric,
236
- strategy,
237
- changeType: rule.changeType,
238
- target: rule.target,
239
- threshold: rule.threshold,
240
- rawMultiplier: rule.multiplier,
241
- appliedMultiplier: round(appliedMultiplier),
242
- cooldownSeconds: 12 + index * 4,
243
- reason: rule.reason
244
- };
245
- }
246
-
247
- function applyRules(triggeredRules: readonly AdaptiveDifficultyTriggeredRule[]): AdaptiveDifficultyAdjustment {
248
- const adjustment: AdaptiveDifficultyAdjustment = {
249
- enemyHealth: 1,
250
- enemyDamage: 1,
251
- enemyCount: 1,
252
- resourceDropRate: 1,
253
- experienceMultiplier: 1,
254
- timerMultiplier: 1,
255
- checkpointMultiplier: 1
256
- };
257
- const mutable = { ...adjustment };
258
- for (const rule of triggeredRules) {
259
- if (rule.changeType === "enemy-health") mutable.enemyHealth *= rule.appliedMultiplier;
260
- else if (rule.changeType === "enemy-damage") mutable.enemyDamage *= rule.appliedMultiplier;
261
- else if (rule.changeType === "enemy-count") mutable.enemyCount *= rule.appliedMultiplier;
262
- else if (rule.changeType === "resource-drop-rate") mutable.resourceDropRate *= rule.appliedMultiplier;
263
- else if (rule.changeType === "experience-multiplier") mutable.experienceMultiplier *= rule.appliedMultiplier;
264
- else if (rule.changeType === "timer-multiplier") mutable.timerMultiplier *= rule.appliedMultiplier;
265
- else mutable.checkpointMultiplier *= rule.appliedMultiplier;
266
- }
267
- return {
268
- enemyHealth: round(mutable.enemyHealth),
269
- enemyDamage: round(mutable.enemyDamage),
270
- enemyCount: round(mutable.enemyCount),
271
- resourceDropRate: round(mutable.resourceDropRate),
272
- experienceMultiplier: round(mutable.experienceMultiplier),
273
- timerMultiplier: round(mutable.timerMultiplier),
274
- checkpointMultiplier: round(mutable.checkpointMultiplier)
275
- };
276
- }
277
-
278
- function ruleTriggered(metric: AdaptiveDifficultyMetricSummary | undefined, rule: RuleDefinition): boolean {
279
- if (!metric) return false;
280
- return rule.direction === "above" ? metric.mean > rule.threshold : metric.mean < rule.threshold;
281
- }
282
-
283
- function sampleSeries(center: number, amplitude: number, seed: number, salt: number): readonly number[] {
284
- return Array.from({ length: 9 }, (_, index) => {
285
- const wave = Math.sin((seed + salt + index * 17) * 0.37) * amplitude;
286
- const drift = (index - 4) * amplitude * 0.08;
287
- return round(Math.max(0, center + wave + drift));
288
- });
289
- }
290
-
291
- function isLowerBetter(type: AdaptiveDifficultyMetricType): boolean {
292
- return type === "death-rate" || type === "completion-time" || type === "damage-taken" || type === "retry-count";
293
- }
294
-
295
- function stableHash(value: string): string {
296
- let hash = 0x811c9dc5;
297
- for (let index = 0; index < value.length; index += 1) {
298
- hash ^= value.charCodeAt(index);
299
- hash = Math.imul(hash, 0x01000193);
300
- }
301
- return (hash >>> 0).toString(16).padStart(8, "0");
302
- }
303
-
304
- function integer(value: number, label: string): number {
305
- if (!Number.isInteger(value)) throw new RangeError(`Adaptive difficulty fixture ${label} must be an integer.`);
306
- return value;
307
- }
308
-
309
- function finite(value: number, label: string): number {
310
- if (!Number.isFinite(value)) throw new RangeError(`Adaptive difficulty fixture ${label} must be finite.`);
311
- return value;
312
- }
313
-
314
- function clamp(value: number, min: number, max: number): number {
315
- return Math.max(min, Math.min(max, value));
316
- }
317
-
318
- function clamp01(value: number): number {
319
- return clamp(value, 0, 1);
320
- }
321
-
322
- function round(value: number): number {
323
- return Number(value.toFixed(4));
324
- }
@@ -1,182 +0,0 @@
1
- export type AnalyticsConsentCategory = "necessary" | "analytics" | "marketing" | "preferences";
2
- export type AnalyticsProviderMode = "console" | "batched-local" | "disabled";
3
-
4
- export interface AnalyticsPrivacyFixtureOptions {
5
- readonly seed?: number;
6
- readonly userId?: string;
7
- readonly sessionId?: string;
8
- readonly frameMs?: number;
9
- readonly eventCount?: number;
10
- readonly errorCount?: number;
11
- readonly analyticsConsent?: boolean;
12
- readonly marketingConsent?: boolean;
13
- }
14
-
15
- export interface AnalyticsPrivacyFixture {
16
- readonly source: "origin-master-analytics-privacy-adapted";
17
- readonly consent: {
18
- readonly version: "1.0";
19
- readonly explicitRequired: true;
20
- readonly categories: Record<AnalyticsConsentCategory, boolean>;
21
- readonly grantedCount: number;
22
- readonly deniedCount: number;
23
- readonly changeEvents: readonly { readonly category: AnalyticsConsentCategory; readonly granted: boolean; readonly previous: boolean }[];
24
- };
25
- readonly anonymization: {
26
- readonly userHash: string;
27
- readonly sessionHash: string;
28
- readonly emailRedacted: true;
29
- readonly tokenRedacted: true;
30
- readonly ipAnonymized: string;
31
- readonly piiPatternHits: number;
32
- };
33
- readonly batching: {
34
- readonly providerMode: AnalyticsProviderMode;
35
- readonly maxBatchSize: number;
36
- readonly queuedEvents: number;
37
- readonly flushedBatches: number;
38
- readonly offlineQueueEnabled: true;
39
- readonly blockedWithoutConsent: number;
40
- };
41
- readonly metrics: {
42
- readonly frameMs: number;
43
- readonly fps: number;
44
- readonly errorCount: number;
45
- readonly errorsPerMinute: number;
46
- readonly loadEvents: number;
47
- readonly customMetricCount: number;
48
- };
49
- readonly productionReadiness: {
50
- readonly consentTelemetry: true;
51
- readonly anonymizationTelemetry: true;
52
- readonly batchingTelemetry: true;
53
- readonly metricsTelemetry: true;
54
- readonly providerBoundaryTelemetry: true;
55
- };
56
- readonly blockedClaims: readonly string[];
57
- readonly claimBoundary: string;
58
- readonly hash: string;
59
- }
60
-
61
- export function sampleAnalyticsPrivacyFixture(options: AnalyticsPrivacyFixtureOptions = {}): AnalyticsPrivacyFixture {
62
- const seed = normalizeSeed(options.seed ?? 0xa11a);
63
- const userId = normalizeText(options.userId ?? "external-parity-runtime-player", "userId");
64
- const sessionId = normalizeText(options.sessionId ?? "runtime-session", "sessionId");
65
- const frameMs = positive(options.frameMs ?? 16.7, "frameMs");
66
- const eventCount = count(options.eventCount ?? 24, "eventCount");
67
- const errorCount = count(options.errorCount ?? 0, "errorCount");
68
- const analyticsConsent = options.analyticsConsent ?? true;
69
- const marketingConsent = options.marketingConsent ?? false;
70
- const categories: Record<AnalyticsConsentCategory, boolean> = {
71
- necessary: true,
72
- analytics: analyticsConsent,
73
- marketing: marketingConsent,
74
- preferences: true
75
- };
76
- const consentValues = Object.values(categories);
77
- const blockedWithoutConsent = analyticsConsent ? 0 : eventCount;
78
- const queuedEvents = analyticsConsent ? Math.min(eventCount, 50) : 0;
79
- const flushedBatches = analyticsConsent ? Math.ceil(queuedEvents / 10) : 0;
80
- const fps = Number((1000 / frameMs).toFixed(2));
81
- const fixture: Omit<AnalyticsPrivacyFixture, "hash"> = {
82
- source: "origin-master-analytics-privacy-adapted",
83
- consent: {
84
- version: "1.0",
85
- explicitRequired: true,
86
- categories,
87
- grantedCount: consentValues.filter(Boolean).length,
88
- deniedCount: consentValues.filter((granted) => !granted).length,
89
- changeEvents: [
90
- { category: "analytics", granted: analyticsConsent, previous: false },
91
- { category: "preferences", granted: true, previous: false }
92
- ]
93
- },
94
- anonymization: {
95
- userHash: stableHash(`user:${userId}:analytics`),
96
- sessionHash: stableHash(`session:${sessionId}:analytics`),
97
- emailRedacted: true,
98
- tokenRedacted: true,
99
- ipAnonymized: anonymizeIp(`198.51.${Math.floor(seededUnit(seed) * 100)}.${Math.floor(seededUnit(seed ^ 0x1979) * 240)}`),
100
- piiPatternHits: 3
101
- },
102
- batching: {
103
- providerMode: analyticsConsent ? "batched-local" : "disabled",
104
- maxBatchSize: 50,
105
- queuedEvents,
106
- flushedBatches,
107
- offlineQueueEnabled: true,
108
- blockedWithoutConsent
109
- },
110
- metrics: {
111
- frameMs: Number(frameMs.toFixed(3)),
112
- fps,
113
- errorCount,
114
- errorsPerMinute: Number((errorCount / 3).toFixed(3)),
115
- loadEvents: 3 + Math.floor(seededUnit(seed ^ 0x441) * 3),
116
- customMetricCount: 4
117
- },
118
- productionReadiness: {
119
- consentTelemetry: true,
120
- anonymizationTelemetry: true,
121
- batchingTelemetry: true,
122
- metricsTelemetry: true,
123
- providerBoundaryTelemetry: true
124
- },
125
- blockedClaims: [
126
- "GDPR/CCPA compliance certification",
127
- "production analytics SaaS integration",
128
- "real provider delivery guarantees",
129
- "cross-device identity graph",
130
- "Unity Analytics parity",
131
- "Unreal Insights/Analytics parity"
132
- ],
133
- claimBoundary: "This fixture ports old analytics, consent, anonymization, batching, and metrics concepts into deterministic local telemetry. It does not send analytics events, certify privacy compliance, integrate a production provider, or claim Unity Analytics or Unreal Insights parity."
134
- };
135
- return {
136
- ...fixture,
137
- hash: stableHash(JSON.stringify(fixture))
138
- };
139
- }
140
-
141
- function anonymizeIp(ip: string): string {
142
- const octets = ip.split(".");
143
- return `${octets[0] ?? "0"}.${octets[1] ?? "0"}.${octets[2] ?? "0"}.0`;
144
- }
145
-
146
- function normalizeSeed(seed: number): number {
147
- if (!Number.isInteger(seed) || seed < 0) throw new Error("seed must be a non-negative integer.");
148
- return seed >>> 0;
149
- }
150
-
151
- function normalizeText(value: string, name: string): string {
152
- const trimmed = value.trim();
153
- if (trimmed.length === 0) throw new Error(`${name} must not be empty.`);
154
- return trimmed;
155
- }
156
-
157
- function positive(value: number, name: string): number {
158
- if (!Number.isFinite(value) || value <= 0) throw new Error(`${name} must be a positive finite number.`);
159
- return value;
160
- }
161
-
162
- function count(value: number, name: string): number {
163
- if (!Number.isInteger(value) || value < 0) throw new Error(`${name} must be a non-negative integer.`);
164
- return value;
165
- }
166
-
167
- function seededUnit(seed: number): number {
168
- let value = seed >>> 0;
169
- value ^= value << 13;
170
- value ^= value >>> 17;
171
- value ^= value << 5;
172
- return (value >>> 0) / 0xffffffff;
173
- }
174
-
175
- function stableHash(value: string): string {
176
- let hash = 2166136261;
177
- for (let index = 0; index < value.length; index += 1) {
178
- hash ^= value.charCodeAt(index);
179
- hash = Math.imul(hash, 16777619);
180
- }
181
- return (hash >>> 0).toString(16).padStart(8, "0");
182
- }
@@ -1,10 +0,0 @@
1
- export const animationNodeCategories = [
2
- "scene",
3
- "dialogue",
4
- "camera",
5
- "audio",
6
- "timing",
7
- "publishing"
8
- ] as const;
9
-
10
- export type AnimationNodeCategory = (typeof animationNodeCategories)[number];
@@ -1,44 +0,0 @@
1
- import type { VisualNodeDefinition } from "./VisualNodeCatalog";
2
-
3
- const flowInput = () => ({ id: "in", direction: "input" as const, type: "flow" as const, optional: true });
4
- const stringInput = (id: string, optional = false) => ({ id, direction: "input" as const, type: "string" as const, ...(optional ? { optional: true } : {}) });
5
- const numberInput = (id: string, defaultValue = 0) => ({ id, direction: "input" as const, type: "number" as const, defaultValue });
6
- const objectInput = (id: string, optional = false) => ({ id, direction: "input" as const, type: "object" as const, ...(optional ? { optional: true } : {}) });
7
- const commandOutputs = () => [{ id: "out", direction: "output" as const, type: "flow" as const }, { id: "command", direction: "output" as const, type: "object" as const }];
8
-
9
- export const animationVisualNodeDefinitions: readonly VisualNodeDefinition[] = [
10
- node("setScene", "scene", "Set Scene", "Switch to a named animation scene.", [flowInput(), stringInput("sceneId")]),
11
- node("transitionTo", "scene", "Transition To", "Transition to a animation scene with a named transition.", [flowInput(), stringInput("sceneId"), stringInput("transition", true), numberInput("duration", 0.5)]),
12
- node("loadSet", "scene", "Load Set", "Load a animation set asset into the episode scene.", [flowInput(), stringInput("setId")]),
13
- node("spawnCharacter", "scene", "Spawn Character", "Spawn a character asset at a scene position.", [flowInput(), stringInput("characterId"), objectInput("position", true)]),
14
- node("sayLine", "dialogue", "Say Line", "Schedule a dialogue line for a speaker.", [flowInput(), stringInput("speakerId"), stringInput("text"), stringInput("emotion", true)]),
15
- node("waitForResponse", "dialogue", "Wait For Response", "Block until a response beat or timeout.", [flowInput(), stringInput("speakerId", true), numberInput("timeout", 1)]),
16
- node("setEmotion", "dialogue", "Set Emotion", "Set a character performance emotion.", [flowInput(), stringInput("characterId"), stringInput("emotion")]),
17
- node("setGesture", "dialogue", "Set Gesture", "Set a character performance gesture.", [flowInput(), stringInput("characterId"), stringInput("gestureId")]),
18
- node("animationCutTo", "camera", "Animation Cut To", "Cut to a named animation camera preset.", [flowInput(), stringInput("presetId"), stringInput("targetId", true)]),
19
- node("dollyTo", "camera", "Dolly To", "Dolly the camera to a target framing.", [flowInput(), objectInput("position"), numberInput("duration", 1)]),
20
- node("frameCharacter", "camera", "Frame Character", "Frame a character with composition rules.", [flowInput(), stringInput("characterId"), stringInput("composition", true)]),
21
- node("shakeCamera", "camera", "Shake Camera", "Add animation camera shake.", [flowInput(), numberInput("intensity", 0.2), numberInput("duration", 0.25)]),
22
- node("playMusic", "audio", "Play Music", "Start a music cue.", [flowInput(), stringInput("musicId"), numberInput("volume", 1)]),
23
- node("stopMusic", "audio", "Stop Music", "Stop the current music cue.", [flowInput(), stringInput("musicId", true)]),
24
- node("playSfx", "audio", "Play SFX", "Play a sound effect cue.", [flowInput(), stringInput("sfxId"), numberInput("volume", 1)]),
25
- node("setVolume", "audio", "Set Volume", "Set an audio bus volume.", [flowInput(), stringInput("busId"), numberInput("volume", 1)]),
26
- node("waitForBeat", "timing", "Wait For Beat", "Wait for a named story beat.", [flowInput(), stringInput("beatId")]),
27
- node("syncToAudio", "timing", "Sync To Audio", "Synchronize downstream actions to an audio timestamp.", [flowInput(), stringInput("audioId"), numberInput("time", 0)]),
28
- node("delay", "timing", "Delay", "Delay a animation action by seconds.", [flowInput(), numberInput("duration", 1)]),
29
- node("schedule", "timing", "Schedule", "Schedule a animation command at a timeline time.", [flowInput(), numberInput("time", 0), stringInput("label", true)]),
30
- node("captureThumbnail", "publishing", "Capture Thumbnail", "Mark a thumbnail capture time.", [flowInput(), numberInput("time", 0), stringInput("path", true)]),
31
- node("exportCaption", "publishing", "Export Caption", "Export a caption track in VTT or SRT format.", [flowInput(), stringInput("format", true), stringInput("path", true)]),
32
- node("markChapter", "publishing", "Mark Chapter", "Create a video chapter marker.", [flowInput(), stringInput("title"), numberInput("time", 0)])
33
- ];
34
-
35
- function node(kind: string, category: VisualNodeDefinition["category"], title: string, description: string, inputs: VisualNodeDefinition["ports"]): VisualNodeDefinition {
36
- return {
37
- kind,
38
- category,
39
- title,
40
- description,
41
- ports: [...inputs, ...commandOutputs()],
42
- oldBranchSource: ["packages/scripting/src/AnimationVisualNodes.ts"]
43
- };
44
- }
package/src/Behavior.ts DELETED
@@ -1,11 +0,0 @@
1
- import type { ScriptContext } from "./ScriptContext";
2
-
3
- export interface Behavior {
4
- enabled?: boolean;
5
- onStart?(context: ScriptContext): void | Promise<void>;
6
- onFixedUpdate?(context: ScriptContext): void | Promise<void>;
7
- onUpdate?(context: ScriptContext): void | Promise<void>;
8
- onDestroy?(context: ScriptContext): void | Promise<void>;
9
- }
10
-
11
- export type BehaviorPhase = "start" | "fixed" | "update" | "destroy";
@@ -1,51 +0,0 @@
1
- import type { Behavior } from "./Behavior";
2
- import { ScriptContext } from "./ScriptContext";
3
-
4
- export interface BehaviorHostOptions {
5
- readonly target?: unknown;
6
- }
7
-
8
- export class BehaviorHost {
9
- readonly target?: unknown;
10
- private readonly behaviors = new Set<Behavior>();
11
- private destroyed = false;
12
-
13
- constructor(options: BehaviorHostOptions = {}) {
14
- this.target = options.target;
15
- }
16
-
17
- get isDestroyed(): boolean {
18
- return this.destroyed;
19
- }
20
-
21
- attach(behavior: Behavior): void {
22
- this.assertAlive();
23
- this.behaviors.add(behavior);
24
- }
25
-
26
- detach(behavior: Behavior): boolean {
27
- return this.behaviors.delete(behavior);
28
- }
29
-
30
- list(): readonly Behavior[] {
31
- return [...this.behaviors];
32
- }
33
-
34
- async destroy(services?: ReadonlyMap<string, unknown>): Promise<void> {
35
- if (this.destroyed) {
36
- return;
37
- }
38
- this.destroyed = true;
39
- const context = new ScriptContext({ phase: "destroy", target: this.target, services });
40
- for (const behavior of [...this.behaviors]) {
41
- await behavior.onDestroy?.(context);
42
- }
43
- this.behaviors.clear();
44
- }
45
-
46
- private assertAlive(): void {
47
- if (this.destroyed) {
48
- throw new Error("Cannot attach behavior to a destroyed host");
49
- }
50
- }
51
- }