@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.
- package/package.json +32 -2
- package/src/AdaptiveDifficultyFixtures.ts +0 -324
- package/src/AnalyticsPrivacyFixtures.ts +0 -182
- package/src/AnimationNodeCategories.ts +0 -10
- package/src/AnimationVisualNodes.ts +0 -44
- package/src/Behavior.ts +0 -11
- package/src/BehaviorHost.ts +0 -51
- package/src/BehaviorRegistry.ts +0 -22
- package/src/BehaviorSystem.ts +0 -103
- package/src/BehaviorTree.ts +0 -234
- package/src/CloudServiceFixtures.ts +0 -225
- package/src/CulturalBehaviorFixtures.ts +0 -262
- package/src/DecisionTree.ts +0 -162
- package/src/GOAP.ts +0 -150
- package/src/HTN.ts +0 -178
- package/src/LearningAgentFixtures.ts +0 -184
- package/src/NetworkReplicationFixtures.ts +0 -440
- package/src/Perception.ts +0 -163
- package/src/PlayerBehaviorTelemetryFixtures.ts +0 -257
- package/src/ProceduralContentAdaptationFixtures.ts +0 -205
- package/src/ScriptContext.ts +0 -34
- package/src/StateMachine.ts +0 -138
- package/src/UtilityAI.ts +0 -158
- package/src/VisualGraph.ts +0 -236
- package/src/VisualGraphContext.ts +0 -138
- package/src/VisualGraphExecutor.ts +0 -709
- package/src/VisualNode.ts +0 -44
- package/src/VisualNodeCatalog.ts +0 -202
- package/src/WeaponSystem.ts +0 -793
- package/src/index.ts +0 -178
- package/tests/scripting.test.ts +0 -253
- package/tsconfig.json +0 -7
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
export type PlayerSkillLevel = "beginner" | "novice" | "intermediate" | "advanced" | "expert";
|
|
2
|
-
export type PlayerPlaystyle = "aggressive" | "defensive" | "balanced" | "stealth" | "exploration" | "speedrun" | "completionist";
|
|
3
|
-
export type PlayerEngagementLevel = "casual" | "regular" | "dedicated" | "hardcore";
|
|
4
|
-
export type PlayerEventCategory = "combat" | "movement" | "interaction" | "progression" | "economy" | "social" | "custom";
|
|
5
|
-
export type PlayerEventSeverity = "low" | "medium" | "high" | "critical";
|
|
6
|
-
|
|
7
|
-
export interface PlayerBehaviorTelemetryOptions {
|
|
8
|
-
readonly seed?: number;
|
|
9
|
-
readonly playerId?: string;
|
|
10
|
-
readonly sessionSeconds?: number;
|
|
11
|
-
readonly combatEvents?: number;
|
|
12
|
-
readonly movementEvents?: number;
|
|
13
|
-
readonly interactionEvents?: number;
|
|
14
|
-
readonly progressionEvents?: number;
|
|
15
|
-
readonly successEvents?: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface PlayerSkillAssessmentTelemetry {
|
|
19
|
-
readonly area: string;
|
|
20
|
-
readonly level: number;
|
|
21
|
-
readonly confidence: number;
|
|
22
|
-
readonly sampleSize: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface PlayerBehaviorPatternTelemetry {
|
|
26
|
-
readonly name: string;
|
|
27
|
-
readonly frequency: number;
|
|
28
|
-
readonly confidence: number;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface PlayerBehaviorTelemetryFixture {
|
|
32
|
-
readonly source: "origin-master-player-profile-event-analysis-adapted";
|
|
33
|
-
readonly playerId: string;
|
|
34
|
-
readonly profile: {
|
|
35
|
-
readonly skillLevel: PlayerSkillLevel;
|
|
36
|
-
readonly playstyle: PlayerPlaystyle;
|
|
37
|
-
readonly engagement: PlayerEngagementLevel;
|
|
38
|
-
readonly skills: readonly PlayerSkillAssessmentTelemetry[];
|
|
39
|
-
readonly preferences: readonly { readonly category: string; readonly value: string; readonly strength: number }[];
|
|
40
|
-
readonly patterns: readonly PlayerBehaviorPatternTelemetry[];
|
|
41
|
-
readonly totalPlaytimeMs: number;
|
|
42
|
-
readonly sessionCount: number;
|
|
43
|
-
};
|
|
44
|
-
readonly events: {
|
|
45
|
-
readonly total: number;
|
|
46
|
-
readonly byCategory: Record<PlayerEventCategory, number>;
|
|
47
|
-
readonly bySeverity: Record<PlayerEventSeverity, number>;
|
|
48
|
-
readonly successRate: number;
|
|
49
|
-
readonly avgDurationMs: number;
|
|
50
|
-
readonly eventsPerMinute: number;
|
|
51
|
-
};
|
|
52
|
-
readonly sessionAnalysis: {
|
|
53
|
-
readonly durationMs: number;
|
|
54
|
-
readonly dominantPlaystyle: PlayerPlaystyle;
|
|
55
|
-
readonly skillEstimate: PlayerSkillLevel;
|
|
56
|
-
readonly insights: readonly {
|
|
57
|
-
readonly type: string;
|
|
58
|
-
readonly confidence: number;
|
|
59
|
-
readonly evidence: readonly string[];
|
|
60
|
-
readonly recommendations: readonly string[];
|
|
61
|
-
}[];
|
|
62
|
-
};
|
|
63
|
-
readonly productionReadiness: {
|
|
64
|
-
readonly playerProfileTelemetry: true;
|
|
65
|
-
readonly eventTrackingTelemetry: true;
|
|
66
|
-
readonly behaviorPatternTelemetry: true;
|
|
67
|
-
readonly adaptiveSystemInputTelemetry: true;
|
|
68
|
-
};
|
|
69
|
-
readonly blockedClaims: readonly string[];
|
|
70
|
-
readonly claimBoundary: string;
|
|
71
|
-
readonly hash: string;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function samplePlayerBehaviorTelemetryFixture(options: PlayerBehaviorTelemetryOptions = {}): PlayerBehaviorTelemetryFixture {
|
|
75
|
-
const seed = normalizeSeed(options.seed ?? 0xb34a);
|
|
76
|
-
const playerId = normalizePlayerId(options.playerId ?? "external-parity-runtime-player");
|
|
77
|
-
const sessionSeconds = positive(options.sessionSeconds ?? 180, "sessionSeconds");
|
|
78
|
-
const combat = count(options.combatEvents ?? 12, "combatEvents");
|
|
79
|
-
const movement = count(options.movementEvents ?? 18, "movementEvents");
|
|
80
|
-
const interaction = count(options.interactionEvents ?? 7, "interactionEvents");
|
|
81
|
-
const progression = count(options.progressionEvents ?? 5, "progressionEvents");
|
|
82
|
-
const success = count(options.successEvents ?? 24, "successEvents");
|
|
83
|
-
const total = combat + movement + interaction + progression;
|
|
84
|
-
const successRate = Number((success / Math.max(1, total)).toFixed(4));
|
|
85
|
-
const eventsPerMinute = Number((total / Math.max(1, sessionSeconds / 60)).toFixed(3));
|
|
86
|
-
const combatRatio = combat / Math.max(1, total);
|
|
87
|
-
const movementRatio = movement / Math.max(1, total);
|
|
88
|
-
const interactionRatio = interaction / Math.max(1, total);
|
|
89
|
-
const progressionRatio = progression / Math.max(1, total);
|
|
90
|
-
const playstyle = detectPlaystyle({ combatRatio, movementRatio, interactionRatio, progressionRatio, successRate, eventsPerMinute });
|
|
91
|
-
const skillScore = successRate * 0.4 + Math.min(eventsPerMinute / 10, 1) * 0.3 + Math.min(4 / Math.max(1, total), 1) * 0.3;
|
|
92
|
-
const skillLevel = skillForScore(skillScore);
|
|
93
|
-
const engagement = sessionSeconds > 900 ? "hardcore" : sessionSeconds > 420 ? "dedicated" : sessionSeconds > 180 ? "regular" : "casual";
|
|
94
|
-
const skills: readonly PlayerSkillAssessmentTelemetry[] = [
|
|
95
|
-
skill("combat", clamp01(successRate * 0.7 + combatRatio * 0.3), combat),
|
|
96
|
-
skill("navigation", clamp01(0.45 + movementRatio * 0.65), movement),
|
|
97
|
-
skill("objective-routing", clamp01(0.36 + progressionRatio * 1.1), progression),
|
|
98
|
-
skill("interaction", clamp01(0.42 + interactionRatio * 1.15), interaction)
|
|
99
|
-
];
|
|
100
|
-
const patterns: readonly PlayerBehaviorPatternTelemetry[] = [
|
|
101
|
-
{ name: `${playstyle}-loop`, frequency: Number(Math.max(1, eventsPerMinute).toFixed(3)), confidence: Number((0.62 + successRate * 0.24).toFixed(3)) },
|
|
102
|
-
{ name: "objective-retry", frequency: Number((Math.max(0, total - success) / Math.max(1, sessionSeconds / 60)).toFixed(3)), confidence: Number((0.5 + (1 - successRate) * 0.32).toFixed(3)) }
|
|
103
|
-
];
|
|
104
|
-
const insights = [
|
|
105
|
-
{
|
|
106
|
-
type: "playstyle",
|
|
107
|
-
confidence: patterns[0]?.confidence ?? 0.6,
|
|
108
|
-
evidence: [`combat=${combat}`, `movement=${movement}`, `progression=${progression}`],
|
|
109
|
-
recommendations: [playstyle === "aggressive" ? "offer-combat-optional-objective" : "offer-navigation-shortcut"]
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
type: "skill-estimate",
|
|
113
|
-
confidence: Number((0.58 + skillScore * 0.3).toFixed(3)),
|
|
114
|
-
evidence: [`successRate=${successRate}`, `eventsPerMinute=${eventsPerMinute}`],
|
|
115
|
-
recommendations: [skillScore > 0.6 ? "raise-optional-challenge" : "keep-assistive-checkpoints"]
|
|
116
|
-
}
|
|
117
|
-
] as const;
|
|
118
|
-
const core = {
|
|
119
|
-
playerId,
|
|
120
|
-
skillLevel,
|
|
121
|
-
playstyle,
|
|
122
|
-
engagement,
|
|
123
|
-
successRate,
|
|
124
|
-
eventsPerMinute,
|
|
125
|
-
skills,
|
|
126
|
-
patterns,
|
|
127
|
-
insights
|
|
128
|
-
};
|
|
129
|
-
return {
|
|
130
|
-
source: "origin-master-player-profile-event-analysis-adapted",
|
|
131
|
-
playerId,
|
|
132
|
-
profile: {
|
|
133
|
-
skillLevel,
|
|
134
|
-
playstyle,
|
|
135
|
-
engagement,
|
|
136
|
-
skills,
|
|
137
|
-
preferences: [
|
|
138
|
-
{ category: "difficulty", value: skillScore > 0.6 ? "advanced" : "adaptive", strength: Number((0.62 + seededUnit(seed) * 0.22).toFixed(3)) },
|
|
139
|
-
{ category: "route", value: movementRatio > combatRatio ? "exploration" : "direct", strength: Number((0.56 + seededUnit(seed ^ 0x2735) * 0.2).toFixed(3)) }
|
|
140
|
-
],
|
|
141
|
-
patterns,
|
|
142
|
-
totalPlaytimeMs: Math.round(sessionSeconds * 1000),
|
|
143
|
-
sessionCount: 3 + Math.round(seededUnit(seed ^ 0x9e37) * 4)
|
|
144
|
-
},
|
|
145
|
-
events: {
|
|
146
|
-
total,
|
|
147
|
-
byCategory: {
|
|
148
|
-
combat,
|
|
149
|
-
movement,
|
|
150
|
-
interaction,
|
|
151
|
-
progression,
|
|
152
|
-
economy: 0,
|
|
153
|
-
social: 0,
|
|
154
|
-
custom: 0
|
|
155
|
-
},
|
|
156
|
-
bySeverity: {
|
|
157
|
-
low: movement,
|
|
158
|
-
medium: interaction + progression,
|
|
159
|
-
high: combat,
|
|
160
|
-
critical: 0
|
|
161
|
-
},
|
|
162
|
-
successRate,
|
|
163
|
-
avgDurationMs: Number((180 + seededUnit(seed ^ 0xf00d) * 140).toFixed(2)),
|
|
164
|
-
eventsPerMinute
|
|
165
|
-
},
|
|
166
|
-
sessionAnalysis: {
|
|
167
|
-
durationMs: Math.round(sessionSeconds * 1000),
|
|
168
|
-
dominantPlaystyle: playstyle,
|
|
169
|
-
skillEstimate: skillLevel,
|
|
170
|
-
insights
|
|
171
|
-
},
|
|
172
|
-
productionReadiness: {
|
|
173
|
-
playerProfileTelemetry: true,
|
|
174
|
-
eventTrackingTelemetry: true,
|
|
175
|
-
behaviorPatternTelemetry: true,
|
|
176
|
-
adaptiveSystemInputTelemetry: true
|
|
177
|
-
},
|
|
178
|
-
blockedClaims: [
|
|
179
|
-
"production player modeling certification",
|
|
180
|
-
"privacy/compliance analytics certification",
|
|
181
|
-
"cloud telemetry pipeline parity",
|
|
182
|
-
"Unity Analytics/Remote Config parity",
|
|
183
|
-
"Unreal Insights gameplay telemetry parity"
|
|
184
|
-
],
|
|
185
|
-
claimBoundary: "This fixture ports old smart PlayerProfile/EventTracker/BehaviorAnalyzer concepts into deterministic local runtime telemetry. It does not claim production player modeling, privacy/compliance analytics certification, cloud telemetry pipelines, Unity Analytics, or Unreal Insights parity.",
|
|
186
|
-
hash: stableHash(JSON.stringify(core))
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function detectPlaystyle(stats: { readonly combatRatio: number; readonly movementRatio: number; readonly interactionRatio: number; readonly progressionRatio: number; readonly successRate: number; readonly eventsPerMinute: number }): PlayerPlaystyle {
|
|
191
|
-
if (stats.combatRatio > 0.45 && stats.eventsPerMinute > 8) return "aggressive";
|
|
192
|
-
if (stats.movementRatio > 0.45 && stats.combatRatio < 0.35) return "exploration";
|
|
193
|
-
if (stats.successRate > 0.7 && stats.combatRatio < 0.4) return "stealth";
|
|
194
|
-
if (stats.eventsPerMinute > 10 && stats.progressionRatio > 0.2) return "speedrun";
|
|
195
|
-
if (stats.interactionRatio > 0.35) return "completionist";
|
|
196
|
-
if (stats.combatRatio > 0.3 && stats.successRate > 0.6) return "defensive";
|
|
197
|
-
return "balanced";
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function skillForScore(score: number): PlayerSkillLevel {
|
|
201
|
-
if (score < 0.2) return "beginner";
|
|
202
|
-
if (score < 0.4) return "novice";
|
|
203
|
-
if (score < 0.6) return "intermediate";
|
|
204
|
-
if (score < 0.8) return "advanced";
|
|
205
|
-
return "expert";
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function skill(area: string, level: number, sampleSize: number): PlayerSkillAssessmentTelemetry {
|
|
209
|
-
return {
|
|
210
|
-
area,
|
|
211
|
-
level: Number(level.toFixed(4)),
|
|
212
|
-
confidence: Number(Math.min(1, Math.max(0.1, sampleSize / 40)).toFixed(3)),
|
|
213
|
-
sampleSize
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function count(value: number, name: string): number {
|
|
218
|
-
if (!Number.isFinite(value) || value < 0) throw new Error(`${name} must be a non-negative finite number.`);
|
|
219
|
-
return Math.trunc(value);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function positive(value: number, name: string): number {
|
|
223
|
-
if (!Number.isFinite(value) || value <= 0) throw new Error(`${name} must be a positive finite number.`);
|
|
224
|
-
return value;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function normalizeSeed(seed: number): number {
|
|
228
|
-
if (!Number.isInteger(seed) || seed < 0) throw new Error("seed must be a non-negative integer.");
|
|
229
|
-
return seed >>> 0;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function normalizePlayerId(playerId: string): string {
|
|
233
|
-
const normalized = playerId.trim();
|
|
234
|
-
if (normalized.length === 0) throw new Error("playerId is required.");
|
|
235
|
-
return normalized;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function clamp01(value: number): number {
|
|
239
|
-
return Math.max(0, Math.min(1, value));
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function seededUnit(seed: number): number {
|
|
243
|
-
let value = seed >>> 0;
|
|
244
|
-
value ^= value << 13;
|
|
245
|
-
value ^= value >>> 17;
|
|
246
|
-
value ^= value << 5;
|
|
247
|
-
return (value >>> 0) / 0xffffffff;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function stableHash(value: string): string {
|
|
251
|
-
let hash = 2166136261;
|
|
252
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
253
|
-
hash ^= value.charCodeAt(index);
|
|
254
|
-
hash = Math.imul(hash, 16777619);
|
|
255
|
-
}
|
|
256
|
-
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
257
|
-
}
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import type { PlayerBehaviorTelemetryFixture, PlayerPlaystyle, PlayerSkillLevel } from "./PlayerBehaviorTelemetryFixtures";
|
|
2
|
-
|
|
3
|
-
export type GeneratedContentType = "level" | "quest" | "enemy_encounter" | "puzzle" | "reward" | "narrative";
|
|
4
|
-
export type GeneratedContentDifficulty = "trivial" | "easy" | "moderate" | "challenging" | "extreme";
|
|
5
|
-
export type AdaptiveAiStrategy = "mirror" | "counter" | "exploit" | "match_skill" | "challenge";
|
|
6
|
-
|
|
7
|
-
export interface ProceduralContentAdaptationOptions {
|
|
8
|
-
readonly seed?: number;
|
|
9
|
-
readonly playerTelemetry: PlayerBehaviorTelemetryFixture;
|
|
10
|
-
readonly strategy?: AdaptiveAiStrategy;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface GeneratedContentTelemetry {
|
|
14
|
-
readonly id: string;
|
|
15
|
-
readonly type: GeneratedContentType;
|
|
16
|
-
readonly difficulty: GeneratedContentDifficulty;
|
|
17
|
-
readonly estimatedDurationMs: number;
|
|
18
|
-
readonly tags: readonly string[];
|
|
19
|
-
readonly parameters: {
|
|
20
|
-
readonly size?: number;
|
|
21
|
-
readonly enemyCount?: number;
|
|
22
|
-
readonly resourceCount?: number;
|
|
23
|
-
readonly hasSecretAreas?: boolean;
|
|
24
|
-
readonly hasTimeLimit?: boolean;
|
|
25
|
-
readonly layout?: "linear" | "branching";
|
|
26
|
-
readonly objectiveCount?: number;
|
|
27
|
-
readonly rewardTier?: string;
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface ProceduralContentAdaptationFixture {
|
|
32
|
-
readonly source: "origin-master-content-generator-adaptive-ai-adapted";
|
|
33
|
-
readonly content: readonly GeneratedContentTelemetry[];
|
|
34
|
-
readonly adaptiveAi: {
|
|
35
|
-
readonly strategy: AdaptiveAiStrategy;
|
|
36
|
-
readonly behaviorMode: "passive" | "balanced" | "aggressive" | "tactical" | "adaptive";
|
|
37
|
-
readonly aggression: number;
|
|
38
|
-
readonly defensiveness: number;
|
|
39
|
-
readonly tacticalAwareness: number;
|
|
40
|
-
readonly reactionSpeed: number;
|
|
41
|
-
readonly accuracy: number;
|
|
42
|
-
readonly abilityUsage: number;
|
|
43
|
-
readonly coordination: number;
|
|
44
|
-
};
|
|
45
|
-
readonly productionReadiness: {
|
|
46
|
-
readonly contentGenerationTelemetry: true;
|
|
47
|
-
readonly playstyleCustomizationTelemetry: true;
|
|
48
|
-
readonly skillScalingTelemetry: true;
|
|
49
|
-
readonly adaptiveAiParameterTelemetry: true;
|
|
50
|
-
};
|
|
51
|
-
readonly blockedClaims: readonly string[];
|
|
52
|
-
readonly claimBoundary: string;
|
|
53
|
-
readonly hash: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function sampleProceduralContentAdaptationFixture(options: ProceduralContentAdaptationOptions): ProceduralContentAdaptationFixture {
|
|
57
|
-
const seed = normalizeSeed(options.seed ?? 0xc067);
|
|
58
|
-
const profile = options.playerTelemetry.profile;
|
|
59
|
-
const difficulty = difficultyForSkill(profile.skillLevel);
|
|
60
|
-
const playstyle = profile.playstyle;
|
|
61
|
-
const content: readonly GeneratedContentTelemetry[] = [
|
|
62
|
-
generateLevel(seed, difficulty, playstyle),
|
|
63
|
-
generateEncounter(seed ^ 0x1138, difficulty, playstyle),
|
|
64
|
-
generateReward(seed ^ 0x9821, difficulty, profile.skillLevel),
|
|
65
|
-
generateQuest(seed ^ 0xa77a, difficulty, playstyle)
|
|
66
|
-
];
|
|
67
|
-
const adaptiveAi = adaptiveAiFor(options.strategy ?? "counter", playstyle, profile.skillLevel, options.playerTelemetry.events.successRate);
|
|
68
|
-
const core = { content, adaptiveAi, playerHash: options.playerTelemetry.hash };
|
|
69
|
-
return {
|
|
70
|
-
source: "origin-master-content-generator-adaptive-ai-adapted",
|
|
71
|
-
content,
|
|
72
|
-
adaptiveAi,
|
|
73
|
-
productionReadiness: {
|
|
74
|
-
contentGenerationTelemetry: true,
|
|
75
|
-
playstyleCustomizationTelemetry: true,
|
|
76
|
-
skillScalingTelemetry: true,
|
|
77
|
-
adaptiveAiParameterTelemetry: true
|
|
78
|
-
},
|
|
79
|
-
blockedClaims: [
|
|
80
|
-
"production procedural content generation parity",
|
|
81
|
-
"runtime-authored quest/narrative generation",
|
|
82
|
-
"Unity Adaptive Performance/Game Foundation parity",
|
|
83
|
-
"Unreal PCG Framework/MassAI parity",
|
|
84
|
-
"LLM-authored live content generation"
|
|
85
|
-
],
|
|
86
|
-
claimBoundary: "This fixture ports old ContentGenerator/AdaptiveAI concepts into deterministic content-plan and adaptive-parameter telemetry. It does not create production PCG levels, generate live narrative, or claim Unity/Unreal PCG or AI-system parity.",
|
|
87
|
-
hash: stableHash(JSON.stringify(core))
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function generateLevel(seed: number, difficulty: GeneratedContentDifficulty, playstyle: PlayerPlaystyle): GeneratedContentTelemetry {
|
|
92
|
-
const multiplier = difficultyMultiplier(difficulty);
|
|
93
|
-
return {
|
|
94
|
-
id: "content-level-1",
|
|
95
|
-
type: "level",
|
|
96
|
-
difficulty,
|
|
97
|
-
estimatedDurationMs: Math.round(240000 * multiplier),
|
|
98
|
-
tags: ["procedural", "level", playstyle],
|
|
99
|
-
parameters: {
|
|
100
|
-
size: Math.round(90 * multiplier),
|
|
101
|
-
enemyCount: playstyle === "stealth" ? Math.round(4 * multiplier) : Math.round(7 * multiplier),
|
|
102
|
-
resourceCount: playstyle === "exploration" || playstyle === "completionist" ? Math.round(10 * multiplier) : Math.round(6 * multiplier),
|
|
103
|
-
hasSecretAreas: playstyle === "exploration" || playstyle === "completionist",
|
|
104
|
-
hasTimeLimit: playstyle === "speedrun",
|
|
105
|
-
layout: seededUnit(seed) > 0.5 ? "branching" : "linear"
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function generateEncounter(seed: number, difficulty: GeneratedContentDifficulty, playstyle: PlayerPlaystyle): GeneratedContentTelemetry {
|
|
111
|
-
const multiplier = difficultyMultiplier(difficulty);
|
|
112
|
-
return {
|
|
113
|
-
id: "content-encounter-1",
|
|
114
|
-
type: "enemy_encounter",
|
|
115
|
-
difficulty,
|
|
116
|
-
estimatedDurationMs: Math.round(95000 * multiplier),
|
|
117
|
-
tags: ["combat", "encounter", playstyle === "defensive" ? "cover" : "pressure"],
|
|
118
|
-
parameters: {
|
|
119
|
-
enemyCount: Math.max(1, Math.round((playstyle === "aggressive" ? 6 : 4) * multiplier)),
|
|
120
|
-
resourceCount: Math.max(1, Math.round((1 + seededUnit(seed) * 3) * multiplier)),
|
|
121
|
-
layout: playstyle === "defensive" ? "branching" : "linear"
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function generateReward(seed: number, difficulty: GeneratedContentDifficulty, skill: PlayerSkillLevel): GeneratedContentTelemetry {
|
|
127
|
-
return {
|
|
128
|
-
id: "content-reward-1",
|
|
129
|
-
type: "reward",
|
|
130
|
-
difficulty,
|
|
131
|
-
estimatedDurationMs: 0,
|
|
132
|
-
tags: ["reward", "loot", skill],
|
|
133
|
-
parameters: {
|
|
134
|
-
rewardTier: skill === "expert" || skill === "advanced" ? "rare" : seededUnit(seed) > 0.55 ? "uncommon" : "standard",
|
|
135
|
-
resourceCount: Math.round(2 + seededUnit(seed ^ 0x55aa) * 4)
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function generateQuest(seed: number, difficulty: GeneratedContentDifficulty, playstyle: PlayerPlaystyle): GeneratedContentTelemetry {
|
|
141
|
-
return {
|
|
142
|
-
id: "content-quest-1",
|
|
143
|
-
type: "quest",
|
|
144
|
-
difficulty,
|
|
145
|
-
estimatedDurationMs: Math.round(360000 * difficultyMultiplier(difficulty)),
|
|
146
|
-
tags: ["quest", playstyle === "exploration" ? "optional" : "objective"],
|
|
147
|
-
parameters: {
|
|
148
|
-
objectiveCount: Math.round(2 + seededUnit(seed) * 3),
|
|
149
|
-
hasSecretAreas: playstyle === "completionist" || playstyle === "exploration"
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function adaptiveAiFor(strategy: AdaptiveAiStrategy, playstyle: PlayerPlaystyle, skill: PlayerSkillLevel, successRate: number): ProceduralContentAdaptationFixture["adaptiveAi"] {
|
|
155
|
-
const skillScale = skill === "expert" ? 1 : skill === "advanced" ? 0.85 : skill === "intermediate" ? 0.65 : skill === "novice" ? 0.48 : 0.35;
|
|
156
|
-
const counterAggression = playstyle === "defensive" || playstyle === "stealth" ? 0.72 : playstyle === "aggressive" ? 0.48 : 0.58;
|
|
157
|
-
return {
|
|
158
|
-
strategy,
|
|
159
|
-
behaviorMode: strategy === "counter" || strategy === "challenge" ? "adaptive" : "balanced",
|
|
160
|
-
aggression: Number(Math.min(1, counterAggression + skillScale * 0.12).toFixed(3)),
|
|
161
|
-
defensiveness: Number((playstyle === "aggressive" ? 0.72 : 0.48 + skillScale * 0.16).toFixed(3)),
|
|
162
|
-
tacticalAwareness: Number((0.42 + skillScale * 0.44).toFixed(3)),
|
|
163
|
-
reactionSpeed: Number((0.82 + skillScale * 0.38).toFixed(3)),
|
|
164
|
-
accuracy: Number((0.36 + skillScale * 0.36 + successRate * 0.08).toFixed(3)),
|
|
165
|
-
abilityUsage: Number((0.3 + skillScale * 0.48).toFixed(3)),
|
|
166
|
-
coordination: Number((0.38 + skillScale * 0.32).toFixed(3))
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function difficultyForSkill(skill: PlayerSkillLevel): GeneratedContentDifficulty {
|
|
171
|
-
if (skill === "beginner") return "easy";
|
|
172
|
-
if (skill === "novice" || skill === "intermediate") return "moderate";
|
|
173
|
-
if (skill === "advanced") return "challenging";
|
|
174
|
-
return "extreme";
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function difficultyMultiplier(difficulty: GeneratedContentDifficulty): number {
|
|
178
|
-
if (difficulty === "trivial") return 0.6;
|
|
179
|
-
if (difficulty === "easy") return 0.8;
|
|
180
|
-
if (difficulty === "moderate") return 1;
|
|
181
|
-
if (difficulty === "challenging") return 1.28;
|
|
182
|
-
return 1.55;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function normalizeSeed(seed: number): number {
|
|
186
|
-
if (!Number.isInteger(seed) || seed < 0) throw new Error("seed must be a non-negative integer.");
|
|
187
|
-
return seed >>> 0;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function seededUnit(seed: number): number {
|
|
191
|
-
let value = seed >>> 0;
|
|
192
|
-
value ^= value << 13;
|
|
193
|
-
value ^= value >>> 17;
|
|
194
|
-
value ^= value << 5;
|
|
195
|
-
return (value >>> 0) / 0xffffffff;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function stableHash(value: string): string {
|
|
199
|
-
let hash = 2166136261;
|
|
200
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
201
|
-
hash ^= value.charCodeAt(index);
|
|
202
|
-
hash = Math.imul(hash, 16777619);
|
|
203
|
-
}
|
|
204
|
-
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
205
|
-
}
|
package/src/ScriptContext.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { BehaviorPhase } from "./Behavior";
|
|
2
|
-
|
|
3
|
-
export interface ScriptContextOptions {
|
|
4
|
-
readonly phase: BehaviorPhase;
|
|
5
|
-
readonly deltaSeconds?: number;
|
|
6
|
-
readonly fixedDeltaSeconds?: number;
|
|
7
|
-
readonly target?: unknown;
|
|
8
|
-
readonly services?: ReadonlyMap<string, unknown>;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export class ScriptContext {
|
|
12
|
-
readonly phase: BehaviorPhase;
|
|
13
|
-
readonly deltaSeconds: number;
|
|
14
|
-
readonly fixedDeltaSeconds: number;
|
|
15
|
-
readonly target?: unknown;
|
|
16
|
-
|
|
17
|
-
private readonly services: ReadonlyMap<string, unknown>;
|
|
18
|
-
|
|
19
|
-
constructor(options: ScriptContextOptions) {
|
|
20
|
-
this.phase = options.phase;
|
|
21
|
-
this.deltaSeconds = options.deltaSeconds ?? 0;
|
|
22
|
-
this.fixedDeltaSeconds = options.fixedDeltaSeconds ?? 0;
|
|
23
|
-
this.target = options.target;
|
|
24
|
-
this.services = options.services ?? new Map();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
getService<T>(name: string): T {
|
|
28
|
-
if (!this.services.has(name)) {
|
|
29
|
-
throw new Error(`Script service is not available in ${this.phase}: ${name}`);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return this.services.get(name) as T;
|
|
33
|
-
}
|
|
34
|
-
}
|
package/src/StateMachine.ts
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { Blackboard, type BlackboardValue } from "./BehaviorTree";
|
|
2
|
-
|
|
3
|
-
export type StateTransitionCondition = (blackboard: Blackboard) => boolean;
|
|
4
|
-
|
|
5
|
-
export interface StateTransition {
|
|
6
|
-
readonly targetState: string;
|
|
7
|
-
readonly condition: StateTransitionCondition;
|
|
8
|
-
readonly priority: number;
|
|
9
|
-
readonly name: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface StateMachineSnapshot {
|
|
13
|
-
readonly running: boolean;
|
|
14
|
-
readonly currentState: string;
|
|
15
|
-
readonly previousState: string;
|
|
16
|
-
readonly tickCount: number;
|
|
17
|
-
readonly transitionCount: number;
|
|
18
|
-
readonly history: readonly string[];
|
|
19
|
-
readonly trace: readonly string[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export class State {
|
|
23
|
-
readonly transitions: StateTransition[] = [];
|
|
24
|
-
onEnter?: (blackboard: Blackboard) => void;
|
|
25
|
-
onUpdate?: (deltaSeconds: number, blackboard: Blackboard) => void;
|
|
26
|
-
onExit?: (blackboard: Blackboard) => void;
|
|
27
|
-
|
|
28
|
-
constructor(readonly id: string, readonly name = id) {}
|
|
29
|
-
|
|
30
|
-
addTransition(targetState: string, condition: StateTransitionCondition, priority = 0, name = `${this.id}->${targetState}`): this {
|
|
31
|
-
this.transitions.push({ targetState, condition, priority, name });
|
|
32
|
-
this.transitions.sort((left, right) => right.priority - left.priority || left.name.localeCompare(right.name));
|
|
33
|
-
return this;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export class StateMachine {
|
|
38
|
-
private readonly states = new Map<string, State>();
|
|
39
|
-
private readonly history: string[] = [];
|
|
40
|
-
private readonly maxHistorySize: number;
|
|
41
|
-
private current?: State;
|
|
42
|
-
private previous?: State;
|
|
43
|
-
private running = false;
|
|
44
|
-
private tickCounter = 0;
|
|
45
|
-
private transitionCounter = 0;
|
|
46
|
-
private lastTrace: string[] = [];
|
|
47
|
-
|
|
48
|
-
constructor(readonly blackboard = new Blackboard(), options: { readonly maxHistorySize?: number } = {}) {
|
|
49
|
-
this.maxHistorySize = options.maxHistorySize ?? 12;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
addState(state: State): this {
|
|
53
|
-
if (this.states.has(state.id)) throw new Error(`State already registered: ${state.id}`);
|
|
54
|
-
this.states.set(state.id, state);
|
|
55
|
-
return this;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
getState(id: string): State | undefined {
|
|
59
|
-
return this.states.get(id);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
start(stateId: string): StateMachineSnapshot {
|
|
63
|
-
const state = this.requireState(stateId);
|
|
64
|
-
this.running = true;
|
|
65
|
-
this.current = state;
|
|
66
|
-
this.previous = undefined;
|
|
67
|
-
this.lastTrace = [`enter:${state.id}`];
|
|
68
|
-
state.onEnter?.(this.blackboard);
|
|
69
|
-
return this.snapshot();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
stop(): StateMachineSnapshot {
|
|
73
|
-
if (this.current) {
|
|
74
|
-
this.current.onExit?.(this.blackboard);
|
|
75
|
-
this.lastTrace = [`exit:${this.current.id}`, "stop"];
|
|
76
|
-
} else {
|
|
77
|
-
this.lastTrace = ["stop"];
|
|
78
|
-
}
|
|
79
|
-
this.running = false;
|
|
80
|
-
this.previous = this.current;
|
|
81
|
-
this.current = undefined;
|
|
82
|
-
return this.snapshot();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
update(deltaSeconds: number): StateMachineSnapshot {
|
|
86
|
-
this.tickCounter += 1;
|
|
87
|
-
this.lastTrace = [];
|
|
88
|
-
if (!this.running || !this.current) {
|
|
89
|
-
this.lastTrace.push("idle");
|
|
90
|
-
return this.snapshot();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
this.current.onUpdate?.(Math.max(0, deltaSeconds), this.blackboard);
|
|
94
|
-
this.lastTrace.push(`update:${this.current.id}`);
|
|
95
|
-
const transition = this.current.transitions.find((entry) => entry.condition(this.blackboard));
|
|
96
|
-
if (transition) {
|
|
97
|
-
this.transitionTo(transition);
|
|
98
|
-
}
|
|
99
|
-
return this.snapshot();
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
set(key: string, value: BlackboardValue): void {
|
|
103
|
-
this.blackboard.set(key, value);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
snapshot(): StateMachineSnapshot {
|
|
107
|
-
return {
|
|
108
|
-
running: this.running,
|
|
109
|
-
currentState: this.current?.id ?? "none",
|
|
110
|
-
previousState: this.previous?.id ?? "none",
|
|
111
|
-
tickCount: this.tickCounter,
|
|
112
|
-
transitionCount: this.transitionCounter,
|
|
113
|
-
history: [...this.history],
|
|
114
|
-
trace: [...this.lastTrace]
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private transitionTo(transition: StateTransition): void {
|
|
119
|
-
if (!this.current) return;
|
|
120
|
-
const from = this.current;
|
|
121
|
-
const to = this.requireState(transition.targetState);
|
|
122
|
-
from.onExit?.(this.blackboard);
|
|
123
|
-
this.previous = from;
|
|
124
|
-
this.current = to;
|
|
125
|
-
this.transitionCounter += 1;
|
|
126
|
-
this.history.push(`${from.id}->${to.id}`);
|
|
127
|
-
while (this.history.length > this.maxHistorySize) this.history.shift();
|
|
128
|
-
this.lastTrace.push(`transition:${transition.name}`);
|
|
129
|
-
to.onEnter?.(this.blackboard);
|
|
130
|
-
this.lastTrace.push(`enter:${to.id}`);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
private requireState(id: string): State {
|
|
134
|
-
const state = this.states.get(id);
|
|
135
|
-
if (!state) throw new Error(`State is not registered: ${id}`);
|
|
136
|
-
return state;
|
|
137
|
-
}
|
|
138
|
-
}
|