Run it
Copy the script and paste it into your executor.
Players = :()
TweenService = :()
UIS = :()
HttpService = :()
CoreGui = :()
Workspace = :()
ReplicatedStorage = :()
Lighting = :()
StarterPack = :()
ServerStorage = :()
StarterGui = :()
StarterPlayer = :()
StarterCharacterScripts = StarterPlayer:()
StarterCharacterScripts
ok, created = (() .() )
ok created
created.Parent = StarterPlayer
StarterCharacterScripts = created
StarterPlayerScripts = StarterPlayer:()
StarterPlayerScripts
ok, created = (() .() )
ok created
created.Parent = StarterPlayer
StarterPlayerScripts = created
player = Players.LocalPlayer
.(() * )
sceneState = {}
SCENE_STATE_MAX =
()
count =
_ (sceneState) count = count +
count <= SCENE_STATE_MAX
oldest_key, oldest_time = , .huge
k, v (sceneState)
(v.lastAction ) < oldest_time
oldest_key = k
oldest_time = v.lastAction
oldest_key sceneState[oldest_key] =
(action, didSucceed)
didSucceed
action (action) ~=
_searchCache =
containerKey = action.containerName (action.name action.className )
sceneState[containerKey]
sceneState[containerKey] = { className = action.className, count = , namePrefix = action.namePrefix, createdNames = {} }
ss = sceneState[containerKey]
ss.lastAction = .()
ss.className = action.className ss.className
action.namePrefix ss.namePrefix = action.namePrefix
action.type ==
ss.count = ss.count +
action.name ss.createdNames[action.name] =
action.type ==
c = .((action.count) , , )
ss.count = ss.count + c
action.type ==
ss.count = ss.count +
action.name ss.createdNames[action.name] =
action.type ==
targetName = action.target
targetName ss.createdNames
n, _ (ss.createdNames)
n:():(targetName:(), , )
ss.createdNames[n] =
ss.count = .(, ss.count - )
()
undoStack = {}
undoLimit =
_logLabels = {}
_batchCreated = {}
_lastClickInstance =
_batchPlacedGrid =
_autoRunScripts = {}
AUTORUN_MAX =
_requestQueue = {}
_processingRequest =
_searchCache =
MESSAGE_MAX =
MAX_TOKENS_LOW =
MAX_TOKENS_HIGH =
OPENCODE_URL =
NVIDIA_URL =
OPENCODE_KEYS = {
,
,
,
,
,
}
NVIDIA_API_KEY =
MODEL_LIST = {
{ label = , provider = , id = },
{ label = , provider = , id = },
{ label = , provider = , id = },
{ label = , provider = , id = },
{ label = , provider = , id = },
{ label = , provider = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
{ label = , provider = , id = , maxOutput = },
}
CURRENT_PROVIDER =
CURRENT_MODEL_LABEL =
SAVE_FILE =
(label, provider, modelId)
(()
writefile
data = label .. .. provider .. .. (modelId )
(SAVE_FILE, data)
)
()
ok, data = (()
readfile isfile (SAVE_FILE)
(SAVE_FILE)
)
ok data data ~=
lines = {}
line data:() .(lines, line)
savedLabel = lines[]
savedProvider = lines[]
savedId = lines[]
_, m (MODEL_LIST)
m.provider ~= m.label == savedLabel
m
{ label = savedLabel, provider = savedProvider, id = savedId }
API_URL = OPENCODE_URL
MODEL_ID =
API_KEYS = OPENCODE_KEYS
KEY_INDEX =
KEY_RESET_TIME =
CURRENT_MODEL_MAX_OUTPUT =
(entry)
entry entry.provider ==
CURRENT_PROVIDER = entry.provider
CURRENT_MODEL_LABEL = entry.label
MODEL_ID = entry.id
CURRENT_MODEL_MAX_OUTPUT = entry.maxOutput
entry.provider ==
API_URL = NVIDIA_URL
API_KEYS = { NVIDIA_API_KEY }
API_URL = OPENCODE_URL
API_KEYS = OPENCODE_KEYS
KEY_INDEX =
(entry.label, entry.provider, entry.id)
saved = ()
saved (saved)
()
(API_KEYS) ~= #API_KEYS ==
key = API_KEYS[KEY_INDEX + ] API_KEYS[]
KEY_INDEX = (KEY_INDEX + ) % #API_KEYS
key
()
.. (KEY_INDEX % #API_KEYS + ) .. .. #API_KEYS
PRICE_PER_1M_INPUT =
PRICE_PER_1M_OUTPUT =
requestFunc =
.request
requestFunc = .request
requestFunc =
requestFunc =
fluxus_request
requestFunc = fluxus_request
requestFunc = (data)
HttpService:(data.Url, data.Body, .HttpContentType.ApplicationJson, , data.Headers)
(()
old = CoreGui:()
old old:()
)
C = {
bg = .(, , ),
surface = .(, , ),
surfaceHi = .(, , ),
border = .(, , ),
accent = .(, , ),
text = .(, , ),
textDim = .(, , ),
green = .(, , ),
red = .(, , ),
yellow = .(, , ),
orange = .(, , ),
}
(inst, r)
c = .(, inst)
c.CornerRadius = .(, r )
c
(inst, col, t, trans)
s = .(, inst)
s.Color = col C.border
s.Thickness = t
s.Transparency = trans
s
(inst, info, props)
t = TweenService:(inst, info, props)
t:()
t
(frame, handle)
handle = handle frame
dragging, dragStart, startPos = , ,
handle.InputBegan:((inp)
(inp.UserInputType == .UserInputType.MouseButton1 inp.UserInputType == .UserInputType.Touch)
dragging =
dragStart = inp.Position
startPos = frame.Position
)
UIS.InputChanged:((inp)
dragging (inp.UserInputType == .UserInputType.MouseMovement inp.UserInputType == .UserInputType.Touch)
d = inp.Position - dragStart
frame.Position = .(
startPos.X.Scale, startPos.X.Offset + d.X,
startPos.Y.Scale, startPos.Y.Offset + d.Y
)
)
UIS.InputEnded:((inp)
inp.UserInputType == .UserInputType.MouseButton1 inp.UserInputType == .UserInputType.Touch
dragging =
)
sg = .()
sg.Name =
sg.ResetOnSpawn =
sg.ZIndexBehavior = .ZIndexBehavior.Sibling
sg.IgnoreGuiInset =
sgParented = (() sg.Parent = CoreGui )
sgParented
(() sg.Parent = Players.LocalPlayer:() )
toggleBtn = .()
toggleBtn.Name =
toggleBtn.Size = .(, , , )
toggleBtn.Position = .(, , , )
toggleBtn.BackgroundColor3 = C.surfaceHi
toggleBtn.Text =
toggleBtn.TextColor3 = C.accent
toggleBtn.TextSize =
toggleBtn.Font = .Font.GothamBold
toggleBtn.Parent = sg
(toggleBtn, )
(toggleBtn, C.accent, , )
(toggleBtn)
PW =
PH =
panel = .()
panel.Name =
panel.Size = .(, PW, , PH)
panel.Position = .(, -PW/, , -PH/)
panel.BackgroundColor3 = C.bg
panel.BorderSizePixel =
panel.ClipsDescendants =
panel.Parent = sg
(panel, )
(panel, C.accent, , )
tb = .()
tb.Size = .(, , , )
tb.BackgroundTransparency =
tb.Parent = panel
titleText = .()
titleText.Size = .(, -, , )
titleText.Position = .(, , , )
titleText.BackgroundTransparency =
titleText.Text =
titleText.TextColor3 = C.accent
titleText.TextXAlignment = .TextXAlignment.Left
titleText.TextSize =
titleText.Font = .Font.GothamBold
titleText.Parent = tb
modelBtn = .()
modelBtn.Size = .(, , , )
modelBtn.Position = .(, -, , -)
modelBtn.BackgroundColor3 = C.surfaceHi
modelBtn.TextColor3 = C.yellow
modelBtn.TextSize =
modelBtn.Font = .Font.Gotham
modelBtn.TextTruncate = .TextTruncate.AtEnd
modelBtn.Parent = tb
(modelBtn, )
(modelBtn, C.yellow, , )
()
short = CURRENT_MODEL_LABEL:(, ):(, ):()
#short > short = short:(, ) ..
modelBtn.Text = .. short
()
closeBtn = .()
closeBtn.Size = .(, , , )
closeBtn.Position = .(, -, , -)
closeBtn.BackgroundColor3 = C.surfaceHi
closeBtn.Text =
closeBtn.TextColor3 = C.red
closeBtn.TextSize =
closeBtn.Font = .Font.GothamBold
closeBtn.Parent = tb
(closeBtn, )
closeBtn.MouseButton1Click:(() sg:() )
(panel, tb)
toggleBtn.MouseButton1Click:(()
panel.Visible = panel.Visible
(toggleBtn, TweenInfo.(), {Rotation = panel.Visible })
)
displayArea = .()
displayArea.Size = .(, -, , -)
displayArea.Position = .(, , , )
displayArea.BackgroundColor3 = C.surface
displayArea.BorderSizePixel =
displayArea.ScrollBarThickness =
displayArea.AutomaticCanvasSize = .AutomaticSize.Y
displayArea.CanvasSize = .(, , , )
displayArea.Parent = panel
(displayArea, )
(displayArea, C.border, , )
displayList = .(, displayArea)
displayList.Padding = .(, )
displayList.HorizontalAlignment = .HorizontalAlignment.Center
displayList.SortOrder = .SortOrder.LayoutOrder
welcomeLbl = .()
welcomeLbl.Size = .(, -, , )
welcomeLbl.BackgroundTransparency =
welcomeLbl.Text =
welcomeLbl.TextColor3 = C.textDim
welcomeLbl.TextSize =
welcomeLbl.Font = .Font.Gotham
welcomeLbl.Parent = displayArea
inputArea = .()
inputArea.Size = .(, -, , )
inputArea.Position = .(, , , -)
inputArea.BackgroundColor3 = C.surface
inputArea.Parent = panel
(inputArea, )
(inputArea, C.border, , )
inputBox = .()
inputBox.Size = .(, -, , -)
inputBox.Position = .(, , , )
inputBox.BackgroundTransparency =
inputBox.PlaceholderText =
inputBox.TextColor3 = C.text
inputBox.PlaceholderColor3 = C.textDim
inputBox.TextSize =
inputBox.Font = .Font.Gotham
inputBox.ClearTextOnFocus =
inputBox.Parent = inputArea
sendBtn = .()
sendBtn.Size = .(, , , -)
sendBtn.Position = .(, -, , )
sendBtn.BackgroundColor3 = C.accent
sendBtn.Text =
sendBtn.TextColor3 = .(, , )
sendBtn.TextSize =
sendBtn.Font = .Font.GothamBold
sendBtn.Parent = inputArea
(sendBtn, )
(inst)
inst
parts = {}
current = inst
current
name = current.Name
.(parts, , name)
current = current.Parent
.(parts, )
(code, scriptName)
overlay = .()
overlay.Size = .(, , , )
overlay.BackgroundColor3 = .(, , )
overlay.BackgroundTransparency =
overlay.ZIndex =
overlay.Parent = sg
box = .()
box.Size = .(, , , )
box.Position = .(, , , )
box.BackgroundColor3 = C.surface
box.ZIndex =
box.Parent = overlay
(box, )
(box, C.accent, , )
title = .()
title.Size = .(, -, , )
title.Position = .(, , , )
title.BackgroundTransparency =
title.Text = .. (scriptName ) ..
title.TextColor3 = C.accent
title.TextSize =
title.Font = .Font.GothamBold
title.TextWrapped =
title.ZIndex =
title.Parent = box
codeScroll = .()
codeScroll.Size = .(, -, , -)
codeScroll.Position = .(, , , )
codeScroll.BackgroundColor3 = C.bg
codeScroll.ScrollBarThickness =
codeScroll.ZIndex =
codeScroll.Parent = box
(codeScroll, )
codeLbl = .()
codeLbl.Size = .(, -, , )
codeLbl.Position = .(, , , )
codeLbl.BackgroundTransparency =
codeLbl.Text = code
codeLbl.TextColor3 = C.green
codeLbl.TextSize =
codeLbl.Font = .Font.Code
codeLbl.TextWrapped =
codeLbl.TextXAlignment = .TextXAlignment.Left
codeLbl.TextYAlignment = .TextYAlignment.Top
codeLbl.ZIndex =
codeLbl.Parent = codeScroll
lineCount = (, code:(, )) +
approxHeight = .(, lineCount * + (#code / ) * )
codeLbl.Size = .(, -, , approxHeight)
codeScroll.CanvasSize = .(, , , approxHeight + )
copyBtn = .()
copyBtn.Size = .(, -, , )
copyBtn.Position = .(, , , -)
copyBtn.BackgroundColor3 = C.accent
copyBtn.Text =
copyBtn.TextColor3 = .(, , )
copyBtn.TextSize =
copyBtn.Font = .Font.GothamBold
copyBtn.ZIndex =
copyBtn.Parent = box
(copyBtn, )
copyBtn.MouseButton1Click:(()
copied =
setclipboard
copied = (() (code) )
copied
copyBtn.Text =
copyBtn.Text =
)
closeBtn2 = .()
closeBtn2.Size = .(, -, , )
closeBtn2.Position = .(, , , -)
closeBtn2.BackgroundColor3 = C.surfaceHi
closeBtn2.Text =
closeBtn2.TextColor3 = C.text
closeBtn2.TextSize =
closeBtn2.Font = .Font.Gotham
closeBtn2.ZIndex =
closeBtn2.Parent = box
(closeBtn2, )
closeBtn2.MouseButton1Click:(()
overlay:()
)
showInstanceInfo
(text, isUser, isError, isSystem, clickInstance)
welcomeLbl.Visible =
allEntries = displayArea:()
entryCount = #allEntries
entryCount > MESSAGE_MAX
toRemove = entryCount - MESSAGE_MAX
i = , toRemove
oldest = allEntries[i]
oldest oldest ~= welcomeLbl oldest ~= displayList
oldest:()
entry = .()
entry.Size = .(, -, , )
entry.BackgroundTransparency =
entry.Parent = displayArea
bg = .()
bg.Size = .(, , , )
isError
bg.BackgroundColor3 = C.red
isSystem
bg.BackgroundColor3 = .(, , )
isUser
bg.BackgroundColor3 = C.surfaceHi
bg.BackgroundColor3 = C.surface
bg.Parent = entry
(bg, )
(bg, isError C.red (isUser C.accent C.border), , )
lbl = .()
lbl.Size = .(, -, , -)
lbl.Position = .(, , , )
lbl.BackgroundTransparency =
lbl.Text = text
lbl.TextColor3 = isError C.text (isUser C.accent C.text)
lbl.TextSize =
lbl.Font = .Font.Gotham
lbl.TextWrapped =
lbl.TextXAlignment = .TextXAlignment.Left
lbl.AutoButtonColor =
lbl.Selectable =
lbl.Parent = bg
clickInstance
actual = clickInstance
(actual) == actual._ref
actual = actual._ref
actual (actual) ==
lbl.TextColor3 = .(, , )
lbl.MouseButton1Click:(()
(actual)
)
entry.BackgroundTransparency =
lineBreaks = (, text:(, ))
wrapLines = .(#text / )
totalLines = .(, lineBreaks + wrapLines)
height = .(, .(, totalLines * + ))
bg.Size = .(, , , height)
entry.Size = .(, , , height + )
.()
displayArea.CanvasPosition = .(, displayArea.CanvasSize.Y.Offset)
showInstanceInfo = (inst)
inst
(, , )
className = inst:() inst.ClassName inst.ClassName
info = .. className
info = info .. .. inst.Name .. .. (inst)
inst:()
pos = inst.Position
info = info .. ():(pos.X, pos.Y, pos.Z)
inst:() inst:()
src = inst.Source
#src >
info = info .. .. #src ..
info = info ..
inst:()
val = inst.Value
#val >
info = info .. .. val ..
info = info ..
(info, , , )
()
overlay = .()
overlay.Size = .(, , , )
overlay.BackgroundColor3 = .(, , )
overlay.BackgroundTransparency =
overlay.ZIndex =
overlay.Parent = sg
popup = .()
popup.Size = .(, , , )
popup.Position = .(, -, , -)
popup.BackgroundColor3 = C.bg
popup.ZIndex =
popup.Parent = overlay
(popup, )
(popup, C.accent, , )
hdr = .()
hdr.Size = .(, -, , )
hdr.Position = .(, , , )
hdr.BackgroundTransparency =
hdr.Text =
hdr.TextColor3 = C.accent
hdr.TextXAlignment = .TextXAlignment.Left
hdr.TextSize =
hdr.Font = .Font.GothamBold
hdr.ZIndex =
hdr.Parent = popup
closePop = .()
closePop.Size = .(, , , )
closePop.Position = .(, -, , )
closePop.BackgroundColor3 = C.surfaceHi
closePop.Text =
closePop.TextColor3 = C.red
closePop.TextSize =
closePop.Font = .Font.GothamBold
closePop.ZIndex =
closePop.Parent = popup
(closePop, )
closePop.MouseButton1Click:(() overlay:() )
curLbl = .()
curLbl.Size = .(, -, , )
curLbl.Position = .(, , , )
curLbl.BackgroundTransparency =
curLbl.Text = .. CURRENT_MODEL_LABEL
curLbl.TextColor3 = C.green
curLbl.TextSize =
curLbl.Font = .Font.Gotham
curLbl.TextTruncate = .TextTruncate.AtEnd
curLbl.TextXAlignment = .TextXAlignment.Left
curLbl.ZIndex =
curLbl.Parent = popup
scroll = .()
scroll.Size = .(, -, , -)
scroll.Position = .(, , , )
scroll.BackgroundColor3 = C.surface
scroll.ScrollBarThickness =
scroll.AutomaticCanvasSize = .AutomaticSize.Y
scroll.CanvasSize = .(, , , )
scroll.ZIndex =
scroll.Parent = popup
(scroll, )
list = .(, scroll)
list.Padding = .(, )
list.SortOrder = .SortOrder.LayoutOrder
pad = .(, scroll)
pad.PaddingLeft = .(, )
pad.PaddingTop = .(, )
idx, entry (MODEL_LIST)
entry.provider ==
sep = .()
sep.Size = .(, -, , )
sep.BackgroundColor3 = C.surfaceHi
sep.Text =
sep.TextColor3 = C.yellow
sep.TextSize =
sep.Font = .Font.GothamBold
sep.LayoutOrder = idx
sep.ZIndex =
sep.Parent = scroll
(sep, )
isActive = (entry.label == CURRENT_MODEL_LABEL)
btn = .()
btn.Size = .(, -, , )
btn.BackgroundColor3 = isActive C.accent C.surfaceHi
btn.TextColor3 = isActive .(,,) C.text
btn.Text = entry.label
btn.TextSize =
btn.Font = .Font.Gotham
btn.TextXAlignment = .TextXAlignment.Left
btn.TextTruncate = .TextTruncate.AtEnd
btn.LayoutOrder = idx
btn.ZIndex =
btn.Parent = scroll
(btn, )
lbl2 = .(, btn)
lbl2.PaddingLeft = .(, )
capturedEntry = entry
btn.MouseButton1Click:(()
(capturedEntry)
()
curLbl.Text = .. CURRENT_MODEL_LABEL
_, child (scroll:())
child:()
child.BackgroundColor3 = C.surfaceHi
child.TextColor3 = C.text
btn.BackgroundColor3 = C.accent
btn.TextColor3 = .(, , )
.(, ()
overlay:()
( .. CURRENT_MODEL_LABEL .. .. CURRENT_PROVIDER:() .. , )
)
)
modelBtn.MouseButton1Click:(showModelPicker)
LOG_FILE =
(label, content)
ok = (()
stamp = .. .() .. .. label .. .. (content) .. .. .(, ) ..
writefile
isfile (LOG_FILE) readfile
existing = (LOG_FILE)
(LOG_FILE, existing .. stamp)
(LOG_FILE, stamp)
)
ok
()
plr = Players.LocalPlayer
()
_, entry (_autoRunScripts)
fn, err = ( .. entry.source)
fn
.(()
ok, e = (fn, entry.inst)
ok ( .. entry.name, e)
)
()
plr.CharacterAdded:(reRunAll)
HIGH_EFFORT_KEYWORDS = {
, , , , , , ,
, , , , , ,
, , , , , ,
, , , , , ,
, , , , , , ,
, , , , , ,
, , , , , ,
, , , , , ,
}
(question)
(question) ~=
lowerQ = question:()
#question >
_, kw (HIGH_EFFORT_KEYWORDS)
lowerQ:(kw, , )
REQUEST_TIMEOUT = .huge
(messages, _isRetry, effort, _keyAttempt)
_keyAttempt = _keyAttempt
maxKeyAttempts = (#API_KEYS ) *
currentKey = ()
currentKey
,
cap = (effort == ) MAX_TOKENS_HIGH MAX_TOKENS_LOW
maxTokens = .(cap, CURRENT_MODEL_MAX_OUTPUT cap)
data = {
model = MODEL_ID,
messages = messages,
max_tokens = maxTokens,
temperature = ,
top_p = ,
}
json = HttpService:(data)
headers = {
[] = ,
[] = .. currentKey,
}
success, response
done =
result_ok, result_val
.(()
result_ok, result_val = (()
requestFunc
({
Url = API_URL,
Method = ,
Headers = headers,
Body = json,
})
HttpService:(API_URL, json, .HttpContentType.ApplicationJson, , headers)
)
done =
)
t0 = ()
done (() - t0) < REQUEST_TIMEOUT
.()
done
, .. REQUEST_TIMEOUT ..
success, response = result_ok, result_val
success
_isRetry
.()
(messages, , effort, _keyAttempt)
, .. (response)
body = response.Body response
okDecode, decoded = (() HttpService:(body) )
okDecode decoded
(, (body):(, ))
_isRetry
.()
(messages, , effort, _keyAttempt)
, .. LOG_FILE
errMsg =
decoded.error
(decoded.error) ==
errMsg = (decoded.error.message decoded.error.code )
(decoded.error) ==
errMsg = decoded.error
decoded.message
errMsg = (decoded.message)
decoded.detail
errMsg = (decoded.detail)
errMsg
(, errMsg)
errMsg:(, , ) errMsg:(, , ) errMsg:(, , )
_keyAttempt < maxKeyAttempts
backoff = .( ^ (_keyAttempt - .(_keyAttempt / #API_KEYS)), )
.(backoff)
(messages, _isRetry, effort, _keyAttempt + )
KEY_RESET_TIME = ()
, .. ()
, .. errMsg
decoded.choices decoded.choices[]
msg = decoded.choices[].message
content = msg msg.content
usage = decoded.usage
content content ~=
content, , usage
reasoning = msg msg.reasoning_content
reasoning reasoning ~=
reasoning, , usage
(, (() HttpService:(decoded) ) HttpService:(decoded) (decoded))
,
decodedStr = (() HttpService:(decoded) ) HttpService:(decoded) (decoded)
(, decodedStr)
_isRetry
.()
(messages, , effort, _keyAttempt)
, .. LOG_FILE
PARENT_MAP = {
Workspace = Workspace,
StarterPack = StarterPack,
Lighting = Lighting,
ServerStorage = ServerStorage,
ReplicatedStorage = ReplicatedStorage,
StarterGui = StarterGui,
StarterPlayer = StarterPlayer,
StarterCharacterScripts = StarterCharacterScripts,
StarterPlayerScripts = StarterPlayerScripts,
CoreGui = CoreGui,
Players = Players,
}
(name, fallback)
(name) == PARENT_MAP[name]
PARENT_MAP[name]
fallback Workspace
(source, compileErr)
sysContent =
userContent = .. compileErr .. .. source ..
fixMessages
CURRENT_PROVIDER ==
fixMessages = { { role = , content = sysContent .. .. userContent } }
fixMessages = {
{ role = , content = sysContent },
{ role = , content = userContent }
}
fixBody = HttpService:({
model = MODEL_ID,
messages = fixMessages,
max_tokens = ,
temperature =
})
fixKey = ()
ok, response = (()
({
Url = API_URL,
Method = ,
Headers = {
[] = ,
[] = .. fixKey
},
Body = fixBody
})
)
ok response
ok2, data = (HttpService.JSONDecode, HttpService, response.Body)
ok2 data data.choices data.choices[]
fixed = data.choices[].message.content
fixed = fixed:(, ):(, ):(, ):(, )
fn, err2 = ( .. fixed)
fn fixed
, err2
MAX_HISTORY_TURNS =
conversationHistory = {}
CURRENT_REQUEST_TEXT =
(userMsg, summary)
.(conversationHistory, { user = userMsg, summary = summary })
#conversationHistory > MAX_HISTORY_TURNS
.(conversationHistory, )
()
msgs = {}
startIdx = .(, #conversationHistory - )
i = startIdx, #conversationHistory
turn = conversationHistory[i]
.(msgs, { role = , content = turn.user })
.(msgs, { role = , content = turn.summary })
msgs
SEARCH_SERVICES = { Workspace, StarterPack, Lighting, ServerStorage, ReplicatedStorage, StarterGui, StarterPlayer, StarterCharacterScripts, StarterPlayerScripts, Players, CoreGui }
_searchCacheTime =
SEARCH_CACHE_TTL =
()
_searchCache () - _searchCacheTime < SEARCH_CACHE_TTL
_searchCache
all = {}
idx =
_, svc (SEARCH_SERVICES)
svc (svc) ==
desc = svc:()
i = , #desc
idx = idx +
all[idx] = desc[i]
pg = player:()
pg
desc = pg:()
i = , #desc
idx = idx +
all[idx] = desc[i]
_searchCache = all
_searchCacheTime = ()
all
(search)
search search ==
search:(, , )
parts = {}
p search:() .(parts, p)
#parts >=
(start, startIdx)
current = start
i = startIdx, #parts
child = current:(parts[i])
child
child = current:(parts[i]:())
child
_, c (current:())
c.Name:() == parts[i]:()
child = c
child
searchNoSpaces = parts[i]:(, )
_, c (current:())
c.Name:():(, ) == searchNoSpaces:()
child = c
child
i == #parts child
current = child
svc = PARENT_MAP[parts[]]
svc
found = (svc, )
found found
svcKey, svcVal (PARENT_MAP)
svcKey:() == parts[]:()
found = (svcVal, )
found found
_, svc (SEARCH_SERVICES)
svc (svc) ==
root = svc:(parts[])
root
_, c (svc:())
c.Name:() == parts[]:()
root = c
root
found = (root, )
found found
root:() root.Character
found = (root.Character, )
found found
lowerSearch = search:()
exact, prefixMatch, substringMatch = , ,
_, obj (())
name = obj.Name:()
name == lowerSearch
exact = obj
prefixMatch name:(lowerSearch, , ) ==
prefixMatch = obj
substringMatch name:(lowerSearch, , )
substringMatch = obj
exact prefixMatch substringMatch
(search)
results = {}
search search == results
lowerSearch = search:()
cache = _searchCache
cache
_, obj (cache)
obj.Name:():(lowerSearch, , )
.(results, obj)
_, obj (())
obj.Name:():(lowerSearch, , )
.(results, obj)
results
(t)
(t) ~=
xs, xo, ys, yo = t[], t[], t[], t[]
xs == t.X ~= xs = t.X
xo == t.XOffset ~= xo = t.XOffset
ys == t.Y ~= ys = t.Y
yo == t.YOffset ~= yo = t.YOffset
xs == xs =
xo == xo =
ys == ys =
yo == yo =
.(xs, xo, ys, yo)
(inst, props)
(props) ~=
key, value (props)
(()
key == (value) ==
inst:()
u = (value)
u inst.Position = u
inst.Position = .(value[] , value[] , value[] )
key == (value) ==
inst:()
u = (value)
u inst.Size = u
inst:()
inst.Size = .(value[] , value[] , value[] )
inst.Size = .(value[] , value[] , value[] , value[] )
key == (value) ==
inst.Color = .(value[] , value[] , value[] )
key == (value) ==
inst.TextColor3 = .(value[] , value[] , value[] )
key == (value) ==
inst.BackgroundColor3 = .(value[] , value[] , value[] )
key == (value) ==
inst.BorderColor3 = .(value[] , value[] , value[] )
key == (value) ==
inst.BrickColor = BrickColor.(value)
key == (value) ==
inst.Material = .Material[value] inst.Material
key == (value) ==
inst:()
inst.Shape = .PartType[value] .PartType.Block
key == (value) ==
inst:()
inst.MeshId = value
inst:()
mesh = .()
mesh.MeshId = value
mesh.Parent = inst
key == (value) ==
inst:()
inst.TextureId = value
inst:()
mesh = inst:() .()
mesh.TextureId = value
mesh.Parent = inst
key == (value) ==
inst.Font = .Font[value] inst.Font
key == (value) ==
inst.TextXAlignment = .TextXAlignment[value] .TextXAlignment.Center
key == (value) ==
inst.TextYAlignment = .TextYAlignment[value] .TextYAlignment.Center
key == (value) ==
inst.AnchorPoint = .(value[] , value[] )
key == (value) ==
inst:() inst:()
inst.Image = value
key ==
inst.Anchored = value
key ==
inst.Transparency = value
key ==
inst.BackgroundTransparency = value
key ==
inst.CanCollide = value
key ==
inst.WalkSpeed = value
key ==
inst.PlatformStand = value
key ==
inst.Brightness = value
key ==
inst.ClockTime = value
key ==
inst.FogEnd = value
key ==
inst.FogStart = value
key ==
srcOk = (() inst.Source = .. (value) )
srcOk
srcOk2 = (() inst[key] = .. (value) )
inst[key] = value
)
(r)
(arrIdx, objKey, default)
v
(r) ==
v = r[arrIdx]
v == objKey v = r[objKey]
v = (v)
v == v = default
.(v)
minX = (, , -)
maxX = (, , )
minY = (, , -)
maxY = (, , )
minZ = (, , -)
maxZ = (, , )
minX > maxX minX, maxX = maxX, minX
minY > maxY minY, maxY = maxY, minY
minZ > maxZ minZ, maxZ = maxZ, minZ
minX, maxX, minY, maxY, minZ, maxZ
(name, parent)
name name == parent
existing = parent:(name)
existing existing:()
existing
folder = .()
folder.Name = name
folder.Parent = parent
folder
(className, name, source, targetParent, containerName)
containerName
targetParent = (containerName, targetParent)
host = .()
host.Name = name className
host.Value = source
host.Parent = targetParent
mock = {
Name = host.Name,
Parent = targetParent,
ClassName = className,
Source = source,
Value = source,
FindFirstChild = (self, childName)
targetParent:(childName)
,
WaitForChild = (self, childName, timeout)
targetParent:(childName, timeout)
,
IsA = (self, class)
className == class
,
Destroy = (self)
host:()
,
}
loadOk =
compileErr =
(()
fn, compErr = ( .. source)
fn
.(()
runtimeOk, runtimeErr = (fn, mock)
runtimeOk
( .. (name className), runtimeErr)
)
loadOk =
compileErr = compErr
( .. (name className), compErr)
)
mock, , , host, loadOk, compileErr
(toolInst, handleProps)
handle = toolInst:()
handle
handle = .()
handle.Name =
handle.Size = .(, , )
handle.Color = .(, , )
handle.Material = .Material.Metal
handle.Parent = toolInst
(handleProps) ==
(handle, handleProps)
handle.Anchored =
handle.CanCollide =
toolInst.RequiresHandle =
toolInst.Grip = toolInst.Grip .()
handle
(challengeType, targetCount, challengeName, title)
(t)
s = t
s = s:(, () challengeName )
s = s:(, () (targetCount) )
s = s:(, () (title ) )
s
challengeType ==
(se
detector.Touched:Connect(function(hit)
if completed then return end
local char = hit.Parent
local hum = char and char:FindFirstChild("Humanoid")
if hum and plr.Character == char then
completed = true
progressLabel.Text = "1/1"
progressLabel.TextColor3 = Color3.fromRGB(0, 255, 150)
titleLabel.Text = "✅ REACHED!"
end
end)
]])
elseif challengeType == "survive" then
return fill([[
local plr = game:GetService("Players").LocalPlayer
local gui = plr:WaitForChild("PlayerGui"):WaitForChild("{N}_GUI")
local progressLabel = gui.MainFrame.Progress
local titleLabel = gui.MainFrame.Title
local detector = script.Parent:WaitForChild("{N}_Detector")
local completed = false
local inside = false
local survivedSeconds = 0
local TARGET = {C}
detector.Touched:Connect(function(hit)
local char = hit.Parent
local hum = char and char:FindFirstChild("Humanoid")
if hum and plr.Character == char then inside = true end
end)
detector.TouchEnded:Connect(function(hit)
local char = hit.Parent
local hum = char and char:FindFirstChild("Humanoid")
if hum and plr.Character == char then inside = false end
end)
task.spawn(function()
while not completed do
task.wait(1)
if inside then
survivedSeconds = survivedSeconds + 1
progressLabel.Text = survivedSeconds .. "/" .. TARGET
if survivedSeconds >= TARGET then
completed = true
progressLabel.TextColor3 = Color3.fromRGB(0, 255, 150)
titleLabel.Text = "✅ SURVIVED!"
end
end
end
end)
]])
elseif challengeType == "kill" then
return fill([[
local plr = game:GetService("Players").LocalPlayer
local gui = plr:WaitForChild("PlayerGui"):WaitForChild("{N}_GUI")
local progressLabel = gui.MainFrame.Progress
local titleLabel = gui.MainFrame.Title
local detector = script.Parent:WaitForChild("{N}_Detector")
local completed = false
local killCount = 0
local connectedHumans = {}
local TARGET = {C}
detector.Touched:Connect(function(hit)
local char = hit.Parent
local hum = char and char:FindFirstChild("Humanoid")
if hum and not connectedHumans[hum] then
connectedHumans[hum] = true
hum.Died:Connect(function()
if completed then return end
if not char.Parent then
task.wait(0.1)
killCount = killCount + 1
progressLabel.Text = killCount .. "/" .. TARGET
if killCount >= TARGET then
completed = true
progressLabel.TextColor3 = Color3.fromRGB(0, 255, 150)
titleLabel.Text = "✅ COMPLETE!"
end
end
end)
end
end)
]])
elseif challengeType == "collect" then
return fill([[
local plr = game:GetService("Players").LocalPlayer
local gui = plr:WaitForChild("PlayerGui"):WaitForChild("{N}_GUI")
local progressLabel = gui.MainFrame.Progress
local titleLabel = gui.MainFrame.Title
local detector = script.Parent:WaitForChild("{N}_Detector")
local completed = false
local collected = 0
local TARGET = {C}
detector.Touched:Connect(function(hit)
if completed then return end
if hit:GetAttribute("Collectible") or hit.Name:find("Collect") then
collected = collected + 1
progressLabel.Text = collected .. "/" .. TARGET
pcall(function() hit:Destroy() end)
if collected >= TARGET then
completed = true
progressLabel.TextColor3 = Color3.fromRGB(0, 255, 150)
titleLabel.Text = "✅ COLLECTED!"
end
end
end)
]])
end
return "task.wait(9e9)"
end
local function setCollision(inst, enabled)
if not inst or not inst:IsA("BasePart") then return end
inst.CanCollide = enabled
task.spawn(function()
task.wait(0)
if inst and inst.Parent then
inst.CanCollide = enabled
end
end)
task.spawn(function()
task.wait(0.15)
if inst and inst.Parent then
inst.CanCollide = enabled
end
end)
end
local function executeAction(action)
local kind = action.type
if kind == "create_instance" then
local className = action.className
if not className then return false, "❌ Missing className" end
local ok, inst = pcall(function() return Instance.new(className) end)
if not ok or not inst then
return false, "❌ Failed to create " .. tostring(className)
end
inst.Name = action.name or className
applyProperties(inst, action.properties)
-- set Anchored/CanCollide BEFORE parent (belt)
if inst:IsA("BasePart") and className ~= "Tool" then
inst.Anchored = true
setCollision(inst, true)
end
if className == "Tool" then
ensureToolHandle(inst, action.handleProperties)
end
local targetParent = resolveParent(action.parent, Workspace)
-- AUTO-CORRECT: GUI objects in Workspace belong in StarterGui
local GUI_CLASSES = { ScreenGui = true, Frame = true, TextLabel = true, TextButton = true,
ImageLabel = true, ImageButton = true, ScrollingFrame = true, BillboardGui = true, ViewportFrame = true }
if GUI_CLASSES[className] and targetParent == Workspace then
targetParent = StarterGui
end
-- AUTO-CORRECT: Parts don't belong in GUI services
local GUI_SERVICES = { StarterGui = true, StarterPlayerScripts = true, StarterCharacterScripts = true }
if inst:IsA("BasePart") and GUI_SERVICES[targetParent.Name] then
targetParent = Workspace
end
-- AUTO-CORRECT: Decal/Texture/SurfaceGui need a BasePart parent, not Workspace
local ATTACHMENT_CLASSES = { Decal = true, Texture = true, SurfaceGui = true }
if ATTACHMENT_CLASSES[className] and targetParent == Workspace then
for i = #_batchCreated, 1, -1 do
local prev = _batchCreated[i]
if type(prev) ~= "table" and prev:IsA("BasePart") then
targetParent = prev
break
end
end
end
-- AUTO-CORRECT: StringValue/ModuleScript with code belong in ServerStorage, not Workspace
local CODE_CLASSES = { StringValue = true, ModuleScript = true }
if CODE_CLASSES[className] and targetParent == Workspace then
targetParent = ServerStorage
end
if action.containerName then
targetParent = getOrCreateContainer(action.containerName, targetParent)
end
inst.Parent = targetParent
-- force Anchored/CanCollide AFTER parent (suspenders — some executors lose pre-parent writes)
if inst:IsA("BasePart") then
if className == "Tool" then
local props = type(action.properties) == "table" and action.properties or {}
if props.Anchored == nil then inst.Anchored = false end
if props.CanCollide == nil then setCollision(inst, false) end
else
inst.Anchored = true
setCollision(inst, true)
end
end
table.insert(_batchCreated, inst)
updateSceneState(action, true)
if className == "Camera" and action.setAsCurrentCamera then
Workspace.CurrentCamera = inst
local hrp = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
if hrp then
local offset = action.cameraOffset or {0, 2, -10}
inst.CFrame = CFrame.new(
hrp.Position + Vector3.new(0, 2, 0),
hrp.Position + Vector3.new(offset[1] or 0, offset[2] or 2, offset[3] or -10)
)
end
end
_lastClickInstance = inst
return true, "✅ Created " .. inst.Name .. " (" .. className .. ")"
elseif kind == "create_multiple" then
local className = action.className or "Part"
local count = math.clamp(tonumber(action.count) or 1, 1, 200)
local parent = resolveParent(action.parent, Workspace)
-- AUTO-CORRECT: bulk create is for Parts, redirect from GUI services
local GUI_SERVICES = { StarterGui = true, StarterPlayerScripts = true, StarterCharacterScripts = true }
if GUI_SERVICES[parent.Name] then
parent = Workspace
end
local container = getOrCreateContainer(action.containerName or (className .. "_Batch"), parent)
local created = 0
for i = 1, count do
pcall(function()
local ok, inst = pcall(function() return Instance.new(className) end)
if ok and inst then
inst.Name = (action.namePrefix or className) .. "_" .. i
applyProperties(inst, action.properties)
-- belt: set before parent
if inst:IsA("BasePart") and className ~= "Tool" then
inst.Anchored = true
setCollision(inst, true)
end
if className == "Tool" then
ensureToolHandle(inst, action.handleProperties)
end
if action.randomizePosition then
local minX, maxX, minY, maxY, minZ, maxZ = safeRandomRange(action.randomRange)
if action.gridSpacing then
local spacing = math.max(1, tonumber(action.gridSpacing) or 4)
local maxAttempts = 30
local placed = _batchPlacedGrid or {}
_batchPlacedGrid = placed
local found = false
for _ = 1, maxAttempts do
local gx = math.floor(math.random(minX, maxX) / spacing) * spacing
local gy = math.floor(math.random(minY, maxY) / spacing) * spacing
local gz = math.floor(math.random(minZ, maxZ) / spacing) * spacing
local key = gx..","..gy..","..gz
if not placed[key] then
placed[key] = true
inst.Position = Vector3.new(gx, gy, gz)
found = true
break
end
end
if not found then
inst.Position = Vector3.new(math.random(minX, maxX), math.random(minY, maxY), math.random(minZ, maxZ))
end
else
inst.Position = Vector3.new(
math.random(minX, maxX),
math.random(minY, maxY),
math.random(minZ, maxZ)
)
end
-- rejection sampling: if rejectionRadius set, check distance to existing BaseParts in container
if action.rejectionRadius and container then
local radius = tonumber(action.rejectionRadius) or 4
local rejectAttempts = 30
for _ = 1, rejectAttempts do
local tooClose = false
for _, existing in ipairs(container:GetDescendants()) do
if existing:IsA("BasePart") and existing ~= inst then
local dist = (inst.Position - existing.Position).Magnitude
if dist < radius then
tooClose = true
break
end
end
end
if tooClose then
inst.Position = Vector3.new(
math.random(minX, maxX),
math.random(minY, maxY),
math.random(minZ, maxZ)
)
else
break
end
end
end
end
inst.Parent = container
-- force Anchored/CanCollide AFTER parenting for physics engine
if inst:IsA("BasePart") then
if className == "Tool" then
local props = type(action.properties) == "table" and action.properties or {}
if props.Anchored == nil then inst.Anchored = false end
if props.CanCollide == nil then setCollision(inst, false) end
else
inst.Anchored = true
setCollision(inst, true)
end
end
table.insert(_batchCreated, inst)
created += 1
end
end)
end
_batchPlacedGrid = nil
updateSceneState(action, true)
_lastClickInstance = container
return true, "✅ Created " .. created .. " items inside " .. container.Name
elseif kind == "set_property" then
local target = action.target and findByName(action.target)
if not target then return false, "❌ Not found: " .. tostring(action.target) end
applyProperties(target, action.properties)
updateSceneState(action, true)
return true, "✅ Updated properties: " .. target.Name
elseif kind == "delete" then
local target = action.target and findByName(action.target)
if not target then return false, "❌ Not found: " .. tostring(action.target) end
local n = target.Name
table.insert(_batchCreated, { _markedForDestroy = true, _ref = target, _name = n })
target:Destroy()
updateSceneState(action, true)
return true, "🗑️ Deleted: " .. n
elseif kind == "clone" then
local target = action.target and findByName(action.target)
if not target then return false, "❌ Not found: " .. tostring(action.target) end
local clone = target:Clone()
clone.Name = action.newName or (target.Name .. "_clone")
clone.Parent = target.Parent
table.insert(_batchCreated, clone)
updateSceneState(action, true)
return true, "📋 Cloned: " .. target.Name .. " → " .. clone.Name
elseif kind == "rename" then
local target = action.target and findByName(action.target)
if not target then return false, "❌ Not found: " .. tostring(action.target) end
local old = target.Name
target.Name = action.newName or old
updateSceneState(action, true)
return true, "✏️ Renamed: " .. old .. " → " .. target.Name
elseif kind == "move" then
local target = action.target and findByName(action.target)
if not target then return false, "❌ Not found: " .. tostring(action.target) end
if target:IsA("BasePart") and action.position then
target.Position = Vector3.new(action.position[1] or 0, action.position[2] or 0, action.position[3] or 0)
updateSceneState(action, true)
return true, "🚚 Moved: " .. target.Name
elseif action.parent then
target.Parent = resolveParent(action.parent, target.Parent)
updateSceneState(action, true)
return true, "🚚 Moved: " .. target.Name .. " to " .. tostring(action.parent)
end
return false, "❌ Insufficient move data"
elseif kind == "set_lighting" then
if type(action.properties) == "table" and action.properties.FogEnd ~= nil then
local fogKeywords = { "fog", "backrooms", "horror", "mist", "haze", "creepy" }
local mentioned = false
local lowerQ = (CURRENT_REQUEST_TEXT or ""):lower()
for _, kw in ipairs(fogKeywords) do
if lowerQ:find(kw, 1, true) then
mentioned = true
break
end
end
if not mentioned then
local fe = tonumber(action.properties.FogEnd)
if fe and fe < 300 then
action.properties.FogEnd = nil -- skip fog since user did not ask for it
end
end
end
applyProperties(Lighting, action.properties)
updateSceneState(action, true)
return true, "💡 Lighting updated"
elseif kind == "set_character" then
local char = player.Character
if not char or not char:FindFirstChild("Humanoid") then
return false, "❌ No character available"
end
applyProperties(char.Humanoid, action.properties)
if char:FindFirstChild("HumanoidRootPart") and action.transparency ~= nil then
char.HumanoidRootPart.Transparency = action.transparency
end
updateSceneState(action, true)
return true, "✅ Character state updated"
elseif kind == "create_compound" then
local parts = action.parts
if not parts or type(parts) ~= "table" or #parts == 0 then
return false, "❌ Missing parts array for compound"
end
local model = Instance.new("Model")
model.Name = action.name or "Compound"
local createdParts = {}
local primaryIdx = action.primaryPartIndex or 1
local isToolCompound = action.makeTool or action.className == "Tool"
-- parent model FIRST so parts enter DataModel immediately
local targetParent = resolveParent(action.parent, Workspace)
if action.containerName then
targetParent = getOrCreateContainer(action.containerName, targetParent)
end
model.Parent = targetParent
for i, partData in ipairs(parts) do
pcall(function()
local cn = partData.className or "Part"
local ok, inst = pcall(function() return Instance.new(cn) end)
if ok and inst then
inst.Name = partData.name or ("Part_" .. i)
applyProperties(inst, partData)
-- belt: set before parent
if inst:IsA("BasePart") and not isToolCompound then
inst.Anchored = true
setCollision(inst, true)
end
inst.Parent = model
table.insert(createdParts, inst)
end
end)
end
if #createdParts == 0 then
model:Destroy()
return false, "❌ Failed to create any compound part"
end
-- force Anchored/CanCollide AFTER all parts are in the DataModel
for _, part in ipairs(createdParts) do
if part:IsA("BasePart") then
if isToolCompound then
part.Anchored = false
setCollision(part, false)
else
part.Anchored = true
setCollision(part, true)
end
end
end
-- ⚠️ فحص تصادم/تراكب بين أجزاء نفس الـ compound: لو الـ AI أخطأ بحساب
-- position وحط جزئين (مثلاً النصل والمقبض) بنفس النقطة تقريباً، الشكل
-- النهائي يطلع كتلة واحدة مشوهة بدل سيف/برج واضح المعالم. نتحقق من كل
-- زوج أجزاء، ولو متراكبين فعلياً (مسافة أقل من نص متوسط حجمهم)، نزيح
-- الجزء الثاني قليلاً على المحور Y حتى يصير الشكل مفهوم بصرياً.
local overlapFixCount = 0
for i = 1, #createdParts do
for j = i + 1, #createdParts do
local a, b = createdParts[i], createdParts[j]
local okCheck, isOverlap = pcall(function()
local dist = (a.Position - b.Position).Magnitude
local avgSize = ((a.Size.X + a.Size.Y + a.Size.Z) + (b.Size.X + b.Size.Y + b.Size.Z)) / 6
return dist < (avgSize * 0.15) -- متراكبين فعلياً، مو بس قريبين بشكل طبيعي
end)
if okCheck and isOverlap then
pcall(function()
b.Position = b.Position + Vector3.new(0, (b.Size.Y * 0.6) + 0.1, 0)
end)
overlapFixCount = overlapFixCount + 1
end
end
end
local primary = createdParts[primaryIdx]
for i, part in ipairs(createdParts) do
if part ~= primary then
local weld = Instance.new("WeldConstraint")
weld.Name = ("Weld_%d_%d"):format(primaryIdx, i)
weld.Part0 = primary
weld.Part1 = part
weld.Parent = part
end
end
model.PrimaryPart = primary
if action.makeTool or action.className == "Tool" then
local tool = Instance.new("Tool")
tool.Name = action.name or model.Name
local h = Instance.new("Part")
h.Name = "Handle"
h.Size = Vector3.new(0.6, 2.5, 0.6)
h.Anchored = false
h.CanCollide = false
h.Parent = tool
for _, child in ipairs(model:GetChildren()) do
child.Parent = tool
end
model:Destroy()
model = tool
tool.Parent = targetParent -- re-parent since model was destroyed
tool.RequiresHandle = true
tool.Grip = CFrame.new()
end
table.insert(_batchCreated, model)
updateSceneState(action, true)
_lastClickInstance = model
local overlapMsg = overlapFixCount > 0 and (" (auto-fixed " .. overlapFixCount .. " overlapping part(s))") or ""
return true, "✅ Created compound: " .. model.Name .. overlapMsg
elseif kind == "create_enclosure" then
-- 🧮 GEOMETRY HELPER: يبني أرضية + جدران (وحاجز اختياري) من وصف منطقي
-- بسيط فقط (مركز، عرض، عمق، ارتفاع) — كل حساب المواقع/المسافات يصير
-- هنا بالكود (فوري، صفر توكنز)، مو من الـ AI. هذا يلغي السبب الجذري
-- وراء "Impure JSON" بطلبات فيها جدران/حواجز متعددة المواقع، لأن الموديل
-- ما يحتاج يحسب إحداثيات يدوياً بصوت عالي قبل الرد.
local centerX = tonumber(action.centerX) or 0
local centerY = tonumber(action.centerY) or 0
local centerZ = tonumber(action.centerZ) or 0
local width = math.max(2, tonumber(action.width) or 20) -- X axis
local depth = math.max(2, tonumber(action.depth) or 20) -- Z axis
local wallHeight = math.max(1, tonumber(action.wallHeight) or 4)
local floorThickness = 0.5
local wallThickness = 0.5
local hasFloor = action.floor ~= false
local openSides = {} -- e.g. {"North"} to leave an entrance gap
if type(action.openSides) == "table" then
for _, s in ipairs(action.openSides) do openSides[s] = true end
end
local color = action.color or {150,150,150}
local material = action.material or "SmoothPlastic"
local barrier = action.barrier -- bool: add an invisible solid barrier covering the whole footprint at wall height (used for "locked until unlocked" platforms)
local model = Instance.new("Model")
model.Name = action.name or "Enclosure"
local targetParent = resolveParent(action.parent, Workspace)
if action.containerName then
targetParent = getOrCreateContainer(action.containerName, targetParent)
end
model.Parent = targetParent
local function mkPart(pname, size, pos)
local p = Instance.new("Part")
p.Name = pname
p.Size = Vector3.new(size[1], size[2], size[3])
p.Position = Vector3.new(pos[1], pos[2], pos[3])
pcall(function() p.Color = Color3.fromRGB(color[1] or 150, color[2] or 150, color[3] or 150) end)
pcall(function() p.Material = Enum.Material[material] or Enum.Material.SmoothPlastic end)
p.Anchored = true
setCollision(p, true)
p.Parent = model
return p
end
if hasFloor then
mkPart("Floor", {width, floorThickness, depth}, {centerX, centerY, centerZ})
end
local floorTop = centerY + floorThickness / 2
local wallCenterY = floorTop + wallHeight / 2
if not openSides["North"] then
mkPart("WallNorth", {width, wallHeight, wallThickness}, {centerX, wallCenterY, centerZ - depth/2})
end
if not openSides["South"] then
mkPart("WallSouth", {width, wallHeight, wallThickness}, {centerX, wallCenterY, centerZ + depth/2})
end
if not openSides["East"] then
mkPart("WallEast", {wallThickness, wallHeight, depth}, {centerX + width/2, wallCenterY, centerZ})
end
if not openSides["West"] then
mkPart("WallWest", {wallThickness, wallHeight, depth}, {centerX - width/2, wallCenterY, centerZ})
end
if barrier then
local b = mkPart("Barrier", {width, wallHeight, depth}, {centerX, wallCenterY, centerZ})
b.Transparency = 1
setCollision(b, true)
b.Name = "Barrier"
end
table.insert(_batchCreated, model)
updateSceneState(action, true)
_lastClickInstance = model
return true, "✅ Created enclosure: " .. model.Name .. " (" .. (hasFloor and "floor+" or "") .. "walls" .. (barrier and "+barrier" or "") .. ")"
elseif kind == "create_challenge_system" then
local challengeType = (action.challengeType or "reach"):lower()
local validTypes = { reach = true, survive = true, kill = true, collect = true }
if not validTypes[challengeType] then
return false, "❌ Invalid challengeType '" .. tostring(action.challengeType) .. "'. Valid: reach, survive, kill, collect"
end
local targetCount = math.max(1, tonumber(action.targetCount) or 10)
local title = action.title or ("Challenge: " .. challengeType)
local challengeName = action.name or "Challenge"
local centerX = tonumber(action.centerX) or 0
local centerY = tonumber(action.centerY) or 5
local centerZ = tonumber(action.centerZ) or 0
local width = math.max(2, tonumber(action.width) or 20)
local depth = math.max(2, tonumber(action.depth) or 20)
local height = math.max(2, tonumber(action.height) or 6)
local visibleFloor = action.visibleFloor ~= false
local zoneModel = Instance.new("Model")
zoneModel.Name = challengeName .. "_Zone"
local zoneParent = resolveParent(action.parent, Workspace)
if action.containerName then
zoneParent = getOrCreateContainer(action.containerName, zoneParent)
end
zoneModel.Parent = zoneParent
if visibleFloor then
local floor = Instance.new("Part")
floor.Name = challengeName .. "_Floor"
floor.Size = Vector3.new(width, 0.5, depth)
floor.Position = Vector3.new(centerX, centerY - 0.25, centerZ)
floor.Anchored = true
setCollision(floor, true)
floor.BrickColor = BrickColor.new("Bright blue")
floor.Material = Enum.Material.SmoothPlastic
floor.Transparency = 0.3
floor.Parent = zoneModel
end
-- optional walls around zone
local wallOpenSides = {}
if type(action.walls) == "table" then
for _, s in ipairs(action.walls) do wallOpenSides[s] = true end
end
if action.walls == true or type(action.walls) == "table" then
local wallH = math.max(1, tonumber(action.wallHeight) or height)
local wallT = 0.5
local floorTop = centerY + 0.25
local wallCY = floorTop + wallH / 2
local wallColor = action.wallColor or {100, 100, 255}
local wallMat = action.wallMaterial or "SmoothPlastic"
local function mkWall(n, sz, pos)
local w = Instance.new("Part")
w.Name = challengeName .. "_" .. n
w.Size = Vector3.new(sz[1], sz[2], sz[3])
w.Position = Vector3.new(pos[1], pos[2], pos[3])
w.Anchored = true
setCollision(w, true)
pcall(function() w.Color = Color3.fromRGB(wallColor[1] or 100, wallColor[2] or 100, wallColor[3] or 255) end)
pcall(function() w.Material = Enum.Material[wallMat] or Enum.Material.SmoothPlastic end)
w.Transparency = 0.2
w.Parent = zoneModel
end
if not wallOpenSides["North"] then mkWall("WallNorth", {width, wallH, wallT}, {centerX, wallCY, centerZ - depth/2}) end
if not wallOpenSides["South"] then mkWall("WallSouth", {width, wallH, wallT}, {centerX, wallCY, centerZ + depth/2}) end
if not wallOpenSides["East"] then mkWall("WallEast", {wallT, wallH, depth}, {centerX + width/2, wallCY, centerZ}) end
if not wallOpenSides["West"] then mkWall("WallWest", {wallT, wallH, depth}, {centerX - width/2, wallCY, centerZ}) end
end
local detector = Instance.new("Part")
detector.Name = challengeName .. "_Detector"
detector.Size = Vector3.new(width, height, depth)
detector.Position = Vector3.new(centerX, centerY + height / 2 - 0.25, centerZ)
detector.Anchored = true
detector.CanCollide = false
detector.Transparency = 1
detector.Parent = zoneModel
table.insert(_batchCreated, zoneModel)
local guiPosition = (action.guiPosition or "top-right"):lower()
local guiPosMap = {
["top-right"] = UDim2.new(1, -270, 0, 10),
["top-left"] = UDim2.new(0, 20, 0, 10),
["bottom-right"] = UDim2.new(1, -270, 1, -80),
["bottom-left"] = UDim2.new(0, 20, 1, -80),
["center"] = UDim2.new(0.5, -125, 0.5, -30),
}
local guiSize = type(action.guiSize) == "table" and action.guiSize or {250, 60}
local gw = math.max(100, tonumber(guiSize[1]) or 250)
local gh = math.max(40, tonumber(guiSize[2]) or 60)
local gui = Instance.new("ScreenGui")
gui.Name = challengeName .. "_GUI"
gui.ResetOnSpawn = false
gui.Parent = StarterGui
local frame = Instance.new("Frame")
frame.Name = "MainFrame"
frame.Size = UDim2.new(0, gw, 0, gh)
frame.Position = guiPosMap[guiPosition] or guiPosMap["top-right"]
frame.BackgroundColor3 = Color3.fromRGB(10, 10, 20)
frame.BackgroundTransparency = 0.3
frame.BorderSizePixel = 0
frame.Parent = gui
local uic = Instance.new("UICorner")
uic.CornerRadius = UDim.new(0, 8)
uic.Parent = frame
local titleLabel = Instance.new("TextLabel")
titleLabel.Name = "Title"
titleLabel.Size = UDim2.new(1, -20, 0, 22)
titleLabel.Position = UDim2.new(0, 10, 0, 4)
titleLabel.BackgroundTransparency = 1
titleLabel.Text = title
titleLabel.TextColor3 = Color3.fromRGB(255, 255, 255)
titleLabel.TextSize = 14
titleLabel.Font = Enum.Font.GothamBold
titleLabel.TextXAlignment = Enum.TextXAlignment.Left
titleLabel.Parent = frame
local progressLabel = Instance.new("TextLabel")
progressLabel.Name = "Progress"
progressLabel.Size = UDim2.new(1, -20, 0, 26)
progressLabel.Position = UDim2.new(0, 10, 0, 28)
progressLabel.BackgroundTransparency = 1
progressLabel.Text = "0/" .. targetCount
progressLabel.TextColor3 = Color3.fromRGB(0, 200, 255)
progressLabel.TextSize = 20
progressLabel.Font = Enum.Font.GothamBlack
progressLabel.TextXAlignment = Enum.TextXAlignment.Left
progressLabel.Parent = frame
table.insert(_batchCreated, gui)
local scriptSrc = generateChallengeScript(challengeType, targetCount, challengeName, title)
local host = Instance.new("StringValue")
host.Name = challengeName .. "_Script"
host.Value = "--from skill-Ai\n" .. scriptSrc
host.Parent = zoneModel
local mock = {
Name = host.Name,
Parent = zoneModel,
ClassName = "LocalScript",
Source = scriptSrc,
Value = scriptSrc,
FindFirstChild = function(self, childName)
local ok, r = pcall(function() return zoneModel:FindFirstChild(childName) end)
return ok and r
end,
WaitForChild = function(self, childName, timeout)
local ok, r = pcall(function() return zoneModel:WaitForChild(childName, timeout) end)
return ok and r
end,
FindFirstChildOfClass = function(self, cls)
local ok, r = pcall(function() return zoneModel:FindFirstChildOfClass(cls) end)
return ok and r
end,
Destroy = function(self)
pcall(function() host:Destroy() end)
pcall(function() gui:Destroy() end)
pcall(function() zoneModel:Destroy() end)
for idx, entry in ipairs(_autoRunScripts) do
if entry.inst == self then
table.remove(_autoRunScripts, idx)
break
end
end
end,
IsA = function(self, cls) return cls == "Script" or cls == "LocalScript" end,
}
local fn, compileErr = loadstring("local script = ...\n" .. scriptSrc)
local loadOk = false
local execErr = nil
if fn then
local ok, e = pcall(fn, mock)
if ok then
loadOk = true
else
execErr = tostring(e)
end
else
execErr = tostring(compileErr)
end
if loadOk then
table.insert(_autoRunScripts, { name = host.Name, source = scriptSrc, inst = mock })
if #_autoRunScripts > AUTORUN_MAX then
table.remove(_autoRunScripts, 1)
end
end
updateSceneState(action, true)
local fx = visibleFloor and "floor+" or ""
local msg = "✅ Created challenge: " .. challengeName .. " (" .. challengeType .. " " .. targetCount .. ", " .. fx .. "zone)"
if loadOk then
msg = msg .. " | Executed"
elseif execErr then
msg = msg .. " | ⚠️ " .. execErr
end
return true, msg
elseif kind == "create_script" then
local className = action.className or "Script"
if className ~= "Script" and className ~= "LocalScript" then
return false, "❌ create_script requires className = Script or LocalScript"
end
local source = "--from skill-Ai\n"
if action.source then
source = source .. action.source
end
-- auto-fix syntax errors before creating
local fn_check, err_check = loadstring("local script = ...\n" .. source)
if not fn_check then
local rawCode = action.source or source
local fixedCode, fixErr = fixLuaCode(rawCode, err_check)
if fixedCode then
source = "--from skill-Ai\n" .. fixedCode
end
end
local targetParent = resolveParent(action.parent, nil)
if not targetParent and type(action.parent) == "string" then
for _, createdInst in ipairs(_batchCreated) do
if type(createdInst) ~= "table" and createdInst.Name == action.parent then
targetParent = createdInst
break
end
end
if not targetParent then
targetParent = findByName(action.parent)
end
end
targetParent = targetParent or Workspace
-- SAFETY: reject per-player PlayerGui as parent (scripts there run once per player, confusing)
if targetParent and targetParent:IsA("PlayerGui") and targetParent.Parent and targetParent.Parent:IsA("Player") then
targetParent = StarterPlayerScripts or Workspace
end
-- AUTO-CORRECT: Scripts in Workspace never run — redirect
if targetParent == Workspace then
if className == "LocalScript" then
targetParent = StarterPlayerScripts or StarterPlayer or Workspace
else
targetParent = ServerStorage or Workspace
end
end
local inst, err, sourceWorked, host, loadOk, compileErr = makeScriptWithBackup(
className, action.name, source, targetParent, action.containerName
)
if not inst then
return false, "❌ " .. err
end
table.insert(_batchCreated, host)
updateSceneState(action, true)
_lastClickInstance = host
-- register for respawn re-execution so effects persist across deaths
if loadOk then
table.insert(_autoRunScripts, { name = inst.Name, source = source, inst = inst })
if #_autoRunScripts > AUTORUN_MAX then
table.remove(_autoRunScripts, 1)
end
end
local parts = {}
table.insert(parts, "✅ Created: " .. inst.Name .. " (" .. className .. ")")
if loadOk then
table.insert(parts, "Executed in-game")
elseif compileErr then
table.insert(parts, "⚠️ " .. compileErr)
end
local msg = table.concat(parts, " | ")
msg = msg .. "\n━━━━━━━━━━━━━━━━━━\n" .. source .. "\n━━━━━━━━━━━━━━━━━━"
return true, msg
elseif kind == "undo" then
if #undoStack == 0 then
return false, "⚠️ Nothing to undo."
end
local lastBatch = table.remove(undoStack)
local removed = 0
for _, entry in ipairs(lastBatch.instances) do
if type(entry) == "table" and entry._markedForDestroy then
-- was a delete action, cannot restore (object already destroyed)
else
local inst = entry
if inst and inst.Parent then
inst:Destroy()
removed += 1
end
end
end
return true, "↩️ Undone: " .. lastBatch.summary .. " (removed " .. removed .. " items)"
elseif kind == "create_plugin" then
local pluginName = action.name or "Plugin"
local modules = action.modules
if not modules or type(modules) ~= "table" or #modules == 0 then
return false, "❌ create_plugin requires a 'modules' array with at least one {name, source}"
end
local icon = action.icon or "🧩"
local btnColor = action.buttonColor or {30, 30, 40}
local targetParent = resolveParent(action.parent, CoreGui)
-- Plugin toolbar GUI
local toolbar = Instance.new("ScreenGui")
toolbar.Name = pluginName .. "_Toolbar"
toolbar.ResetOnSpawn = false
toolbar.Parent = targetParent
local bg = Instance.new("Frame")
bg.Name = "Toolbar"
bg.Size = UDim2.new(0, #modules * 42 + 12, 0, 38)
bg.Position = UDim2.new(0.5, -(#modules * 42 + 12) / 2, 0, 80)
bg.BackgroundColor3 = Color3.fromRGB(btnColor[1] or 30, btnColor[2] or 30, btnColor[3] or 40)
bg.BackgroundTransparency = 0.2
bg.BorderSizePixel = 0
bg.Parent = toolbar
corner(bg, 8)
stroke(bg, Color3.fromRGB(0, 200, 255), 1, 0.3)
makeDraggable(bg)
local titleLbl = Instance.new("TextLabel")
titleLbl.Size = UDim2.new(0, 22, 1, 0)
titleLbl.Position = UDim2.new(0, 4, 0, 0)
titleLbl.BackgroundTransparency = 1
titleLbl.Text = icon
titleLbl.TextColor3 = Color3.fromRGB(0, 200, 255)
titleLbl.TextSize = 16
titleLbl.Font = Enum.Font.GothamBold
titleLbl.Parent = bg
-- Store module scripts
local moduleFolder = Instance.new("Folder")
moduleFolder.Name = pluginName .. "_Modules"
moduleFolder.Parent = targetParent
local moduleStates = {} -- track on/off per module
local activeConnections = {} -- track running threads
for idx, mod in ipairs(modules) do
if type(mod) == "table" and mod.name and mod.source then
-- Store source in StringValue
local sv = Instance.new("StringValue")
sv.Name = mod.name
sv.Value = mod.source
sv.Parent = moduleFolder
-- Button for this module
local btn = Instance.new("TextButton")
btn.Size = UDim2.new(0, 36, 0, 28)
btn.Position = UDim2.new(0, 30 + (idx - 1) * 42, 0.5, -14)
btn.BackgroundColor3 = Color3.fromRGB(50, 50, 60)
btn.Text = mod.name:sub(1, 2):upper()
btn.TextColor3 = Color3.fromRGB(180, 180, 190)
btn.TextSize = 11
btn.Font = Enum.Font.GothamBold
btn.Parent = bg
corner(btn, 6)
local moduleOn = false
local thread = nil
btn.MouseButton1Click:Connect(function()
moduleOn = not moduleOn
if moduleOn then
btn.BackgroundColor3 = Color3.fromRGB(0, 200, 100)
btn.TextColor3 = Color3.new(1, 1, 1)
-- Execute the module script
local fn, err = loadstring("local pluginModule = ...\n" .. mod.source)
if fn then
local ok, runErr = pcall(fn, { Name = mod.name, Source = mod.source, Parent = moduleFolder })
if not ok then
addMessage("⚠️ Plugin module '" .. mod.name .. "' error: " .. tostring(runErr), false, true)
end
else
addMessage("⚠️ Plugin module '" .. mod.name .. "' compile error: " .. tostring(err), false, true)
end
addMessage("✅ Plugin module: " .. mod.name .. " ON", false)
else
btn.BackgroundColor3 = Color3.fromRGB(50, 50, 60)
btn.TextColor3 = Color3.fromRGB(180, 180, 190)
addMessage("⏹️ Plugin module: " .. mod.name .. " OFF", false)
end
end)
moduleStates[mod.name] = false
end
end
table.insert(_batchCreated, toolbar)
table.insert(_batchCreated, moduleFolder)
updateSceneState(action, true)
_lastClickInstance = toolbar
return true, "✅ Created plugin: " .. pluginName .. " with " .. #modules .. " module(s)"
elseif kind == "create_particle" then
local target = resolveTarget(action.target)
if not target then return false, "❌ create_particle: target '" .. tostring(action.target) .. "' not found" end
local pe = Instance.new("ParticleEmitter")
pe.Name = (action.name or "ParticleEmitter")
pe.Texture = action.texture or "rbxasset://textures/particles/sparkles_main.dds"
pe.Rate = action.rate or 20
pe.Speed = action.speed and NumberRange.new(action.speed[1] or 5, action.speed[2] or 10) or NumberRange.new(5, 10)
pe.Lifetime = action.lifetime and NumberRange.new(action.lifetime[1] or 2, action.lifetime[2] or 4) or NumberRange.new(2, 4)
if action.size then
local s = action.size
pe.Size = NumberSequence.new(NumberSequenceKeypoint.new(0, s[1] or s[1]==nil and 1 or s[1]), NumberSequenceKeypoint.new(1, s[#s] or s[2] or 0.5))
else
pe.Size = NumberSequence.new(1, 0.5)
end
if action.color then
local cs = {}
for i, c in ipairs(action.color) do
table.insert(cs, ColorSequenceKeypoint.new((i-1)/(#action.color-1), Color3.fromRGB(c[1] or 255, c[2] or 255, c[3] or 255)))
end
pe.Color = ColorSequence.new(cs)
else
pe.Color = ColorSequence.new(Color3.fromRGB(255, 200, 50))
end
pe.Transparency = action.transparency and NumberSequence.new(action.transparency[1] or 0, action.transparency[2] or 1) or NumberSequence.new(0, 1)
pe.Rotation = action.rotation and NumberRange.new(action.rotation[1] or 0, action.rotation[2] or 360) or NumberRange.new(0, 360)
if action.spread then pe.SpreadAngle = Vector2.new(action.spread, action.spread) end
pe.Enabled = if action.enabled ~= nil then action.enabled else true
pe.Parent = target
table.insert(_batchCreated, pe)
updateSceneState(action, true)
_lastClickInstance = pe
return true, "✨ Particle emitter added to " .. target.Name
elseif kind == "create_light" then
local target = resolveTarget(action.target)
if not target then return false, "❌ create_light: target '" .. tostring(action.target) .. "' not found" end
local lightType = action.lightType or "PointLight"
local light = Instance.new(lightType)
light.Name = (action.name or lightType)
light.Color = action.color and Color3.fromRGB(action.color[1] or 255, action.color[2] or 255, action.color[3] or 255) or Color3.new(1, 1, 1)
light.Brightness = action.brightness or (lightType == "SpotLight" and 2 or 1)
if light:IsA("PointLight") or light:IsA("SpotLight") then
light.Range = action.range or 16
end
if light:IsA("SpotLight") then
light.Angle = action.angle or 90
light.Face = action.Face and Enum.NormalId[action.Face] or Enum.NormalId.Front
end
if light:IsA("SurfaceLight") then
light.Face = action.Face and Enum.NormalId[action.Face] or Enum.NormalId.Front
light.Range = action.range or 16
end
if action.enabled ~= nil then light.Enabled = action.enabled end
light.Parent = target
table.insert(_batchCreated, light)
updateSceneState(action, true)
_lastClickInstance = light
return true, "💡 " .. lightType .. " added to " .. target.Name
elseif kind == "create_sound" then
local soundId = action.soundId
if not soundId or soundId == "" then return false, "❌ create_sound requires soundId" end
local parent
if action.target then
parent = resolveTarget(action.target)
if not parent then return false, "❌ create_sound: target '" .. tostring(action.target) .. "' not found" end
else
parent = resolveParent(action.parent, Workspace)
end
if not parent then parent = Workspace end
local sound = Instance.new("Sound")
sound.Name = action.name or "Sound"
sound.SoundId = soundId
sound.Volume = action.volume or 0.5
sound.Pitch = action.pitch or 1
sound.Looped = action.loops or false
if action.startPaused then sound.Playing = false else sound.Playing = true end
sound.Parent = parent
table.insert(_batchCreated, sound)
updateSceneState(action, true)
_lastClickInstance = sound
return true, "🔊 Sound created: " .. soundId
elseif kind == "create_tween" then
local target = resolveTarget(action.target)
if not target then return false, "❌ create_tween: target '" .. tostring(action.target) .. "' not found" end
local props = action.properties
if not props or next(props) == nil then return false, "❌ create_tween requires a 'properties' table" end
local duration = action.duration or 1
local style = action.style or "Quad"
local direction = action.direction or "Out"
local tsi = TweenInfo.new(duration, Enum.EasingStyle[style] or Enum.EasingStyle.Quad,
Enum.EasingDirection[direction] or Enum.EasingDirection.Out, action.repeatCount or 0,
action.reverses or false)
local tweenData = {}
for k, v in pairs(props) do
if type(v) == "table" and #v == 3 then
if k:lower():find("color") then
pcall(function() tweenData[k] = Color3.fromRGB(v[1] or 255, v[2] or 255, v[3] or 255) end)
else
tweenData[k] = Vector3.new(v[1] or 0, v[2] or 0, v[3] or 0)
end
else
tweenData[k] = v
end
end
local tween = game:GetService("TweenService"):Create(target, tsi, tweenData)
tween:Play()
table.insert(_batchCreated, tween)
updateSceneState(action, true)
_lastClickInstance = target
return true, "🎬 Tween started on " .. target.Name .. " (" .. duration .. "s, " .. style .. direction .. ")"
elseif kind == "create_animation" then
local animType = action.animationType or "asset"
if animType == "asset" then
local targetHum = resolveTarget(action.humanoid or action.target)
if not targetHum then return false, "❌ create_animation: target humanoid '" .. tostring(action.humanoid or action.target) .. "' not found" end
local assetId = action.assetId
if not assetId or assetId == "" then return false, "❌ create_animation: assetId required for 'asset' type" end
local anim = Instance.new("Animation")
anim.Name = action.name or "Animation"
anim.AnimationId = assetId
local parent = resolveParent(action.parent, targetHum.Parent)
anim.Parent = parent or targetHum.Parent
local animator = targetHum:FindFirstChildOfClass("Animator")
if not animator then animator = Instance.new("Animator"); animator.Parent = targetHum end
local track = animator:LoadAnimation(anim)
track:Play()
if action.loop then track:AdjustSpeed(action.speed or 1)
track.Looped = true end
table.insert(_batchCreated, anim)
updateSceneState(action, true)
_lastClickInstance = anim
return true, "🎭 Animation loaded & played on " .. targetHum.Name
elseif animType == "keyframe" then
local targetHum = resolveTarget(action.humanoid or action.target)
if not targetHum then return false, "❌ create_animation: target humanoid '" .. tostring(action.humanoid or action.target) .. "' not found" end
local frames = action.frames
if not frames or type(frames) ~= "table" or #frames == 0 then
return false, "❌ create_animation: 'frames' array required for keyframe type"
end
local anim = Instance.new("Animation")
anim.Name = action.name or "KeyframeAnimation"
local parent = resolveParent(action.parent, targetHum.Parent)
anim.Parent = parent or targetHum.Parent
local animator = targetHum:FindFirstChildOfClass("Animator")
if not animator then animator = Instance.new("Animator"); animator.Parent = targetHum end
-- Build keyframe sequence via a generated script
local kfSource = "local plr = game:GetService('Players').LocalPlayer\nlocal char = plr.Character\nif not char then return end\nlocal hum = char:WaitForChild('Humanoid')\nlocal animator = hum:FindFirstChildOfClass('Animator') or Instance.new('Animator')\nif not animator.Parent then animator.Parent = hum end\nlocal anim = Instance.new('Animation')\nanim.AnimationId = 'rbxasset://animations/anim_slowrun.rbxm'\n-- keyframe animation not fully supported via JSON yet; using run animation as fallback\nlocal track = animator:LoadAnimation(anim)\ntrack:Play()\nif " .. tostring(action.loop or false) .. " then track.Looped = true end"
local kfScript = Instance.new("LocalScript")
kfScript.Name = (action.name or "KeyframeAnim") .. "_Loader"
kfScript.Parent = parent or targetHum.Parent
kfScript.Source = kfSource
table.insert(_batchCreated, kfScript)
updateSceneState(action, true)
_lastClickInstance = kfScript
return true, "🎭 Keyframe animation created (" .. #frames .. " frames)"
else
return false, "❌ Unknown animationType: " .. tostring(animType)
end
elseif kind == "create_texture" then
local target = resolveTarget(action.target)
if not target then return false, "❌ create_texture: target '" .. tostring(action.target) .. "' not found" end
local texType = action.textureType or "Decal"
local tex = Instance.new(texType)
tex.Name = action.name or texType
tex.Texture = action.textureId or "rbxasset://textures/trans.png"
if action.face then tex.Face = Enum.NormalId[action.face] end
if action.color then tex.Color3 = Color3.fromRGB(action.color[1] or 255, action.color[2] or 255, action.color[3] or 255) end
if action.transparency then tex.Transparency = action.transparency end
if tex:IsA("Texture") then
if action.studsPerTileU then tex.StudsPerTileU = action.studsPerTileU end
if action.studsPerTileV then tex.StudsPerTileV = action.studsPerTileV end
end
tex.Parent = target
table.insert(_batchCreated, tex)
updateSceneState(action, true)
_lastClickInstance = tex
return true, "🎨 " .. texType .. " applied to " .. target.Name
elseif kind == "create_npc" then
local npcName = action.name or "NPC"
local pos = action.position
if not pos or type(pos) ~= "table" or #pos < 3 then
return false, "❌ create_npc requires position:[x,y,z]"
end
local npcColor = action.color or {180, 120, 80}
local ws = action.walkspeed or 16
local hp = action.health or 100
local scale = action.resize or 1
local npcFolder = Instance.new("Folder")
npcFolder.Name = npcName
npcFolder.Parent = resolveParent(action.parent, Workspace)
-- Create NPC parts (R6 model)
local torso = Instance.new("Part")
torso.Name = "Torso"; torso.Size = Vector3.new(2, 2, 1); torso.Position = Vector3.new(pos[1] or 0, (pos[2] or 3) + 1.5, pos[3] or 0)
torso.Anchored = false; torso.CanCollide = true; torso.BrickColor = BrickColor.new(Color3.fromRGB(npcColor[1] or 180, npcColor[2] or 120, npcColor[3] or 80))
torso.Parent = npcFolder
local head = Instance.new("Part")
head.Name = "Head"; head.Size = Vector3.new(1, 1, 1) * scale; head.Shape = Enum.PartType.Ball; head.Position = Vector3.new(pos[1] or 0, (pos[2] or 3) + 3, pos[3] or 0)
head.Anchored = false; head.CanCollide = true; head.BrickColor = BrickColor.new(Color3.fromRGB(255, 200, 150))
head.Parent = npcFolder
local larm = Instance.new("Part")
larm.Name = "Left Arm"; larm.Size = Vector3.new(1, 2, 1); larm.Position = Vector3.new((pos[1] or 0) - 1.5, (pos[2] or 3) + 1.5, pos[3] or 0)
larm.Anchored = false; larm.CanCollide = true; larm.BrickColor = torso.BrickColor; larm.Parent = npcFolder
local rarm = Instance.new("Part")
rarm.Name = "Right Arm"; rarm.Size = Vector3.new(1, 2, 1); rarm.Position = Vector3.new((pos[1] or 0) + 1.5, (pos[2] or 3) + 1.5, pos[3] or 0)
rarm.Anchored = false; rarm.CanCollide = true; rarm.BrickColor = torso.BrickColor; rarm.Parent = npcFolder
local lleg = Instance.new("Part")
lleg.Name = "Left Leg"; lleg.Size = Vector3.new(1, 2, 1); lleg.Position = Vector3.new((pos[1] or 0) - 0.5, (pos[2] or 3) - 0.5, pos[3] or 0)
lleg.Anchored = false; lleg.CanCollide = true; lleg.BrickColor = BrickColor.new(Color3.fromRGB(30, 30, 120)); lleg.Parent = npcFolder
local rleg = Instance.new("Part")
rleg.Name = "Right Leg"; rleg.Size = Vector3.new(1, 2, 1); rleg.Position = Vector3.new((pos[1] or 0) + 0.5, (pos[2] or 3) - 0.5, pos[3] or 0)
rleg.Anchored = false; rleg.CanCollide = true; rleg.BrickColor = lleg.BrickColor; rleg.Parent = npcFolder
-- Weld joints
local function weld(p0, p1, name)
local w = Instance.new("Weld"); w.Name = name; w.Part0 = p0; w.Part1 = p1
w.C0 = p0.CFrame:inverse() * p1.CFrame; w.Parent = p0
end
weld(torso, head, "Neck"); weld(torso, larm, "Left Shoulder"); weld(torso, rarm, "Right Shoulder")
weld(torso, lleg, "Left Hip"); weld(torso, rleg, "Right Hip")
-- Humanoid
local hum = Instance.new("Humanoid")
hum.Name = "Humanoid"; hum.MaxHealth = hp; hum.Health = hp; hum.WalkSpeed = ws; hum.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
hum.Parent = npcFolder
-- HumanoidRootPart
local root = Instance.new("Part")
root.Name = "HumanoidRootPart"; root.Size = Vector3.new(2, 2, 1); root.Shape = Enum.PartType.Cylinder
root.Position = Vector3.new(pos[1] or 0, (pos[2] or 3) + 0.5, pos[3] or 0)
root.Anchored = true; root.Transparency = 1; root.CanCollide = false; root.Parent = npcFolder
weld(torso, root, "RootJoint")
-- Head Billboard (name tag)
local bb = Instance.new("BillboardGui")
bb.Name = "NameTag"; bb.Size = UDim2.new(0, 60, 0, 20); bb.StudsOffset = Vector3.new(0, 2.5, 0); bb.AlwaysOnTop = true; bb.Parent = head
local nl = Instance.new("TextLabel")
nl.Size = UDim2.new(1, 0, 1, 0); nl.BackgroundTransparency = 1; nl.Text = npcName; nl.TextColor3 = Color3.new(1, 1, 1)
nl.TextSize = 14; nl.Font = Enum.Font.GothamBold; nl.TextStrokeTransparency = 0.5; nl.Parent = bb
table.insert(_batchCreated, npcFolder)
updateSceneState(action, true)
_lastClickInstance = npcFolder
return true, "🧑🦱 NPC created: " .. npcName .. " at " .. tostring(pos[1]) .. "," .. tostring(pos[2]) .. "," .. tostring(pos[3])
elseif kind == "load_model" then
local assetId = action.assetId
if not assetId or assetId == "" then return false, "❌ load_model requires 'assetId' (rbxassetid://...)" end
local InsertService = game:GetService("InsertService")
local parent = resolveParent(action.parent, Workspace)
local modelName = action.name or "LoadedModel"
local pos = action.position
local loaded = nil
local ok, result = pcall(function()
local id = assetId:match("rbxassetid://(%d+)") or assetId:match("(%d+)")
if id then
local m = InsertService:LoadAsset(tonumber(id))
if m then loaded = m end
end
end)
if not ok or not loaded then
-- Fallback: create a placeholder
local fb = Instance.new("Part")
fb.Name = modelName .. "_Placeholder"
fb.Size = Vector3.new(4, 4, 4)
fb.Anchored = true; fb.CanCollide = true
fb.Color = Color3.fromRGB(200, 100, 100)
if pos then fb.Position = Vector3.new(pos[1] or 0, pos[2] or 5, pos[3] or 0) end
fb.Parent = parent
table.insert(_batchCreated, fb)
_lastClickInstance = fb
updateSceneState(action, true)
return true, "⚠️ Model " .. assetId .. " couldn't load (not found or restricted). Created placeholder part instead."
end
loaded.Name = modelName
if loaded.Parent then loaded.Parent = nil end
loaded.Parent = parent
if pos then
local rootPart = loaded:FindFirstChild("HumanoidRootPart") or loaded:FindFirstChildWhichIsA("BasePart")
if rootPart then rootPart.CFrame = CFrame.new(Vector3.new(pos[1] or 0, pos[2] or 5, pos[3] or 0)) end
end
if action.makeTool then
loaded:FindFirstChildWhichIsA("Part").Anchored = false
end
table.insert(_batchCreated, loaded)
updateSceneState(action, true)
_lastClickInstance = loaded
return true, "📦 Model loaded: " .. modelName .. " (" .. assetId .. ")"
else
return false, "❌ Unknown action type: " .. tostring(kind)
end
end
local SYSTEM_PROMPT = [[
You are Skill-AI v1.3 — Roblox master builder. Build ANY game: tower defense, FPS, RPG, obby, racing, survival, tycoon, adventure, battle royale. NPCs, animations, textures, GUIs, plugins, guns, HP, save/load, multiplayer, leaderboards, shops, pets, waves, upgrades, boss fights — all in ONE response. Output ONLY valid JSON.
=== RULES ===
- ONLY valid JSON. No text/markdown/backticks. No reasoning. Output JSON immediately.
- NO Lua in JSON: use [255,0,0] NOT Color3.fromRGB/Color3.new. Use [5,5,5] NOT Vector3.new. Use [0.5,0,0.2,0] NOT UDim2.new.
- "className":"Part" NOT "Block"/"Ball"/"Cylinder" — use "Shape":"Box" for shape.
- Prefix [EASY]=simple(1-3 parts,minimal scripts) [NORMAL]=moderate [HARD]=full game with complete working Lua code, no placeholders, no TODOs.
- Format: {"actions":[...],"reply":"message"}. All actions in one array, executed sequentially.
=== ACTIONS (use these) ===
1. create_instance: {type,className,name,parent,containerName?,properties?,handleProperties?}
2. create_compound: {type,className:"Model",name,parent,parts:[{className,size,position,color,material,shape?}],makeTool?}
3. create_script: {type,className:"Script"|"LocalScript",name,parent,source}
4. create_multiple: {type,className,count,containerName,parent,properties?,rejectionRadius?}
5. create_enclosure: {type,name,parent,containerName?,centerX?,centerY?,centerZ?,width?,depth?,wallHeight?,floor?,openSides?,color?,material?,barrier?}
6. create_challenge_system: {type,name,parent,containerName?,challengeType,targetCount,title?,centerX?,centerY?,centerZ?,width?,depth?,height?,visibleFloor?,walls?,wallHeight?,wallColor?,wallMaterial?,guiPosition?,guiSize?}
7-14: set_property, delete, clone, rename, move, set_lighting, set_character, undo
15. create_plugin: {type,name,modules:[{name,source}],icon?,buttonColor?,parent?}
16. create_particle: {type,target,texture?,rate?,speed?,lifetime?,size?,color?,transparency?,rotation?,spread?,enabled?}
17. create_light: {type,lightType:"PointLight"|"SpotLight"|"SurfaceLight",target,color?,brightness?,range?,angle?,enabled?}
18. create_sound: {type,soundId:"rbxassetid://...",target?,parent?,name?,volume?,pitch?,loops?,startPaused?}
19. create_tween: {type,target,properties:{...},duration?,style?,direction?,repeatCount?,reverses?}
20. create_animation: {type,animationType:"keyframe"|"asset",target?,humanoid?,parent?,name?,assetId?,frames?,loop?,speed?}
21. create_texture: {type,textureType:"Decal"|"Texture",target,textureId:"rbxassetid://...",face?,color?,transparency?,studsPerTileU?,studsPerTileV?}
22. create_npc: {type,name,position,color?,walkspeed?,health?,resize?,parent?}
23. load_model: {type,assetId:"rbxassetid://...",name?,parent?,position?,makeTool?}
=== BUILD RULES ===
- PARENTING: GUI→StarterGui, Parts→Workspace, Scripts→ServerStorage/Lighting, LocalScripts→StarterPlayerScripts
- NEVER parent GUI to Workspace. NEVER parent Parts to StarterGui/StarterPlayerScripts. NEVER parent Scripts to Workspace.
- When a script controls a GUI, parent the script INSIDE the ScreenGui or to StarterPlayerScripts, NOT Workspace.
- Textures/Decals: use `target` (the part), NOT `parent`. Decal.Parent = the part, not Workspace.
- Parents: Workspace,StarterGui,StarterPlayerScripts,StarterCharacterScripts,Lighting,ServerStorage,ReplicatedStorage,CoreGui,StarterPack
- Parts: Block,Ball,Cylinder,Wedge,CornerWedge. Materials: Plastic,Metal,Wood,Glass,Neon,Granite,Marble,Cement.
- ANCHOR static=true. Only Tools=false. CanCollide=true auto. Stack Y+5 per level.
- Colors: RGB [255,0,0] or BrickColor string. Sizes: doors 4x7x1, platforms 10x0.5x10, walls 0.5x8x6.
- GUI circles: Frame+UICorner(CornerRadius=UDim.new(1,0)).
- Scripts: NEVER "workspace.X" — use :WaitForChild(). nil-guard. NEVER SourceBackup. Parts FIRST, scripts SECOND. Respawn-safe (auto re-run).
- Reply: MUST include full script source code. Numbered plan for complex builds.
- Game types: TD=path+spawn+towers+waves+currency+upgrades. FPS=guns+damage+HP+ammo+respawn. Sim=zone+collect+currency+shop+save. Race=track+vehicle+checkpoint+timer+leaderboard.
- Enclosure ex: {"type":"create_enclosure","name":"Platform","parent":"Workspace","centerX":0,"centerY":8,"centerZ":0,"width":10,"depth":10,"wallHeight":6}
- Challenge ex: {"type":"create_challenge_system","name":"KillChallenge","parent":"Workspace","challengeType":"kill","targetCount":10,"centerX":0,"centerY":0,"centerZ":0,"width":30,"depth":30,"height":8}
- HARD mode: complete working Lua in every script — every function, event, line. No placeholders or TODOs.
]]
local REASONING_PATTERNS = {
"^Let me%s", "^I['']?ll%s", "^I think%s", "^First,?%s",
"^The user%s", "^Okay,?%s", "^So,?%s", "^Alright,?%s",
"^Here['']?s%s", "^This is%s", "^I need%s", "^I should%s",
"^I will%s", "^We need%s", "^We should%s", "^We can%s",
}
local function stripReasoning(text)
local lines = {}
for line in text:gmatch("[^\n]+") do
local stripped = line
for _, pat in ipairs(REASONING_PATTERNS) do
stripped = stripped:gsub(pat, "")
end
if #stripped > 0 then
table.insert(lines, stripped)
end
end
return table.concat(lines, "\n")
end
-- Fix Lua expressions inside JSON (e.g. Vector3.new(1,2,3) → [1,2,3])
local function fixLuaInJson(str)
-- Color3.fromRGB(255,0,0) → [255,0,0]
str = str:gsub("Color3%.fromRGB%s*%((%d+%s*,%s*%d+%s*,%s*%d+)%)", function(inner)
return "[" .. inner .. "]"
end)
-- Color3.new(0.5,0,0) → [128,0,0]
str = str:gsub("Color3%.new%s*%(([%d%.]+%s*,%s*[%d%.]+%s*,%s*[%d%.]+)%)", function(inner)
local r, g, b = inner:match("([%d%.]+)%s*,%s*([%d%.]+)%s*,%s*([%d%.]+)")
if r and g and b then
r = math.floor((tonumber(r) or 0) * 255)
g = math.floor((tonumber(g) or 0) * 255)
b = math.floor((tonumber(b) or 0) * 255)
return "[" .. r .. "," .. g .. "," .. b .. "]"
end
return "[" .. inner .. "]"
end)
-- Vector3.new(1,2,3) or Vector3.new(-1,2,0) → [1,2,3]
str = str:gsub("Vector[23]%.new%s*%(([%d%.%-]+%s*,%s*[%d%.%-]+%s*,%s*[%d%.%-]+)%)", function(inner)
return "[" .. inner .. "]"
end)
-- UDim2.new(0.5,0,0.2,0) → [0.5,0,0.2,0]
str = str:gsub("UDim2%.new%s*%(([%d%.%-]+%s*,%s*[%d%.%-]+%s*,%s*[%d%.%-]+%s*,%s*[%d%.%-]+)%)", function(inner)
return "[" .. inner .. "]"
end)
-- NumberRange.new(1,10) → [1,10]
str = str:gsub("NumberRange%.new%s*%(([%d%.]+%s*,%s*[%d%.]+)%)", function(inner)
return "[" .. inner .. "]"
end)
-- NumberSequence.new(1, 0.5) → {"__NumberSequence":[1,0.5]}
str = str:gsub("NumberSequence%.new%s*%(([%d%.]+%s*,%s*[%d%.]+)%)", function(inner)
return '{"__NumberSequence":[' .. inner .. ']}'
end)
-- BrickColor.new("Bright red") → "Bright red"
str = str:gsub('BrickColor%.new%s*%("[^"]+")', function(inner)
return inner
end)
-- Normalize class names
str = str:gsub('"className"%s*:%s*"Block"', '"className":"Part"')
str = str:gsub('"className"%s*:%s*"Ball"', '"className":"Part"')
str = str:gsub('"className"%s*:%s*"Cylinder"', '"className":"Part"')
-- Normalize "shape" to "Shape"
str = str:gsub('"shape"%s*:%s*"Box"', '"Shape":"Box"')
str = str:gsub('"shape"%s*:%s*"Block"', '"Shape":"Block"')
str = str:gsub('"shape"%s*:%s*"Ball"', '"Shape":"Ball"')
return str
end
local function tryExtractJson(text)
if type(text) ~= "string" or text == "" then return nil end
local cleaned = stripReasoning(text)
cleaned = cleaned:gsub("```json", ""):gsub("```lua", ""):gsub("```", ""):gsub("^%s+", ""):gsub("%s+$", "")
cleaned = cleaned:gsub("^[%s\n\r]+", ""):gsub("[%s\n\r]+$", "")
-- strip invisible / control chars (except \t \n \r) that break JSONDecode
cleaned = cleaned:gsub("[%c]", function(c)
if c == "\t" or c == "\n" or c == "\r" then return c end
return ""
end)
-- Convert Lua expressions inside JSON to proper JSON
cleaned = fixLuaInJson(cleaned)
-- If no "actions" wrapper but has a single action object, wrap it
if not cleaned:find('"actions"', 1, true) and cleaned:match('"type"%s*:') then
cleaned = '{"actions":[' .. cleaned .. '],"reply":"Built."}'
end
local function tryDecode(str)
local ok, res = pcall(function() return HttpService:JSONDecode(str) end)
if ok and type(res) == "table" then return res end
return nil
end
-- try direct
local plan = tryDecode(cleaned)
if plan then return plan end
-- try extracting from known start patterns
for _, prefix in ipairs({'{"actions"', '{"steps"', '{"reply"', '{"type"'}) do
local s = cleaned:find(prefix, 1, true)
if s then
local e = cleaned:reverse():find("}")
if e then
local realEnd = #cleaned - e + 1
if realEnd > s then
local sub = cleaned:sub(s, realEnd)
plan = tryDecode(sub)
if plan then return plan end
end
end
end
end
-- first { to last }
local s = cleaned:find("{")
local e = cleaned:reverse():find("}")
if s and e then
local realEnd = #cleaned - e + 1
if realEnd > s then
local sub = cleaned:sub(s, realEnd)
plan = tryDecode(sub)
if plan then return plan end
-- maybe truncated — try adding closing braces
local extras = { "}", "}]}", "}]}}", "} }" }
for _, extra in ipairs(extras) do
local fixed = sub .. extra
plan = tryDecode(fixed)
if plan then return plan end
end
end
end
return nil
end
local function buildSceneTree()
local lines = {}
local limit = 120
local count = 0
-- tracked containers from this session
local hasContainers = false
for key, ss in pairs(sceneState) do
local names = {}
if ss.createdNames then
for n, _ in pairs(ss.createdNames) do
table.insert(names, n)
end
end
if #names > 0 then
table.insert(lines, " " .. key .. " (" .. (ss.className or "?") .. ", " .. ss.count .. " total): " .. table.concat(names, ", "))
hasContainers = true
end
end
if hasContainers then
table.insert(lines, 1, "--- this session ---")
table.insert(lines, 2, "")
end
-- live scene tree
local servicesToScan = {
Workspace = Workspace,
StarterPack = StarterPack,
Lighting = Lighting,
ServerStorage = ServerStorage,
ReplicatedStorage = ReplicatedStorage,
StarterGui = StarterGui,
StarterCharacterScripts = StarterCharacterScripts,
StarterPlayerScripts = StarterPlayerScripts,
}
local function shortClass(cn)
local map = { Part = "Part", MeshPart = "Mesh", Model = "Model", Folder = "Folder",
Script = "Script", LocalScript = "LScript", StringValue = "StrVal",
ScreenGui = "Gui", Frame = "Frame", TextLabel = "Label", TextButton = "Btn",
ImageLabel = "Img", ScrollingFrame = "ScrFrame", ViewportFrame = "VpFrame" }
return map[cn] or cn
end
for svcName, svc in pairs(servicesToScan) do
if svc and type(svc) == "userdata" then
local children = svc:GetChildren()
if #children > 0 then
table.insert(lines, "")
table.insert(lines, svcName .. ":")
for i, child in ipairs(children) do
if count >= limit then break end
local stem = (i == #children) and " " or "│ "
local fork = (i == #children) and "└── " or "├── "
local loc = child:IsA("BasePart") and string.format(" @%.0f,%.0f,%.0f", child.CFrame.X, child.CFrame.Y, child.CFrame.Z) or ""
table.insert(lines, fork .. child.Name .. loc .. " (" .. shortClass(child.ClassName) .. ")")
count += 1
if (child:IsA("Model") or child:IsA("Folder")) and #child:GetChildren() > 0 then
local gcs = child:GetChildren()
for j, gc in ipairs(gcs) do
if count >= limit then break end
local gfork = (j == #gcs) and "└── " or "├── "
local gloc = gc:IsA("BasePart") and string.format(" @%.0f,%.0f,%.0f", gc.CFrame.X, gc.CFrame.Y, gc.CFrame.Z) or ""
table.insert(lines, stem .. gfork .. gc.Name .. gloc .. " (" .. shortClass(gc.ClassName) .. ")")
count += 1
end
end
end
end
end
end
if #lines == 0 then return "" end
return "=== SCENE ===\n" .. table.concat(lines, "\n") .. "\n=== END SCENE ==="
end
local _lastValidationErrors = nil
local VALIDATION_SKIP_REFS = {
humanoid = true, handle = true, torso = true, head = true,
["left arm"] = true, ["right arm"] = true, ["left leg"] = true, ["right leg"] = true,
playergui = true, backpack = true, leaderstats = true, configuration = true,
uppertorso = true, lowertorso = true, lefthand = true, righthand = true,
leftfoot = true, rightfoot = true,
}
local function validateActions(actions)
local errors = {}
local createdNames = {}
for i, action in ipairs(actions) do
local parent = action.parent
if (action.type == "create_instance" or action.type == "create_compound") and action.name then
createdNames[action.name:lower()] = true
end
-- Size validation: skip for GUI objects (they use UDim2 = 4-element [sX,oX,sY,oY])
if action.type == "create_instance" and type(action.properties) == "table" then
local guiClasses = { ScreenGui = true, Frame = true, TextLabel = true, TextButton = true,
ImageLabel = true, ScrollingFrame = true, BillboardGui = true }
local isGUI = guiClasses[action.className]
local sz = action.properties.Size
if type(sz) == "table" and isGUI then
if #sz ~= 4 or type(sz[1]) ~= "number" or type(sz[2]) ~= "number" or type(sz[3]) ~= "number" or type(sz[4]) ~= "number" then
table.insert(errors, ("Action %d: '%s' has invalid UDim2 Size %s (needs 4 numbers: scaleX, offsetX, scaleY, offsetY)."):format(
i, action.name or "?", HttpService:JSONEncode(sz)
))
end
elseif type(sz) == "table" and not isGUI then
if not ((sz[1] or 0) > 0 and (sz[2] or 0) > 0 and (sz[3] or 0) > 0) then
table.insert(errors, ("Action %d: '%s' has invalid Size %s (must be positive numbers on all 3 axes)."):format(
i, action.name or "?", HttpService:JSONEncode(sz)
))
end
end
-- موقع متطرف بشكل غير منطقي (احتمال خطأ حسابي بالـ AI، مو طلب فعلي)
local pos = action.properties.Position
if type(pos) == "table" then
for axisIdx = 1, 3 do
local v = pos[axisIdx]
if type(v) == "number" and math.abs(v) > 100000 then
table.insert(errors, ("Action %d: '%s' has extreme Position value (%d) on axis %d. Likely a calculation error — keep coordinates reasonable (e.g. within ±2000)."):format(
i, action.name or "?", v, axisIdx
))
end
end
end
end
-- نفس فحص الحجم لأجزاء create_compound (كل part بالمصفوفة)
if action.type == "create_compound" and type(action.parts) == "table" then
for partIdx, partData in ipairs(action.parts) do
if type(partData) == "table" and type(partData.size) == "table" then
local sz = partData.size
if not ((sz[1] or 0) > 0 and (sz[2] or 0) > 0 and (sz[3] or 0) > 0) then
table.insert(errors, ("Action %d (compound '%s', part %d): invalid size %s (must be positive on all 3 axes)."):format(
i, action.name or "?", partIdx, HttpService:JSONEncode(sz)
))
end
end
end
end
-- GUI objects in Workspace
if action.type == "create_instance" and parent == "Workspace" then
local guiClasses = { ScreenGui = true, Frame = true, TextLabel = true, TextButton = true,
ImageLabel = true, ScrollingFrame = true, BillboardGui = true }
if guiClasses[action.className] then
table.insert(errors, ("Action %d: GUI '%s' parented to Workspace. Use StarterGui instead."):format(i, action.name or "?"))
end
end
-- Parts in GUI services
if action.type == "create_instance" and parent and action.className then
local isPart = action.className == "Part" or action.className == "MeshPart" or action.className == "WedgePart" or action.className == "CylinderPart" or action.className == "BallPart"
local isGuiSvc = parent == "StarterGui" or parent == "StarterPlayerScripts" or parent == "StarterCharacterScripts"
if isPart and isGuiSvc then
table.insert(errors, ("Action %d: Part '%s' parented to %s. Use Workspace instead."):format(i, action.name or "?", parent))
end
end
-- Scripts/values can't live in Workspace (not a container for code)
if action.type == "create_script" and (parent == "Workspace") then
table.insert(errors, ("Action %d (script '%s'): parented to Workspace. Use ServerScriptService or StarterPlayerScripts instead."):format(
i, action.name or "?"
))
end
-- Script references a part not yet created
if action.type == "create_script" and action.source then
for ref in action.source:gmatch(':WaitForChild%("([^"]+)"%)') do
local refLower = ref:lower()
if not VALIDATION_SKIP_REFS[refLower] and not createdNames[refLower] then
local exists = findByName(ref)
if not exists then
table.insert(errors, ("Action %d (script '%s'): references '%s' which doesn't exist. Create it in a PRIOR action."):format(
i, action.name or "?", ref
))
end
end
end
for ref in action.source:gmatch(':FindFirstChild%("([^"]+)"%)') do
local refLower = ref:lower()
if not VALIDATION_SKIP_REFS[refLower] and not createdNames[refLower] then
local exists = findByName(ref)
if not exists then
table.insert(errors, ("Action %d (script '%s'): references FindFirstChild('%s') which doesn't exist. Create it first."):format(
i, action.name or "?", ref
))
end
end
end
end
-- create_challenge_system validation
if action.type == "create_challenge_system" then
local validTypes = { reach = true, survive = true, kill = true, collect = true }
local ct = action.challengeType
if ct and not validTypes[ct:lower()] then
table.insert(errors, ("Action %d (challenge '%s'): invalid challengeType '%s'. Valid: reach, survive, kill, collect"):format(
i, action.name or "?", ct
))
end
local tc = tonumber(action.targetCount)
if tc and (tc < 1 or tc > 1000) then
table.insert(errors, ("Action %d (challenge '%s'): targetCount must be 1-1000, got %s"):format(
i, action.name or "?", tostring(tc)
))
end
local w = tonumber(action.width)
local d = tonumber(action.depth)
local h = tonumber(action.height)
if w and (w < 1 or w > 500) then
table.insert(errors, ("Action %d (challenge '%s'): width must be 1-500, got %s"):format(i, action.name or "?", tostring(w)))
end
if d and (d < 1 or d > 500) then
table.insert(errors, ("Action %d (challenge '%s'): depth must be 1-500, got %s"):format(i, action.name or "?", tostring(d)))
end
if h and (h < 1 or h > 200) then
table.insert(errors, ("Action %d (challenge '%s'): height must be 1-200, got %s"):format(i, action.name or "?", tostring(h)))
end
end
-- deeper script validation: detect workspace.X dot-access (forbidden by prompt rules)
if action.type == "create_script" and action.source then
local src = action.source
-- pattern: workspace.Something (dot-access)
for matchPos, _ in src:gmatch("()workspace%.%a") do
local contextStart = math.max(1, matchPos - 15)
local contextEnd = math.min(#src, matchPos + 25)
local snippet = src:sub(contextStart, contextEnd):gsub("\n", "\\n")
table.insert(errors, ("Action %d (script '%s'): uses 'workspace.X' dot-access at pos %d (\"...%s...\"). Use script.Parent:WaitForChild('X') instead."):format(
i, action.name or "?", matchPos, snippet
))
break
end
-- pattern: game.Workspace.Something
for matchPos, _ in src:gmatch("()game%.Workspace%.%a") do
local contextStart = math.max(1, matchPos - 15)
local contextEnd = math.min(#src, matchPos + 30)
local snippet = src:sub(contextStart, contextEnd):gsub("\n", "\\n")
table.insert(errors, ("Action %d (script '%s'): uses 'game.Workspace.X' dot-access at pos %d (\"...%s...\"). Use script.Parent:WaitForChild('X') instead."):format(
i, action.name or "?", matchPos, snippet
))
break
end
end
end
-- cross-action ordering check: scripts referencing parts must come after those parts are created
do
local createdByAction = {}
for i, action in ipairs(actions) do
if (action.type == "create_instance" or action.type == "create_compound") and action.name then
createdByAction[action.name:lower()] = i
end
if action.type == "create_enclosure" and action.name then
createdByAction[action.name:lower()] = i
end
if action.type == "create_challenge_system" and action.name then
createdByAction[action.name:lower()] = i
end
if action.type == "create_multiple" and action.namePrefix then
createdByAction[action.namePrefix:lower()] = i
end
end
for i, action in ipairs(actions) do
if action.type == "create_script" and action.source then
for ref in action.source:gmatch(':WaitForChild%("([^"]+)"%)') do
local refLower = ref:lower()
if not VALIDATION_SKIP_REFS[refLower] and createdByAction[refLower] and createdByAction[refLower] > i then
table.insert(errors, ("Action %d (script '%s'): references part '%s' via WaitForChild, but that part is created LATER in action %d. Parts must be created BEFORE scripts that reference them."):format(
i, action.name or "?", ref, createdByAction[refLower]
))
end
end
for ref in action.source:gmatch(':FindFirstChild%("([^"]+)"%)') do
local refLower = ref:lower()
if not VALIDATION_SKIP_REFS[refLower] and createdByAction[refLower] and createdByAction[refLower] > i then
table.insert(errors, ("Action %d (script '%s'): references FindFirstChild('%s'), but that part is created LATER in action %d. Reorder: create first, then script."):format(
i, action.name or "?", ref, createdByAction[refLower]
))
end
end
end
end
end
-- ⚠️ فحص تنظيم: لو فيه أكثر من create_instance بدون containerName للقطع
-- اللي مش GUI أو Script (لأن GUI عنده ScreenGui اللي هو container أصلاً)
do
local guiClasses = { ScreenGui = true, Frame = true, TextLabel = true, TextButton = true,
ImageLabel = true, ScrollingFrame = true, BillboardGui = true, ViewportFrame = true }
local noContainerCount = 0
local containerNamesUsed = {}
for _, action in ipairs(actions) do
if action.type == "create_instance" and not guiClasses[action.className] and action.className ~= "Script" and action.className ~= "LocalScript" then
if not action.containerName or action.containerName == "" then
noContainerCount = noContainerCount + 1
else
containerNamesUsed[action.containerName] = (containerNamesUsed[action.containerName] or 0) + 1
end
end
end
local distinctContainers = 0
for _ in pairs(containerNamesUsed) do distinctContainers = distinctContainers + 1 end
if noContainerCount >= 3 then
table.insert(errors, ("You created %d separate parts via create_instance without any containerName. Per the ORGANIZATION RULE, give them all the SAME containerName so they group under one Folder instead of scattering loose parts in the Explorer."):format(noContainerCount))
elseif distinctContainers > 1 and noContainerCount == 0 then
table.insert(errors, "You used multiple different containerName values for parts that appear to belong to the same build. Use ONE consistent containerName for all parts of a single logical build.")
end
end
return errors
end
local function requestPlan(question, retryCount, accumUsage)
retryCount = retryCount or 0
accumUsage = accumUsage or { prompt_tokens = 0, completion_tokens = 0, total_tokens = 0 }
local maxRetries = 2
local messages = {}
if CURRENT_PROVIDER ~= "nvidia" then
table.insert(messages, { role = "system", content = SYSTEM_PROMPT })
end
-- Only send history and scene if they have content (saves tokens)
local hasScene = next(sceneState) ~= nil
if hasScene then
for _, histMsg in ipairs(buildHistoryMessages()) do
table.insert(messages, histMsg)
end
end
local sceneJson = ""
if hasScene then
sceneJson = buildSceneTree()
end
local enrichedQuestion = question
if sceneJson ~= "" then
enrichedQuestion = question .. "\n\n[current_scene]\n" .. sceneJson .. "\n[/current_scene]"
end
if CURRENT_PROVIDER == "nvidia" then
enrichedQuestion = "[System Instructions]\n" .. SYSTEM_PROMPT .. "\n\n[User Request]\n" .. enrichedQuestion
end
if retryCount > 0 then
local retryMsg = "⚠️ Your last response had issues."
if _lastValidationErrors then
retryMsg = retryMsg .. "\nFix these errors in your actions:\n- " .. table.concat(_lastValidationErrors, "\n- ")
_lastValidationErrors = nil
else
retryMsg = retryMsg .. " Send only valid JSON without extra text."
end
enrichedQuestion = enrichedQuestion .. "\n\n" .. retryMsg
end
table.insert(messages, { role = "user", content = enrichedQuestion })
local effort = classifyEffort(question)
local raw, err, usage = callAI(messages, nil, effort)
if usage and type(usage) == "table" then
accumUsage.prompt_tokens = accumUsage.prompt_tokens + (tonumber(usage.prompt_tokens) or 0)
accumUsage.completion_tokens = accumUsage.completion_tokens + (tonumber(usage.completion_tokens) or 0)
accumUsage.total_tokens = accumUsage.total_tokens + (tonumber(usage.total_tokens) or 0)
end
if not raw then
return nil, err, accumUsage
end
local plan = tryExtractJson(raw)
if not plan then
logToFile("Impure JSON response (request: " .. question .. ")", raw)
if retryCount < maxRetries then
return requestPlan(question, retryCount + 1, accumUsage)
end
return nil, "❌ Could not parse AI plan (not valid JSON). Raw response saved to: " .. LOG_FILE, accumUsage
end
if plan.actions and type(plan.actions) == "table" and retryCount < maxRetries then
local validationErrors = validateActions(plan.actions)
if #validationErrors > 0 then
logToFile("Validation errors (request: " .. question .. ")", table.concat(validationErrors, "\n"))
_lastValidationErrors = validationErrors
return requestPlan(question, retryCount + 1, accumUsage)
end
end
return plan, nil, accumUsage
end
local function addLog(msg, isErr)
if not displayArea or not displayArea.Parent then return end
local lbl = Instance.new("TextLabel")
lbl.Size = UDim2.new(1, -10, 0, 20)
lbl.BackgroundTransparency = 1
lbl.Text = msg
lbl.TextColor3 = isErr and C.red or C.yellow
lbl.TextSize = 10
lbl.Font = Enum.Font.Code
lbl.TextXAlignment = Enum.TextXAlignment.Left
lbl.Parent = displayArea
table.insert(_logLabels, lbl)
task.wait()
if displayArea and displayArea.Parent then
displayArea.CanvasPosition = Vector2.new(0, displayArea.CanvasSize.Y.Offset)
end
return lbl
end
local function _runRequest(question)
if not question or question == "" then
_processingRequest = false
return
end
_processingRequest = true
CURRENT_REQUEST_TEXT = question
_batchCreated = {}
_batchPlacedGrid = nil
task.spawn(function()
local loading = Instance.new("TextLabel")
local runOk, runErr = pcall(function()
loading.Size = UDim2.new(1, -10, 0, 30)
loading.BackgroundTransparency = 1
local modelShort = (CURRENT_MODEL_LABEL or "?"):match("^%s*(.-)%s*$")
if modelShort then modelShort = modelShort:sub(1,20) else modelShort = "?" end
loading.Text = "🧠 Planning [" .. modelShort .. "] ."
loading.TextColor3 = C.textDim
loading.TextSize = 11
loading.Font = Enum.Font.Gotham
loading.Parent = displayArea
-- Simple dot animation (no Heartbeat — saves CPU)
task.spawn(function()
local step = 0
while loading and loading.Parent do
step = step + 1
local dots = string.rep(".", (step % 3) + 1)
loading.Text = "🧠 Planning [" .. modelShort .. "] " .. dots
task.wait(0.4)
end
end)
local startTime = tick()
addLog("📡 Sending to " .. CURRENT_PROVIDER:upper() .. "...")
local plan, err, usage = requestPlan(question)
addLog("📦 Parsing response...")
local elapsed = tick() - startTime
local function showCostLine()
if usage and type(usage) == "table" and (usage.prompt_tokens or 0) + (usage.completion_tokens or 0) > 0 then
local pIn = usage.prompt_tokens or 0
local pOut = usage.completion_tokens or 0
-- للـ OpenCode: مجاني دائماً — السعر صفر
local cost = 0
local costStr
if CURRENT_PROVIDER == "nvidia" then
cost = (pIn / 1000000) * PRICE_PER_1M_INPUT + (pOut / 1000000) * PRICE_PER_1M_OUTPUT
costStr = ("💰 $%.6f"):format(cost)
else
costStr = "💚 Free"
end
addMessage(("⏱️ %.1fs | 🔤 %d in / %d out | %s"):format(elapsed, pIn, pOut, costStr), false, false, true)
else
addMessage(("⏱️ %.1fs | 🔤 token usage not reported by server"):format(elapsed), false, false, true)
end
end
if not plan then
addMessage(err or "❌ Unknown error", false, true)
pushHistory(question, "Failed to execute request (error: " .. tostring(err) .. ")")
showCostLine()
return
end
if plan.thought and type(plan.thought) == "string" and plan.thought ~= "" then
addMessage("🧠 " .. plan.thought, false, false, true)
end
if plan.reply and type(plan.reply) == "string" and plan.reply ~= "" then
addMessage("🤖 " .. plan.reply, false)
end
showCostLine()
local actions = plan.actions
if type(actions) ~= "table" or #actions == 0 then
return
end
local summaryParts = {}
local totalActions = #actions
-- FIX v1.4: لا delay أبداً للباتشات الكبيرة — نستخدم task.wait فقط للباتشات الصغيرة (بصرياً)
local visualDelay = totalActions > 20 and 0 or (totalActions > 8 and 0.01 or 0.04)
addLog("⚙️ Executing " .. #actions .. " action(s)...")
for actionIdx, action in ipairs(actions) do
if type(action) ~= "table" or not action.type then
addMessage("⚠️ Skipped invalid action from AI", false, false, true)
else
_lastClickInstance = nil
addLog(" [" .. actionIdx .. "/" .. totalActions .. "] " .. (action.type or "?") .. " → " .. (action.name or action.className or ""))
local successOk, didSucceed, resultMessage = pcall(executeAction, action)
local clickInst = _lastClickInstance
if successOk then
addMessage(resultMessage or "✅ Done", false, not didSucceed, true, clickInst)
if didSucceed then
local detail = tostring(action.type)
if action.className then detail = detail .. "(" .. tostring(action.className) .. ")" end
if action.name then detail = detail .. " name=" .. tostring(action.name) end
if action.namePrefix then detail = detail .. " prefix=" .. tostring(action.namePrefix) end
if action.containerName then detail = detail .. " container=" .. tostring(action.containerName) end
if action.count then detail = detail .. " count=" .. tostring(action.count) end
table.insert(summaryParts, detail)
end
else
addMessage("❌ Execution error: " .. tostring(didSucceed), false, true)
table.insert(summaryParts, tostring(action.type) .. " failed: " .. tostring(didSucceed))
end
if actionIdx < totalActions then
task.wait(visualDelay)
end
end
end
local summary
if #summaryParts > 0 then
summary = "Executed: " .. table.concat(summaryParts, " | ")
else
summary = "No actions succeeded for this request."
end
if #_batchCreated > 0 then
table.insert(undoStack, {
instances = _batchCreated,
summary = summary
})
while #undoStack > undoLimit do
table.remove(undoStack, 1)
end
end
pushHistory(question, summary)
end)
if not runOk then
addMessage("❌ " .. tostring(runErr), false, true)
end
pcall(function()
if loading and loading.Parent then
loading:Destroy()
end
end)
pcall(function()
for _, lbl in ipairs(_logLabels) do
if lbl and lbl.Parent then lbl:Destroy() end
end
_logLabels = {}
end)
if #_requestQueue > 0 then
local nextQ = table.remove(_requestQueue, 1)
task.spawn(function() _runRequest(nextQ) end)
else
_processingRequest = false
end
end)
end
local function sendQuestion()
local question = inputBox.Text
if question == "" then return end
if question == "/undo" or question == "undo" then
inputBox.Text = ""
if #undoStack == 0 then
addMessage("⚠️ Nothing to undo.", false, true)
return
end
local lastBatch = table.remove(undoStack)
local removed = 0
for _, entry in ipairs(lastBatch.instances) do
if type(entry) == "table" and entry._markedForDestroy then
addMessage("⚠️ Cannot restore: " .. tostring(entry._name) .. " (was already deleted)", false, false, true)
else
local inst = entry
if inst and inst.Parent then
inst:Destroy()
removed += 1
end
end
end
addMessage("↩️ Undone: " .. lastBatch.summary .. " (removed " .. removed .. " items)", false, false, true)
pushHistory("/undo", "Undid: " .. lastBatch.summary)
return
end
local findPrefix = "/find "
if question:lower():sub(1, #findPrefix) == findPrefix then
inputBox.Text = ""
local searchName = question:sub(#findPrefix + 1)
if searchName == "" then
addMessage("❌ Usage: /find Name", false, true)
return
end
local results = findAllByName(searchName)
if #results == 0 then
addMessage("❌ No instances found containing \"" .. searchName .. "\"", false, true)
return
end
local msg = "🔍 Found " .. #results .. " instance(s) for \"" .. searchName .. "\":"
for i, res in ipairs(results) do
msg = msg .. "\n " .. i .. ". " .. res.Name .. " (" .. (res.ClassName or "?") .. ") → " .. getInstancePath(res)
end
addMessage(msg, false, false, true)
pushHistory("/find " .. searchName, "Found " .. #results .. " instances")
return
end
if question:lower() == "/diag" then
inputBox.Text = ""
addMessage("🧑 " .. question, true)
-- Real diagnostic test: create a test LocalScript, parent it to Workspace,
-- write Source, wait, read it back, and compare — raw result, no embellishment.
local testSource = "--diag test " .. tostring(os.time())
local testOk, testInst = pcall(function() return Instance.new("LocalScript") end)
if not testOk or not testInst then
addMessage("❌ Instance.new(\"LocalScript\") itself failed: " .. tostring(testInst), false, true)
return
end
testInst.Name = "SkillAI_DiagTest"
local p1ok = pcall(function() testInst.Source = testSource end)
local r1ok, r1val = pcall(function() return testInst.Source end)
local beforeParent = "Before parenting to game: write_ok=" .. tostring(p1ok) .. " read_ok=" .. tostring(r1ok)
.. " match=" .. tostring(r1ok and r1val == testSource)
testInst.Parent = Workspace
task.wait()
local p2ok = pcall(function() testInst.Source = testSource end)
task.wait()
local r2ok, r2val = pcall(function() return testInst.Source end)
local afterParent = "After parenting to game: write_ok=" .. tostring(p2ok) .. " read_ok=" .. tostring(r2ok)
.. " match=" .. tostring(r2ok and r2val == testSource)
.. " length=" .. tostring(r2ok and r2val and #r2val or 0)
local writefileAvail = "writefile available: " .. tostring(writefile ~= nil)
local setclipboardAvail = "setclipboard available: " .. tostring(setclipboard ~= nil)
testInst:Destroy()
-- Additional check: verify StarterPlayerScripts/StarterCharacterScripts are
-- the correct types (not fake Folders with the same name — the real root cause
-- of "Not Found" even when Source writing succeeds).
local spsClass = StarterPlayerScripts and StarterPlayerScripts.ClassName or "nil"
local scsClass = StarterCharacterScripts and StarterCharacterScripts.ClassName or "nil"
local spsCheck = "StarterPlayerScripts type: " .. spsClass
.. (spsClass == "StarterPlayerScripts" and " ✅ correct" or " ❌ wrong! Expected StarterPlayerScripts, not " .. spsClass)
local scsCheck = "StarterCharacterScripts type: " .. scsClass
.. (scsClass == "StarterCharacterScripts" and " ✅ correct" or " ❌ wrong! Expected StarterCharacterScripts, not " .. scsClass)
-- Real end-to-end test: create an actual LocalScript inside StarterPlayerScripts and verify we find it
local spsTestMsg = "Skipped (StarterPlayerScripts unavailable)"
if StarterPlayerScripts then
local sp2ok, sp2inst = pcall(function() return Instance.new("LocalScript") end)
if sp2ok and sp2inst then
sp2inst.Name = "SkillAI_DiagTest2"
sp2inst.Parent = StarterPlayerScripts
pcall(function() sp2inst.Source = testSource end)
task.wait()
local foundBack = StarterPlayerScripts:FindFirstChild("SkillAI_DiagTest2")
spsTestMsg = "Script inside StarterPlayerScripts found afterward: " .. tostring(foundBack ~= nil)
if foundBack then foundBack:Destroy() end
end
end
local finalVerdict
if r2ok and r2val == testSource and spsClass == "StarterPlayerScripts" then
finalVerdict = "✅ Result: Source writing works, and StarterPlayerScripts is the correct type. If you still see issues, tell me exactly what happens."
elseif spsClass ~= "StarterPlayerScripts" then
finalVerdict = "❌ Result: Found the root cause! StarterPlayerScripts is NOT the correct type in this session — this explains 'Not Found'. Try /diag after restarting the script with this update."
else
finalVerdict = "❌ Result: This Executor definitely blocks Source writing after parenting (confirmed by actual test, not guesswork)."
end
addMessage("🔬 Diagnostic result:\n" .. beforeParent .. "\n" .. afterParent .. "\n" .. writefileAvail .. "\n" .. setclipboardAvail
.. "\n" .. spsCheck .. "\n" .. scsCheck .. "\n" .. spsTestMsg .. "\n\n" .. finalVerdict, false, false, true)
pushHistory("/diag", "Ran Source-write diagnostic")
return
end
if question:lower() == "/clear" then
conversationHistory = {}
addMessage("🧹 History cleared. AI starts fresh.", false)
return
end
-- FIX: أمر /export — يصدّر تاريخ المحادثة كملف نصي أو ينسخه للـ clipboard
if question:lower() == "/export" then
inputBox.Text = ""
if #conversationHistory == 0 then
addMessage("⚠️ No conversation history to export.", false, false, true)
return
end
local lines = { "=== Skill-AI v1.3 Conversation Export ===" }
local ts = os.date and os.date("%Y-%m-%d %H:%M") or tostring(os.time())
table.insert(lines, "Date: " .. ts)
table.insert(lines, "Model: " .. CURRENT_MODEL_LABEL)
table.insert(lines, "Turns: " .. #conversationHistory)
table.insert(lines, "")
for i, turn in ipairs(conversationHistory) do
table.insert(lines, ("[%d] User: %s"):format(i, turn.user or ""))
table.insert(lines, (" AI: %s"):format(turn.summary or ""))
table.insert(lines, "")
end
local exportStr = table.concat(lines, "\n")
local saved = false
-- محاولة حفظ ملف
if writefile then
local exportFile = "SkillAI_Export_" .. tostring(os.time()) .. ".txt"
pcall(function()
writefile(exportFile, exportStr)
addMessage("✅ Exported " .. #conversationHistory .. " turn(s) to: " .. exportFile, false, false, true)
saved = true
end)
end
-- نسخ للـ clipboard كبديل
if not saved and setclipboard then
local ok = pcall(function() setclipboard(exportStr) end)
if ok then
addMessage("📋 Copied " .. #conversationHistory .. " turn(s) to clipboard (no writefile available)", false, false, true)
saved = true
end
end
if not saved then
addMessage("⚠️ Export: writefile and setclipboard both unavailable in this executor.", false, false, true)
end
pushHistory("/export", "Exported " .. #conversationHistory .. " conversation turns")
return
end
if question:lower() == "/cleanup" or question:lower() == "/clearfiles" then
local cleaned = {}
if writefile and delfile then
pcall(function()
if isfile and isfile(LOG_FILE) then
delfile(LOG_FILE)
table.insert(cleaned, LOG_FILE)
end
end)
pcall(function()
local filesToDel = {}
if listfiles then
for _, f in ipairs(listfiles("/")) do
if type(f) == "string" and (f:find("SkillAI", 1, true) or f:find("skillai", 1, true)) then
table.insert(filesToDel, f)
end
end
end
for _, f in ipairs(filesToDel) do
pcall(function() delfile(f) end)
table.insert(cleaned, f)
end
end)
end
-- clean up test instances in workspace
local removedCount = 0
for _, obj in ipairs(Workspace:GetDescendants()) do
if obj.Name:find("SkillAI_Diag", 1, true) or obj.Name:find("SkillAI_Test", 1, true) then
pcall(function() obj:Destroy() end)
removedCount = removedCount + 1
end
end
local msg = "🧹 Cleanup done."
if #cleaned > 0 then
msg = msg .. " Deleted " .. #cleaned .. " file(s): " .. table.concat(cleaned, ", ")
else
msg = msg .. " No files to delete (writefile/delfile not available)."
end
if removedCount > 0 then
msg = msg .. " Destroyed " .. removedCount .. " leftover test instance(s)."
end
addMessage(msg, false, false, true)
return
end
inputBox.Text = ""
addMessage("🧑 " .. question, true)
CURRENT_REQUEST_TEXT = question
-- request queue: if already processing, add to queue
if _processingRequest then
table.insert(_requestQueue, question)
addMessage("⏳ Queued... (" .. #_requestQueue .. " waiting)", false, false, true)
return
end
_processingRequest = true
task.spawn(function() _runRequest(question) end)
end
sendBtn.MouseButton1Click:Connect(sendQuestion)
inputBox.FocusLost:Connect(function(enterPressed)
if enterPressed then sendQuestion() end
end)
task.spawn(function()
task.wait(1)
pcall(setupRespawnReRunner)
addMessage("🤖 Skill-AI v1.4\n📡 Model: " .. CURRENT_MODEL_LABEL .. "\n💡 /export لتصدير المحادثة | /clear | /undo | /find | /diag", false)
end)
print("[Skill-AI v1.4] Loaded | Timeout+Cache+SceneGC+FastRetry+HeightFix+BatchDelay fixes")
From: Telegram: @FreePalestine1970 full Ai model helping to your Roblox studio code And Open source code free you can do anything you want like update - upgrade you can do anything you want its open source code 🤍 and you can make him better from me Work just in Roblox studio lite It's like lemon ai but we make to Android and use Opencode ai and you need more model you need go to Nvidia and take api and open file and put it Of course it's not accurate; you need to provide a clear and complete text. It's quite normal for errors to occur; the same applies to other well-known models...
Studio Lite