@vylos/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/package.json +34 -0
  2. package/src/components/app/EngineView.vue +40 -0
  3. package/src/components/app/GameShell.vue +265 -0
  4. package/src/components/app/LoadingScreen.vue +10 -0
  5. package/src/components/app/MainMenu.vue +243 -0
  6. package/src/components/core/BackgroundLayer.vue +27 -0
  7. package/src/components/core/ChoicePanel.vue +115 -0
  8. package/src/components/core/CustomOverlay.vue +27 -0
  9. package/src/components/core/DialogueBox.vue +144 -0
  10. package/src/components/core/DrawableOverlay.vue +109 -0
  11. package/src/components/core/ForegroundLayer.vue +31 -0
  12. package/src/components/menu/ActionOverlay.vue +126 -0
  13. package/src/components/menu/LocationOverlay.vue +136 -0
  14. package/src/components/menu/PauseMenu.vue +196 -0
  15. package/src/components/menu/SaveLoadMenu.vue +377 -0
  16. package/src/components/menu/SettingsMenu.vue +111 -0
  17. package/src/components/menu/TopBar.vue +65 -0
  18. package/src/composables/useConfig.ts +4 -0
  19. package/src/composables/useEngine.ts +16 -0
  20. package/src/composables/useGameState.ts +9 -0
  21. package/src/composables/useLanguage.ts +31 -0
  22. package/src/engine/core/CheckpointManager.ts +122 -0
  23. package/src/engine/core/Engine.ts +272 -0
  24. package/src/engine/core/EngineFactory.ts +102 -0
  25. package/src/engine/core/EventRunner.ts +488 -0
  26. package/src/engine/errors/EventEndError.ts +7 -0
  27. package/src/engine/errors/InterruptSignal.ts +10 -0
  28. package/src/engine/errors/JumpSignal.ts +10 -0
  29. package/src/engine/errors/StateValidationError.ts +13 -0
  30. package/src/engine/managers/ActionManager.ts +62 -0
  31. package/src/engine/managers/EventManager.ts +166 -0
  32. package/src/engine/managers/HistoryManager.ts +84 -0
  33. package/src/engine/managers/InputManager.ts +117 -0
  34. package/src/engine/managers/LanguageManager.ts +51 -0
  35. package/src/engine/managers/LocationManager.ts +76 -0
  36. package/src/engine/managers/NavigationManager.ts +75 -0
  37. package/src/engine/managers/SaveManager.ts +86 -0
  38. package/src/engine/managers/SettingsManager.ts +70 -0
  39. package/src/engine/managers/WaitManager.ts +47 -0
  40. package/src/engine/schemas/baseGameState.schema.ts +19 -0
  41. package/src/engine/schemas/checkpoint.schema.ts +11 -0
  42. package/src/engine/schemas/engineState.schema.ts +59 -0
  43. package/src/engine/schemas/location.schema.ts +21 -0
  44. package/src/engine/storage/VylosStorage.ts +131 -0
  45. package/src/engine/types/actions.ts +20 -0
  46. package/src/engine/types/checkpoint.ts +31 -0
  47. package/src/engine/types/config.ts +9 -0
  48. package/src/engine/types/dialogue.ts +15 -0
  49. package/src/engine/types/engine.ts +85 -0
  50. package/src/engine/types/events.ts +117 -0
  51. package/src/engine/types/game-state.ts +15 -0
  52. package/src/engine/types/index.ts +10 -0
  53. package/src/engine/types/locations.ts +32 -0
  54. package/src/engine/types/plugin.ts +11 -0
  55. package/src/engine/types/save.ts +40 -0
  56. package/src/engine/utils/TimeHelper.ts +39 -0
  57. package/src/engine/utils/logger.ts +43 -0
  58. package/src/env.d.ts +17 -0
  59. package/src/index.ts +74 -0
  60. package/src/stores/engineState.ts +127 -0
  61. package/src/stores/gameState.ts +49 -0
  62. package/tests/engine/ActionManager.test.ts +94 -0
  63. package/tests/engine/CheckpointManager.test.ts +136 -0
  64. package/tests/engine/EventManager.test.ts +145 -0
  65. package/tests/engine/EventRunner.test.ts +318 -0
  66. package/tests/engine/HistoryManager.test.ts +113 -0
  67. package/tests/engine/LocationManager.test.ts +128 -0
  68. package/tests/engine/schemas.test.ts +250 -0
  69. package/tests/engine/utils.test.ts +75 -0
  70. package/tests/integration/game-loop.test.ts +201 -0
  71. package/tests/safety/event-validation.test.ts +102 -0
  72. package/tests/safety/state-schema.test.ts +96 -0
  73. package/tests/setup.ts +2 -0
  74. package/tsconfig.json +14 -0
  75. package/vitest.config.ts +16 -0
