@aura3d/scripting 1.3.3 → 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.
@@ -1,262 +0,0 @@
1
- export type CulturalCommunicationStyle = "direct" | "balanced" | "indirect";
2
- export type CulturalPersonalSpace = "close" | "medium" | "distant";
3
- export type CulturalRelationship = "stranger" | "acquaintance" | "friend" | "family" | "superior" | "subordinate";
4
- export type ProxemicZone = "intimate" | "personal" | "social" | "public";
5
-
6
- export interface CulturalBehaviorFixtureOptions {
7
- readonly seed?: number;
8
- readonly relationship?: CulturalRelationship;
9
- readonly initiatorPosition?: readonly [number, number, number];
10
- readonly targetPosition?: readonly [number, number, number];
11
- readonly message?: string;
12
- }
13
-
14
- export interface CultureDescriptor {
15
- readonly id: string;
16
- readonly name: string;
17
- readonly individualismScore: number;
18
- readonly powerDistanceScore: number;
19
- readonly uncertaintyAvoidanceScore: number;
20
- readonly communicationStyle: CulturalCommunicationStyle;
21
- readonly personalSpacePreference: CulturalPersonalSpace;
22
- readonly eyeContactNorm: "low" | "moderate" | "high";
23
- readonly greetingStyle: "formal" | "casual" | "bow" | "handshake";
24
- }
25
-
26
- export interface CulturalEntityFixture {
27
- readonly id: string;
28
- readonly cultureId: string;
29
- readonly position: readonly [number, number, number];
30
- readonly personalityVariance: number;
31
- }
32
-
33
- export interface CulturalBehaviorFixture {
34
- readonly source: "origin-master-cultural-ai-adapted";
35
- readonly cultures: readonly CultureDescriptor[];
36
- readonly entities: readonly CulturalEntityFixture[];
37
- readonly proxemics: {
38
- readonly relationship: CulturalRelationship;
39
- readonly distanceMeters: number;
40
- readonly acceptableDistanceMeters: number;
41
- readonly zone: ProxemicZone;
42
- readonly comfort: number;
43
- readonly approachAllowed: boolean;
44
- };
45
- readonly communication: {
46
- readonly input: string;
47
- readonly formatted: string;
48
- readonly directness: CulturalCommunicationStyle;
49
- readonly formality: "formal" | "neutral" | "informal";
50
- readonly audienceAdapted: boolean;
51
- };
52
- readonly socialNorms: {
53
- readonly greetingExpected: string;
54
- readonly eyeContactExpected: string;
55
- readonly hierarchyAware: boolean;
56
- readonly normViolations: readonly string[];
57
- };
58
- readonly gesture: {
59
- readonly id: string;
60
- readonly allowed: boolean;
61
- readonly intensity: number;
62
- };
63
- readonly decision: {
64
- readonly selectedAction: "approach" | "wait" | "request-distance" | "formal-greeting";
65
- readonly score: number;
66
- readonly reason: string;
67
- };
68
- readonly productionReadiness: {
69
- readonly proxemicTelemetry: true;
70
- readonly communicationStyleTelemetry: true;
71
- readonly socialNormTelemetry: true;
72
- readonly gestureBoundaryTelemetry: true;
73
- };
74
- readonly blockedClaims: readonly string[];
75
- readonly claimBoundary: string;
76
- readonly hash: string;
77
- }
78
-
79
- const collectivistCulture: CultureDescriptor = {
80
- id: "collective-formal",
81
- name: "Collective Formal Fixture",
82
- individualismScore: 0.28,
83
- powerDistanceScore: 0.68,
84
- uncertaintyAvoidanceScore: 0.74,
85
- communicationStyle: "indirect",
86
- personalSpacePreference: "distant",
87
- eyeContactNorm: "moderate",
88
- greetingStyle: "formal"
89
- };
90
-
91
- const directCulture: CultureDescriptor = {
92
- id: "direct-pragmatic",
93
- name: "Direct Pragmatic Fixture",
94
- individualismScore: 0.72,
95
- powerDistanceScore: 0.36,
96
- uncertaintyAvoidanceScore: 0.42,
97
- communicationStyle: "direct",
98
- personalSpacePreference: "medium",
99
- eyeContactNorm: "high",
100
- greetingStyle: "handshake"
101
- };
102
-
103
- export function sampleCulturalBehaviorFixture(options: CulturalBehaviorFixtureOptions = {}): CulturalBehaviorFixture {
104
- const seed = normalizeSeed(options.seed ?? 0xc017);
105
- const relationship = options.relationship ?? "superior";
106
- const initiatorPosition = options.initiatorPosition ?? [0, 0, 0] as const;
107
- const targetPosition = options.targetPosition ?? [0.82, 0, 0.34] as const;
108
- const initiator: CulturalEntityFixture = {
109
- id: "npc-diplomat",
110
- cultureId: collectivistCulture.id,
111
- position: initiatorPosition,
112
- personalityVariance: Number((0.08 + seededUnit(seed) * 0.08).toFixed(3))
113
- };
114
- const target: CulturalEntityFixture = {
115
- id: "player-envoy",
116
- cultureId: directCulture.id,
117
- position: targetPosition,
118
- personalityVariance: Number((0.1 + seededUnit(seed ^ 0x9e37) * 0.06).toFixed(3))
119
- };
120
- const distanceMeters = distance(initiator.position, target.position);
121
- const acceptableDistanceMeters = acceptableDistance(collectivistCulture, relationship);
122
- const comfort = comfortLevel(distanceMeters, acceptableDistanceMeters);
123
- const approachAllowed = distanceMeters >= acceptableDistanceMeters * 0.7;
124
- const formality = determineFormality(collectivistCulture, directCulture, relationship);
125
- const input = options.message ?? "Could you help me with the gate plan?";
126
- const formatted = formatMessage(input, collectivistCulture, directCulture, relationship, formality);
127
- const normViolations = [
128
- ...(approachAllowed ? [] : ["personal-space-too-close"]),
129
- ...(relationship === "superior" && !formatted.toLowerCase().startsWith("please") ? ["missing-hierarchy-marker"] : [])
130
- ];
131
- const selectedAction = !approachAllowed
132
- ? "request-distance"
133
- : formality === "formal"
134
- ? "formal-greeting"
135
- : "approach";
136
- const core = {
137
- relationship,
138
- distanceMeters,
139
- acceptableDistanceMeters,
140
- comfort,
141
- selectedAction,
142
- formatted
143
- };
144
- return {
145
- source: "origin-master-cultural-ai-adapted",
146
- cultures: [collectivistCulture, directCulture],
147
- entities: [initiator, target],
148
- proxemics: {
149
- relationship,
150
- distanceMeters,
151
- acceptableDistanceMeters,
152
- zone: zoneForDistance(distanceMeters),
153
- comfort,
154
- approachAllowed
155
- },
156
- communication: {
157
- input,
158
- formatted,
159
- directness: collectivistCulture.communicationStyle,
160
- formality,
161
- audienceAdapted: formatted !== input
162
- },
163
- socialNorms: {
164
- greetingExpected: collectivistCulture.greetingStyle,
165
- eyeContactExpected: collectivistCulture.eyeContactNorm,
166
- hierarchyAware: relationship === "superior" || relationship === "subordinate",
167
- normViolations
168
- },
169
- gesture: {
170
- id: collectivistCulture.greetingStyle === "bow" ? "bow" : "measured-handshake",
171
- allowed: comfort >= 0.35,
172
- intensity: Number((0.42 + comfort * 0.38).toFixed(3))
173
- },
174
- decision: {
175
- selectedAction,
176
- score: Number(((approachAllowed ? 0.52 : 0.18) + comfort * 0.36 + (formality === "formal" ? 0.08 : 0)).toFixed(3)),
177
- reason: normViolations.length > 0 ? normViolations.join(",") : "proxemics-and-formality-within-bounds"
178
- },
179
- productionReadiness: {
180
- proxemicTelemetry: true,
181
- communicationStyleTelemetry: true,
182
- socialNormTelemetry: true,
183
- gestureBoundaryTelemetry: true
184
- },
185
- blockedClaims: [
186
- "real cultural modeling certification",
187
- "LLM-driven NPC dialogue parity",
188
- "Unity Behavior Designer or ML-Agents social AI parity",
189
- "Unreal MassAI/SmartObject social simulation parity",
190
- "human-subject validated intercultural behavior"
191
- ],
192
- claimBoundary: "This fixture ports old cultural AI concepts into deterministic proxemics, communication-style, social-norm, gesture, and decision telemetry for runtime evidence. It does not claim validated cultural modeling, generated NPC dialogue, or Unity/Unreal social-AI parity.",
193
- hash: stableHash(JSON.stringify(core))
194
- };
195
- }
196
-
197
- function acceptableDistance(culture: CultureDescriptor, relationship: CulturalRelationship): number {
198
- const base = relationship === "family" ? 0.3 : relationship === "friend" ? 0.6 : relationship === "acquaintance" ? 1.0 : relationship === "superior" || relationship === "subordinate" ? 1.25 : 1.5;
199
- const space = culture.personalSpacePreference === "close" ? 0.7 : culture.personalSpacePreference === "distant" ? 1.4 : 1;
200
- const individualism = 1 - culture.individualismScore * 0.2;
201
- const hierarchy = 1 + culture.powerDistanceScore * 0.15;
202
- return Number((base * space * individualism * hierarchy).toFixed(3));
203
- }
204
-
205
- function formatMessage(message: string, speaker: CultureDescriptor, audience: CultureDescriptor, relationship: CulturalRelationship, formality: CulturalBehaviorFixture["communication"]["formality"]): string {
206
- let formatted = message.trim();
207
- const indirect = speaker.communicationStyle === "indirect" || audience.communicationStyle === "indirect";
208
- if (indirect && !/\b(could|would|might)\b/i.test(formatted)) {
209
- formatted = `I was wondering if you might ${formatted.charAt(0).toLowerCase()}${formatted.slice(1)}`;
210
- }
211
- if (formality === "formal") {
212
- formatted = formatted.replace(/\bhey\b/gi, "Hello").replace(/\byeah\b/gi, "yes").replace(/\bgonna\b/gi, "going to");
213
- }
214
- if (relationship === "superior" && !formatted.toLowerCase().startsWith("please")) {
215
- formatted = `Please, ${formatted.charAt(0).toLowerCase()}${formatted.slice(1)}`;
216
- }
217
- return formatted;
218
- }
219
-
220
- function determineFormality(speaker: CultureDescriptor, audience: CultureDescriptor, relationship: CulturalRelationship): CulturalBehaviorFixture["communication"]["formality"] {
221
- if (relationship === "superior" || speaker.powerDistanceScore > 0.6 || audience.powerDistanceScore > 0.6) return "formal";
222
- if (relationship === "friend" || relationship === "family") return "informal";
223
- return "neutral";
224
- }
225
-
226
- function zoneForDistance(distanceMeters: number): ProxemicZone {
227
- if (distanceMeters < 0.45) return "intimate";
228
- if (distanceMeters < 1.2) return "personal";
229
- if (distanceMeters < 3.6) return "social";
230
- return "public";
231
- }
232
-
233
- function comfortLevel(distanceMeters: number, acceptableDistanceMeters: number): number {
234
- const deviation = Math.abs(distanceMeters - acceptableDistanceMeters);
235
- return Number(Math.max(0, 1 - deviation / Math.max(0.001, acceptableDistanceMeters * 0.5)).toFixed(3));
236
- }
237
-
238
- function distance(left: readonly [number, number, number], right: readonly [number, number, number]): number {
239
- return Number(Math.hypot(left[0] - right[0], left[1] - right[1], left[2] - right[2]).toFixed(3));
240
- }
241
-
242
- function normalizeSeed(seed: number): number {
243
- if (!Number.isInteger(seed) || seed < 0) throw new Error("seed must be a non-negative integer.");
244
- return seed >>> 0;
245
- }
246
-
247
- function seededUnit(seed: number): number {
248
- let value = seed >>> 0;
249
- value ^= value << 13;
250
- value ^= value >>> 17;
251
- value ^= value << 5;
252
- return (value >>> 0) / 0xffffffff;
253
- }
254
-
255
- function stableHash(value: string): string {
256
- let hash = 2166136261;
257
- for (let index = 0; index < value.length; index += 1) {
258
- hash ^= value.charCodeAt(index);
259
- hash = Math.imul(hash, 16777619);
260
- }
261
- return (hash >>> 0).toString(16).padStart(8, "0");
262
- }
@@ -1,162 +0,0 @@
1
- export type DecisionTreeNodeType = "decision" | "action";
2
-
3
- export interface DecisionTreeContext {
4
- readonly values?: Record<string, number | boolean | string | undefined>;
5
- }
6
-
7
- export interface DecisionTreeStats {
8
- readonly totalNodes: number;
9
- readonly decisionNodes: number;
10
- readonly actionNodes: number;
11
- readonly maxDepth: number;
12
- readonly averageActionDepth: number;
13
- }
14
-
15
- export interface DecisionTreeDecision {
16
- readonly action: string;
17
- readonly path: readonly string[];
18
- readonly executed: boolean;
19
- }
20
-
21
- export type DecisionCondition = (context: DecisionTreeContext) => boolean;
22
- export type DecisionAction = (context: DecisionTreeContext) => void;
23
-
24
- export interface DecisionTreeNode {
25
- readonly type: DecisionTreeNodeType;
26
- readonly name: string;
27
- condition?: DecisionCondition;
28
- action?: DecisionAction;
29
- trueBranch?: DecisionTreeNode;
30
- falseBranch?: DecisionTreeNode;
31
- parent?: DecisionTreeNode;
32
- }
33
-
34
- export class DecisionTree {
35
- private root: DecisionTreeNode | undefined;
36
-
37
- createDecision(name: string, condition: DecisionCondition): DecisionTreeNode {
38
- assertName(name);
39
- return { type: "decision", name, condition };
40
- }
41
-
42
- createAction(name: string, action: DecisionAction = () => undefined): DecisionTreeNode {
43
- assertName(name);
44
- return { type: "action", name, action };
45
- }
46
-
47
- setBranches(node: DecisionTreeNode, trueBranch: DecisionTreeNode, falseBranch: DecisionTreeNode): void {
48
- if (node.type !== "decision") throw new Error(`Cannot set branches on action node: ${node.name}`);
49
- node.trueBranch = trueBranch;
50
- node.falseBranch = falseBranch;
51
- trueBranch.parent = node;
52
- falseBranch.parent = node;
53
- }
54
-
55
- setRoot(node: DecisionTreeNode): void {
56
- this.root = node;
57
- }
58
-
59
- getRoot(): DecisionTreeNode | undefined {
60
- return this.root;
61
- }
62
-
63
- decide(context: DecisionTreeContext, maxDepth = 32): DecisionTreeDecision {
64
- const action = this.traverse(context, maxDepth);
65
- if (!action?.action) return { action: "none", path: this.getTraversalPath(context, maxDepth), executed: false };
66
- action.action(context);
67
- return { action: action.name, path: this.getTraversalPath(context, maxDepth), executed: true };
68
- }
69
-
70
- traverse(context: DecisionTreeContext, maxDepth = 32): DecisionTreeNode | undefined {
71
- if (!this.root || maxDepth < 1) return undefined;
72
- let current: DecisionTreeNode | undefined = this.root;
73
- let depth = 0;
74
- while (current && depth < maxDepth) {
75
- depth += 1;
76
- if (current.type === "action") return current;
77
- if (!current.condition) return undefined;
78
- current = current.condition(context) ? current.trueBranch : current.falseBranch;
79
- }
80
- return undefined;
81
- }
82
-
83
- getTraversalPath(context: DecisionTreeContext, maxDepth = 32): readonly string[] {
84
- if (!this.root || maxDepth < 1) return [];
85
- const path: string[] = [];
86
- let current: DecisionTreeNode | undefined = this.root;
87
- let depth = 0;
88
- while (current && depth < maxDepth) {
89
- depth += 1;
90
- path.push(current.name);
91
- if (current.type === "action") break;
92
- if (!current.condition) break;
93
- current = current.condition(context) ? current.trueBranch : current.falseBranch;
94
- }
95
- return path;
96
- }
97
-
98
- validate(): boolean {
99
- return this.root ? validateNode(this.root) : false;
100
- }
101
-
102
- getStats(): DecisionTreeStats {
103
- if (!this.root) return { totalNodes: 0, decisionNodes: 0, actionNodes: 0, maxDepth: 0, averageActionDepth: 0 };
104
- const stats = { totalNodes: 0, decisionNodes: 0, actionNodes: 0, actionDepths: [] as number[] };
105
- collectStats(this.root, 0, stats);
106
- const maxDepth = stats.actionDepths.length > 0 ? Math.max(...stats.actionDepths) : 0;
107
- const averageActionDepth = stats.actionDepths.length > 0
108
- ? stats.actionDepths.reduce((sum, depth) => sum + depth, 0) / stats.actionDepths.length
109
- : 0;
110
- return {
111
- totalNodes: stats.totalNodes,
112
- decisionNodes: stats.decisionNodes,
113
- actionNodes: stats.actionNodes,
114
- maxDepth,
115
- averageActionDepth: Number(averageActionDepth.toFixed(3))
116
- };
117
- }
118
-
119
- clone(): DecisionTree {
120
- const tree = new DecisionTree();
121
- if (this.root) tree.setRoot(cloneNode(this.root));
122
- return tree;
123
- }
124
- }
125
-
126
- function validateNode(node: DecisionTreeNode): boolean {
127
- if (node.type === "action") return typeof node.action === "function";
128
- return typeof node.condition === "function" && Boolean(node.trueBranch) && Boolean(node.falseBranch) && validateNode(node.trueBranch!) && validateNode(node.falseBranch!);
129
- }
130
-
131
- function collectStats(
132
- node: DecisionTreeNode,
133
- depth: number,
134
- stats: { totalNodes: number; decisionNodes: number; actionNodes: number; actionDepths: number[] }
135
- ): void {
136
- stats.totalNodes += 1;
137
- if (node.type === "action") {
138
- stats.actionNodes += 1;
139
- stats.actionDepths.push(depth);
140
- return;
141
- }
142
- stats.decisionNodes += 1;
143
- if (node.trueBranch) collectStats(node.trueBranch, depth + 1, stats);
144
- if (node.falseBranch) collectStats(node.falseBranch, depth + 1, stats);
145
- }
146
-
147
- function cloneNode(node: DecisionTreeNode): DecisionTreeNode {
148
- const cloned: DecisionTreeNode = { type: node.type, name: node.name, condition: node.condition, action: node.action };
149
- if (node.trueBranch) {
150
- cloned.trueBranch = cloneNode(node.trueBranch);
151
- cloned.trueBranch.parent = cloned;
152
- }
153
- if (node.falseBranch) {
154
- cloned.falseBranch = cloneNode(node.falseBranch);
155
- cloned.falseBranch.parent = cloned;
156
- }
157
- return cloned;
158
- }
159
-
160
- function assertName(name: string): void {
161
- if (name.trim().length === 0) throw new Error("Decision tree node name cannot be empty.");
162
- }
package/src/GOAP.ts DELETED
@@ -1,150 +0,0 @@
1
- export type GOAPValue = string | number | boolean | null;
2
- export type GOAPStateShape = Record<string, GOAPValue>;
3
-
4
- export interface GOAPActionOptions {
5
- readonly name: string;
6
- readonly cost?: number;
7
- readonly preconditions?: GOAPStateShape;
8
- readonly effects: GOAPStateShape;
9
- readonly enabled?: boolean;
10
- }
11
-
12
- export interface GOAPPlannerOptions {
13
- readonly maxIterations?: number;
14
- readonly maxPlanLength?: number;
15
- }
16
-
17
- export interface GOAPPlan {
18
- readonly valid: boolean;
19
- readonly actions: readonly string[];
20
- readonly cost: number;
21
- readonly nodesExplored: number;
22
- readonly finalState: GOAPStateShape;
23
- }
24
-
25
- type PlanNode = {
26
- readonly state: WorldState;
27
- readonly actions: readonly string[];
28
- readonly cost: number;
29
- };
30
-
31
- export class WorldState {
32
- private readonly values = new Map<string, GOAPValue>();
33
-
34
- constructor(initial: GOAPStateShape = {}) {
35
- for (const [key, value] of Object.entries(initial)) this.values.set(key, value);
36
- }
37
-
38
- static from(initial: GOAPStateShape): WorldState {
39
- return new WorldState(initial);
40
- }
41
-
42
- set(key: string, value: GOAPValue): void {
43
- this.values.set(key, value);
44
- }
45
-
46
- get(key: string): GOAPValue | undefined {
47
- return this.values.get(key);
48
- }
49
-
50
- satisfies(goal: WorldState): boolean {
51
- return goal.entries().every(([key, value]) => this.values.get(key) === value);
52
- }
53
-
54
- matches(shape: GOAPStateShape): boolean {
55
- return Object.entries(shape).every(([key, value]) => this.values.get(key) === value);
56
- }
57
-
58
- apply(shape: GOAPStateShape): WorldState {
59
- const next = this.clone();
60
- for (const [key, value] of Object.entries(shape)) next.set(key, value);
61
- return next;
62
- }
63
-
64
- clone(): WorldState {
65
- return new WorldState(this.toObject());
66
- }
67
-
68
- entries(): readonly (readonly [string, GOAPValue])[] {
69
- return [...this.values.entries()].sort(([left], [right]) => left.localeCompare(right));
70
- }
71
-
72
- toObject(): GOAPStateShape {
73
- return Object.fromEntries(this.entries());
74
- }
75
-
76
- key(): string {
77
- return this.entries().map(([key, value]) => `${key}:${String(value)}`).join("|");
78
- }
79
- }
80
-
81
- export class GOAPAction {
82
- readonly name: string;
83
- readonly cost: number;
84
- readonly preconditions: GOAPStateShape;
85
- readonly effects: GOAPStateShape;
86
- enabled: boolean;
87
-
88
- constructor(options: GOAPActionOptions) {
89
- this.name = options.name;
90
- this.cost = options.cost ?? 1;
91
- this.preconditions = options.preconditions ?? {};
92
- this.effects = options.effects;
93
- this.enabled = options.enabled ?? true;
94
- if (!Number.isFinite(this.cost) || this.cost < 0) throw new RangeError("GOAP action cost must be a finite non-negative number.");
95
- }
96
-
97
- canRun(state: WorldState): boolean {
98
- return this.enabled && state.matches(this.preconditions);
99
- }
100
-
101
- apply(state: WorldState): WorldState {
102
- return state.apply(this.effects);
103
- }
104
- }
105
-
106
- export class GOAPPlanner {
107
- private readonly maxIterations: number;
108
- private readonly maxPlanLength: number;
109
-
110
- constructor(options: GOAPPlannerOptions = {}) {
111
- this.maxIterations = options.maxIterations ?? 128;
112
- this.maxPlanLength = options.maxPlanLength ?? 8;
113
- }
114
-
115
- plan(current: WorldState, goal: WorldState, actions: readonly GOAPAction[]): GOAPPlan {
116
- if (current.satisfies(goal)) {
117
- return { valid: true, actions: [], cost: 0, nodesExplored: 0, finalState: current.toObject() };
118
- }
119
- const enabledActions = actions.filter((action) => action.enabled).sort((left, right) => left.cost - right.cost || left.name.localeCompare(right.name));
120
- const open: PlanNode[] = [{ state: current, actions: [], cost: 0 }];
121
- const bestCostByState = new Map<string, number>([[current.key(), 0]]);
122
- let nodesExplored = 0;
123
-
124
- while (open.length > 0 && nodesExplored < this.maxIterations) {
125
- open.sort((left, right) => left.cost - right.cost || left.actions.join(",").localeCompare(right.actions.join(",")));
126
- const node = open.shift()!;
127
- nodesExplored += 1;
128
- if (node.state.satisfies(goal)) {
129
- return { valid: true, actions: node.actions, cost: round3(node.cost), nodesExplored, finalState: node.state.toObject() };
130
- }
131
- if (node.actions.length >= this.maxPlanLength) continue;
132
-
133
- for (const action of enabledActions) {
134
- if (!action.canRun(node.state)) continue;
135
- const nextState = action.apply(node.state);
136
- const nextCost = round3(node.cost + action.cost);
137
- const key = nextState.key();
138
- if ((bestCostByState.get(key) ?? Number.POSITIVE_INFINITY) <= nextCost) continue;
139
- bestCostByState.set(key, nextCost);
140
- open.push({ state: nextState, actions: [...node.actions, action.name], cost: nextCost });
141
- }
142
- }
143
-
144
- return { valid: false, actions: [], cost: 0, nodesExplored, finalState: current.toObject() };
145
- }
146
- }
147
-
148
- function round3(value: number): number {
149
- return Number(value.toFixed(3));
150
- }