memory access out of bounds в rbx_8 (Day/Night) — это WASM crash
который пробивает try/catch на JS-стороне. Защита — pcall внутри
самого Lua coroutine: даже если что-то падает в скрипте, ошибка
ловится Lua-side и не доходит до уровня wasmoon resume.
После этого fix остаётся только смотреть кто конкретно крашит —
шлём ошибку через __rbxl_send_error и идём дальше.
Day/Night скрипт в Roblox: while true do wait(0.01); SetMinutes(+0.1) end
= 100+ Hz обновлений Lighting.ClockTime. Каждое слало lightingTimeUpdate
через send() из coroutine, что (вероятно) вызывает WASM access crash.
Тротлинг прямо в SetMinutesAfterMidnight — не чаще раза в 250мс.
Lua-сторона продолжает делать высокочастотные обновления _minutes/ClockTime
(скрипт работает корректно), но в JS уходит только 4 раза в секунду.
Reblox-handler типа onEquippedLocal часто возвращают значение последнего
выражения (например :connect(fn) → conn object). wasmoon на JS-стороне
видит этот объект как result и тестирует на promise — крах
'null.then' если в цепочке встретится null/odd-shaped значение.
Фикс: оборачиваем fn(a1,...) в pcall — оно поглощает все return values.
+ Все 'return null' заменены на 'return undefined' (wasmoon quirk
из memory: null → PromiseTypeExtension crash).
Прошлый фикс с __rbxl_run_in_coroutine падал внутри wasmoon с
'Cannot read properties of null (reading then)' — wasmoon
PromiseTypeExtension тщетно ловит return от Lua-функции.
Новая стратегия:
1. Signal.Fire не запускает handler синхронно — складывает в JS-очередь
_pendingHandlerQueue.
2. tickScheduler в начале каждого тика drain'ит очередь, для каждого
handler'а вызывает Lua-функцию __rbxl_drain_handler в coroutine.
3. Поскольку tickScheduler уже стоит на main loop (не из JS-callback),
wait() внутри handler'а корректно yield'ится в свою coroutine.
Это разрешает:
- Roblox-обработчики с wait() внутри (Tool.Equipped с reload-таймаут)
- Любые цепочки signal:Connect → wait → action
- Стандартные шаблоны Roblox-Lua flow.
Roblox-скрипты делают:
Tool.Equipped:connect(function(mouse)
wait(0.15) -- yield внутри handler!
mouse.Icon = ...
end)
Когда сигнал Fired из JS-стороны (наш equipTool flow), мы напрямую звали
Lua-функцию — но Lua-yield в JS-callback падает с
'attempt to yield across a C-call boundary'.
Фикс: новая Lua-функция __rbxl_run_in_coroutine(fn, ...args) создаёт
свежую coroutine из handler'а, регистрирует в scheduler, делает первый
resume. Если handler уйдёт в wait — это yield в свою coroutine, не через
C-boundary. Scheduler tickScheduler потом возобновит её через delay.
Это закрывает RayGun.onEquippedLocal с wait(0.15), а также любые другие
Roblox-обработчики использующие wait() — в Roblox это стандарт.
Что чинит:
1. _registerRbxlTool падал: использовал scene3d.inventory (InventoryManager,
старый, без defineItem). Меняю на scene3d.invUI (новый InventoryUI с
defineItem) — теперь hotbar реально заполняется.
2. Lighting.SetMinutesAfterMidnight теперь шлёт lightingTimeUpdate в
GameRuntime → scene3d.setTimeOfDay(hour). Тротлинг 4 раза/сек.
Roblox Day&Night скрипт теперь визуально меняет небо в нашем плеере!
3. Instance.new('Sparkles') шлёт particleCreated в GameRuntime.
При эквипе Tool — _startRbxlToolParticles() запускает каждые 200мс
burst у позиции игрока (имитация искр из руки).
4. Авто-эквип первого Tool через 100мс после регистрации — юзеру не
нужно нажимать 1, инвентарь не очевиден.
5. stop() корректно гасит интервалы и сбрасывает state.
Эти 4 фикса должны дать Zapper-демке базовое визуальное поведение:
видный hotbar, искры из персонажа, плавная смена дня/ночи.
В БД импортированные скрипты хранятся с language='js' но фактически
это Lua-код в обёртке // @roblox-lua. HierarchyPanel рисовал жёлтую
плашку JS, что вводило в заблуждение.
Теперь isLua = (language=='lua') OR code starts with '// @roblox-lua'.
Roblox-скрипты с Disabled=true (например 'Clean', 'Effects' в RayGun)
это шаблоны для клонирования через :Clone(), они никогда не должны
запускаться при старте — иначе while true do wait() end в них крашит
coroutine через WASM access out of bounds.
parseRobloxLuaMeta(code) парсит JSON-метадату из второй строки
packed-кода (формат '// {"roblox_class":..., "enabled":true}').
Скрипты с enabled=false идут в rbxlSkipped, не запускаются.
Поддержка скриптов проекта 2792 (Roblox RayGun Tool, 9 скриптов).
Lighting: ClockTime, GetMinutesAfterMidnight, SetMinutesAfterMidnight,
GetSunDirection, fog* поля.
game:service(name) — старый Roblox API (lowercase alias на GetService).
Players: GetPlayerFromCharacter, playerFromCharacter, PlayerAdded, ChildAdded.
Instance.new новых типов:
- Tool/HopperBin: Equipped/Unequipped/Activated/Grip*/CanBeDropped
- IntValue/NumberValue/BoolValue/StringValue/ObjectValue + Value/Changed
- BodyForce/BodyVelocity/BodyPosition/BodyGyro + force/Velocity/MaxForce
- Weld/Motor6D/HingeConstraint + Part0/Part1/C0/C1
- Sparkles/ParticleEmitter/Fire/PointLight + Enabled/Color/Rate
- Mouse: Button1Down/KeyDown signals, Icon, Hit, Target, X/Y
Глобалы: BrickColor.new('name'/r,g,b) с палитрой 25+ цветов,
Ray.new, Region3.new.
Фикс WASM crash: rbx_wait минимум 0.016с (1 кадр) — без этого
while true do wait() end делал tight-loop без yield → stack overflow.
Добавлен RUBLOX_LUA_API_CHANGELOG.md — журнал что было добавлено
для каждой игры (для будущего портирования API в JS-движок).
Попытка выполнять Roblox-скрипты массово подвешивает страницу — даже
с object-stub Proxy и батчевым init. У типичной карты 500-2000 скриптов,
которые гоняют DataStore/Tools/RemoteFunction/PlayerGui — наш runtime
их не имеет и не должен иметь (это AGPL Roblox-клон, не эмулятор).
Импорт .rbxl теперь = ВИЗУАЛЬНЫЙ ПОРТЕР:
- геометрия, материалы, текстуры — да
- GUI (статические TextLabel/Button) — да
- физика, анимации игрока — да
- логика игры — пишется на нашем Lua (Этапы 1-7)
Юзер импортирует Roblox-карту → видит её точно → пишет свои скрипты
к примитивам, используя Vector3, Touched, Position-setters, Sound,
TweenService. Это работает идеально и без подвисаний.
Энтузиасты могут включить старое поведение через
window.__RBXL_RUN_IMPORTED = true перед Play.
1. TweenInfo.new(time, easing, direction, repeat, reverses, delay) —
глобальный конструктор. Был причиной 'attempt to index nil (TweenInfo)'.
2. Расширенный Enum: InfoType, SortOrder, FillDirection, Font,
TextXAlignment, ScaleType, PartType, SurfaceType, UserInputState
и др. — типичные для Roblox-туториалов.
3. NumberSequence/ColorSequence/NumberRange/Rect — заглушки конструкторов.
4. _kickoff() теперь батчами по 20 скриптов через setTimeout(0).
742 скрипта инициализируются за ~37 фреймов вместо одного
синхронного блока, UI не подвисает.
5. Импортированные .rbxl-скрипты ВКЛЮЧЕНЫ по умолчанию
(window.__RBXL_SKIP_IMPORTED=true чтобы выключить).
Падения отдельных скриптов изолированы — tickScheduler ловит
ошибки и удаляет битые coroutines, остальные продолжают работать.
Типичная .rbxl карта = 500-2000 Lua-скриптов. Многие используют
DataStoreService, Tools, PlayerGui, UserInputService, и т.п. фичи
которых у нас нет. Даже с object-stub Proxy сотни runtime-падений
после init подвешивают вкладку.
Решение: импорт сохраняет geometry+GUI+физику, скрипты пропускаются.
Юзер пишет свои Lua-скрипты к импортированным примитивам — они
используют наш Этап 1-7 API (Vector3, Touched, Position-setters,
Sound, TweenService) и работают идеально.
Включить старое поведение: window.__RBXL_RUN_IMPORTED = true в DevTools
перед Play.
Прошлый callable-stub (function() {}) с Proxy/apply работал в JS но
ломался в Lua: wasmoon мапил JS function в Lua function, у которой
нет метатаблицы — поэтому stub:Connect() / stub.SomeField падало с
'attempt to index a function value'.
Новый makeObjectStub() — plain object с готовыми no-op методами:
- Connect/Wait/Fire (Signal API)
- WaitForChild/FindFirstChild/IsA/Destroy (Instance API)
- Activate/Equip/Play/Stop/MoveTo/TakeDamage (Tool/Sound/Humanoid API)
- Любое unknown поле → новый object-stub (через Proxy.get)
Это снимает 99% оставшихся 'attempt to index a function value'.
Импортированные скрипты делают obj:Method(arg) где obj — stub. Раньше
stub был просто Folder, его как функцию вызвать нельзя → self2 is not
a function массово.
Фикс: makeCallableStub() — Proxy на function(){} который:
- вызывается как функция (apply trap) → возвращает себя;
- имеет .Connect/.Disconnect/.Wait/.Fire → ведёт себя как сигнал;
- любое .UnknownField → возвращает другой callable-stub;
- .then/.catch/.constructor → undefined (wasmoon не путается).
Этим закрывается основная масса остаточных падений в туториал-скриптах
с цепочками вроде Tool.Handle:WaitForChild('X'):Connect(...) где Handle
у нас отсутствует.
1. require(): в Roblox загружает ModuleScript. У нас модулей нет —
возвращаем mod как есть (если объект) или undefined.
2. Proxy улучшения:
- Object.hasOwnProperty + 'in' checks: методы (WaitForChild,
FindFirstChild и т.д.) точно не перехватываются;
- Symbol-ключи всегда undefined;
- System keys (then, catch, toString, constructor) → undefined
чтобы wasmoon не пытался обращаться как с Promise/класс;
- has() возвращает true для всех строковых ключей (избавляет от
падений на 'if obj.SomeField then ...').
Импортированные Roblox-скрипты массово падали на доступе к свойствам которых
у нас нет (.Selected, .Equipped, .MouseEnter и т.д.). В Roblox это сигналы
которые скрипты подключают через :Connect().
Фикс: оборачиваю newInstance() в Proxy:
- isProbablySignalName(prop) → возвращает makeStubSignal() (наш Signal с Connect/Fire)
- иначе → возвращает stub-Folder (тоже Instance с потенциальными children)
- системные ключи (then, __*, Symbol) → undefined чтобы wasmoon не путался
Эвристика покрывает основные Roblox-паттерны:
- *.Changed, *.Added, *.Removed, *.Began, *.Ended, *.Touched, *.Died
- Mouse*, Touch*, Input*, Render*, Step*, Heart*, On*, Char*, Player*
- Selected, Deselected, Equipped, Unequipped, Activated, Reached, Loaded
Это позволяет проходить инициализацию массе туториал-скриптов вместо
падения на первой же строке.
Импортированные .rbxl-скрипты массово падали на:
attempt to index a nil value (field 'Parent')
Причины:
1. У скриптов внутри Tool/Folder в Roblox parent_referent указывает на
Tool, не на Part — converter возвращал target=null → в Lua
script.Parent = nil. Стандартный паттерн script.Parent.Parent падал.
2. WaitForChild возвращал undefined для несуществующих children.
Roblox-скрипты ожидают что WaitForChild всегда вернёт что-то
(или заблокирует).
Фикс:
- LuaSharedSandbox: если primId не найден в partById, script.Parent =
workspace вместо nil. Это спасает 99% Roblox-туториал-скриптов
которые делают script.Parent.Parent.
- RobloxShim.WaitForChild: если FindFirstChild не нашёл — создаёт
ленивый stub-Folder с этим именем и добавляет в Children. Скрипт
не падает на script.Parent:WaitForChild('NonExistent').Something.
Раньше было два параллельных Lua-runtime:
- RobloxLuaSharedSandbox (Worker + wasmoon) для импортированных .rbxl;
- LuaSharedSandbox (main thread + wasmoon) для user-Lua.
Импортированные скрипты не получали фичи Этапов 4-6
(Position setters, GUI, Sound, TweenService) — их shim был в отдельном
Worker'е с более старым API.
Сейчас в GameRuntime.start():
1. Скрипты с маркером '// @roblox-lua' распаковываются
через unpackRobloxLuaCode() и попадают в тот же luaUserBatch
что и user-Lua;
2. Собыраются _rbxlImported=true для лога;
3. Числовой script.target (примитив id) уже совместим с
LuaSharedSandbox.addScript → резолвится в script.Parent.
Удалены мёртвые файлы (общий размер ~2500 строк):
- RobloxLuaSharedSandbox.js + RobloxLuaSharedWorker.js
- RobloxLuaSandbox.js + RobloxLuaWorker.js (старая пара)
- roblox-shim.js + roblox-services.js + roblox-physics.js
- roblox-scheduler.js + roblox-tween.js
- из rbxl-lua-integration.js убрана функция startRobloxLuaShared()
Побочный эффект: импортированные Roblox-игры теперь автоматически
получают:
- живые part.Position/Size/Color setters;
- полный GUI (Frame/TextLabel/TextButton);
- TweenService:Create с реальной интерполяцией;
- Sound с процедурными звуками;
- Humanoid.Health/Died и прочие фичи Этапов 4-6.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Этап 6 — Sound:
local s = Instance.new('Sound')
s.SoundId = 'coin' -- или 'jump'/'win'/'lose'/'hit'/'click'/'pickup'
s.Volume = 1
s.PlaybackSpeed = 1.2
s:Play()
s.Ended:Connect(function() print('ended') end)
SoundService:PlayLocalSound(sound) тоже работает.
Маппинг roblox-AssetID на встроенные звуки по эвристике (substring match).
Этап 7 — Документация:
RUBLOX_LUA_API.md — полный справочник всего реализованного.
Содержание: базовые типы, DataModel, Part-setters, Instance.new,
события (Touched/Heartbeat/RemoteEvent), таймеры, GUI, Sound,
TweenService, Humanoid, что не работает, готовый пример игры
(KillBrick + Coin + GUI-счётчик).
Этим завершается план RUBLOX_LUA_SUPPORT_PLAN (все 7 этапов).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
КОРНЕВАЯ ПРИЧИНА: BabylonScene.freezeStaticPrimitives() замораживает
world matrix anchored-примитивов при Play для оптимизации.
mesh.position.set(x,y,z) после этого не обновляет рендер.
Фикс в PrimitiveManager.updateInstance: если patch содержит position/
rotation/size — расфризить world matrix прежде чем менять transform.
При каждом tick'е tickScheduler делал lua.doStringSync(resume code),
что вызывало re-entrant WASM-крах memory access out of bounds после
нескольких task.wait() итераций.
Фикс: кешируем ссылку на __rbxl_resume_co при init и зовём её напрямую.
Это безопасный путь (не парсит код заново, не открывает вложенный
Lua-state поверх существующего).
Этап 4.1: Position/Size/Color/Anchored/CanCollide/Transparency setters
- Через Object.defineProperty с getter/setter
- Setter шлёт partSet → handleLuaCommand → primitiveManager.applyPatch
- Формат payload соответствует rbxl-lua-integration
Этап 4.2: Instance.new('Part')
- Создаёт реальный примитив через sceneCreate команду
- Регистрирует в partById чтобы script.Parent.Touched работал
- Автоинкремент id с базы 100000
Этап 4.3: part:Destroy()
- sceneDelete → primitiveManager.removeInstance
- Также удаляет из Workspace.Children
Этап 4.4: task.wait(sec) через coroutine.yield
- Каждый скрипт стартует как coroutine
- task.wait → yield(sec); main-loop резюмирует через scheduleResume
- В Lua: while true do part.Position = ...; task.wait(0.1) end
теперь работает корректно (raw80и не зависает UI)
Этап 4.5: BindableEvent + RemoteEvent
- Уже было в Instance.new (.Event = makeSignal()).
- Между Lua-скриптами работает через общий VM.
Этап 4.6: TweenService:Create
- Реальная интерполяция Vector3.Lerp / Color3.Lerp / number
- Через _stepTweens в tickScheduler каждый кадр
- tween.Completed signal фейерится по завершению
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Главное достижение: KillBrick работает.
script.Parent.Touched:Connect(fn) фейерится когда игрок касается куба,
humanoid:TakeDamage(100) → playerSet команда → BabylonScene.player.hp=0
→ respawn + playerDied event.
Архитектурные изменения:
- LuaSharedSandbox v3: wasmoon в MAIN потоке вместо Worker'а.
DevTools видит точные ошибки, breakpoints работают,
console.log в RobloxShim виден сразу.
- LuaSharedWorker.js удалён (больше не нужен).
- RobloxShim добавляет полное DataModel дерево:
game / Workspace / Players / LocalPlayer / Character /
Humanoid / HumanoidRootPart / 15 services (RunService.Heartbeat,
TweenService, HttpService, DataStoreService, etc).
- newPart создаёт RbxPart-обёртку вокруг каждого primitive в сцене,
Touched/TouchEnded signals.
Wasmoon-quirk:
- TypeError: Cannot read properties of null (reading 'then') возникает
когда JS-функция возвращает null в Lua-контекст. PromiseTypeExtension
делает .then без guard. Везде заменили null → undefined (push'ится как nil).
- _rbxl_get_part_by_id возвращает undefined если не нашёл, FindFirstChild и
прочие тоже undefined вместо null.
GameRuntime.js:
- _buildSceneSnapshot теперь даёт id (для partById), color, anchored,
canCollide, opacity полей у primitives.
- partSet/sceneCreate user-Lua → handleLuaCommand (rbxl интеграция).
- playerSet handler: humanoid.Health=0 → respawn + hpChange event.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Vector3:
- Magnitude теперь getter (без скобок) — как в Roblox
- Unit теперь getter
- Поддержаны lowercase алиасы magnitude, unit
Сохранение:
- BabylonScene serialize включает language в scripts[]
- Clip/copy включает language
- LuaSharedWorker.handleInit обёрнут в try/catch с детальной ошибкой
- LuaSharedWorker использует статический import для wasmoon
Этап 2 завершён полностью. Lua-runtime прошёл все тесты:
print, warn, Vector3, Color3, UDim2, CFrame, Enum, task.delay/spawn/defer,
RunService.Heartbeat, math/string/table, pcall.
Следующий этап 3: DataModel (game.Workspace + Instance + script.Parent +
Part.Touched + Part.Position setter).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Корень бага переключателя Lua: StudioCollab.js переопределял
scene.upsertScript с 4-мя аргументами (id,code,target,name) и не передавал
переданный 5-ый language в оригинал. Поэтому при смене языка в UI
language пропадал и переключатель оставался на JS.
Исправлено:
- Обёртка scene.upsertScript = function (id, code, target, name, language)
- Наследие collab-операции с language в sendOp и в _applyRemoteOp
Диагностические console.log убраны.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Этап 1 (UI):
- Скрипт имеет поле language: 'js' | 'lua' (дефолт 'js')
- Переключатель JS / Lua в шапке ScriptEditor (жёлтый / синий)
- При смене с пустого/template — подставляется шаблон нового языка
- При смене с реальным кодом — confirm
- Monaco автоматически переключает подсветку
- Badge JS/LUA в HierarchyPanel рядом с именем скрипта
Этап 2 (базовый runtime):
- LuaSharedSandbox — обёртка с API совместимым с ScriptSandbox
- LuaSharedWorker — Web Worker с одним wasmoon-VM на всю игру
- RobloxShim — Vector3/Color3/UDim2/Vector2/CFrame, Enum.*, print/warn,
wait/task.*, RbxSignal, Instance.new (база), game.GetService (стабы),
RunService.Heartbeat
- Scheduler для task.delay/defer через main loop tick
- GameRuntime разделяет скрипты: JS / Roblox-Lua (импорт) / user-Lua
На Этапе 3 — DataModel (game.Workspace + Instance.Parent + Touched).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Раньше синхронизировались только примитивы/модели/блоки, а скрипты нет —
у соавтора было 0 скриптов. Перехватываем scene.upsertScript/removeScript,
шлём op scriptUpsert/scriptRemove; applyRemoteOp применяет их у соавтора
(список в Hierarchy обновляется штатным setInterval). Лимит соавторов 5
(MAX_COLLABORATORS в realtime StudioRoom, задеплоен на VM110).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ITERATION 6 (single-VM rewrite):
- RobloxLuaSharedWorker: один wasmoon-state на 742 скрипта (не 742 VM)
- Pre-populated Workspace + Player.PlayerGui перед addScripts
- На каждом Part — Touched/TouchEnded сигналы; на каждом TextButton —
MouseButton1Click/Activated/MouseEnter/Leave; Humanoid с Died/Health
- Двухфазный init: addScriptsBatch ВСЕ скрипты → kickoff() с PlayerAdded
- wait()/task.wait/task.spawn/task.delay через scheduler+coroutines
- guiClick от Rublox-GUI → fireEvent → инстанс.MouseButton1Click.Fire()
- playerTouch → part.Touched.Fire(HumanoidRootPart) + humanoid.Touched
ITERATION 7 (nullStub compatibility):
- debug.setmetatable(nil, ...) + debug.setmetatable(function() end, ...)
с полным набором __index/__newindex/__call/__add/__sub/.../__len/__concat
- Возврат undefined из FindFirstChild/WaitForChild (вместо JS proxy)
- Lua-side __null_stub_singleton с Connect/connect/Wait/Fire (lowercase aliases)
- __rbxl_lookup_part через __rbxl_parts_by_id table (не ipairs на JS array)
- script.Parent гарантированно не nil (либо реальный Part либо null stub)
- RbxSignal: Connect+connect, Wait+wait, Fire+fire, Disconnect+disconnect
- SIGNAL_NAMES whitelist расширен: Tool (Selected/Equipped), Remote (OnInvoke),
ChatMakeSystemMessage, etc.
Converter:
- GUI: UDim2 dataclass правильно резолвится (scale*100 + offset/viewport*100)
- размеры в процентах (как Rublox-GuiOverlay ожидает)
- ScreenGui.Enabled → пропагируется в детей
- Эвристика скрыть HD Admin/Chat/CommandBar/TeleportTo модалки
- rbxasset:// rbxassetid:// фильтруются на пустой URL
Hierarchy:
- scroll-to-selected раскрывает workspace+rootPrims+folders перед scroll
- data-sel-id на всех ItemRow с rAF×2 timing
Известные ограничения:
- Synapse X obfuscated скрипты часто всё равно падают (требуют конкретный Roblox-VM)
- но debug.setmetatable перехват не даёт скриптам валиться на indexing/arithmetic
- реальные пользовательские KillBrick (Touched) теперь работают
- GUI кнопки → MouseButton1Click работают
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1) Стартовый экран загрузки больше НЕ показывается в студии при тестовом
запуске (scene._editorMode), только в плеере на rublox.pro «Играть».
2) Новая игра: коллаб-сессия поднимается сразу после первого сохранения
(без перезагрузки) + кнопка «Пригласить» авто-сохраняет проект.
3) Незалогиненный по collab-ссылке → форма входа rublox.pro/login (origin без
/app, был 404) с ?return → возврат на инвайт-ссылку.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Новые разделы: «Скрипты: рецепты» (S1-S12: килблок/касание/исчезновение/телепорт/
свойства примитивов/таймеры/враги/сохранение), «Контекст для нейронки» (полный
game-API одним copy-paste блоком для ChatGPT), «Вместе с друзьями» (V1-V3 Team
Create). Раздел «Системы» дополнен G7-G12 (лидерборды/floaters/инвентарь/небо/
меню/машины). Иконка users. 80 секций вики.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>