@@ -0,0 +1,70 @@
1
+ import type { EngineSettings } from '../types';
2
+ import { VylosStorage } from '../storage/VylosStorage';
3
+ import { logger } from '../utils/logger';
4
+
5
+ const STORE = 'settings';
6
+ const SETTINGS_KEY = 'engine';
7
+
8
+ const DEFAULT_SETTINGS: EngineSettings = {
9
+ textSpeed: 50,
10
+ autoSpeed: 3,
11
+ volume: { master: 80, music: 70, sfx: 80, voice: 100 },
12
+ language: 'en',
13
+ fullscreen: false,
14
+ };
15
+
16
+ /**
17
+ * Manages engine settings via IndexedDB.
18
+ * Settings are loaded once at startup and cached in memory.
19
+ */
20
+ export class SettingsManager {
21
+ private storage: VylosStorage;
22
+ private ready: Promise<void>;
23
+ private current: EngineSettings = { ...DEFAULT_SETTINGS };
24
+
25
+ constructor(storage: VylosStorage) {
26
+ this.storage = storage;
27
+ this.ready = this.init();
28
+ }
29
+
30
+ private async init(): Promise<void> {
31
+ await this.storage.open();
32
+ try {
33
+ const saved = await this.storage.get<EngineSettings>(STORE, SETTINGS_KEY);
34
+ if (saved) {
35
+ this.current = { ...DEFAULT_SETTINGS, ...saved };
36
+ logger.debug('Settings loaded from IndexedDB');
37
+ }
38
+ } catch {
39
+ logger.warn('Failed to load settings, using defaults');
40
+ }
41
+ }
42
+
43
+ /** Wait until settings are loaded */
44
+ async whenReady(): Promise<void> {
45
+ await this.ready;
46
+ }
47
+
48
+ /** Get current settings (synchronous after init) */
49
+ get settings(): EngineSettings {
50
+ return this.current;
51
+ }
52
+
53
+ /** Update settings and persist */
54
+ async update(partial: Partial<EngineSettings>): Promise<void> {
55
+ await this.ready;
56
+ this.current = JSON.parse(JSON.stringify({ ...this.current, ...partial }));
57
+ try {
58
+ await this.storage.put(STORE, SETTINGS_KEY, this.current);
59
+ logger.debug('Settings saved');
60
+ } catch (error) {
61
+ logger.error('Settings save failed:', error);
62
+ }
63
+ }
64
+
65
+ /** Reset to defaults */
66
+ async reset(): Promise<void> {
67
+ this.current = { ...DEFAULT_SETTINGS };
68
+ await this.storage.put(STORE, SETTINGS_KEY, this.current);
69
+ }
70
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Generic promise-based wait mechanism.
3
+ * Creates a promise that external code can resolve (e.g., UI click handlers).
4
+ */
5
+ export class WaitManager {
6
+ private resolveFunc: ((value: unknown) => void) | null = null;
7
+ private rejectFunc: ((reason: unknown) => void) | null = null;
8
+
9
+ /** Whether a wait is currently active */
10
+ get isWaiting(): boolean {
11
+ return this.resolveFunc !== null;
12
+ }
13
+
14
+ /** Create a new wait and return a promise that resolves when resolve() is called */
15
+ wait<T = void>(): Promise<T> {
16
+ return new Promise<T>((resolve, reject) => {
17
+ this.resolveFunc = resolve as (value: unknown) => void;
18
+ this.rejectFunc = reject;
19
+ });
20
+ }
21
+
22
+ /** Resolve the current wait with a value */
23
+ resolve(value?: unknown): void {
24
+ if (this.resolveFunc) {
25
+ const fn = this.resolveFunc;
26
+ this.resolveFunc = null;
27
+ this.rejectFunc = null;
28
+ fn(value);
29
+ }
30
+ }
31
+
32
+ /** Reject the current wait with an error */
33
+ reject(reason?: unknown): void {
34
+ if (this.rejectFunc) {
35
+ const fn = this.rejectFunc;
36
+ this.resolveFunc = null;
37
+ this.rejectFunc = null;
38
+ fn(reason);
39
+ }
40
+ }
41
+
42
+ /** Cancel any pending wait */
43
+ cancel(): void {
44
+ this.resolveFunc = null;
45
+ this.rejectFunc = null;
46
+ }
47
+ }
@@ -0,0 +1,19 @@
1
+ import { z } from 'zod';
2
+
3
+ /** Base game state schema — projects extend this with their own fields */
4
+ export const baseGameStateSchema = z.object({
5
+ locationId: z.string().min(1),
6
+ gameTime: z.number().nonnegative(),
7
+ flags: z.record(z.string(), z.boolean()),
8
+ counters: z.record(z.string(), z.number()),
9
+ player: z.object({
10
+ name: z.string().min(1),
11
+ }),
12
+ });
13
+
14
+ export type BaseGameStateFromSchema = z.infer<typeof baseGameStateSchema>;
15
+
16
+ /** Create an extended game state schema by merging with project-specific fields */
17
+ export function extendGameStateSchema<T extends z.ZodRawShape>(extension: T) {
18
+ return baseGameStateSchema.extend(extension);
19
+ }
@@ -0,0 +1,11 @@
1
+ import { z } from 'zod';
2
+ import { baseGameStateSchema } from './baseGameState.schema';
3
+
4
+ export const checkpointSchema = z.object({
5
+ step: z.number().int().nonnegative(),
6
+ gameState: baseGameStateSchema,
7
+ type: z.enum(['say', 'choice', 'wait', 'overlay']),
8
+ choiceResult: z.string().optional(),
9
+ });
10
+
11
+ export type CheckpointFromSchema = z.infer<typeof checkpointSchema>;
@@ -0,0 +1,59 @@
1
+ import { z } from 'zod';
2
+
3
+ const dialogueStateSchema = z.object({
4
+ text: z.string(),
5
+ speaker: z.string().nullable(),
6
+ isNarration: z.boolean(),
7
+ });
8
+
9
+ const choiceOptionSchema = z.object({
10
+ text: z.string(),
11
+ value: z.string(),
12
+ disabled: z.boolean().optional(),
13
+ });
14
+
15
+ const choiceStateSchema = z.object({
16
+ prompt: z.string().nullable(),
17
+ options: z.array(choiceOptionSchema),
18
+ });
19
+
20
+ const actionEntrySchema = z.object({
21
+ id: z.string(),
22
+ label: z.string(),
23
+ locationId: z.string(),
24
+ });
25
+
26
+ const locationEntrySchema = z.object({
27
+ id: z.string(),
28
+ name: z.string(),
29
+ accessible: z.boolean(),
30
+ });
31
+
32
+ export const engineStateSchema = z.object({
33
+ phase: z.enum(['created', 'loading', 'main_menu', 'running', 'paused']),
34
+ background: z.string().nullable(),
35
+ foreground: z.string().nullable(),
36
+ dialogue: dialogueStateSchema.nullable(),
37
+ choices: choiceStateSchema.nullable(),
38
+ currentLocationId: z.string().nullable(),
39
+ availableActions: z.array(actionEntrySchema),
40
+ availableLocations: z.array(locationEntrySchema),
41
+ menuOpen: z.enum(['save_load', 'settings']).nullable(),
42
+ skipMode: z.boolean(),
43
+ autoMode: z.boolean(),
44
+ });
45
+
46
+ export type EngineStateFromSchema = z.infer<typeof engineStateSchema>;
47
+
48
+ export const engineSettingsSchema = z.object({
49
+ textSpeed: z.number().min(0).max(1),
50
+ autoSpeed: z.number().min(0).max(1),
51
+ volume: z.object({
52
+ master: z.number().min(0).max(1),
53
+ music: z.number().min(0).max(1),
54
+ sfx: z.number().min(0).max(1),
55
+ voice: z.number().min(0).max(1),
56
+ }),
57
+ language: z.string().min(1),
58
+ fullscreen: z.boolean(),
59
+ });
@@ -0,0 +1,21 @@
1
+ import { z } from 'zod';
2
+
3
+ const backgroundEntrySchema = z.object({
4
+ path: z.string().min(1),
5
+ timeRange: z.tuple([z.number(), z.number()]).optional(),
6
+ });
7
+
8
+ export const locationSchema = z.object({
9
+ id: z.string().min(1),
10
+ name: z.union([z.string(), z.record(z.string(), z.string())]),
11
+ backgrounds: z.array(backgroundEntrySchema),
12
+ });
13
+
14
+ export type LocationFromSchema = z.infer<typeof locationSchema>;
15
+
16
+ export const locationLinkSchema = z.object({
17
+ from: z.string().min(1),
18
+ to: z.string().min(1),
19
+ });
20
+
21
+ export type LocationLinkFromSchema = z.infer<typeof locationLinkSchema>;
@@ -0,0 +1,131 @@
1
+ import { logger } from '../utils/logger';
2
+
3
+ const DB_VERSION = 1;
4
+
5
+ /**
6
+ * Low-level IndexedDB wrapper for Vylos.
7
+ * One database per project, with typed object stores.
8
+ *
9
+ * Usage:
10
+ * const storage = new VylosStorage('my-project');
11
+ * await storage.open();
12
+ * await storage.put('saves', 1, { ... });
13
+ * const data = await storage.get('saves', 1);
14
+ */
15
+ export class VylosStorage {
16
+ private db: IDBDatabase | null = null;
17
+ private dbName: string;
18
+
19
+ static readonly STORES = ['saves', 'settings', 'cache'] as const;
20
+
21
+ constructor(projectId = 'default') {
22
+ this.dbName = `vylos_${projectId}`;
23
+ }
24
+
25
+ async open(): Promise<void> {
26
+ if (this.db) return;
27
+
28
+ return new Promise((resolve, reject) => {
29
+ const request = indexedDB.open(this.dbName, DB_VERSION);
30
+
31
+ request.onupgradeneeded = () => {
32
+ const db = request.result;
33
+ for (const name of VylosStorage.STORES) {
34
+ if (!db.objectStoreNames.contains(name)) {
35
+ db.createObjectStore(name);
36
+ }
37
+ }
38
+ };
39
+
40
+ request.onsuccess = () => {
41
+ this.db = request.result;
42
+ logger.debug(`VylosStorage opened: ${this.dbName}`);
43
+ resolve();
44
+ };
45
+
46
+ request.onerror = () => {
47
+ logger.error('VylosStorage open failed:', request.error);
48
+ reject(request.error);
49
+ };
50
+ });
51
+ }
52
+
53
+ async put<T>(storeName: string, key: IDBValidKey, value: T): Promise<void> {
54
+ const db = this.requireDb();
55
+ return new Promise((resolve, reject) => {
56
+ const tx = db.transaction(storeName, 'readwrite');
57
+ tx.objectStore(storeName).put(value, key);
58
+ tx.oncomplete = () => resolve();
59
+ tx.onerror = () => reject(tx.error);
60
+ });
61
+ }
62
+
63
+ async get<T>(storeName: string, key: IDBValidKey): Promise<T | undefined> {
64
+ const db = this.requireDb();
65
+ return new Promise((resolve, reject) => {
66
+ const tx = db.transaction(storeName, 'readonly');
67
+ const request = tx.objectStore(storeName).get(key);
68
+ request.onsuccess = () => resolve(request.result as T | undefined);
69
+ request.onerror = () => reject(request.error);
70
+ });
71
+ }
72
+
73
+ async delete(storeName: string, key: IDBValidKey): Promise<void> {
74
+ const db = this.requireDb();
75
+ return new Promise((resolve, reject) => {
76
+ const tx = db.transaction(storeName, 'readwrite');
77
+ tx.objectStore(storeName).delete(key);
78
+ tx.oncomplete = () => resolve();
79
+ tx.onerror = () => reject(tx.error);
80
+ });
81
+ }
82
+
83
+ async keys(storeName: string): Promise<IDBValidKey[]> {
84
+ const db = this.requireDb();
85
+ return new Promise((resolve, reject) => {
86
+ const tx = db.transaction(storeName, 'readonly');
87
+ const request = tx.objectStore(storeName).getAllKeys();
88
+ request.onsuccess = () => resolve(request.result);
89
+ request.onerror = () => reject(request.error);
90
+ });
91
+ }
92
+
93
+ async getAll<T>(storeName: string): Promise<T[]> {
94
+ const db = this.requireDb();
95
+ return new Promise((resolve, reject) => {
96
+ const tx = db.transaction(storeName, 'readonly');
97
+ const request = tx.objectStore(storeName).getAll();
98
+ request.onsuccess = () => resolve(request.result as T[]);
99
+ request.onerror = () => reject(request.error);
100
+ });
101
+ }
102
+
103
+ async clear(storeName: string): Promise<void> {
104
+ const db = this.requireDb();
105
+ return new Promise((resolve, reject) => {
106
+ const tx = db.transaction(storeName, 'readwrite');
107
+ tx.objectStore(storeName).clear();
108
+ tx.oncomplete = () => resolve();
109
+ tx.onerror = () => reject(tx.error);
110
+ });
111
+ }
112
+
113
+ close(): void {
114
+ this.db?.close();
115
+ this.db = null;
116
+ }
117
+
118
+ async destroy(): Promise<void> {
119
+ this.close();
120
+ return new Promise((resolve, reject) => {
121
+ const request = indexedDB.deleteDatabase(this.dbName);
122
+ request.onsuccess = () => resolve();
123
+ request.onerror = () => reject(request.error);
124
+ });
125
+ }
126
+
127
+ private requireDb(): IDBDatabase {
128
+ if (!this.db) throw new Error('VylosStorage not opened. Call open() first.');
129
+ return this.db;
130
+ }
131
+ }
@@ -0,0 +1,20 @@
1
+ import type { BaseGameState } from './game-state';
2
+ import type { TextEntry } from './events';
3
+
4
+ /** An action available to the player at a location */
5
+ export interface VylosAction<TState extends BaseGameState = BaseGameState> {
6
+ /** Unique action ID */
7
+ id: string;
8
+
9
+ /** Display label */
10
+ label: string | TextEntry;
11
+
12
+ /** Location this action belongs to (null for global) */
13
+ locationId?: string;
14
+
15
+ /** Whether this action is currently available */
16
+ unlocked?(state: TState): boolean;
17
+
18
+ /** Execute the action (synchronous state mutation) */
19
+ execute(state: TState): void;
20
+ }
@@ -0,0 +1,31 @@
1
+ import type { BaseGameState } from './game-state';
2
+ import type { DialogueState, ChoiceOption } from './engine';
3
+
4
+ /** A checkpoint captured at each interaction point */
5
+ export interface Checkpoint {
6
+ /** Sequential step number within the event */
7
+ step: number;
8
+ /** Deep clone of game state at this point */
9
+ gameState: BaseGameState;
10
+ /** Type of interaction at this step */
11
+ type: CheckpointType;
12
+ /** Stored choice result (for replay during rollback) */
13
+ choiceResult?: string;
14
+ /** Dialogue state at this point (for history browsing) */
15
+ dialogue?: DialogueState | null;
16
+ /** Background path at this point */
17
+ background?: string | null;
18
+ /** Available choice options at this step (for history redo) */
19
+ choiceOptions?: ChoiceOption[];
20
+ }
21
+
22
+ export enum CheckpointType {
23
+ /** Player pressed continue after dialogue */
24
+ Say = 'say',
25
+ /** Player made a choice */
26
+ Choice = 'choice',
27
+ /** Wait completed */
28
+ Wait = 'wait',
29
+ /** Overlay interaction */
30
+ Overlay = 'overlay',
31
+ }
@@ -0,0 +1,9 @@
1
+ export interface VylosConfig {
2
+ name: string;
3
+ id: string;
4
+ version: string;
5
+ languages: string[];
6
+ defaultLanguage: string;
7
+ defaultLocation: string;
8
+ resolution: { width: number; height: number };
9
+ }
@@ -0,0 +1,15 @@
1
+ import type { TextEntry } from './events';
2
+
3
+ /** Resolved dialogue line ready for display */
4
+ export interface DialogueLine {
5
+ text: string;
6
+ speaker: string | null;
7
+ isNarration: boolean;
8
+ }
9
+
10
+ /** Character definition for speaker resolution */
11
+ export interface Character {
12
+ id: string;
13
+ name: string | TextEntry;
14
+ color?: string;
15
+ }
@@ -0,0 +1,85 @@
1
+ import type { DrawableEventEntry } from './events';
2
+
3
+ /** Engine lifecycle phases */
4
+ export enum EnginePhase {
5
+ /** Engine created but not initialized */
6
+ Created = 'created',
7
+ /** Loading project data */
8
+ Loading = 'loading',
9
+ /** Main menu displayed */
10
+ MainMenu = 'main_menu',
11
+ /** Game running */
12
+ Running = 'running',
13
+ /** Game paused (save/load/settings menu) */
14
+ Paused = 'paused',
15
+ }
16
+
17
+ /** Engine UI state (managed by Pinia, drives Vue rendering) */
18
+ export interface EngineState {
19
+ phase: EnginePhase;
20
+ background: string | null;
21
+ foreground: string | null;
22
+ dialogue: DialogueState | null;
23
+ choices: ChoiceState | null;
24
+ currentLocationId: string | null;
25
+ availableActions: ActionEntry[];
26
+ availableLocations: LocationEntry[];
27
+ drawableEvents: DrawableEventEntry[];
28
+ menuOpen: MenuType | null;
29
+ skipMode: boolean;
30
+ autoMode: boolean;
31
+ }
32
+
33
+ export interface DialogueState {
34
+ text: string;
35
+ speaker: string | null;
36
+ isNarration: boolean;
37
+ }
38
+
39
+ export interface ChoiceState {
40
+ prompt: string | null;
41
+ options: ChoiceOption[];
42
+ /** Checkpoint step index (present when displaying a history choice for redo) */
43
+ historyStepIndex?: number;
44
+ /** Previously chosen value (for highlighting in redo mode) */
45
+ historySelectedValue?: string;
46
+ }
47
+
48
+ export interface ChoiceOption {
49
+ text: string;
50
+ value: string;
51
+ disabled?: boolean;
52
+ }
53
+
54
+ export interface ActionEntry {
55
+ id: string;
56
+ label: string;
57
+ locationId: string;
58
+ }
59
+
60
+ export interface LocationEntry {
61
+ id: string;
62
+ name: string;
63
+ accessible: boolean;
64
+ }
65
+
66
+ export enum MenuType {
67
+ PauseMenu = 'pause_menu',
68
+ Save = 'save',
69
+ Load = 'load',
70
+ Settings = 'settings',
71
+ }
72
+
73
+ /** Engine settings (persisted separately) */
74
+ export interface EngineSettings {
75
+ textSpeed: number;
76
+ autoSpeed: number;
77
+ volume: {
78
+ master: number;
79
+ music: number;
80
+ sfx: number;
81
+ voice: number;
82
+ };
83
+ language: string;
84
+ fullscreen: boolean;
85
+ }
@@ -0,0 +1,117 @@
1
+ import type { BaseGameState } from './game-state';
2
+
3
+ /** A text entry with per-language strings */
4
+ export type TextEntry = Record<string, string>;
5
+
6
+ /** Options for engine.say() */
7
+ export interface SayOptions {
8
+ from?: string | TextEntry;
9
+ variables?: Record<string, string | number>;
10
+ }
11
+
12
+ /** Options for engine.choice() */
13
+ export interface ChoiceItem<T extends string = string> {
14
+ text: string | TextEntry;
15
+ value: T;
16
+ disabled?: boolean;
17
+ condition?: () => boolean;
18
+ }
19
+
20
+ /**
21
+ * The API available to event execute() functions.
22
+ * This is what visual novel authors interact with.
23
+ */
24
+ export interface VylosAPI {
25
+ /** Show dialogue text and wait for player to continue */
26
+ say(text: string | TextEntry, options?: SayOptions): Promise<void>;
27
+
28
+ /** Show choices and return the selected value */
29
+ choice<T extends string>(items: ChoiceItem<T>[]): Promise<T>;
30
+
31
+ /** Set the background image */
32
+ setBackground(path: string): void;
33
+
34
+ /** Show/hide a foreground image (character sprite, etc.) */
35
+ setForeground(path: string | null): void;
36
+
37
+ /** Show a custom overlay component */
38
+ showOverlay(componentId: string, props?: Record<string, unknown>): Promise<void>;
39
+
40
+ /** Hide overlay */
41
+ hideOverlay(): void;
42
+
43
+ /** Jump to another event */
44
+ jump(eventId: string): never;
45
+
46
+ /** End the current event */
47
+ end(): never;
48
+
49
+ /** Wait for a specified duration (ms) */
50
+ wait(ms: number): Promise<void>;
51
+
52
+ /** Change location */
53
+ setLocation(locationId: string): void;
54
+
55
+ /** Play sound effect */
56
+ playSfx(path: string): void;
57
+
58
+ /** Play background music */
59
+ playMusic(path: string): void;
60
+
61
+ /** Stop background music */
62
+ stopMusic(): void;
63
+ }
64
+
65
+ /** Configuration for drawable events — clickable characters/objects in a location */
66
+ export interface DrawableEventConfig {
67
+ /** Display text for the clickable element */
68
+ label: string | TextEntry;
69
+ /** Placement hint on screen */
70
+ position?: 'left' | 'center' | 'right';
71
+ /** Emoji/unicode icon */
72
+ icon?: string;
73
+ }
74
+
75
+ /** A drawable event entry for UI rendering */
76
+ export interface DrawableEventEntry {
77
+ id: string;
78
+ label: string;
79
+ position: 'left' | 'center' | 'right';
80
+ icon?: string;
81
+ }
82
+
83
+ /** A visual novel event definition */
84
+ export interface VylosEvent<TState extends BaseGameState = BaseGameState> {
85
+ /** Unique event ID (derived from file path if not specified) */
86
+ id: string;
87
+
88
+ /** Location this event belongs to (null for global events) */
89
+ locationId?: string;
90
+
91
+ /** Make this event drawable — a clickable element the player interacts with instead of auto-triggering */
92
+ draw?: DrawableEventConfig;
93
+
94
+ /** Whether this event should trigger — checked each game loop tick */
95
+ conditions?(state: TState): boolean;
96
+
97
+ /** Called when event transitions from NotReady → Unlocked */
98
+ unlocked?(state: TState): void;
99
+
100
+ /** Called when event completes (Unlocked → Locked) */
101
+ locked?(state: TState): void;
102
+
103
+ /** The event's narrative logic */
104
+ execute(engine: VylosAPI, state: TState): Promise<void>;
105
+ }
106
+
107
+ /** Event lifecycle status */
108
+ export enum EventStatus {
109
+ /** Conditions not yet met */
110
+ NotReady = 'not_ready',
111
+ /** Conditions met, ready to execute */
112
+ Unlocked = 'unlocked',
113
+ /** Currently executing */
114
+ Running = 'running',
115
+ /** Completed */
116
+ Locked = 'locked',
117
+ }
@@ -0,0 +1,15 @@
1
+ /** Base game state that all projects extend via Zod schema */
2
+ export interface BaseGameState {
3
+ /** Current location ID */
4
+ locationId: string;
5
+ /** In-game time (hours since game start, e.g. 14.5 = 2:30 PM) */
6
+ gameTime: number;
7
+ /** Generic flags for event gating */
8
+ flags: Record<string, boolean>;
9
+ /** Generic counters */
10
+ counters: Record<string, number>;
11
+ /** Player data */
12
+ player: {
13
+ name: string;
14
+ };
15
+ }
@@ -0,0 +1,10 @@
1
+ export * from './engine';
2
+ export * from './events';
3
+ export * from './actions';
4
+ export * from './locations';
5
+ export * from './dialogue';
6
+ export * from './save';
7
+ export * from './game-state';
8
+ export * from './plugin';
9
+ export * from './checkpoint';
10
+ export * from './config';