fix(g7): спавн через __rbxl_spawn_part + Heartbeat вместо task.spawn

Корни:
1. task.spawn(function() task.wait() end) → 'attempt to yield across
   a C-call boundary' — task.spawn в shim синхронно зовёт fn из JS.
   Замена: накопление dt в RunService.Heartbeat → spawnCube() каждые 1.5с.

2. Instance.new('Part', workspace) с последующим .Anchored=false
   создавал anchored=true примитив + патч → primitiveManager не пересоздавал
   rigid body, куб не падал. Новый хелпер __rbxl_spawn_part(opts) шлёт
   sceneCreate с правильным anchored СРАЗУ — куб создаётся динамическим
   и падает.
This commit is contained in:
min 2026-06-09 17:38:16 +03:00
parent 8021ed6a20
commit c9acb4fb3b
2 changed files with 79 additions and 34 deletions

View File

@ -510,22 +510,24 @@ coinSound.SoundId = "coin"; coinSound.Volume = 1
local winSound = Instance.new("Sound", workspace) local winSound = Instance.new("Sound", workspace)
winSound.SoundId = "win"; winSound.Volume = 1 winSound.SoundId = "win"; winSound.Volume = 1
-- Каждые 1.5 сек роняем куб -- Каждые 1.5 сек роняем куб (через Heartbeat task.spawn не умеет yield)
task.spawn(function() local RunService = game:GetService("RunService")
while not won do local _spawnTimer = 0
task.wait(1.5)
if won then break end
local cube = Instance.new("Part", workspace)
cube.Size = Vector3.new(0.8, 0.8, 0.8)
cube.Position = Vector3.new(math.random(-6, 6), 14, math.random(-6, 6))
cube.Color = Color3.fromRGB(255, 204, 51)
cube.Material = Enum.Material.Neon
cube.Anchored = false -- падает под действием гравитации
-- Куб исчезает через 6с если не поймали local function spawnCube()
-- Используем хелпер __rbxl_spawn_part он сразу создаёт примитив
-- с правильными свойствами (включая anchored=false реальная гравитация).
local cube = __rbxl_spawn_part({
type = "cube",
x = math.random(-6, 6), y = 14, z = math.random(-6, 6),
sx = 0.8, sy = 0.8, sz = 0.8,
color = "#ffcc33",
anchored = false, -- падает
canCollide = true,
})
if not cube then return end
Debris:AddItem(cube, 6) Debris:AddItem(cube, 6)
-- Ловля: при касании игроком +1 очко
local caught = false local caught = false
cube.Touched:Connect(function(hit) cube.Touched:Connect(function(hit)
if caught or won then return end if caught or won then return end
@ -546,6 +548,14 @@ task.spawn(function()
__rbxl_spawn_particles("confetti", px, py + 3, pz, 3, 3) __rbxl_spawn_particles("confetti", px, py + 3, pz, 3, 3)
end end
end) end)
end
RunService.Heartbeat:Connect(function(dt)
if won then return end
_spawnTimer = _spawnTimer + (dt or 0.016)
if _spawnTimer >= 1.5 then
_spawnTimer = 0
spawnCube()
end end
end)`, end)`,
}, },

View File

@ -1826,6 +1826,41 @@ export function registerRobloxShim(lua, opts) {
count: Number(count) || 1, count: Number(count) || 1,
}); });
}); });
// Спавн примитива (паритет с JS game.scene.spawn) — кладёт в сцену
// примитив с указанным состоянием (включая anchored/canCollide). Возвращает
// id примитива (число) для дальнейших операций.
let _nextSpawnedId = 800000 + Math.floor(Math.random() * 10000);
global.set('__rbxl_spawn_part', (opts) => {
try {
const id = _nextSpawnedId++;
const o = opts || {};
send('sceneCreate', {
primId: id,
type: String(o.type || 'cube'),
x: +o.x || 0, y: +o.y || 0, z: +o.z || 0,
sx: +o.sx || 1, sy: +o.sy || 1, sz: +o.sz || 1,
color: o.color || '#A0A0A0',
anchored: o.anchored !== false,
canCollide: o.canCollide !== false,
});
// Создаём Lua-side представление для скриптов
const fakePrim = {
id, name: o.name || `Spawned_${id}`,
x: +o.x || 0, y: +o.y || 0, z: +o.z || 0,
sx: +o.sx || 1, sy: +o.sy || 1, sz: +o.sz || 1,
color: o.color || '#A0A0A0',
anchored: o.anchored !== false,
canCollide: o.canCollide !== false,
};
const part = newPart(fakePrim, send);
partById.set(id, part);
return part;
} catch (e) {
// eslint-disable-next-line no-console
console.warn('[__rbxl_spawn_part]', e?.message || e);
return null;
}
});
// Позиция игрока для удобства — отдельные функции для x/y/z, чтобы // Позиция игрока для удобства — отдельные функции для x/y/z, чтобы
// wasmoon не оборачивал результат в userdata-proxy. // wasmoon не оборачивал результат в userdata-proxy.
global.set('__rbxl_player_x', () => { global.set('__rbxl_player_x', () => {