/**
 * АВТОСГЕНЕРИРОВАНО _build_bundle.py — не править вручную.
 *
 * Bundle TypeScript-деклараций game.* API для Monaco-редактора.
 * Каждая запись передаётся в monaco.languages.typescript.javascriptDefaults.addExtraLib(content, uri).
 */

export const GAME_TYPE_LIBS = [
    {
        uri: "ts:rublox/common.d.ts",
        content: `/**
 * Общие типы для game.* API.
 * Никаких функций здесь — только формы данных, которые встречаются по всем модулям.
 */
declare namespace game {
    /**
     * Точка/вектор в 3D-пространстве.
     * Y — высота (вверх), X и Z — горизонтальная плоскость.
     */
    interface Vector3 {
        x: number;
        y: number;
        z: number;
    }

    /**
     * Цвет в HEX-формате: '#ff0000' (красный), '#0080ff' (голубой), '#fff' (белый).
     * Можно также именами: 'red', 'blue', 'green', 'white', 'black'.
     */
    type Color = string;

    /**
     * Ref — строковый идентификатор объекта на сцене.
     * Возвращается из spawn(), findOne(), all() и используется в setColor, move, delete и пр.
     */
    type Ref = string;

    /**
     * Кривая сглаживания для tween-анимаций.
     *   linear  — без замедления (равномерно)
     *   ease    — ускорение + замедление (плавный старт и финиш) — самое естественное
     *   bounce  — отскок в конце (как мячик на полу)
     *   elastic — пружинистый перелёт через цель
     *   back    — лёгкий откат назад перед движением (как замах)
     */
    type Easing = 'linear' | 'ease' | 'bounce' | 'elastic' | 'back';

    /** Состояние игрока: 'ground' — на земле, 'air' — в воздухе/прыжке, 'water' — в воде. */
    type PlayerState = 'ground' | 'air' | 'water';

    /** Тип прицела игрока. */
    type Crosshair = 'none' | 'dot' | 'cross' | 'circle';

    /** Режим камеры. sideview — вид сбоку для GD-уровней. */
    type CameraMode = 'first' | 'third' | 'front' | 'sideview';

    /** Тип материала примитива (визуально). */
    type Material = 'default' | 'matte' | 'metal' | 'glass' | 'neon';

    /** Тип GUI-элемента. */
    type GuiType = 'frame' | 'text' | 'button' | 'image' | 'textbox' | 'scroll';

    /** Якорь GUI-элемента. */
    type GuiAnchor = 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
        | 'top' | 'bottom' | 'left' | 'right';
}
`,
    },
    {
        uri: "ts:rublox/player.d.ts",
        content: `/**
 * Игрок (персонаж) — управляется WASD/прыжком в Play.
 * Скрипт может: перемещать, наносить урон/лечить, менять скорость/гравитацию,
 * включать GD-гейммоды, проигрывать эмоции, проверять состояние.
 *
 * Пример:
 *   game.player.teleport(0, 10, 0);
 *   game.player.setSpeed(0.5);            // в 2 раза медленнее
 *   if (!game.player.alive) { game.player.respawn(); }
 */
declare namespace game.player {
    // ── Позиция и поворот ──────────────────────────────────────────────

    /** Текущая позиция «низа ног» игрока (read-only — менять через teleport). */
    const position: game.Vector3;

    /**
     * Угол поворота игрока вокруг Y (в радианах).
     * 0 = смотрит в +Z, π/2 = +X, π = -Z, -π/2 = -X.
     */
    const yaw: number;

    /** Наклон вверх/вниз (в радианах). >0 = вверх. */
    const pitch: number;

    /**
     * Нормализованный вектор направления взгляда — куда смотрит игрок.
     * Удобно для спавна объектов «перед собой»:
     * @example
     * const f = game.player.forward;
     * const p = game.player.position;
     * game.scene.spawn('primitive:cube', {
     *     x: p.x + f.x * 3,
     *     y: p.y,
     *     z: p.z + f.z * 3,
     * });
     */
    const forward: game.Vector3;

    /**
     * Мгновенно переместить игрока в указанную точку.
     * @example
     * game.player.teleport(0, 5, 10);
     */
    function teleport(x: number, y: number, z: number): void;

    /**
     * Сдвинуть игрока ТОЛЬКО по X (не трогая Y/Z). Для раннеров — смена полосы.
     * @example game.player.setLaneX(-2);
     */
    function setLaneX(x: number): void;

    /** Развернуть модель игрока на угол yaw (радианы). 0 = лицом в +Z. */
    function setFacing(yaw: number): void;

    // ── Здоровье ───────────────────────────────────────────────────────

    /** Текущее HP игрока. */
    const hp: number;

    /** Максимальное HP. */
    const maxHp: number;

    /** Жив ли игрок (hp > 0). */
    const alive: boolean;

    /**
     * Состояние игрока: 'ground' (на земле), 'air' (в воздухе/прыжке), 'water'.
     * @example
     * game.onTick(() => {
     *     if (game.player.state === 'air') game.ui.showText('Лечу!', 0.2);
     * });
     */
    const state: game.PlayerState;

    /**
     * Нанести урон игроку. Учитываются i-frames (повтор в течение ~0.5с игнорится).
     * @example game.player.damage(10);
     */
    function damage(amount: number): void;

    /** Мгновенно убить игрока (игнорит i-frames). */
    function kill(): void;

    /** Восстановить здоровье. */
    function heal(amount: number): void;

    /** Вернуть игрока на spawn-point с полным HP. */
    function respawn(): void;

    /**
     * Назначить активный спавн (один из чек-поинтов сцены).
     * @param target ref чек-поинта или имя.
     */
    function setSpawn(target: game.Ref | string): void;

    // ── Движение и физика ──────────────────────────────────────────────

    /**
     * Множитель скорости. 1 = норма, 2 = в 2 раза быстрее, 0.5 = вдвое медленнее.
     * Действует пока не вызван \`setSpeed(1)\`.
     * @example
     * game.player.setSpeed(0.3); // замедление в болоте
     */
    function setSpeed(mul: number): void;

    /** Множитель силы прыжка. 1 = норма, 2 = в 2 раза выше. */
    function setJumpPower(mul: number): void;

    /**
     * Множитель гравитации. 1 = норма (-22 м/с²), 1.23 = GD-стиль (-27 м/с²).
     * Работает в обоих направлениях gravityDir.
     */
    function setGravityMul(mul: number): void;

    /** Явно установить направление гравитации: 1 = вниз (норма), -1 = вверх. */
    function setGravityDir(dir: 1 | -1): void;

    /**
     * Перевернуть гравитацию (как gravity portal в GD).
     * После flipGravity игрок прыгает к потолку. Повторный вызов возвращает.
     * Доступно только в sideview-режиме.
     */
    function flipGravity(): void;

    /**
     * Задать вертикальную скорость (м/с). +значение = вверх, - = вниз.
     * Используется для трамплинов (vy=16), jump orb (vy=14), boost-зон.
     */
    function setVy(vy: number): void;

    /**
     * Мгновенный подброс игрока вверх. strength=1 = обычный прыжок, strength=2 = в 2 раза выше.
     * Не требует Space — срабатывает сразу. Для трамплинов.
     */
    function boostJump(strength: number): void;

    /** Скользкость поверхности под игроком. 0 = норма, 0.85 = лёд, 1 = бесконечная инерция. */
    function setIceFriction(value: number): void;

    /** Двойной прыжок: true = разрешён, false = выключен. */
    function setDoubleJump(enabled: boolean): void;

    /** Текущее направление гравитации (+1 вниз, -1 вверх). */
    const gravityDir: 1 | -1;

    // ── Состояние клавиш и ввод ────────────────────────────────────────

    /**
     * Проверить зажата ли клавиша ПРЯМО СЕЙЧАС (для плавного движения по удержанию).
     * @param key 'w', 'a', 's', 'd', 'space', 'shift', 'arrowup' (lowercase)
     * @example
     * game.onTick(() => {
     *     if (game.player.isKeyDown('w')) game.ui.set('move', 'идёт вперёд');
     * });
     */
    function isKeyDown(key: string): boolean;

    // ── GD-гейммоды ────────────────────────────────────────────────────

    /**
     * GD-гейммод Ship: тап-удержание Space = подъём (как вертолёт).
     * Обычный прыжок отключается.
     */
    function setShipMode(enabled: boolean): void;

    /** GD-гейммод UFO: каждый тап Space = микропрыжок в воздухе (даже без касания земли). */
    function setUfoMode(enabled: boolean): void;

    /** GD-гейммод Wave: движение под ±45°. Space зажат → вверх, отпущен → вниз. */
    function setWaveMode(enabled: boolean): void;

    /** GD-гейммод Robot: высота прыжка зависит от длительности удержания Space. */
    function setRobotMode(enabled: boolean): void;

    /**
     * Кубикон Dash: авто-бег по +X со скоростью speed (м/с). 0 — отключить.
     * Работает только в sideview-камере.
     */
    function setAutoRun(speed: number): void;

    // ── Камера ─────────────────────────────────────────────────────────

    /**
     * Режим камеры: 'first' (от первого лица), 'third' (от третьего),
     * 'front' (вид спереди), 'sideview' (вид сбоку, для GD).
     */
    function setCameraMode(mode: game.CameraMode): void;

    // ── Прицел ─────────────────────────────────────────────────────────

    /**
     * Прицел в Play. Чтение — текущий тип, запись — меняет:
     * @example game.player.crosshair = 'cross';
     */
    let crosshair: game.Crosshair;

    // ── Анимации и эмоции ──────────────────────────────────────────────

    /**
     * Проиграть эмоцию-анимацию персонажа один раз.
     * @param name 'wave' (помахать) | 'dance' | 'cheer' (радость) | 'sit' | 'paint'
     * @example game.onKey('e', () => game.player.playAnimation('wave'));
     */
    function playAnimation(name: 'wave' | 'dance' | 'cheer' | 'sit' | 'paint' | string): void;

    /** Прервать текущую эмоцию персонажа. */
    function stopAnimation(): void;

    /** Алиас playAnimation — короче. */
    function playEmote(name: string): void;
    function stopEmote(): void;

    // ── Внешний вид ────────────────────────────────────────────────────

    /** Показать/скрыть скин персонажа. */
    function setSkinVisible(visible: boolean): void;

    // ── Команда (Teams) ────────────────────────────────────────────────

    /** Назначить игрока в команду по имени. */
    function setTeam(teamName: string): void;

    /** Текущая команда игрока (или null). */
    const team: string | null;

    // ── Инвентарь / Tool ───────────────────────────────────────────────

    /**
     * Выдать игроку предмет в руки/хотбар.
     * Принимает строку (id готовой модели типа 'weapon-sword') или Tool из
     * game.tools.create() (кастомный tool со своим onActivated).
     */
    function giveTool(toolType: string | game.Tool, opts?: { equip?: boolean }): void;

    /** Убрать предмет из инвентаря игрока. */
    function removeTool(toolType: string): void;

    /**
     * Подписаться на использование предмета (нажатие ЛКМ когда tool в руках).
     * @example
     * game.player.onToolUse(({ tool }) => game.log('применил:', tool));
     */
    function onToolUse(fn: (event: { tool: string }) => void): void;
}
`,
    },
    {
        uri: "ts:rublox/scene.d.ts",
        content: `/**
 * Сцена — управление 3D-объектами (блоки, примитивы, модели).
 *
 * Каждый объект имеет строковый ref, возвращаемый из spawn / findOne.
 * По ref можно: двигать, красить, удалять, проверять позицию.
 *
 * ВНИМАНИЕ: в Monaco TypeScript объявление функции \`delete\` невозможно
 * (reserved word). Поэтому метод обозначен через интерфейс SceneApi
 * в declaration ниже, а в реальном game.scene.delete() он работает как
 * обычное свойство объекта.
 */
declare namespace game.scene {
    // ── Создание и удаление ────────────────────────────────────────────

    /**
     * Создать объект на сцене.
     * @param type 'block:<id>' | 'primitive:<id>' | 'model:<id>'
     *   Примеры: 'block:grass', 'primitive:cube', 'primitive:sphere', 'model:tree-pine'.
     * @returns ref для последующих операций или null если тип невалиден.
     * @example
     * const cube = game.scene.spawn('primitive:cube', {
     *     x: 0, y: 5, z: 0,
     *     sx: 2, sy: 2, sz: 2,
     *     color: '#ff0000',
     *     material: 'metal',
     *     name: 'MyCube',
     *     lifetime: 5,  // авто-удаление через 5 сек
     * });
     */
    function spawn(type: string, opts?: {
        x?: number; y?: number; z?: number;
        sx?: number; sy?: number; sz?: number;
        rotationX?: number; rotationY?: number; rotationZ?: number;
        color?: game.Color;
        material?: game.Material;
        name?: string;
        opacity?: number;
        lifetime?: number;
    }): game.Ref | null;

    // Удалить объект по ref. Поскольку 'delete' зарезервированное слово,
    // declaration через дополнительный namespace в самом низу:
    // см. blockEnd ниже — там Object.defineProperty-эмуляция через интерфейс.

    /** Удалить объект через \`seconds\` секунд (отложенное удаление). */
    function deleteAfter(ref: game.Ref, seconds: number): void;

    /**
     * Создать клон существующего объекта.
     * @param ref исходный объект
     * @param offset смещение от исходной позиции
     * @returns ref нового объекта
     */
    function clone(ref: game.Ref, offset?: Partial<game.Vector3>): game.Ref | null;

    // ── Поиск ──────────────────────────────────────────────────────────

    /** Найти все объекты с указанным именем (для примитивов/моделей с заданным name). */
    function find(name: string): game.Ref[];

    /** Первый объект с этим именем или null. */
    function findOne(name: string): game.Ref | null;

    /** Список ref'ов всех объектов заданного типа. */
    function all(kind: 'block' | 'model' | 'primitive'): game.Ref[];

    // ── Геттеры ────────────────────────────────────────────────────────

    /** Текущая позиция объекта или null если не существует. */
    function getPosition(ref: game.Ref): game.Vector3 | null;

    /** Прочитать произвольное пользовательское данное, ранее сохранённое через setData. */
    function getData(ref: game.Ref, key: string): any;

    // ── Изменение свойств ──────────────────────────────────────────────

    /** Переместить объект в указанную точку. */
    function move(ref: game.Ref, x: number, y: number, z: number): void;

    /** Изменить только позицию по одной оси (например, только Y). */
    function setPosition(ref: game.Ref, pos: Partial<game.Vector3>): void;

    /**
     * Повернуть объект на указанные углы (радианы).
     * Если параметр не указан — текущий угол не меняется.
     */
    function setRotation(ref: game.Ref, rot: { x?: number; y?: number; z?: number }): void;

    /** Поворот объекта на дельта-углы (относительно текущих). */
    function rotate(ref: game.Ref, dx: number, dy: number, dz: number): void;

    /** Сменить цвет (примитивы). */
    function setColor(ref: game.Ref, color: game.Color): void;

    /** Прозрачность 0..1. 1 = непрозрачно, 0 = невидимо. */
    function setOpacity(ref: game.Ref, value: number): void;

    /** Изменить размер объекта (анимировать через game.tween). */
    function setScale(ref: game.Ref, sx: number, sy: number, sz: number): void;

    /** Изменить материал примитива. */
    function setMaterial(ref: game.Ref, name: game.Material): void;

    /** Видимость объекта. */
    function setVisible(ref: game.Ref, visible: boolean): void;

    /** Коллизии объекта. false = игрок проходит сквозь. */
    function setCollide(ref: game.Ref, collide: boolean): void;

    /**
     * Сохранить произвольное данное на объекте.
     * @example
     * game.scene.setData(door, 'locked', true);
     * if (game.scene.getData(door, 'locked')) { /* ... *\\/ }
     */
    function setData(ref: game.Ref, key: string, value: any): void;

    /**
     * Установить текстовую метку (Billboard) над объектом.
     *
     * ВНИМАНИЕ: size — **множитель размера плашки в МИРЕ**, НЕ пиксели!
     * 1.0 = плашка ~2.2×0.55 м (это base). 0.6 = ~1.3×0.33 м (рекомендую для ников).
     * Если поставить size: 14 — плашка станет 30 МЕТРОВ и закроет экран.
     *
     * @param ref ref объекта-носителя.
     * @param text текст метки (UTF-8 ок).
     * @param opts.color HEX-цвет шрифта (по умолчанию '#fff').
     * @param opts.size множитель плашки 0.3..1.5 (по умолчанию 1.0).
     * @param opts.height высота над объектом в метрах (по умолчанию 2.5).
     * @example
     * game.scene.setLabel(npc, 'Стив', { color: '#fff', size: 0.6, height: 2 });
     */
    function setLabel(ref: game.Ref, text: string, opts?: {
        color?: game.Color;
        size?: number;
        height?: number;
    }): void;

    /** Убрать Billboard-метку. */
    function clearLabel(ref: game.Ref): void;

    /** Применить текстуру на грани примитива (через AssetManager). */
    function setTexture(ref: game.Ref, assetId: string): void;

    // ── Частицы и эмиттеры ─────────────────────────────────────────────

    /**
     * Эффект частиц в точке. Авто-удаляется через duration секунд.
     * @example
     * game.scene.spawnParticles('fire', { x: 0, y: 2, z: 0 }, { duration: 3, count: 2 });
     */
    function spawnParticles(
        type: 'fire' | 'smoke' | 'sparks' | 'magic' | 'explosion' | 'confetti',
        position: game.Vector3,
        options?: { duration?: number; count?: number; color?: game.Color }
    ): void;

    /** Включить/выключить постоянный эмиттер-объект сцены. */
    function setEmitter(ref: game.Ref, enabled: boolean): void;

    // ── Мобы (Zombie) ──────────────────────────────────────────────────

    /** Получить список мобов на сцене. */
    function mobs(filter?: { type?: string; alive?: boolean }): Array<{
        ref: game.Ref;
        type: string;
        position: game.Vector3;
        hp: number;
    }>;

    /** Убить моба по ref. */
    function killMob(ref: game.Ref): void;

    // ── NPC ────────────────────────────────────────────────────────────

    /**
     * Создать нового NPC из модели. Возвращает прокси с методами moveTo/follow/say/damage/remove.
     * @example
     * const trader = game.scene.spawnNpc('character-a', {
     *     x: 0, y: 1, z: 5, name: 'Торговец', hp: 100,
     * });
     * trader.say('Привет, путник!');
     * trader.moveTo(10, 5);
     */
    function spawnNpc(modelType: string, opts?: {
        x?: number; y?: number; z?: number;
        name?: string;
        hp?: number;
    }): game.NpcRef;

    /** Список созданных NPC. */
    function npcs(filter?: { name?: string; alive?: boolean }): game.NpcRef[];

    // ── Высота поверхности террейна ────────────────────────────────────

    /** Y-координата поверхности террейна в точке (x, z). */
    function surfaceY(x: number, z: number): number;
}

// 'delete' — зарезервированное слово, его нельзя объявить как function,
// но можно через interface-расширение объекта-namespace.
declare namespace game {
    interface SceneNamespaceExtras {
        /** Удалить объект по ref. game.scene.delete('ref_123'). */
        delete(ref: game.Ref): void;
    }
}

`,
    },
    {
        uri: "ts:rublox/physics.d.ts",
        content: `/**
 * Физика — raycast, velocity, импульсы, взрывы.
 * Phase 6.5: добавлены Rapier3D методы setBodyType/applyImpulseV2/setVelocityV2.
 */
declare namespace game.physics {
    /**
     * Phase 6.5: переключить примитив в режим Rapier-физики.
     * @param ref ref примитива.
     * @param bodyType 'dynamic' (катится/падает) | 'static' (не двигается)
     *                 | 'kinematic' (двигается скриптом, столкновения учитываются)
     *                 | 'none' (отключить Rapier).
     * @param opts mass, friction (0..1), restitution (упругость 0..1).
     * @example
     * const ball = game.scene.spawn('primitive:sphere', { x:0, y:10, z:0 });
     * game.physics.setBodyType(ball, 'dynamic', { mass: 2, restitution: 0.7 });
     * // мяч падает и отскакивает от земли
     */
    function setBodyType(
        ref: game.Ref,
        bodyType: 'dynamic' | 'static' | 'kinematic' | 'none',
        opts?: { mass?: number; friction?: number; restitution?: number }
    ): void;

    /**
     * Phase 6.5: настоящий импульс Rapier (для тел в режиме 'dynamic').
     * В отличие от applyImpulse, тело реально двигается под физикой.
     */
    function applyImpulseV2(ref: game.Ref, ix: number, iy: number, iz: number): void;
    function setVelocityV2(ref: game.Ref, vx: number, vy: number, vz: number): void;

    /**
     * Луч от origin в направлении dir. Возвращает первое попадание или null.
     * @example
     * const hit = game.physics.raycast(
     *     game.player.position,
     *     game.player.forward,
     *     { maxDist: 50, ignored: ['ref_self'] }
     * );
     * if (hit) game.log('Попал:', hit.ref, 'на расстоянии', hit.distance);
     */
    function raycast(
        origin: game.Vector3,
        dir: game.Vector3,
        opts?: {
            maxDist?: number;
            ignored?: game.Ref[];
        }
    ): {
        hit: boolean;
        point?: game.Vector3;
        ref?: game.Ref;
        distance?: number;
        normal?: game.Vector3;
    } | null;

    /**
     * Задать скорость объекту (для физически-анимированных тел).
     */
    function setVelocity(ref: game.Ref, vx: number, vy: number, vz: number): void;

    /**
     * Применить мгновенный импульс к объекту.
     */
    function applyImpulse(ref: game.Ref, ix: number, iy: number, iz: number): void;

    /**
     * Взрыв в точке: визуал + урон в радиусе игроку и мобам.
     * @example
     * game.physics.explode(game.player.position, 5, { damage: 50, force: 10 });
     */
    function explode(
        pos: game.Vector3,
        radius: number,
        opts?: { damage?: number; force?: number }
    ): void;

    /**
     * Проходимость объекта/группы (collision groups через теги).
     */
    function passThrough(target: game.Ref | string, enabled: boolean): void;
}
`,
    },
    {
        uri: "ts:rublox/ui.d.ts",
        content: `/**
 * UI и HUD: текст поверх viewport (счёт, таймер, метки),
 * 2D-интерфейс из элементов (frame/text/button/image/textbox/scroll).
 */
declare namespace game {
    interface UiApi {
        /** Счётчик в правом верхнем углу. null убирает. */
        score: number | string | null;

        /** Таймер (секунды) в правом верхнем углу. null убирает. */
        timer: number | null;

        /**
         * Большой текст по центру экрана на N секунд.
         * @example game.ui.showText('Победа!', 3);
         */
        showText(text: string | number, seconds?: number): void;

        /**
         * Произвольная именованная метка на HUD.
         * @example
         * game.ui.set('hp_label', \`HP: \${game.player.hp}\`,
         *             { x: 10, y: 10, color: '#fff', size: 20 });
         */
        set(id: string, text: string | number | null, opts?: {
            x?: number; y?: number;
            color?: game.Color; size?: number;
        }): void;

        /** Убрать метку по id. */
        remove(id: string): void;

        /** Стереть весь HUD. */
        clear(): void;
    }
    const ui: UiApi;

    /** Показ/скрытие стандартного HUD (HP-бар, hotbar, миникарта). */
    namespace hud {
        function setVisible(visible: boolean): void;
        function reset(): void;
    }

    interface GuiProps {
        id?: string;
        type?: game.GuiType;
        name?: string;
        parentId?: string | null;
        x?: number; y?: number; w?: number; h?: number;
        anchor?: game.GuiAnchor;
        /**
         * Phase 6.3.1: AnchorPoint -- точка ВНУТРИ элемента, относительно которой
         * считается x/y. {x:0,y:0} = левый верх, {x:0.5,y:0.5} = центр (по умолч.),
         * {x:1,y:1} = правый низ. Удобно для центрирования всплывающих окон.
         */
        anchorPoint?: { x: number; y: number };
        visible?: boolean;
        bgColor?: game.Color; bgOpacity?: number;
        borderColor?: game.Color; borderWidth?: number; borderRadius?: number;
        text?: string;
        textColor?: game.Color; textSize?: number;
        textAlign?: 'left' | 'center' | 'right';
        fontWeight?: number;
        imageUrl?: string;
        placeholder?: string;
        shadow?: boolean;
        /**
         * Phase 6.3.2: авто-раскладка детей контейнера (только для frame/scroll).
         *   'none' -- дети по своим x/y
         *   'vertical' -- в столбик
         *   'horizontal' -- в строку
         *   'grid' -- сетка (требует layoutCellW/H/Cols)
         */
        layout?: 'none' | 'vertical' | 'horizontal' | 'grid';
        /** Отступ между детьми (в % от размера контейнера). */
        layoutGap?: number;
        /** Внутреннее поле (в % от размера контейнера). */
        layoutPad?: number;
        /** Phase 6.3.2: размер ячейки в grid-layout (%). */
        layoutCellW?: number;
        layoutCellH?: number;
        /** Phase 6.3.2: число колонок в grid (0 = авто). */
        layoutCols?: number;
    }

    namespace gui {
        function find(name: string): string | null;
        function all(): string[];
        function get(id: string): game.GuiProps | null;
        function update(id: string, patch: Partial<game.GuiProps>): void;
        function show(id: string): void;
        function hide(id: string): void;
        /**
         * Создать новый GUI-элемент. Возвращает id.
         * @example
         * const lbl = game.gui.create('text', {
         *     x: 50, y: 10, text: 'Привет!', textColor: '#fff',
         * });
         */
        function create(type: game.GuiType, opts?: Partial<game.GuiProps>): string;
        function remove(id: string): void;
        /** Клик по кнопке. id или имя. */
        function onClick(idOrName: string, fn: (event: { id: string }) => void): void;
        /** Изменение текста в TextBox. */
        function onChange(idOrName: string, fn: (event: { id: string; text: string }) => void): void;
        /** Submit в TextBox (Enter). */
        function onSubmit(idOrName: string, fn: (event: { id: string; text: string }) => void): void;
    }
}
`,
    },
    {
        uri: "ts:rublox/audio.d.ts",
        content: `/**
 * Звук: процедурные SFX + фоновая музыка + свои mp3 + 3D-позиционный звук.
 */
declare namespace game {
    interface SoundApi {
        /**
         * Воспроизвести встроенный звук или свой загруженный.
         * @param name 'jump' | 'pickup' | 'coin' | 'win' | 'lose' | 'click' | 'hit'
         *             или ID своего звука из библиотеки.
         * @example
         * game.sound.play('coin');
         * game.sound.play('hit', { volume: 0.5, pitch: 1.2 });
         */
        play(
            name: 'jump' | 'pickup' | 'coin' | 'win' | 'lose' | 'click' | 'hit' | string,
            opts?: { volume?: number; pitch?: number; position?: game.Vector3 }
        ): void;

        /** Привязать звук к 3D-объекту (3D-позиционный звук). */
        attach(ref: game.Ref, soundName: string, opts?: {
            volume?: number; pitch?: number; loop?: boolean; range?: number;
        }): string;

        /** Остановить звук, привязанный к объекту. */
        detach(handle: string): void;
    }
    const sound: SoundApi;

    interface AudioApi {
        /** Воспроизвести встроенный SFX (алиас sound.play). */
        playSfx(name: string, opts?: { volume?: number; pitch?: number }): void;

        /** Включить фоновую музыку (пресет). */
        playMusic(preset: 'calm' | 'happy' | 'epic' | 'mystery' | string): void;

        /** Остановить фоновую музыку. */
        stopMusic(): void;

        /** Полностью отключить весь звук. */
        setMuted(muted: boolean): void;
    }
    const audio: AudioApi;
}
`,
    },
    {
        uri: "ts:rublox/save.d.ts",
        content: `/**
 * Сохранения: каждое сохранение под namespace, поддерживает merge/increment/max.
 * Лидерборды.
 *
 * Per-player: данные принадлежат конкретному игроку и привязаны к проекту.
 */
declare namespace game {
    interface SaveApi {
        /**
         * Сохранить данные под именем (replace).
         * @example game.save.set('progress', { level: 3, coins: 150 });
         */
        set(namespace: string, data: any, fn?: (ok: boolean) => void): void;

        /**
         * Прочитать данные namespace.
         * @example game.save.get('progress', (data) => game.log(data));
         */
        get(namespace: string, fn: (data: any) => void): void;

        /** Все namespace игрока разом. */
        getAll(fn: (allData: Record<string, any>) => void): void;

        /**
         * Слияние данных: { patch: {...}, increment: {...}, max: {...} }.
         * - patch: перезаписать ключи
         * - increment: прибавить к существующему (для счётчиков)
         * - max: max(старое, новое) (для рекордов)
         * @example
         * game.save.merge('stats', {
         *     patch: { lastLevel: 5 },
         *     increment: { coins: 10, deaths: 1 },
         *     max: { bestTime: 47.3 },
         * });
         */
        merge(namespace: string, opts: {
            patch?: Record<string, any>;
            increment?: Record<string, number>;
            max?: Record<string, number>;
        }, fn?: (result: any) => void): void;

        /** Атомарно увеличить счётчик. */
        increment(namespace: string, key: string, delta: number, fn?: (newValue: number) => void): void;

        /**
         * Положить в общий лидерборд (топ всех игроков игры).
         * @example game.save.leaderboard.submit('time', 47.3);
         */
        leaderboard: {
            submit(boardName: string, value: number): void;
            top(boardName: string, limit: number, fn: (rows: Array<{
                userId: number;
                username: string;
                value: number;
            }>) => void): void;
        };
    }
    const save: SaveApi;
}
`,
    },
    {
        uri: "ts:rublox/economy.d.ts",
        content: `/**
 * Экономика: алмазы платформы, награды за достижения, рейтинг, ежедневный бонус.
 */
declare namespace game {
    interface EconomyApi {
        /** Текущий баланс алмазов игрока. */
        getBalance(fn: (data: {
            balance: number;
            rating: number;
        }) => void): void;

        /**
         * Выдать награду игроку (algorithm-defined: ratingDelta, diamonds).
         * @param id уникальный ID награды (one-time per player).
         * @example
         * game.economy.reward('first_win', (data) => {
         *     if (data.granted) game.ui.showText(\`+\${data.diamonds} 💎\`, 3);
         * });
         */
        reward(id: string, fn?: (data: {
            granted: boolean;
            diamonds: number;
            ratingDelta: number;
        }) => void): void;

        /** Потратить алмазы. */
        spend(amount: number, reason: string, fn?: (ok: boolean) => void): void;

        /** Ежедневный бонус (один раз в сутки). */
        dailyCheck(fn?: (data: {
            available: boolean;
            diamonds: number;
        }) => void): void;
    }
    const economy: EconomyApi;
}
`,
    },
    {
        uri: "ts:rublox/input.d.ts",
        content: `/**
 * Ввод: клавиши, клик мыши, режим курсора.
 */
declare namespace game {
    /**
     * Каждый кадр render-loop'а. dt — секунды с предыдущего кадра.
     * @example
     * game.onTick((dt) => {
     *     const p = game.player.position;
     *     game.ui.set('xz', \`x=\${p.x.toFixed(1)} z=\${p.z.toFixed(1)}\`);
     * });
     */
    function onTick(fn: (dt: number) => void): void;

    /**
     * Подписаться на нажатие клавиши.
     * @param key 'w'|'a'|'s'|'d'|'space'|'shift'|'enter'|'escape'|'arrowup'|'1'|...
     *            Не указано → срабатывает на любую клавишу.
     * @example
     * game.onKey('space', () => game.player.boostJump(2));
     */
    function onKey(key: string, fn: (event: {
        type: 'keydown'; key: string; code: string;
    }) => void): void;
    function onKey(fn: (event: {
        type: 'keydown'; key: string; code: string;
    }) => void): void;

    /** Подписаться на отпускание клавиши. */
    function onKeyUp(key: string, fn: (event: {
        type: 'keyup'; key: string; code: string;
    }) => void): void;
    function onKeyUp(fn: (event: {
        type: 'keyup'; key: string; code: string;
    }) => void): void;

    /**
     * Глобальный клик мышью в Play-режиме.
     * event.target = объект под прицелом (или null), event.point = точка попадания луча.
     */
    function onClick(fn: (event: {
        type: 'click';
        point: game.Vector3 | null;
        target: { kind: string; ref?: any; id?: number } | null;
    }) => void): void;

    /** Игрок коснулся объекта (с привязанным скриптом — глобальное событие). */
    function onPlayerTouch(fn: (event: {
        type: 'playerTouch';
        target: { kind: string; ref?: any; id?: number };
    }) => void): void;

    interface InputApi {
        /**
         * Режим курсора: 'ui' (виден и кликабелен) или 'game' (скрыт, для FPS).
         */
        setCursorMode(mode: 'ui' | 'game'): void;
    }
    const input: InputApi;
}
`,
    },
    {
        uri: "ts:rublox/tweens.d.ts",
        content: `/**
 * Таймеры и плавные переходы (твининг). Главная фича Фазы 1.
 */
declare namespace game {
    /**
     * Выполнить fn один раз через \`seconds\` секунд.
     * @returns id — отменяется через game.cancel(id).
     * @example
     * game.after(3, () => game.ui.showText('Прошло 3 секунды'));
     */
    function after(seconds: number, fn: () => void): number;

    /**
     * Повторять fn каждые \`seconds\` секунд.
     * @returns id — остановить через game.cancel(id).
     * @example
     * const tickerId = game.every(1, () => game.log('тик'));
     * game.after(10, () => game.cancel(tickerId));
     */
    function every(seconds: number, fn: () => void): number;

    /** Отменить отложенный after/every по id. */
    function cancel(id: number): void;

    /**
     * Плавный переход свойств объекта или GUI.
     * @param ref ref 3D-объекта или id GUI-элемента.
     * @param props что менять: { x, y, z, rotationY, sx, sy, sz, color, opacity }.
     * @param opts длительность, easing, повторы.
     * @returns tweenId — можно отменить через game.cancelTween(id).
     * @example
     * // плавное открытие двери:
     * game.tween(door, { rotationY: Math.PI / 2 }, {
     *     duration: 1, easing: 'ease',
     * });
     * // пульсирующая монета:
     * game.tween(coin, { sx: 1.3, sy: 1.3, sz: 1.3 }, {
     *     duration: 0.5, easing: 'ease', repeat: -1, yoyo: true,
     * });
     */
    function tween(
        ref: game.Ref | string,
        props: {
            x?: number; y?: number; z?: number;
            rotationX?: number; rotationY?: number; rotationZ?: number;
            sx?: number; sy?: number; sz?: number;
            color?: game.Color;
            opacity?: number;
        },
        opts: {
            duration: number;
            easing?: game.Easing;
            delay?: number;
            repeat?: number; // -1 = бесконечно
            yoyo?: boolean;
            onDone?: () => void;
        }
    ): number;

    /** Отменить активный твин по id. */
    function cancelTween(id: number): void;
}
`,
    },
    {
        uri: "ts:rublox/multiplayer.d.ts",
        content: `/**
 * Мультиплеер: игроки комнаты, общее состояние комнаты, команды,
 * адресные сообщения.
 */
declare namespace game {
    interface PlayerInfo {
        id: number | string;
        name: string;
        position: game.Vector3;
        hp: number;
        team: string | null;
    }

    interface PlayersApi {
        /** Я — текущий игрок в комнате. */
        me(): PlayerInfo | null;

        /** Список всех игроков комнаты. */
        all(): PlayerInfo[];

        /** Кол-во игроков в комнате. */
        count(): number;

        /** Получить игрока по id. */
        get(id: number | string): PlayerInfo | null;
    }
    const players: PlayersApi;

    /**
     * Игрок зашёл в комнату.
     * @example
     * game.onPlayerJoin((player) => game.ui.showText(\`+ \${player.name}\`, 2));
     */
    function onPlayerJoin(fn: (player: PlayerInfo) => void): void;

    /** Игрок вышел. */
    function onPlayerLeave(fn: (player: PlayerInfo) => void): void;

    /** Игрок умер. */
    function onPlayerDied(fn: (event: { source?: string }) => void): void;

    /** Игрок прыгнул. */
    function onPlayerJump(fn: () => void): void;

    /** Игрок приземлился. */
    function onPlayerLand(fn: () => void): void;

    /**
     * Адресное сообщение конкретному игроку (только в мультиплеере).
     * @example
     * game.sendTo(targetPlayer, 'invite', { gameId: 123 });
     */
    function sendTo(player: PlayerInfo | number | string, name: string, data?: any): void;

    /** Подписка на адресное сообщение. */
    function onMessage(name: string, fn: (data: any, from?: PlayerInfo) => void): void;

    /**
     * Состояние комнаты — синхронизировано между всеми клиентами.
     */
    interface RoomApi {
        set(key: string, value: any): void;
        get(key: string): any;
        onChange(key: string, fn: (newValue: any, oldValue: any) => void): void;
    }
    const room: RoomApi;

    /**
     * Команды (Teams) — командные спавны и лидерборды.
     */
    interface TeamsApi {
        create(name: string, color: game.Color): void;
        remove(name: string): void;
        all(): Array<{ name: string; color: game.Color; players: PlayerInfo[] }>;
        get(name: string): { name: string; color: game.Color; players: PlayerInfo[] } | null;
    }
    const teams: TeamsApi;

    /**
     * Phase 6.6: RemoteEvent -- именованное сетевое событие (как в Roblox).
     */
    interface RemoteEvent {
        readonly name: string;
        /** Отправить событие ВСЕМ клиентам комнаты (включая отправителя). */
        fireAllClients(data?: any): void;
        /** Отправить событие всем КРОМЕ отправителя. */
        fireOthers(data?: any): void;
        /** Отправить событие конкретному игроку (по sessionId или PlayerInfo). */
        fireClient(player: PlayerInfo | string, data?: any): void;
        /**
         * Подписаться на событие от любого клиента.
         * @param fn получает { from: sessionId, data }
         */
        on(fn: (event: { from: string; data: any }) => void): void;
    }

    interface RemoteApi {
        /**
         * Создать (или получить) именованное сетевое событие.
         * @example
         * const ev = game.remote.create('PlayerShoot');
         * ev.on(({ from, data }) => game.log(from, 'shot at', data));
         * ev.fireAllClients({ x: 10, z: 5 });
         */
        create(name: string): RemoteEvent;
    }
    const remote: RemoteApi;
}
`,
    },
    {
        uri: "ts:rublox/tools.d.ts",
        content: `/**
 * Tool / инвентарь — предметы в руках и на хотбаре.
 */
declare namespace game {
    /**
     * Phase 6.4: кастомный tool с собственным onActivated.
     * В отличие от game.player.giveTool() (готовое оружие из палитры),
     * tools.create() позволяет сделать СВОЙ инструмент с любой логикой
     * (удочка/лопата/фонарик/мегафон).
     */
    interface Tool {
        readonly id: string;
        readonly name: string;
        readonly modelTypeId: string | null;
        readonly kind: string;
        /** ЛКМ когда tool активен в hotbar. */
        onActivated(fn: (ctx: {
            tool: { kind: string; modelTypeId: string | null; name: string; customToolId?: string };
            point: game.Vector3 | null;
            target: { kind: string; ref?: any; id?: number } | null;
        }) => void): void;
        /** Игрок взял tool в руки (выбрал слот). */
        onEquipped(fn: (ctx: { tool: any }) => void): void;
        /** Игрок убрал tool из рук (сменил слот). */
        onUnequipped(fn: (ctx: { tool: any }) => void): void;
        /** Уронить tool на землю как pickup-объект. */
        dropAt(pos: game.Vector3): void;
    }

    interface ToolsApi {
        /**
         * Создать кастомный tool.
         * @example
         * const sword = game.tools.create('Меч огня', {
         *     model: 'weapon-sword',
         *     cooldown: 0.5,
         *     params: { damage: 50 },
         * });
         * sword.onActivated(({ target }) => {
         *     if (target) game.scene.killMob(target.ref);
         * });
         * game.player.giveTool(sword);
         */
        create(name: string, opts?: {
            model?: string;
            kind?: string;
            cooldown?: number;
            icon?: string;
            description?: string;
            grip?: game.Vector3;
            params?: Record<string, any>;
        }): Tool;
    }
    const tools: ToolsApi;

    interface InventoryApi {
        /** Добавить предмет в инвентарь. */
        add(item: string, count?: number): void;
        /** Удалить предмет. */
        remove(item: string, count?: number): void;
        /** Есть ли предмет (с указанным количеством). */
        has(item: string, count?: number): boolean;
        /** Список всех предметов в инвентаре. */
        list(): Array<{ item: string; count: number }>;
        /** Активный (выбранный) предмет в хотбаре. */
        active(): string | null;
        /** Очистить инвентарь. */
        clear(): void;
    }
    const inventory: InventoryApi;
}
`,
    },
    {
        uri: "ts:rublox/npcs.d.ts",
        content: `/**
 * NPC API — программно создаваемые персонажи (дружественные и враги).
 */
declare namespace game {
    /**
     * Прокси на созданного NPC. Получается из game.scene.spawnNpc().
     */
    interface NpcRef {
        readonly ref: string;
        readonly name: string;
        readonly position: game.Vector3;
        readonly hp: number;
        readonly alive: boolean;

        /** Идти в точку (с pathfinding). */
        moveTo(x: number, z: number): void;

        /** Следовать за объектом/игроком. */
        follow(target: game.Ref | 'player'): void;

        /** Остановить движение. */
        stop(): void;

        /** Реплика над головой. */
        say(text: string, seconds?: number): void;

        /** Нанести урон. */
        damage(amount: number): void;

        /** Удалить NPC. */
        remove(): void;

        /** Подписка на смерть NPC. */
        onDeath(fn: () => void): void;
    }

    /** Подписка на смерть любого NPC. */
    function onNpcDeath(fn: (npc: NpcRef) => void): void;

    /** Подписка на убийство моба (zombie). */
    function onMobKilled(fn: (event: { ref: game.Ref; type: string }) => void): void;

    /** Изменение HP игрока (любой источник). */
    function onHpChange(fn: (event: { delta: number; source?: string }) => void): void;
}
`,
    },
    {
        uri: "ts:rublox/fx.d.ts",
        content: `/**
 * FX, теги, constraints, камера, кат-сцены.
 */
declare namespace game {
    interface FxApi {
        /** Beam — линия-молния между двумя точками или объектами. */
        beam(from: game.Vector3 | game.Ref, to: game.Vector3 | game.Ref, opts?: {
            color?: game.Color; width?: number; duration?: number;
        }): string;

        /** Trail — след за движущимся объектом. */
        trail(ref: game.Ref, opts?: {
            color?: game.Color; length?: number; lifetime?: number;
        }): string;

        /** Остановить FX по handle. */
        stop(handle: string): void;
    }
    const fx: FxApi;

    /** Теги — группировка объектов для массовых операций. */
    namespace tags {
        function add(ref: game.Ref, tag: string): void;
        function untag(ref: game.Ref, tag: string): void;
        function hasTag(ref: game.Ref, tag: string): boolean;
        function getTagged(tag: string): game.Ref[];
    }

    /**
     * Constraints — связи между объектами (Фаза 5).
     * Сейчас кинематические; в Фазе 6.5 станут физическими.
     */
    interface ConstraintsApi {
        /**
         * Phase 6.5: настоящая физ-петля Rapier между двумя rigid bodies.
         * Оба тела должны быть в Rapier-режиме (physics.setBodyType).
         */
        physicsHinge(refA: game.Ref, refB: game.Ref, opts?: {
            anchorA?: game.Vector3;
            anchorB?: game.Vector3;
            axis?: game.Vector3;
        }): { ref: string; remove(): void };

        /** Phase 6.5: жёсткая дистанция между rigid bodies (верёвка). */
        physicsDistance(refA: game.Ref, refB: game.Ref, opts?: {
            anchorA?: game.Vector3;
            anchorB?: game.Vector3;
        }): { ref: string; remove(): void };

        /** Жёсткая склейка двух объектов. */
        weld(a: game.Ref, b: game.Ref): string;
        /** Шарнир (петля) — A может вращаться вокруг оси относительно B. */
        hinge(a: game.Ref, b: game.Ref, opts?: {
            axis?: game.Vector3; limits?: { min: number; max: number };
        }): string;
        /** Пружина. */
        spring(a: game.Ref, b: game.Ref, opts?: {
            stiffness?: number; damping?: number; restLength?: number;
        }): string;
        /** Удалить constraint. */
        remove(handle: string): void;
    }
    const constraints: ConstraintsApi;

    /**
     * Камера в Play.
     */
    interface CameraApi {
        /** FOV в градусах (по умолчанию 60). */
        setFov(fov: number): void;
        /** Привязать камеру к объекту. */
        focusOn(ref: game.Ref, opts?: { offset?: game.Vector3; smooth?: number }): void;
        /** Кат-сцена — облёт камеры по точкам. onDone — game.onCutsceneDone. */
        cutscene(points: Array<game.Vector3 & { look?: game.Vector3; duration?: number }>): void;
        /** Сбросить камеру на игрока. */
        reset(): void;
        /** Тряска камеры на N секунд. */
        shake(strength: number, seconds: number): void;
    }
    const camera: CameraApi;

    /** Кат-сцена завершилась. */
    function onCutsceneDone(fn: () => void): void;

    /** Победа команды (мультиплеер). */
    function onTeamWin(teamName: string, fn: () => void): void;
}
`,
    },
    {
        uri: "ts:rublox/self.d.ts",
        content: `/**
 * \`game.self\` — объект-носитель текущего скрипта.
 *
 * Если скрипт привязан к блоку/модели/примитиву/GUI — self содержит ссылку
 * на этот объект. Если скрипт глобальный — self === null.
 */
declare namespace game {
    interface Self {
        /** Тип носителя: 'block' | 'model' | 'primitive' | 'gui'. */
        readonly kind: string;

        /** ref-идентификатор объекта для использования в scene API. */
        readonly ref: string;

        /** Текущая позиция (для 3D-объектов). */
        readonly position: game.Vector3;

        /** Свойства GUI-элемента (если kind='gui'). */
        readonly props: game.GuiProps | null;

        /** Клик по 3D-объекту (луч от игрока) или клик мышью по GUI. */
        onClick(fn: (event: { type: 'click'; point?: game.Vector3 }) => void): void;

        /** Игрок коснулся объекта. */
        onTouch(fn: (event: { type: 'touch' }) => void): void;

        /** Игрок ВЫШЕЛ из объекта (был внутри AABB и вышел). */
        onUntouch(fn: (event: { type: 'untouch' }) => void): void;

        /**
         * ProximityPrompt — подсказка «нажми E чтобы…».
         * Игрок подходит ближе чем distance, видит подсказку, жмёт клавишу — срабатывает fn.
         * @example
         * game.self.onInteract(() => {
         *     game.ui.showText('Дверь открыта!', 2);
         * }, { text: 'Открыть', distance: 4, key: 'e' });
         */
        onInteract(fn: () => void, opts?: {
            text?: string;
            distance?: number;
            key?: string;
        }): void;

        /** Переместить объект (модели/примитивы). */
        move(x: number, y: number, z: number): void;

        /** Удалить объект-носитель. */
        delete(): void;

        /** Изменить свойства GUI-элемента (только для kind='gui'). */
        update(patch: Partial<game.GuiProps>): void;

        /** Шорткат для смены текста GUI-элемента. */
        setText(text: string): void;

        /** Показать GUI-элемент. */
        show(): void;

        /** Скрыть GUI-элемент. */
        hide(): void;

        /** Сохранить произвольное данное на объекте. */
        setData(key: string, value: any): void;

        /** Прочитать данное. */
        getData(key: string): any;
    }
    const self: Self | null;
}
`,
    },
    {
        uri: "ts:rublox/misc.d.ts",
        content: `/**
 * Утилиты: логирование, математика, broadcast, require.
 */
declare namespace game {
    /**
     * Вывести значения в Консоль скриптов (аналог console.log).
     * @example
     * game.log('игрок:', game.player.position);
     */
    function log(...args: any[]): void;

    /** Предупреждение (жёлтое в консоли). */
    function warn(...args: any[]): void;

    /** Ошибка (красное в консоли). */
    function error(...args: any[]): void;

    /**
     * Случайное число.
     * - random() — 0..1
     * - random(max) — 0..max
     * - random(min, max) — min..max
     * - random(min, max, true) — целое min..max включительно
     * @example
     * const dmg = game.random(10, 20, true); // 10..20 целое
     */
    function random(): number;
    function random(max: number): number;
    function random(min: number, max: number, integer?: boolean): number;

    /** Расстояние между двумя точками или ref'ами. */
    function distance(
        a: game.Vector3 | game.Ref,
        b: game.Vector3 | game.Ref
    ): number;

    /** Зажать значение в диапазоне. */
    function clamp(value: number, min: number, max: number): number;

    /** Линейная интерполяция: lerp(a, b, 0)=a, lerp(a, b, 1)=b. */
    function lerp(a: number, b: number, t: number): number;

    /**
     * Отправить именованное сообщение всем скриптам (включая себя).
     * Для обмена данными между скриптами в разных Worker'ах.
     * @example
     * // в одном скрипте:
     * game.broadcast('checkpoint_reached', { num: 2 });
     * // в другом:
     * game.onMessage('checkpoint_reached', (data) => game.log(data.num));
     */
    function broadcast(name: string, data?: any): void;

    /** Подписаться на broadcast-сообщение. */
    function onMessage(name: string, fn: (data: any) => void): void;

    /**
     * Подключить переиспользуемый код из другого скрипта (ModuleScript).
     * В целевом скрипте экспорт через \`exports.foo = ...\`.
     * @example
     * // в скрипте 'utils':
     * exports.greet = (name) => game.ui.showText('Привет, ' + name);
     * // в обычном скрипте:
     * const utils = game.require('utils');
     * utils.greet('Стив');
     */
    function require(scriptName: string): Record<string, any>;

    interface AppApi {
        /** Перейти в другую игру на платформе. */
        navigate(gameId: number | string): void;
    }
    const app: AppApi;
}

/**
 * Глобальный объект exports — для скриптов-модулей (ModuleScript).
 * Любые поля, присвоенные exports, видны при game.require() из другого скрипта.
 */
declare const exports: Record<string, any>;
`,
    },
];
