
KeylessUniversal

--[[
VoidAI - Self Learning Roblox AI Companion
Version: 1.0.0
Author: Taz / TheStrongestOfTomorrow
Features:
- Observer Mode (learns movement, UI, sequences)
- Per-universe brain files with per-place entries
- Ghost Mode (client-side AI playback)
- Lend Mode (AI takes full input control)
- LLM Mode (Claude / ChatGPT / Gemini with brain context)
- GUI Panel with minimize to draggable button
- PC Keybinds (rebindable) + Mobile floating buttons
- Platform auto-detection
- Hotkey/tap reclaim control
- AI Confidence Meter
- Session Replay Viewer
- Persona Profiles
- Stats Dashboard
- Stealth Mode
- Schedule Mode
- Threat Detection Mode
- Mental Map
- Adaptive Learning Cooldown
- Place Transition Continuity
- Base Knowledge embedded
- Universal Executor Compatibility
- LLM gate (requires trained brain)
]]
-- ============================================================
-- SECTION 1: SERVICES
-- ============================================================
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")
local TeleportService = game:GetService("TeleportService")
local HttpService = game:GetService("HttpService")
local GuiService = game:GetService("GuiService")
local StarterGui = game:GetService("StarterGui")
local LocalPlayer = Players.LocalPlayer
local PlayerGui = LocalPlayer:WaitForChild("PlayerGui")
local Camera = workspace.CurrentCamera
-- ============================================================
-- SECTION 2: PLATFORM DETECTION
-- ============================================================
local isMobile = UserInputService.TouchEnabled and not UserInputService.KeyboardEnabled
local isPC = not isMobile
-- ============================================================
-- SECTION 3: EXECUTOR COMPATIBILITY LAYER
-- ============================================================
local Exec = {}
-- File system
function Exec.readFile(path)
if readfile then
local ok, result = pcall(readfile, path)
if ok then return result end
end
return nil
end
function Exec.writeFile(path, data)
if writefile then
pcall(writefile, path, data)
return true
end
return false
end
function Exec.appendFile(path, data)
if appendfile then
pcall(appendfile, path, data)
return true
elseif writefile then
local existing = Exec.readFile(path) or ""
pcall(writefile, path, existing .. data)
return true
end
return false
end
function Exec.isFile(path)
if isfile then
local ok, result = pcall(isfile, path)
return ok and result
elseif readfile then
local ok = pcall(readfile, path)
return ok
end
return false
end
function Exec.makeFolder(path)
if makefolder then
if isfolder then
local ok, exists = pcall(isfolder, path)
if not (ok and exists) then
pcall(makefolder, path)
end
else
pcall(makefolder, path)
end
end
end
function Exec.isFolder(path)
if isfolder then
local ok, result = pcall(isfolder, path)
return ok and result
end
return false
end
function Exec.listFiles(path)
if listfiles then
local ok, result = pcall(listfiles, path)
if ok then return result end
end
return {}
end
-- Input simulation fallback chain
function Exec.mouseClick()
if mouse1click then
pcall(mouse1click)
elseif Input and Input.click then
pcall(Input.click)
end
end
function Exec.mouseMove(x, y)
if mousemoveabs then
pcall(mousemoveabs, x, y)
elseif mousemoverel then
pcall(mousemoverel, x, y)
end
end
function Exec.keyPress(keycode)
if keypress then
pcall(keypress, keycode)
elseif Input and Input.keypress then
pcall(Input.keypress, keycode)
end
end
function Exec.keyRelease(keycode)
if keyrelease then
pcall(keyrelease, keycode)
elseif Input and Input.keyrelease then
pcall(Input.keyrelease, keycode)
end
end
function Exec.fireButton(button)
-- Universal UI click fallback - works on all executors + mobile
if button and button:IsA("GuiButton") then
pcall(function()
button:FireMouseButton1Click()
end)
end
end
-- HTTP for LLM calls
function Exec.httpPost(url, headers, body)
-- Try syn.request first (Synapse), then request (most executors), then HttpService
if syn and syn.request then
local ok, res = pcall(syn.request, {
Url = url, Method = "POST",
Headers = headers, Body = body
})
if ok then return res end
end
if request then
local ok, res = pcall(request, {
Url = url, Method = "POST",
Headers = headers, Body = body
})
if ok then return res end
end
if http and http.request then
local ok, res = pcall(http.request, {
Url = url, Method = "POST",
Headers = headers, Body = body
})
if ok then return res end
end
-- Fallback: HttpService (limited but better than nothing)
local ok, res = pcall(function()
return HttpService:RequestAsync({
Url = url, Method = "POST",
Headers = headers, Body = body
})
end)
if ok then return res end
return nil
end
-- ============================================================
-- SECTION 4: BASE KNOWLEDGE (EMBEDDED)
-- ============================================================
local BASE_KNOWLEDGE = {
version = "1.0",
pc = {
movement = {
forward = Enum.KeyCode.W,
backward = Enum.KeyCode.S,
left = Enum.KeyCode.A,
right = Enum.KeyCode.D,
jump = Enum.KeyCode.Space,
sprint = Enum.KeyCode.LeftShift,
},
camera = "MouseDelta",
},
mobile = {
movement = "ThumbstickPosition",
jump = "JumpButton",
camera = "TouchDelta",
},
commonUI = {
playButton = {"Play", "Start", "Begin", "PlayButton", "StartButton"},
exitButton = {"Exit", "Leave", "Quit", "Back"},
confirmButton= {"Confirm", "Yes", "OK", "Accept"},
shopButton = {"Shop", "Store", "Buy"},
},
horrorDefaults = {
threatKeywords = {
"Entity", "Monster", "Rush", "Ambush", "Halt",
"Eyes", "Figure", "Seek", "Screech", "Dupe",
"Jack", "Hide", "Timothy", "El Goblino",
},
safeObjects = {"Locker", "Bed", "Cabinet", "Closet"},
runTriggers = {"Roar", "Scream", "Chase"},
}
}
-- ============================================================
-- SECTION 5: BRAIN FILE SYSTEM
-- ============================================================
local BRAIN_FOLDER = "VoidAI"
local CONFIG_FILE = "VoidAI/config.json"
local Brain = {}
local BrainPath = ""
local ActivePlace = ""
local function getUniverseId()
local ok, id = pcall(function()
return game.GameId
end)
return ok and tostring(id) or tostring(game.PlaceId)
end
local function getPlaceId()
return tostring(game.PlaceId)
end
local function getGameName()
local ok, name = pcall(function()
return MarketplaceService:GetProductInfo(game.PlaceId).Name
end)
if ok and name then return name end
return "UnknownGame_" .. game.PlaceId
end
local function getBrainPath()
return BRAIN_FOLDER .. "/universe_" .. getUniverseId() .. ".json"
end
local function newPlaceEntry(placeName)
return {
placeName = placeName or ("Place_" .. getPlaceId()),
sessions = {},
mentalMap = {
landmarks = {},
safeZones = {},
dangerZones = {},
itemSpawns = {},
exits = {},
},
confidence = {
movement = 0,
ui = 0,
sequences = 0,
},
topUIButtons = {},
totalActions = 0,
totalPlaytime = 0,
lastUpdated = os.time(),
}
end
local function newBrainFile()
return {
version = "1.0",
universeId = getUniverseId(),
baseKnowledge = BASE_KNOWLEDGE,
places = {},
config = {
activeProvider = "local",
apiKeys = { claude = "", chatgpt = "", gemini = "" },
activePersona = "Mirror",
keybinds = {
reclaim = "F",
toggleObs = "G",
toggleLend= "H",
toggleGUI = "J",
},
scheduleEnabled = false,
scheduleStartMin = 0,
scheduleDuration = 30,
stealthMode = false,
},
totalSessions = 0,
createdAt = os.time(),
lastUpdated = os.time(),
}
end
local function saveBrain()
if not Brain or not BrainPath or BrainPath == "" then return end
Brain.lastUpdated = os.time()
local ok, encoded = pcall(HttpService.JSONEncode, HttpService, Brain)
if ok then
Exec.writeFile(BrainPath, encoded)
end
end
local function loadBrain()
Exec.makeFolder(BRAIN_FOLDER)
BrainPath = getBrainPath()
ActivePlace = getPlaceId()
if Exec.isFile(BrainPath) then
local raw = Exec.readFile(BrainPath)
if raw and raw ~= "" then
local ok, decoded = pcall(HttpService.JSONDecode, HttpService, raw)
if ok and decoded then
Brain = decoded
-- Ensure base knowledge is always up to date
Brain.baseKnowledge = BASE_KNOWLEDGE
-- Ensure this place entry exists
if not Brain.places[ActivePlace] then
Brain.places[ActivePlace] = newPlaceEntry()
end
return true
end
end
end
-- New brain
Brain = newBrainFile()
Brain.places[ActivePlace] = newPlaceEntry()
saveBrain()
return false
end
local function getCurrentPlace()
if Brain and Brain.places and Brain.places[ActivePlace] then
return Brain.places[ActivePlace]
end
return nil
end
-- ============================================================
-- SECTION 6: CONFIG SYSTEM
-- ============================================================
local Config = {}
local function loadConfig()
if Exec.isFile(CONFIG_FILE) then
local raw = Exec.readFile(CONFIG_FILE)
if raw and raw ~= "" then
local ok, decoded = pcall(HttpService.JSONDecode, HttpService, raw)
if ok then
Config = decoded
return
end
end
end
Config = {
apiKeys = { claude = "", chatgpt = "", gemini = "" },
activeProvider= "local",
activePersona = "Mirror",
stealthMode = false,
keybinds = {
reclaim = "F",
toggleObs = "G",
toggleLend = "H",
toggleGUI = "J",
}
}
end
local function saveConfig()
local ok, encoded = pcall(HttpService.JSONEncode, HttpService, Config)
if ok then
Exec.writeFile(CONFIG_FILE, encoded)
end
end
-- ============================================================
-- SECTION 7: ADAPTIVE COOLDOWN + FPS MONITOR
-- ============================================================
local FPS = 60
local LearnCooldown = 0.1
local lastFPSUpdate = 0
local frameCount = 0
local function updateFPS(dt)
frameCount += 1
lastFPSUpdate += dt
if lastFPSUpdate >= 1 then
FPS = frameCount
frameCount = 0
lastFPSUpdate = 0
-- Adaptive cooldown based on FPS
if FPS > 50 then
LearnCooldown = 0.1 -- High end: fast learning
elseif FPS >= 30 then
LearnCooldown = 0.3 -- Mid range: balanced
elseif FPS >= 15 then
LearnCooldown = 0.7 -- Low end: stable
else
LearnCooldown = 1.5 -- Potato mode: survive
end
end
end
-- ============================================================
-- SECTION 8: AI STATE
-- ============================================================
local AIState = {
mode = "idle", -- idle | observing | ghost | lend | llm
isLearning = false,
isPaused = false,
isStealth = false,
persona = "Mirror", -- Mirror | Aggressive | Passive
currentSession = nil,
sessionStart = 0,
lastLearnTick = 0,
lastSaveTick = 0,
tickCounter = 0, -- For throttling low-priority sampling
llmProvider = "gemini",
llmCooldown = 0,
threatDetected = false,
threatCooldown = 0,
reclaimPending = false,
scheduleTimer = 0,
scheduleActive = false,
}
local PERSONAS = {
Mirror = { aggressionBias = 0.5, grindBias = 0.5, explorationBias = 0.5 },
Aggressive = { aggressionBias = 0.9, grindBias = 0.3, explorationBias = 0.7 },
Passive = { aggressionBias = 0.1, grindBias = 0.9, explorationBias = 0.3 },
}
-- ============================================================
-- SECTION 9: OBSERVER - DATA RECORDING
-- ============================================================
local Observer = {}
Observer.lastPosition = Vector3.new(0,0,0)
Observer.pendingBatch = {}
Observer.batchFlushInterval = 30 -- seconds
function Observer.startSession()
AIState.currentSession = {
id = os.time(),
placeId = getPlaceId(),
starred = false,
priority = 1,
actions = {},
movements = {},
uiClicks = {},
sequences = {},
startTime = os.time(),
endTime = nil,
notes = "",
}
AIState.sessionStart = os.time()
end
function Observer.endSession()
if not AIState.currentSession then return end
AIState.currentSession.endTime = os.time()
local place = getCurrentPlace()
if place then
table.insert(place.sessions, AIState.currentSession)
place.totalPlaytime += (os.time() - AIState.sessionStart)
Observer.updateConfidence(place)
saveBrain()
end
AIState.currentSession = nil
end
function Observer.recordMovement(pos)
if not AIState.currentSession then return end
local delta = (pos - Observer.lastPosition).Magnitude
if delta < 2 then return end -- Ignore micro-movements
Observer.lastPosition = pos
local entry = {
pos = { x = pos.X, y = pos.Y, z = pos.Z },
t = os.time() - AIState.sessionStart,
}
table.insert(AIState.currentSession.movements, entry)
table.insert(AIState.currentSession.actions, { type = "move", data = entry })
-- Update mental map
Observer.updateMentalMap(pos)
end
function Observer.recordUIClick(buttonName, buttonPath)
if not AIState.currentSession then return end
local entry = {
name = buttonName,
path = buttonPath or "",
t = os.time() - AIState.sessionStart,
}
table.insert(AIState.currentSession.uiClicks, entry)
table.insert(AIState.currentSession.actions, { type = "ui_click", data = entry })
-- Update top UI buttons tally
local place = getCurrentPlace()
if place then
place.topUIButtons[buttonName] = (place.topUIButtons[buttonName] or 0) + 1
place.totalActions += 1
end
end
function Observer.recordSequence(label)
if not AIState.currentSession then return end
table.insert(AIState.currentSession.sequences, {
label = label,
t = os.time() - AIState.sessionStart,
actionIndex = #AIState.currentSession.actions,
})
end
function Observer.updateConfidence(place)
local sessions = place.sessions
local n = #sessions
if n == 0 then return end
local totalMoves, totalUI, totalSeq = 0, 0, 0
for _, s in ipairs(sessions) do
totalMoves += #(s.movements or {})
totalUI += #(s.uiClicks or {})
totalSeq += #(s.sequences or {})
end
-- Confidence grows logarithmically, capped at 100
local moveCap = math.min(100, math.floor(math.log(totalMoves + 1) * 15))
local uiCap = math.min(100, math.floor(math.log(totalUI + 1) * 20))
local seqCap = math.min(100, math.floor(math.log(totalSeq + 1) * 25))
place.confidence.movement = moveCap
place.confidence.ui = uiCap
place.confidence.sequences = seqCap
end
function Observer.updateMentalMap(pos)
local place = getCurrentPlace()
if not place then return end
-- Only update every 10 ticks (very low priority)
if AIState.tickCounter % 10 ~= 0 then return end
local map = place.mentalMap
local posKey = math.floor(pos.X/10) .. "_" .. math.floor(pos.Z/10)
if not map.landmarks[posKey] then
map.landmarks[posKey] = {
pos = { x = pos.X, y = pos.Y, z = pos.Z },
tag = "visited",
visits= 1,
}
else
map.landmarks[posKey].visits += 1
-- High visit count = likely safe zone
if map.landmarks[posKey].visits > 5 then
map.landmarks[posKey].tag = "safe"
end
end
end
function Observer.tagCurrentZone(tag)
local char = LocalPlayer.Character
if not char then return end
local hrp = char:FindFirstChild("HumanoidRootPart")
if not hrp then return end
local pos = hrp.Position
local place = getCurrentPlace()
if not place then return end
local posKey = math.floor(pos.X/10) .. "_" .. math.floor(pos.Z/10)
if not place.mentalMap.landmarks[posKey] then
place.mentalMap.landmarks[posKey] = {
pos = { x = pos.X, y = pos.Y, z = pos.Z },
tag = tag, visits = 1,
}
else
place.mentalMap.landmarks[posKey].tag = tag
end
end
-- ============================================================
-- SECTION 10: THREAT DETECTION
-- ============================================================
local ThreatDetector = {}
function ThreatDetector.scan()
local place = getCurrentPlace()
local keywords = BASE_KNOWLEDGE.horrorDefaults.threatKeywords
-- Extend with any game-specific keywords from brain
if place and place.customThreatKeywords then
for _, kw in ipairs(place.customThreatKeywords) do
table.insert(keywords, kw)
end
end
-- Scan workspace for threat entities by name
for _, obj in ipairs(workspace:GetDescendants()) do
local name = obj.Name:lower()
for _, kw in ipairs(keywords) do
if name:find(kw:lower()) then
return true, obj.Name
end
end
end
-- Scan for players sprinting (3+ players suddenly running = threat)
local runningCount = 0
for _, p in ipairs(Players:GetPlayers()) do
if p ~= LocalPlayer and p.Character then
local hum = p.Character:FindFirstChildOfClass("Humanoid")
if hum and hum.WalkSpeed > 20 then
runningCount += 1
end
end
end
if runningCount >= 2 then
return true, "MultiPlayerFlee"
end
return false, nil
end
function ThreatDetector.respondToThreat(threatName)
AIState.threatDetected = true
AIState.threatCooldown = 8 -- seconds of threat mode
-- Tag current zone as danger
Observer.tagCurrentZone("danger")
-- Log threat in session
if AIState.currentSession then
table.insert(AIState.currentSession.actions, {
type = "threat_detected",
data = { name = threatName, t = os.time() - AIState.sessionStart }
})
end
-- If in lend/ghost mode, switch to survival behavior
if AIState.mode == "lend" or AIState.mode == "ghost" then
GhostController.runSurvivalPattern()
end
end
-- ============================================================
-- SECTION 11: GHOST / LEND CONTROLLER
-- ============================================================
local GhostController = {}
GhostController.replayIndex = 1
GhostController.replaySession = nil
GhostController.inputDisabled = false
function GhostController.pickBestSession()
local place = getCurrentPlace()
if not place or #place.sessions == 0 then return nil end
-- Prioritize starred sessions, then by action count
local best = nil
local bestScore = -1
for _, s in ipairs(place.sessions) do
local score = #(s.actions or {}) * (s.priority or 1)
if s.starred then score = score * 2 end
if score > bestScore then
bestScore = score
best = s
end
end
return best
end
-- Ghost avatar reference
GhostController.ghostModel = nil
function GhostController.spawnGhostAvatar()
-- Destroy old ghost if any
if GhostController.ghostModel then
pcall(function() GhostController.ghostModel:Destroy() end)
GhostController.ghostModel = nil
end
local char = LocalPlayer.Character
if not char then return end
-- Clone the player's character model
local ghost = char:Clone()
ghost.Name = "VoidAI_Ghost"
-- Remove scripts, animate, humanoid state changes that could error
for _, v in ipairs(ghost:GetDescendants()) do
if v:IsA("Script") or v:IsA("LocalScript") or v:IsA("ModuleScript") then
v:Destroy()
end
end
-- Make every part semi-transparent ghostly blue
for _, part in ipairs(ghost:GetDescendants()) do
if part:IsA("BasePart") then
part.Transparency = 0.55
part.CanCollide = false
part.Anchored = false
part.CastShadow = false
-- Tint blue-white
part.Color = Color3.fromRGB(140, 180, 255)
part.Material = Enum.Material.Neon
end
-- Hide accessories/hats being weird
if part:IsA("Decal") or part:IsA("SpecialMesh") then
-- keep them, just let transparency handle it
end
end
-- Put the ghost near the player
local hrp = char:FindFirstChild("HumanoidRootPart")
local ghostHRP = ghost:FindFirstChild("HumanoidRootPart")
if hrp and ghostHRP then
ghostHRP.CFrame = hrp.CFrame * CFrame.new(3, 0, 0)
end
-- Give it a name tag
local billboard = Instance.new("BillboardGui")
billboard.Size = UDim2.new(0, 80, 0, 20)
billboard.StudsOffset = Vector3.new(0, 3, 0)
billboard.AlwaysOnTop = false
billboard.Parent = ghostHRP or ghost.PrimaryPart or ghost:FindFirstChildOfClass("BasePart")
local tag = Instance.new("TextLabel")
tag.Size = UDim2.new(1,0,1,0)
tag.BackgroundTransparency = 1
tag.Text = "👻 VoidAI"
tag.TextColor3 = Color3.fromRGB(160, 200, 255)
tag.Font = Enum.Font.GothamBold
tag.TextSize = 11
tag.Parent = billboard
ghost.Parent = workspace
GhostController.ghostModel = ghost
return ghost
end
function GhostController.destroyGhostAvatar()
if GhostController.ghostModel then
pcall(function() GhostController.ghostModel:Destroy() end)
GhostController.ghostModel = nil
end
end
function GhostController.startGhost()
GhostController.replaySession = GhostController.pickBestSession()
if not GhostController.replaySession then
VoidGUI.notify("No sessions to replay. Train the AI first!")
return false
end
GhostController.replayIndex = 1
AIState.mode = "ghost"
-- Spawn the ghost avatar
GhostController.spawnGhostAvatar()
VoidGUI.notify("👻 Ghost spawned!")
return true
end
function GhostController.startLend()
GhostController.replaySession = GhostController.pickBestSession()
if not GhostController.replaySession then
VoidGUI.notify("No sessions to replay. Train the AI first!")
return false
end
GhostController.replayIndex = 1
GhostController.inputDisabled = true
AIState.mode = "lend"
-- Disable player input
if isMobile then
-- On mobile we directly control HumanoidRootPart, nothing to disable
else
-- Block keyboard/mouse by consuming input
UserInputService.InputBegan:Connect(function(input, processed)
if GhostController.inputDisabled and not AIState.reclaimPending then
-- Allow reclaim key through
end
end)
end
return true
end
function GhostController.reclaim()
GhostController.inputDisabled = false
AIState.mode = "idle"
AIState.reclaimPending = true
GhostController.destroyGhostAvatar()
VoidGUI.notify("Control reclaimed!")
task.wait(0.1)
AIState.reclaimPending = false
end
function GhostController.stepReplay()
if not GhostController.replaySession then return end
local actions = GhostController.replaySession.actions
if not actions or #actions == 0 then return end
if GhostController.replayIndex > #actions then
GhostController.replayIndex = 1 -- Loop
return
end
local action = actions[GhostController.replayIndex]
GhostController.replayIndex += 1
if action.type == "move" then
GhostController.executeMove(action.data)
elseif action.type == "ui_click" then
GhostController.executeUIClick(action.data)
end
end
function GhostController.executeMove(data)
local targetPos = Vector3.new(data.pos.x, data.pos.y, data.pos.z)
-- In ghost mode: move the ghost avatar, NOT the real player
if AIState.mode == "ghost" and GhostController.ghostModel then
local ghost = GhostController.ghostModel
local ghostHRP = ghost:FindFirstChild("HumanoidRootPart")
local ghostHum = ghost:FindFirstChildOfClass("Humanoid")
if ghostHRP and ghostHum then
local speed = 16
if AIState.persona == "Aggressive" then speed = 24
elseif AIState.persona == "Passive" then speed = 14 end
ghostHum.WalkSpeed = speed
ghostHum:MoveTo(targetPos)
elseif ghostHRP then
-- Fallback: lerp CFrame if humanoid is gone
ghostHRP.CFrame = CFrame.new(targetPos)
end
return
end
-- Lend / LLM mode: move the real character
local char = LocalPlayer.Character
if not char then return end
local hrp = char:FindFirstChild("HumanoidRootPart")
local hum = char:FindFirstChildOfClass("Humanoid")
if not hrp or not hum then return end
if AIState.persona == "Aggressive" then
hum.WalkSpeed = 24
elseif AIState.persona == "Passive" then
hum.WalkSpeed = 14
else
hum.WalkSpeed = 16
end
hum:MoveTo(targetPos)
end
function GhostController.executeUIClick(data)
-- Search PlayerGui for button by name
local function searchGUI(parent, targetName)
for _, child in ipairs(parent:GetDescendants()) do
if child:IsA("GuiButton") and child.Name == targetName then
return child
end
end
return nil
end
local button = searchGUI(PlayerGui, data.name)
if button then
Exec.fireButton(button)
end
end
function GhostController.runSurvivalPattern()
local char = LocalPlayer.Character
if not char then return end
local hrp = char:FindFirstChild("HumanoidRootPart")
local hum = char:FindFirstChildOfClass("Humanoid")
if not hrp or not hum then return end
-- Find nearest tagged safe zone from mental map
local place = getCurrentPlace()
if not place then return end
local nearest = nil
local nearestDist = math.huge
for _, landmark in pairs(place.mentalMap.landmarks) do
if landmark.tag == "safe" then
local lpos = Vector3.new(landmark.pos.x, landmark.pos.y, landmark.pos.z)
local dist = (hrp.Position - lpos).Magnitude
if dist < nearestDist then
nearestDist = dist
nearest = lpos
end
end
end
if nearest then
hum.WalkSpeed = 24 -- Sprint to safety
hum:MoveTo(nearest)
else
-- No safe zone known: run away from danger direction
hum.WalkSpeed = 24
local runDir = hrp.CFrame.LookVector * -1
hum:MoveTo(hrp.Position + runDir * 20)
end
end
-- ============================================================
-- SECTION 12: LLM CONTROLLER
-- ============================================================
local LLMController = {}
LLMController.isWaiting = false
LLMController.callCooldown = 3 -- seconds between LLM calls
local LLM_PROVIDERS = {
claude = {
url = "https://api.anthropic.com/v1/messages",
model = "claude-opus-4-5",
buildHeaders = function(key)
return {
["Content-Type"] = "application/json",
["x-api-key"] = key,
["anthropic-version"] = "2023-06-01",
}
end,
buildBody = function(prompt)
return HttpService:JSONEncode({
model = "claude-opus-4-5",
max_tokens = 512,
messages = {{ role = "user", content = prompt }}
})
end,
parseResponse = function(body)
local ok, decoded = pcall(HttpService.JSONDecode, HttpService, body)
if ok and decoded.content and decoded.content[1] then
return decoded.content[1].text
end
return nil
end,
},
chatgpt = {
url = "https://api.openai.com/v1/chat/completions",
model = "gpt-4o",
buildHeaders = function(key)
return {
["Content-Type"] = "application/json",
["Authorization"] = "Bearer " .. key,
}
end,
buildBody = function(prompt)
return HttpService:JSONEncode({
model = "gpt-4o",
messages = {{ role = "user", content = prompt }},
max_tokens = 512,
})
end,
parseResponse = function(body)
local ok, decoded = pcall(HttpService.JSONDecode, HttpService, body)
if ok and decoded.choices and decoded.choices[1] then
return decoded.choices[1].message.content
end
return nil
end,
},
gemini = {
url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent",
model = "gemini-pro",
buildHeaders = function(key)
return {
["Content-Type"] = "application/json",
["x-goog-api-key"]= key,
}
end,
buildBody = function(prompt)
return HttpService:JSONEncode({
contents = {{ parts = {{ text = prompt }} }}
})
end,
parseResponse = function(body)
local ok, decoded = pcall(HttpService.JSONDecode, HttpService, body)
if ok and decoded.candidates and decoded.candidates[1] then
local parts = decoded.candidates[1].content.parts
if parts and parts[1] then
return parts[1].text
end
end
return nil
end,
},
}
function LLMController.canUseLLM()
local place = getCurrentPlace()
if not place then return false, "No brain loaded" end
if not place.sessions or #place.sessions == 0 then
return false, "No sessions trained for this game"
end
local totalActions = 0
for _, s in ipairs(place.sessions) do
totalActions += #(s.actions or {})
end
if totalActions == 0 then
return false, "Sessions exist but no actions recorded"
end
local provider = Config.activeProvider
if provider == "local" then return false, "No LLM provider selected" end
local key = Config.apiKeys[provider]
if not key or key == "" then
return false, "No API key for " .. provider
end
return true, nil
end
function LLMController.buildPrompt(gameState)
local place = getCurrentPlace()
if not place then return nil end
local safeZones, dangerZones = {}, {}
for _, lm in pairs(place.mentalMap.landmarks) do
if lm.tag == "safe" then
table.insert(safeZones, string.format("(%.0f,%.0f,%.0f)", lm.pos.x, lm.pos.y, lm.pos.z))
elseif lm.tag == "danger" then
table.insert(dangerZones, string.format("(%.0f,%.0f,%.0f)", lm.pos.x, lm.pos.y, lm.pos.z))
end
end
local topUI = {}
for name, count in pairs(place.topUIButtons) do
table.insert(topUI, name .. "(" .. count .. "x)")
end
return string.format([[
You are an AI controlling a Roblox character in %s.
You have been trained by a human player and know this game.
GAME KNOWLEDGE:
- Sessions recorded: %d
- Total actions learned: %d
- Known safe zones: %s
- Known danger zones: %s
- Frequently clicked UI: %s
- Confidence: Movement %d%%, UI %d%%, Sequences %d%%
- Active persona: %s
CURRENT SITUATION:
- Character position: (%.1f, %.1f, %.1f)
- Nearby players: %d
- Threat detected: %s
- Current mode: %s
Respond ONLY with a JSON object. No explanation. No markdown. Just raw JSON.
Use this exact format:
{"action":"move","direction":"forward","duration":1.0,"next":null}
Available actions: move, jump, sprint, click_ui, wait, hide, run_to_safety
For click_ui, add "target":"ButtonName"
For move, direction can be: forward, backward, left, right, or "to_safe_zone"
]],
place.placeName,
#place.sessions,
place.totalActions,
table.concat(safeZones, ", "),
table.concat(dangerZones, ", "),
table.concat(topUI, ", "),
place.confidence.movement,
place.confidence.ui,
place.confidence.sequences,
AIState.persona,
gameState.pos.x, gameState.pos.y, gameState.pos.z,
gameState.nearbyPlayers,
tostring(gameState.threatDetected),
AIState.mode
)
end
function LLMController.parseAction(responseText)
if not responseText then return nil end
-- Strip any accidental markdown
local clean = responseText:gsub("```json", ""):gsub("```", ""):match("^%s*(.-)%s*$")
local ok, action = pcall(HttpService.JSONDecode, HttpService, clean)
if ok and action and action.action then
return action
end
return nil
end
function LLMController.executeAction(action)
if not action then return end
local char = LocalPlayer.Character
if not char then return end
local hrp = char:FindFirstChild("HumanoidRootPart")
local hum = char:FindFirstChildOfClass("Humanoid")
if action.action == "move" then
if hrp and hum then
local dir = action.direction
local moveVec = Vector3.new(0,0,0)
if dir == "forward" then moveVec = hrp.CFrame.LookVector
elseif dir == "backward" then moveVec = hrp.CFrame.LookVector * -1
elseif dir == "left" then moveVec = hrp.CFrame.RightVector * -1
elseif dir == "right" then moveVec = hrp.CFrame.RightVector
elseif dir == "to_safe_zone" then
GhostController.runSurvivalPattern()
return
end
hum:MoveTo(hrp.Position + moveVec * 10)
end
elseif action.action == "jump" then
if hum then hum.Jump = true end
elseif action.action == "sprint" then
if hum then hum.WalkSpeed = 24 end
elseif action.action == "click_ui" then
GhostController.executeUIClick({ name = action.target or "" })
elseif action.action == "hide" then
-- Find nearest safe object (locker/cabinet)
for _, obj in ipairs(workspace:GetDescendants()) do
for _, safeObj in ipairs(BASE_KNOWLEDGE.horrorDefaults.safeObjects) do
if obj.Name:find(safeObj) and obj:IsA("BasePart") then
if hum then hum:MoveTo(obj.Position) end
return
end
end
end
elseif action.action == "run_to_safety" then
GhostController.runSurvivalPattern()
elseif action.action == "wait" then
task.wait(action.duration or 1)
end
end
function LLMController.callLLM()
if LLMController.isWaiting then return end
local canUse, reason = LLMController.canUseLLM()
if not canUse then
VoidGUI.notify("LLM unavailable: " .. (reason or "unknown"))
AIState.mode = "idle"
return
end
local char = LocalPlayer.Character
local hrp = char and char:FindFirstChild("HumanoidRootPart")
local pos = hrp and hrp.Position or Vector3.new(0,0,0)
local nearby = 0
for _, p in ipairs(Players:GetPlayers()) do
if p ~= LocalPlayer and p.Character then
local phrp = p.Character:FindFirstChild("HumanoidRootPart")
if phrp and (phrp.Position - pos).Magnitude < 50 then
nearby += 1
end
end
end
local gameState = {
pos = { x = pos.X, y = pos.Y, z = pos.Z },
nearbyPlayers = nearby,
threatDetected = AIState.threatDetected,
}
local prompt = LLMController.buildPrompt(gameState)
if not prompt then return end
local provider = Config.activeProvider
local prov = LLM_PROVIDERS[provider]
if not prov then return end
LLMController.isWaiting = true
task.spawn(function()
local headers = prov.buildHeaders(Config.apiKeys[provider])
local body = prov.buildBody(prompt)
local response = Exec.httpPost(prov.url, headers, body)
if response and response.Body then
local text = prov.parseResponse(response.Body)
local action = LLMController.parseAction(text)
if action then
LLMController.executeAction(action)
else
-- Fallback to local AI
GhostController.stepReplay()
end
else
-- No response: fallback to local
GhostController.stepReplay()
end
LLMController.isWaiting = false
end)
end
-- ============================================================
-- SECTION 13: PLACE TRANSITION HANDLING
-- ============================================================
local function handlePlaceTransition()
-- Save current session before transition
if AIState.isLearning and AIState.currentSession then
Observer.endSession()
end
saveBrain()
-- Wait for new place to load
task.wait(2)
-- Re-initialize brain for new place
local newPlaceId = getPlaceId()
ActivePlace = newPlaceId
if not Brain.places[ActivePlace] then
Brain.places[ActivePlace] = newPlaceEntry()
end
if AIState.isLearning then
Observer.startSession()
end
VoidGUI.notify("Transitioned to new place. Brain updated.")
VoidGUI.updateDisplay()
end
-- Listen for teleport arrivals
pcall(function()
TeleportService.LocalPlayerArrivedFromTeleport:Connect(function()
handlePlaceTransition()
end)
end)
-- ============================================================
-- SECTION 14: UI EVENT HOOKS (Event-driven, zero polling cost)
-- ============================================================
local UIHooks = {}
local function hookUIClicks(parent)
for _, obj in ipairs(parent:GetDescendants()) do
if obj:IsA("GuiButton") then
obj.MouseButton1Click:Connect(function()
if AIState.isLearning and AIState.currentSession then
Observer.recordUIClick(obj.Name, obj:GetFullName())
end
end)
end
end
parent.DescendantAdded:Connect(function(obj)
if obj:IsA("GuiButton") then
obj.MouseButton1Click:Connect(function()
if AIState.isLearning and AIState.currentSession then
Observer.recordUIClick(obj.Name, obj:GetFullName())
end
end)
end
end)
end
-- Hook existing GUI
task.spawn(function()
task.wait(2)
for _, gui in ipairs(PlayerGui:GetChildren()) do
if gui:IsA("ScreenGui") and gui.Name ~= "VoidAIGui" then
hookUIClicks(gui)
end
end
PlayerGui.ChildAdded:Connect(function(gui)
if gui:IsA("ScreenGui") and gui.Name ~= "VoidAIGui" then
hookUIClicks(gui)
end
end)
end)
-- ============================================================
-- SECTION 15: KEYBIND SYSTEM (PC + Rebindable)
-- ============================================================
local Keybinds = {}
Keybinds.binds = {
reclaim = Enum.KeyCode.F,
toggleObs = Enum.KeyCode.G,
toggleLend = Enum.KeyCode.H,
toggleGUI = Enum.KeyCode.J,
}
local KEY_MAP = {
F = Enum.KeyCode.F, G = Enum.KeyCode.G, H = Enum.KeyCode.H,
J = Enum.KeyCode.J, K = Enum.KeyCode.K, L = Enum.KeyCode.L,
Z = Enum.KeyCode.Z, X = Enum.KeyCode.X, C = Enum.KeyCode.C,
V = Enum.KeyCode.V, B = Enum.KeyCode.B, N = Enum.KeyCode.N,
M = Enum.KeyCode.M, R = Enum.KeyCode.R, T = Enum.KeyCode.T,
Y = Enum.KeyCode.Y, U = Enum.KeyCode.U, I = Enum.KeyCode.I,
O = Enum.KeyCode.O, P = Enum.KeyCode.P,
}
function Keybinds.setKey(action, keyName)
if KEY_MAP[keyName] then
Keybinds.binds[action] = KEY_MAP[keyName]
if Config.keybinds then
Config.keybinds[action] = keyName
saveConfig()
end
end
end
if not isMobile then
UserInputService.InputBegan:Connect(function(input, processed)
if processed then return end
if input.KeyCode == Keybinds.binds.reclaim then
if AIState.mode == "lend" or AIState.mode == "ghost" or AIState.mode == "llm" then
GhostController.reclaim()
VoidGUI.updateDisplay()
end
elseif input.KeyCode == Keybinds.binds.toggleObs then
if AIState.isLearning then
AIState.isLearning = false
Observer.endSession()
VoidGUI.notify("Learning stopped")
else
AIState.isLearning = true
Observer.startSession()
VoidGUI.notify("Learning started")
end
VoidGUI.updateDisplay()
elseif input.KeyCode == Keybinds.binds.toggleLend then
if AIState.mode == "lend" then
GhostController.reclaim()
else
GhostController.startLend()
end
VoidGUI.updateDisplay()
elseif input.KeyCode == Keybinds.binds.toggleGUI then
VoidGUI.toggle()
end
end)
end
-- ============================================================
-- SECTION 16: SCHEDULE MODE
-- ============================================================
local function updateSchedule(dt)
if not Config.scheduleEnabled then return end
AIState.scheduleTimer += dt
local startSec = (Config.scheduleStartMin or 0) * 60
local durSec = (Config.scheduleDuration or 30) * 60
if not AIState.scheduleActive and AIState.scheduleTimer >= startSec then
AIState.scheduleActive = true
GhostController.startLend()
VoidGUI.notify("Schedule: Lend Mode started")
end
if AIState.scheduleActive and AIState.scheduleTimer >= startSec + durSec then
AIState.scheduleActive = false
GhostController.reclaim()
AIState.scheduleTimer = 0
VoidGUI.notify("Schedule: Session complete")
end
end
-- ============================================================
-- SECTION 17: MAIN LOOP
-- ============================================================
local lastLearnTick = 0
local lastSaveTick = 0
local lastThreatTick= 0
local lastLLMTick = 0
RunService.Heartbeat:Connect(function(dt)
updateFPS(dt)
updateSchedule(dt)
AIState.tickCounter += 1
-- Threat detection (always runs, every 0.5s)
lastThreatTick += dt
if lastThreatTick >= 0.5 then
lastThreatTick = 0
local isThreat, tName = ThreatDetector.scan()
if isThreat and not AIState.threatDetected then
ThreatDetector.respondToThreat(tName)
end
if AIState.threatCooldown > 0 then
AIState.threatCooldown -= 0.5
if AIState.threatCooldown <= 0 then
AIState.threatDetected = false
end
end
end
-- Observer learning tick (adaptive cooldown)
lastLearnTick += dt
if lastLearnTick >= LearnCooldown then
lastLearnTick = 0
if AIState.isLearning and AIState.currentSession then
local char = LocalPlayer.Character
if char then
local hrp = char:FindFirstChild("HumanoidRootPart")
if hrp then
Observer.recordMovement(hrp.Position)
end
end
end
end
-- Ghost/Lend step (every 0.2s)
if AIState.mode == "ghost" or AIState.mode == "lend" then
lastLLMTick += dt
if lastLLMTick >= 0.2 then
lastLLMTick = 0
if not AIState.isPaused then
GhostController.stepReplay()
end
end
end
-- LLM step (every LLMController.callCooldown seconds)
if AIState.mode == "llm" then
lastLLMTick += dt
if lastLLMTick >= LLMController.callCooldown then
lastLLMTick = 0
if not AIState.isPaused then
LLMController.callLLM()
end
end
end
-- Batch save (every 30s)
lastSaveTick += dt
if lastSaveTick >= 30 then
lastSaveTick = 0
if AIState.isLearning then
saveBrain()
end
end
end)
-- ============================================================
-- SECTION 18: GUI SYSTEM
-- ============================================================
VoidGUI = {}
VoidGUI.isOpen = true
VoidGUI.tab = "main" -- main | stats | replay | settings
local screenGui, mainFrame, miniButton
local COLORS = {
bg = Color3.fromRGB(12, 12, 18),
panel = Color3.fromRGB(18, 18, 28),
accent = Color3.fromRGB(88, 101, 242),
accentHot = Color3.fromRGB(120, 130, 255),
text = Color3.fromRGB(220, 220, 235),
textDim = Color3.fromRGB(120, 120, 145),
success = Color3.fromRGB(87, 242, 135),
warning = Color3.fromRGB(254, 231, 92),
danger = Color3.fromRGB(237, 66, 69),
border = Color3.fromRGB(35, 35, 55),
}
local MODE_ICONS = {
idle = "🌙",
observing = "🧠",
ghost = "👻",
lend = "🎮",
llm = "🤖",
paused = "⏸",
stealth = "🔇",
}
local function makeDraggable(frame, handle)
local dragging, dragStart, startPos
handle = handle or frame
handle.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1
or input.UserInputType == Enum.UserInputType.Touch then
dragging = true
dragStart = input.Position
startPos = frame.Position
end
end)
-- Use UserInputService so the drag never reaches Roblox camera
UserInputService.InputChanged:Connect(function(input)
if not dragging then return end
if input.UserInputType == Enum.UserInputType.MouseMovement or
input.UserInputType == Enum.UserInputType.Touch then
local delta = input.Position - dragStart
frame.Position = UDim2.new(
startPos.X.Scale,
startPos.X.Offset + delta.X,
startPos.Y.Scale,
startPos.Y.Offset + delta.Y
)
end
end)
UserInputService.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1
or input.UserInputType == Enum.UserInputType.Touch then
dragging = false
end
end)
end
local function makeButton(parent, text, size, pos, color)
local btn = Instance.new("TextButton")
btn.Size = size
btn.Position = pos
btn.BackgroundColor3 = color or COLORS.accent
btn.TextColor3 = COLORS.text
btn.Text = text
btn.Font = Enum.Font.GothamBold
btn.TextSize = isMobile and 13 or 12
btn.BorderSizePixel = 0
btn.AutoButtonColor = false
btn.Parent = parent
local corner = Instance.new("UICorner")
corner.CornerRadius = UDim.new(0, 6)
corner.Parent = btn
-- Hover effect
btn.MouseEnter:Connect(function()
TweenService:Create(btn, TweenInfo.new(0.15), {BackgroundColor3 = COLORS.accentHot}):Play()
end)
btn.MouseLeave:Connect(function()
TweenService:Create(btn, TweenInfo.new(0.15), {BackgroundColor3 = color or COLORS.accent}):Play()
end)
return btn
end
local function makeLabel(parent, text, size, pos, textSize, color)
local lbl = Instance.new("TextLabel")
lbl.Size = size
lbl.Position = pos
lbl.BackgroundTransparency = 1
lbl.TextColor3 = color or COLORS.text
lbl.Text = text
lbl.Font = Enum.Font.Gotham
lbl.TextSize = textSize or 12
lbl.TextXAlignment = Enum.TextXAlignment.Left
lbl.Parent = parent
return lbl
end
local function makeBar(parent, pos, value, color)
local track = Instance.new("Frame")
track.Size = UDim2.new(1, -12, 0, 6)
track.Position = pos
track.BackgroundColor3= COLORS.border
track.BorderSizePixel = 0
track.Parent = parent
local tc = Instance.new("UICorner")
tc.CornerRadius = UDim.new(0,3)
tc.Parent = track
local fill = Instance.new("Frame")
fill.Size = UDim2.new(value/100, 0, 1, 0)
fill.BackgroundColor3 = color or COLORS.accent
fill.BorderSizePixel = 0
fill.Parent = track
local fc = Instance.new("UICorner")
fc.CornerRadius = UDim.new(0,3)
fc.Parent = fill
return track, fill
end
function VoidGUI.notify(msg)
if AIState.isStealth then return end
-- Simple toast notification
local toast = Instance.new("TextLabel")
toast.Size = UDim2.new(0, 220, 0, 32)
toast.Position = UDim2.new(0.5, -110, 0, 60)
toast.BackgroundColor3 = COLORS.panel
toast.TextColor3 = COLORS.text
toast.Text = msg
toast.Font = Enum.Font.Gotham
toast.TextSize = 12
toast.BorderSizePixel = 0
toast.ZIndex = 100
toast.Parent = screenGui
local corner = Instance.new("UICorner")
corner.CornerRadius = UDim.new(0,8)
corner.Parent = toast
TweenService:Create(toast, TweenInfo.new(0.3), {BackgroundTransparency=0}):Play()
task.delay(2.5, function()
TweenService:Create(toast, TweenInfo.new(0.3), {BackgroundTransparency=1, TextTransparency=1}):Play()
task.wait(0.35)
toast:Destroy()
end)
end
function VoidGUI.getModeIcon()
if AIState.isStealth then return MODE_ICONS.stealth end
if AIState.isPaused then return MODE_ICONS.paused end
if AIState.mode == "llm" then return MODE_ICONS.llm end
return MODE_ICONS[AIState.mode] or MODE_ICONS.idle
end
function VoidGUI.getModeLabel()
if AIState.isStealth then return "STEALTH" end
if AIState.isPaused then return "PAUSED" end
local labels = {
idle = "IDLE",
observing = "OBSERVING",
ghost = "GHOST MODE",
lend = "LEND MODE",
llm = "LLM MODE",
}
return labels[AIState.mode] or "IDLE"
end
-- Main GUI labels refs for live updates
local lblMode, lblGame, lblSessions, lblActions, lblFPS
local barMove, barUI, barSeq
local btnLearn, btnGhost, btnLend, btnLLM, btnPause
function VoidGUI.updateDisplay()
if AIState.isStealth then return end
local place = getCurrentPlace()
if lblMode then lblMode.Text = VoidGUI.getModeIcon() .. " " .. VoidGUI.getModeLabel() end
if lblGame then lblGame.Text = "Game: " .. (place and place.placeName or "Unknown") end
if lblSessions then
lblSessions.Text = "Sessions: " .. (place and #place.sessions or 0)
end
if lblActions then
lblActions.Text = "Actions: " .. (place and place.totalActions or 0)
end
if lblFPS then
lblFPS.Text = "FPS: " .. FPS .. " Cooldown: " .. string.format("%.0fms", LearnCooldown * 1000)
end
if place then
local c = place.confidence
if barMove then barMove.Size = UDim2.new(c.movement/100, 0, 1, 0) end
if barUI then barUI.Size = UDim2.new(c.ui/100, 0, 1, 0) end
if barSeq then barSeq.Size = UDim2.new(c.sequences/100,0, 1, 0) end
end
-- Update mini button icon
if miniButton then
miniButton.Text = VoidGUI.getModeIcon()
end
end
function VoidGUI.toggle()
if mainFrame then
mainFrame.Visible = not mainFrame.Visible
if miniButton then
miniButton.Visible = not mainFrame.Visible
end
end
end
function VoidGUI.minimize()
if mainFrame then
mainFrame.Visible = false
if miniButton then miniButton.Visible = true end
end
end
function VoidGUI.maximize()
if mainFrame then
mainFrame.Visible = true
if miniButton then miniButton.Visible = false end
end
end
function VoidGUI.buildMainTab(container)
local y = 0
-- Mode display
lblMode = makeLabel(container, "🌙 IDLE",
UDim2.new(1,-12,0,22), UDim2.new(0,6,0,y), 15, COLORS.accent)
lblMode.Font = Enum.Font.GothamBold
y += 26
lblGame = makeLabel(container, "Game: Loading...",
UDim2.new(1,-12,0,16), UDim2.new(0,6,0,y), 11, COLORS.textDim)
y += 18
-- Divider
local div = Instance.new("Frame")
div.Size = UDim2.new(1,-12,0,1)
div.Position = UDim2.new(0,6,0,y)
div.BackgroundColor3 = COLORS.border
div.BorderSizePixel = 0
div.Parent = container
y += 8
lblSessions = makeLabel(container, "Sessions: 0",
UDim2.new(0.5,-6,0,14), UDim2.new(0,6,0,y), 11)
lblActions = makeLabel(container, "Actions: 0",
UDim2.new(0.5,-6,0,14), UDim2.new(0.5,0,0,y), 11)
y += 18
lblFPS = makeLabel(container, "FPS: 60 Cooldown: 100ms",
UDim2.new(1,-12,0,14), UDim2.new(0,6,0,y), 10, COLORS.textDim)
y += 20
-- Confidence bars
makeLabel(container, "Confidence", UDim2.new(1,-12,0,14), UDim2.new(0,6,0,y), 11, COLORS.textDim)
y += 15
makeLabel(container, "Move", UDim2.new(0,30,0,12), UDim2.new(0,6,0,y), 10, COLORS.textDim)
local _, mf = makeBar(container, UDim2.new(0,38,0,y+2), 0, COLORS.success)
barMove = mf
y += 14
makeLabel(container, "UI ", UDim2.new(0,30,0,12), UDim2.new(0,6,0,y), 10, COLORS.textDim)
local _, uf = makeBar(container, UDim2.new(0,38,0,y+2), 0, COLORS.accent)
barUI = uf
y += 14
makeLabel(container, "Seq ", UDim2.new(0,30,0,12), UDim2.new(0,6,0,y), 10, COLORS.textDim)
local _, sf = makeBar(container, UDim2.new(0,38,0,y+2), 0, COLORS.warning)
barSeq = sf
y += 20
-- Action buttons
btnLearn = makeButton(container, "▶ Start Learning",
UDim2.new(1,-12,0,28), UDim2.new(0,6,0,y), COLORS.success)
btnLearn.TextColor3 = Color3.fromRGB(20,20,30)
y += 32
btnGhost = makeButton(container, "👻 Ghost Mode",
UDim2.new(1,-12,0,28), UDim2.new(0,6,0,y))
y += 32
btnLend = makeButton(container, "🎮 Lend Control",
UDim2.new(1,-12,0,28), UDim2.new(0,6,0,y), COLORS.warning)
btnLend.TextColor3 = Color3.fromRGB(20,20,30)
y += 32
-- LLM button (gated)
btnLLM = makeButton(container, "🤖 Lend to LLM",
UDim2.new(1,-12,0,28), UDim2.new(0,6,0,y), COLORS.panel)
btnLLM.TextColor3 = COLORS.textDim
y += 32
btnPause = makeButton(container, "⏸ Pause",
UDim2.new(0.5,-9,0,26), UDim2.new(0,6,0,y), COLORS.panel)
local btnReset = makeButton(container, "🗑 Reset",
UDim2.new(0.5,-9,0,26), UDim2.new(0.5,3,0,y), COLORS.danger)
y += 32
-- Wire up buttons
btnLearn.MouseButton1Click:Connect(function()
if AIState.isLearning then
AIState.isLearning = false
Observer.endSession()
btnLearn.Text = "▶ Start Learning"
VoidGUI.notify("Learning stopped. Brain saved.")
else
AIState.isLearning = true
Observer.startSession()
btnLearn.Text = "⏹ Stop Learning"
VoidGUI.notify("Learning started!")
end
VoidGUI.updateDisplay()
end)
btnGhost.MouseButton1Click:Connect(function()
if AIState.mode == "ghost" then
GhostController.reclaim()
btnGhost.Text = "👻 Ghost Mode"
else
if GhostController.startGhost() then
btnGhost.Text = "⏹ Stop Ghost"
end
end
VoidGUI.updateDisplay()
end)
btnLend.MouseButton1Click:Connect(function()
if AIState.mode == "lend" then
GhostController.reclaim()
btnLend.Text = "🎮 Lend Control"
else
if GhostController.startLend() then
btnLend.Text = "⏹ Reclaim"
if isMobile then
VoidGUI.notify("Tap ✋ to reclaim control")
else
VoidGUI.notify("Press " .. (Config.keybinds and Config.keybinds.reclaim or "F") .. " to reclaim")
end
end
end
VoidGUI.updateDisplay()
end)
btnLLM.MouseButton1Click:Connect(function()
local canUse, reason = LLMController.canUseLLM()
if not canUse then
VoidGUI.notify("⚠ " .. (reason or "Train AI first!"))
return
end
if AIState.mode == "llm" then
GhostController.reclaim()
btnLLM.Text = "🤖 Lend to LLM"
btnLLM.BackgroundColor3 = COLORS.panel
btnLLM.TextColor3 = COLORS.textDim
else
AIState.mode = "llm"
btnLLM.Text = "⏹ Stop LLM"
btnLLM.BackgroundColor3 = COLORS.accent
btnLLM.TextColor3 = COLORS.text
local place = getCurrentPlace()
if place and place.confidence.movement < 40 then
VoidGUI.notify("⚠ Low training data. LLM may perform poorly.")
end
end
VoidGUI.updateDisplay()
end)
btnPause.MouseButton1Click:Connect(function()
AIState.isPaused = not AIState.isPaused
btnPause.Text = AIState.isPaused and "▶ Resume" or "⏸ Pause"
VoidGUI.notify(AIState.isPaused and "Paused" or "Resumed")
VoidGUI.updateDisplay()
end)
btnReset.MouseButton1Click:Connect(function()
-- Confirmation inline
btnReset.Text = "Confirm?"
task.delay(2, function()
if btnReset.Text == "Confirm?" then
btnReset.Text = "🗑 Reset"
end
end)
if btnReset.Text == "Confirm?" then return end
local place = getCurrentPlace()
if place then
place.sessions = {}
place.totalActions = 0
place.confidence = { movement=0, ui=0, sequences=0 }
saveBrain()
VoidGUI.notify("Brain reset for this game.")
end
btnReset.Text = "🗑 Reset"
VoidGUI.updateDisplay()
end)
-- Update LLM button state periodically
task.spawn(function()
while true do
task.wait(2)
local canUse = LLMController.canUseLLM()
if canUse and AIState.mode ~= "llm" then
btnLLM.BackgroundColor3 = COLORS.accent
btnLLM.TextColor3 = COLORS.text
elseif AIState.mode ~= "llm" then
btnLLM.BackgroundColor3 = COLORS.panel
btnLLM.TextColor3 = COLORS.textDim
end
end
end)
end
function VoidGUI.buildStatsTab(container)
local place = getCurrentPlace()
local y = 0
makeLabel(container, "📊 Stats Dashboard",
UDim2.new(1,-12,0,18), UDim2.new(0,6,0,y), 14, COLORS.accent).Font = Enum.Font.GothamBold
y += 24
local sessions = place and #place.sessions or 0
local actions = place and place.totalActions or 0
local playtime = place and place.totalPlaytime or 0
local brainSize = 0
if Exec.isFile(BrainPath) then
local raw = Exec.readFile(BrainPath)
brainSize = raw and #raw or 0
end
local stats = {
{"Sessions", sessions},
{"Total Actions", actions},
{"Playtime", string.format("%.0fm %.0fs", playtime/60, playtime%60)},
{"Brain File Size", string.format("%.1f KB", brainSize/1024)},
{"Current FPS", FPS},
{"Learn Cooldown", string.format("%.0fms", LearnCooldown*1000)},
{"Universe ID", getUniverseId()},
{"Place ID", getPlaceId()},
}
for _, stat in ipairs(stats) do
makeLabel(container, stat[1], UDim2.new(0.5,-6,0,16), UDim2.new(0,6,0,y), 11, COLORS.textDim)
makeLabel(container, tostring(stat[2]), UDim2.new(0.5,-6,0,16), UDim2.new(0.5,0,0,y), 11, COLORS.text)
y += 18
end
y += 6
makeLabel(container, "Top UI Buttons", UDim2.new(1,-12,0,16), UDim2.new(0,6,0,y), 11, COLORS.accent)
y += 18
if place then
local sorted = {}
for name, count in pairs(place.topUIButtons) do
table.insert(sorted, {name=name, count=count})
end
table.sort(sorted, function(a,b) return a.count > b.count end)
for i = 1, math.min(5, #sorted) do
local s = sorted[i]
makeLabel(container, s.name .. " x" .. s.count,
UDim2.new(1,-12,0,14), UDim2.new(0,6,0,y), 10, COLORS.text)
y += 15
end
end
end
function VoidGUI.buildReplayTab(container)
local y = 0
makeLabel(container, "📼 Session Replay",
UDim2.new(1,-12,0,18), UDim2.new(0,6,0,y), 14, COLORS.accent).Font = Enum.Font.GothamBold
y += 24
local place = getCurrentPlace()
if not place or #place.sessions == 0 then
makeLabel(container, "No sessions recorded yet.",
UDim2.new(1,-12,0,20), UDim2.new(0,6,0,y), 11, COLORS.textDim)
return
end
-- Scrollable session list
local scroll = Instance.new("ScrollingFrame")
scroll.Size = UDim2.new(1,0,0,180)
scroll.Position = UDim2.new(0,0,0,y)
scroll.BackgroundTransparency = 1
scroll.ScrollBarThickness = 3
scroll.ScrollBarImageColor3 = COLORS.accent
scroll.Parent = container
local layout = Instance.new("UIListLayout")
layout.Padding = UDim.new(0,4)
layout.Parent = scroll
for i, session in ipairs(place.sessions) do
local row = Instance.new("Frame")
row.Size = UDim2.new(1,-6,0,40)
row.BackgroundColor3 = COLORS.panel
row.BorderSizePixel = 0
row.Parent = scroll
local rc = Instance.new("UICorner")
rc.CornerRadius = UDim.new(0,6)
rc.Parent = row
local starIcon = session.starred and "⭐" or "☆"
local lbl = makeLabel(row,
string.format("%s Session %d — %d actions", starIcon, i, #(session.actions or {})),
UDim2.new(0.7,0,0,18), UDim2.new(0,6,0,4), 10)
makeLabel(row,
"Priority: " .. (session.priority or 1),
UDim2.new(0.7,0,0,14), UDim2.new(0,6,0,22), 9, COLORS.textDim)
-- Star button
local starBtn = makeButton(row, starIcon, UDim2.new(0,28,0,28), UDim2.new(1,-64,0,6), COLORS.panel)
starBtn.MouseButton1Click:Connect(function()
session.starred = not session.starred
starBtn.Text = session.starred and "⭐" or "☆"
lbl.Text = string.format("%s Session %d — %d actions",
session.starred and "⭐" or "☆", i, #(session.actions or {}))
saveBrain()
end)
-- Delete button
local delBtn = makeButton(row, "🗑", UDim2.new(0,28,0,28), UDim2.new(1,-32,0,6), COLORS.danger)
delBtn.MouseButton1Click:Connect(function()
table.remove(place.sessions, i)
saveBrain()
row:Destroy()
VoidGUI.notify("Session " .. i .. " deleted")
Observer.updateConfidence(place)
end)
-- Priority +/-
local prioUp = makeButton(row, "+", UDim2.new(0,18,0,18), UDim2.new(1,-96,0,6), COLORS.success)
local prioDn = makeButton(row, "-", UDim2.new(0,18,0,18), UDim2.new(1,-96,0,24), COLORS.danger)
prioUp.TextColor3 = Color3.fromRGB(20,20,30)
prioUp.MouseButton1Click:Connect(function()
session.priority = math.min(5, (session.priority or 1) + 1)
saveBrain()
end)
prioDn.MouseButton1Click:Connect(function()
session.priority = math.max(1, (session.priority or 1) - 1)
saveBrain()
end)
end
scroll.CanvasSize = UDim2.new(0,0,0,#place.sessions * 44)
end
function VoidGUI.buildSettingsTab(container)
local y = 0
makeLabel(container, "⚙ Settings",
UDim2.new(1,-12,0,18), UDim2.new(0,6,0,y), 14, COLORS.accent).Font = Enum.Font.GothamBold
y += 24
-- Persona
makeLabel(container, "Persona", UDim2.new(1,-12,0,14), UDim2.new(0,6,0,y), 11, COLORS.textDim)
y += 16
local personas = {"Mirror","Aggressive","Passive"}
for _, pname in ipairs(personas) do
local isActive = Config.activePersona == pname
local pbtn = makeButton(container, pname,
UDim2.new(1/3,-5,0,24),
UDim2.new((table.find(personas,pname)-1)/3, 3, 0, y),
isActive and COLORS.accent or COLORS.panel)
pbtn.MouseButton1Click:Connect(function()
Config.activePersona = pname
AIState.persona = pname
saveConfig()
VoidGUI.notify("Persona: " .. pname)
end)
end
y += 30
-- LLM Provider
makeLabel(container, "LLM Provider", UDim2.new(1,-12,0,14), UDim2.new(0,6,0,y), 11, COLORS.textDim)
y += 16
local providers = {"claude","chatgpt","gemini"}
for _, prov in ipairs(providers) do
local isActive = Config.activeProvider == prov
local pvbtn = makeButton(container, prov,
UDim2.new(1/3,-5,0,24),
UDim2.new((table.find(providers,prov)-1)/3, 3, 0, y),
isActive and COLORS.accent or COLORS.panel)
pvbtn.MouseButton1Click:Connect(function()
Config.activeProvider = prov
saveConfig()
VoidGUI.notify("Provider: " .. prov)
end)
end
y += 30
-- API Key input
makeLabel(container, "API Key (active provider)",
UDim2.new(1,-12,0,14), UDim2.new(0,6,0,y), 11, COLORS.textDim)
y += 16
local keyBox = Instance.new("TextBox")
keyBox.Size = UDim2.new(1,-12,0,28)
keyBox.Position = UDim2.new(0,6,0,y)
keyBox.BackgroundColor3 = COLORS.panel
keyBox.TextColor3 = COLORS.text
keyBox.PlaceholderText = "Enter API key..."
keyBox.PlaceholderColor3= COLORS.textDim
keyBox.Text = Config.apiKeys[Config.activeProvider] or ""
keyBox.Font = Enum.Font.Code
keyBox.TextSize = 10
keyBox.ClearTextOnFocus = false
keyBox.BorderSizePixel = 0
keyBox.TextXAlignment = Enum.TextXAlignment.Left
keyBox.TextEditable = true
keyBox.Parent = container
local kbc = Instance.new("UICorner"); kbc.CornerRadius = UDim.new(0,6); kbc.Parent = keyBox
local kbp = Instance.new("UIPadding"); kbp.PaddingLeft = UDim.new(0,6); kbp.Parent = keyBox
keyBox.FocusLost:Connect(function()
Config.apiKeys[Config.activeProvider] = keyBox.Text
saveConfig()
VoidGUI.notify("API key saved")
end)
y += 34
-- Keybinds (PC only)
if isPC then
makeLabel(container, "Keybinds (PC)",
UDim2.new(1,-12,0,14), UDim2.new(0,6,0,y), 11, COLORS.textDim)
y += 16
local bindNames = {
{key="reclaim", label="Reclaim"},
{key="toggleObs", label="Observe"},
{key="toggleLend",label="Lend"},
{key="toggleGUI", label="GUI"},
}
for _, b in ipairs(bindNames) do
makeLabel(container, b.label,
UDim2.new(0.4,0,0,22), UDim2.new(0,6,0,y), 10)
local bindBox = Instance.new("TextBox")
bindBox.Size = UDim2.new(0.3,-3,0,22)
bindBox.Position = UDim2.new(0.4,3,0,y)
bindBox.BackgroundColor3 = COLORS.panel
bindBox.TextColor3 = COLORS.text
bindBox.Text = Config.keybinds and Config.keybinds[b.key] or "F"
bindBox.Font = Enum.Font.GothamBold
bindBox.TextSize = 11
bindBox.BorderSizePixel = 0
bindBox.Parent = container
local bbc = Instance.new("UICorner"); bbc.CornerRadius = UDim.new(0,4); bbc.Parent = bindBox
bindBox.FocusLost:Connect(function()
local k = bindBox.Text:upper()
Keybinds.setKey(b.key, k)
bindBox.Text = k
end)
y += 26
end
end
-- Stealth toggle
local stealthBtn = makeButton(container,
Config.stealthMode and "🔇 Stealth: ON" or "🔈 Stealth: OFF",
UDim2.new(1,-12,0,28), UDim2.new(0,6,0,y),
Config.stealthMode and COLORS.accent or COLORS.panel)
stealthBtn.MouseButton1Click:Connect(function()
Config.stealthMode = not Config.stealthMode
AIState.isStealth = Config.stealthMode
stealthBtn.Text = Config.stealthMode and "🔇 Stealth: ON" or "🔈 Stealth: OFF"
stealthBtn.BackgroundColor3 = Config.stealthMode and COLORS.accent or COLORS.panel
saveConfig()
if Config.stealthMode then VoidGUI.minimize() end
end)
y += 34
-- Schedule
makeLabel(container, "Schedule (minutes)",
UDim2.new(1,-12,0,14), UDim2.new(0,6,0,y), 11, COLORS.textDim)
y += 16
local schedBtn = makeButton(container,
Config.scheduleEnabled and "⏱ Schedule: ON" or "⏱ Schedule: OFF",
UDim2.new(1,-12,0,28), UDim2.new(0,6,0,y),
Config.scheduleEnabled and COLORS.success or COLORS.panel)
schedBtn.MouseButton1Click:Connect(function()
Config.scheduleEnabled = not Config.scheduleEnabled
schedBtn.Text = Config.scheduleEnabled and "⏱ Schedule: ON" or "⏱ Schedule: OFF"
schedBtn.BackgroundColor3 = Config.scheduleEnabled and COLORS.success or COLORS.panel
saveConfig()
end)
end
function VoidGUI.build()
-- Main ScreenGui
screenGui = Instance.new("ScreenGui")
screenGui.Name = "VoidAIGui"
screenGui.ResetOnSpawn = false
screenGui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
screenGui.IgnoreGuiInset = true
screenGui.Parent = PlayerGui
-- ---- MINI BUTTON (shown when minimized) ----
miniButton = Instance.new("TextButton")
miniButton.Size = UDim2.new(0, isMobile and 52 or 44, 0, isMobile and 52 or 44)
miniButton.Position = UDim2.new(0, 10, 0.4, 0)
miniButton.BackgroundColor3 = COLORS.accent
miniButton.TextColor3 = COLORS.text
miniButton.Text = "🌙"
miniButton.Font = Enum.Font.GothamBold
miniButton.TextSize = isMobile and 22 or 20
miniButton.BorderSizePixel = 0
miniButton.Visible = false
miniButton.ZIndex = 50
miniButton.Parent = screenGui
local mc = Instance.new("UICorner")
mc.CornerRadius = UDim.new(1,0)
mc.Parent = miniButton
makeDraggable(miniButton)
miniButton.MouseButton1Click:Connect(function()
VoidGUI.maximize()
end)
-- ---- MAIN PANEL ----
local panelW = isMobile and 420 or 480
local panelH = isMobile and 280 or 320
mainFrame = Instance.new("Frame")
mainFrame.Name = "VoidAIPanel"
mainFrame.Size = UDim2.new(0, panelW, 0, panelH)
mainFrame.Position = UDim2.new(0, 10, 0.1, 0)
mainFrame.BackgroundColor3 = COLORS.bg
mainFrame.BorderSizePixel = 0
mainFrame.ClipsDescendants = true
mainFrame.Parent = screenGui
local mc2 = Instance.new("UICorner")
mc2.CornerRadius = UDim.new(0,10)
mc2.Parent = mainFrame
-- Border glow
local stroke = Instance.new("UIStroke")
stroke.Color = COLORS.accent
stroke.Thickness = 1
stroke.Transparency = 0.5
stroke.Parent = mainFrame
-- Title bar
local titleBar = Instance.new("Frame")
titleBar.Size = UDim2.new(1,0,0,32)
titleBar.BackgroundColor3 = COLORS.panel
titleBar.BorderSizePixel = 0
titleBar.Parent = mainFrame
local tbc = Instance.new("UICorner"); tbc.CornerRadius = UDim.new(0,10); tbc.Parent = titleBar
makeLabel(titleBar, " 🤖 VoidAI",
UDim2.new(0.7,0,1,0), UDim2.new(0,0,0,0), 13, COLORS.text).Font = Enum.Font.GothamBold
local closeBtn = Instance.new("TextButton")
closeBtn.Size = UDim2.new(0,28,0,28)
closeBtn.Position = UDim2.new(1,-32,0,2)
closeBtn.BackgroundColor3 = COLORS.danger
closeBtn.Text = "×"
closeBtn.Font = Enum.Font.GothamBold
closeBtn.TextSize = 16
closeBtn.TextColor3 = COLORS.text
closeBtn.BorderSizePixel = 0
closeBtn.Parent = titleBar
local cc = Instance.new("UICorner"); cc.CornerRadius = UDim.new(0,6); cc.Parent = closeBtn
closeBtn.MouseButton1Click:Connect(function()
VoidGUI.minimize()
end)
makeDraggable(mainFrame, titleBar)
-- ---- RESIZE HANDLE (bottom-right corner) ----
local resizeHandle = Instance.new("TextButton")
resizeHandle.Size = UDim2.new(0, 16, 0, 16)
resizeHandle.Position = UDim2.new(1, -16, 1, -16)
resizeHandle.BackgroundColor3 = COLORS.accent
resizeHandle.Text = ""
resizeHandle.BorderSizePixel = 0
resizeHandle.ZIndex = 20
resizeHandle.Parent = mainFrame
local rhc = Instance.new("UICorner"); rhc.CornerRadius = UDim.new(0,3); rhc.Parent = resizeHandle
-- small diagonal lines icon
local rhLbl = Instance.new("TextLabel")
rhLbl.Size = UDim2.new(1,0,1,0); rhLbl.BackgroundTransparency=1
rhLbl.Text = "⤡"; rhLbl.TextSize=10; rhLbl.TextColor3=COLORS.text
rhLbl.Font=Enum.Font.GothamBold; rhLbl.Parent=resizeHandle
do
local resizing, resizeStart, startSize = false, nil, nil
resizeHandle.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or
input.UserInputType == Enum.UserInputType.Touch then
resizing = true
resizeStart = input.Position
startSize = mainFrame.AbsoluteSize
end
end)
UserInputService.InputChanged:Connect(function(input)
if not resizing then return end
if input.UserInputType == Enum.UserInputType.MouseMovement or
input.UserInputType == Enum.UserInputType.Touch then
local delta = input.Position - resizeStart
local newW = math.clamp(startSize.X + delta.X, 320, 700)
local newH = math.clamp(startSize.Y + delta.Y, 220, 600)
mainFrame.Size = UDim2.new(0, newW, 0, newH)
end
end)
UserInputService.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or
input.UserInputType == Enum.UserInputType.Touch then
resizing = false
end
end)
end
-- Tab bar
local tabBar = Instance.new("Frame")
tabBar.Size = UDim2.new(1,0,0,28)
tabBar.Position = UDim2.new(0,0,0,32)
tabBar.BackgroundColor3 = COLORS.panel
tabBar.BorderSizePixel = 0
tabBar.Parent = mainFrame
local tabs = {
{id="main", label="Main"},
{id="stats", label="Stats"},
{id="replay", label="Replay"},
{id="settings", label="Setup"},
}
local tabContents = {}
for i, tab in ipairs(tabs) do
local tbtn = Instance.new("TextButton")
tbtn.Size = UDim2.new(1/#tabs,0,1,0)
tbtn.Position = UDim2.new((i-1)/#tabs,0,0,0)
tbtn.BackgroundTransparency = 1
tbtn.Text = tab.label
tbtn.Font = Enum.Font.GothamBold
tbtn.TextSize = 10
tbtn.TextColor3 = COLORS.textDim
tbtn.Parent = tabBar
-- Content frame
local content = Instance.new("ScrollingFrame")
content.Size = UDim2.new(1,0,1,0)
content.Position = UDim2.new(0,0,0,60)
content.BackgroundTransparency = 1
content.ScrollBarThickness = 3
content.ScrollBarImageColor3 = COLORS.accent
content.CanvasSize = UDim2.new(0,0,0,500)
content.Visible = (tab.id == "main")
content.Parent = mainFrame
tabContents[tab.id] = content
tbtn.MouseButton1Click:Connect(function()
VoidGUI.tab = tab.id
for _, tc in pairs(tabContents) do tc.Visible = false end
content.Visible = true
for _, child in ipairs(tabBar:GetChildren()) do
if child:IsA("TextButton") then
child.TextColor3 = COLORS.textDim
end
end
tbtn.TextColor3 = COLORS.accent
end)
-- Active underline
if tab.id == "main" then
tbtn.TextColor3 = COLORS.accent
end
end
-- Build tab contents
VoidGUI.buildMainTab(tabContents["main"])
VoidGUI.buildStatsTab(tabContents["stats"])
VoidGUI.buildReplayTab(tabContents["replay"])
VoidGUI.buildSettingsTab(tabContents["settings"])
-- Mobile floating action buttons (reclaim etc)
if isMobile then
local reclaimBtn = Instance.new("TextButton")
reclaimBtn.Size = UDim2.new(0,60,0,60)
reclaimBtn.Position = UDim2.new(1,-70,0.8,0)
reclaimBtn.BackgroundColor3 = COLORS.danger
reclaimBtn.Text = "✋"
reclaimBtn.Font = Enum.Font.GothamBold
reclaimBtn.TextSize = 26
reclaimBtn.TextColor3 = COLORS.text
reclaimBtn.BorderSizePixel = 0
reclaimBtn.Visible = false
reclaimBtn.ZIndex = 60
reclaimBtn.Parent = screenGui
local rc3 = Instance.new("UICorner"); rc3.CornerRadius = UDim.new(1,0); rc3.Parent = reclaimBtn
makeDraggable(reclaimBtn)
reclaimBtn.MouseButton1Click:Connect(function()
GhostController.reclaim()
VoidGUI.updateDisplay()
end)
-- Show reclaim button only when AI is in control
RunService.Heartbeat:Connect(function()
reclaimBtn.Visible = (AIState.mode == "lend" or AIState.mode == "ghost" or AIState.mode == "llm")
end)
end
end
-- ============================================================
-- SECTION 20: THREAT HUD
-- ============================================================
local ThreatHUD = {}
local function buildThreatHUD()
if AIState.isStealth or not screenGui then return end
local indicator = Instance.new("Frame")
indicator.Name = "ThreatIndicator"
indicator.Size = UDim2.new(0, 190, 0, 30)
indicator.Position = UDim2.new(0.5, -95, 0, 8)
indicator.BackgroundColor3 = Color3.fromRGB(180, 30, 30)
indicator.BorderSizePixel = 0
indicator.Visible = false
indicator.ZIndex = 80
indicator.Parent = screenGui
local ic = Instance.new("UICorner")
ic.CornerRadius = UDim.new(0, 8)
ic.Parent = indicator
local lbl = Instance.new("TextLabel")
lbl.Size = UDim2.new(1, 0, 1, 0)
lbl.BackgroundTransparency = 1
lbl.TextColor3 = Color3.fromRGB(255, 255, 255)
lbl.Font = Enum.Font.GothamBold
lbl.TextSize = 13
lbl.Text = "🚨 THREAT DETECTED"
lbl.Parent = indicator
ThreatHUD.frame = indicator
ThreatHUD.label = lbl
-- Pulse
task.spawn(function()
while indicator and indicator.Parent do
TweenService:Create(indicator, TweenInfo.new(0.3),
{BackgroundTransparency = 0.5}):Play()
task.wait(0.3)
TweenService:Create(indicator, TweenInfo.new(0.3),
{BackgroundTransparency = 0}):Play()
task.wait(0.3)
end
end)
RunService.Heartbeat:Connect(function()
if indicator and indicator.Parent then
indicator.Visible = AIState.threatDetected and not AIState.isStealth
end
end)
end
-- ============================================================
-- SECTION 21: SEQUENCE DETECTOR
-- ============================================================
local SequenceDetector = {}
SequenceDetector.windowSize = 5
SequenceDetector.knownPatterns = {}
function SequenceDetector.analyze()
local place = getCurrentPlace()
if not place or #place.sessions < 2 then return end
local seqMap = {}
for _, session in ipairs(place.sessions) do
local actions = session.actions or {}
for i = 1, #actions - SequenceDetector.windowSize do
local key = ""
for j = i, i + SequenceDetector.windowSize - 1 do
key = key .. (actions[j].type or "?") .. "|"
end
seqMap[key] = (seqMap[key] or 0) + 1
end
end
SequenceDetector.knownPatterns = {}
for seq, count in pairs(seqMap) do
if count >= 3 then
table.insert(SequenceDetector.knownPatterns, {seq = seq, count = count})
end
end
table.sort(SequenceDetector.knownPatterns, function(a, b) return a.count > b.count end)
end
function SequenceDetector.matchCurrent(recentActions)
if #SequenceDetector.knownPatterns == 0 then return nil end
local key = ""
local start = math.max(1, #recentActions - SequenceDetector.windowSize + 1)
for i = start, #recentActions do
key = key .. (recentActions[i].type or "?") .. "|"
end
for _, pat in ipairs(SequenceDetector.knownPatterns) do
if pat.seq == key then return pat end
end
return nil
end
task.spawn(function()
while true do
task.wait(60)
SequenceDetector.analyze()
end
end)
-- ============================================================
-- SECTION 22: SMART REPLAY (pattern-aware)
-- ============================================================
local _originalStepReplay = GhostController.stepReplay
function GhostController.stepReplay()
if not GhostController.replaySession then return end
local actions = GhostController.replaySession.actions
if not actions or #actions == 0 then return end
-- Pattern-based decision
local place = getCurrentPlace()
if place and #SequenceDetector.knownPatterns > 0 then
local match = SequenceDetector.matchCurrent(actions)
if match then
for _, session in ipairs(place.sessions) do
local sactions = session.actions or {}
for i = 1, #sactions - SequenceDetector.windowSize do
local key = ""
for j = i, i + SequenceDetector.windowSize - 1 do
key = key .. (sactions[j].type or "?") .. "|"
end
if key == match.seq and sactions[i + SequenceDetector.windowSize] then
local nextAction = sactions[i + SequenceDetector.windowSize]
if nextAction.type == "move" then
GhostController.executeMove(nextAction.data)
elseif nextAction.type == "ui_click" then
GhostController.executeUIClick(nextAction.data)
elseif nextAction.type == "jump" then
local char = LocalPlayer.Character
local hum = char and char:FindFirstChildOfClass("Humanoid")
if hum then hum.Jump = true end
end
return
end
end
end
end
end
-- Linear fallback
if GhostController.replayIndex > #actions then
GhostController.replayIndex = 1
return
end
local action = actions[GhostController.replayIndex]
GhostController.replayIndex += 1
local char = LocalPlayer.Character
local hum = char and char:FindFirstChildOfClass("Humanoid")
if action.type == "move" then
GhostController.executeMove(action.data)
elseif action.type == "ui_click" then
GhostController.executeUIClick(action.data)
elseif action.type == "jump" then
if hum then hum.Jump = true end
elseif action.type == "sprint_start" then
if hum then hum.WalkSpeed = AIState.persona == "Passive" and 14 or 24 end
elseif action.type == "sprint_end" then
if hum then hum.WalkSpeed = AIState.persona == "Aggressive" and 20 or 16 end
end
end
-- ============================================================
-- SECTION 23: JUMP + SPRINT TRACKING (Event-driven)
-- ============================================================
if not isMobile then
UserInputService.InputBegan:Connect(function(input, processed)
if processed then return end
if not AIState.isLearning or not AIState.currentSession then return end
if input.KeyCode == Enum.KeyCode.Space then
table.insert(AIState.currentSession.actions, {
type = "jump",
data = {t = os.time() - AIState.sessionStart}
})
elseif input.KeyCode == Enum.KeyCode.LeftShift then
table.insert(AIState.currentSession.actions, {
type = "sprint_start",
data = {t = os.time() - AIState.sessionStart}
})
end
end)
UserInputService.InputEnded:Connect(function(input, processed)
if not AIState.isLearning or not AIState.currentSession then return end
if input.KeyCode == Enum.KeyCode.LeftShift then
table.insert(AIState.currentSession.actions, {
type = "sprint_end",
data = {t = os.time() - AIState.sessionStart}
})
end
end)
end
-- Re-hook on respawn
LocalPlayer.CharacterAdded:Connect(function(char)
task.wait(1)
local hum = char:WaitForChild("Humanoid", 5)
if hum then
if isMobile then
hum.Jumping:Connect(function(active)
if active and AIState.isLearning and AIState.currentSession then
table.insert(AIState.currentSession.actions, {
type = "jump",
data = {t = os.time() - AIState.sessionStart}
})
end
end)
end
-- Reset position tracking
local hrp = char:WaitForChild("HumanoidRootPart", 5)
if hrp then Observer.lastPosition = hrp.Position end
end
-- Auto-pause AI control during respawn
if AIState.mode == "lend" or AIState.mode == "ghost" then
AIState.isPaused = true
task.wait(2)
AIState.isPaused = false
end
end)
-- ============================================================
-- SECTION 24: OTHER PLAYER OBSERVER (low priority)
-- ============================================================
RunService.Heartbeat:Connect(function()
if AIState.tickCounter % 5 ~= 0 then return end
if not AIState.isLearning then return end
for _, p in ipairs(Players:GetPlayers()) do
if p ~= LocalPlayer and p.Character then
local hum = p.Character:FindFirstChildOfClass("Humanoid")
local hrp = p.Character:FindFirstChild("HumanoidRootPart")
if hum and hrp and hum.WalkSpeed > 20 then
-- Multiple players fleeing = potential threat signal
-- Already handled in ThreatDetector.scan()
end
end
end
end)
-- ============================================================
-- SECTION 25: CONFIDENCE COLOR CODING
-- ============================================================
local function getConfidenceColor(value)
if value >= 70 then return COLORS.success
elseif value >= 40 then return COLORS.warning
else return COLORS.danger end
end
local _origUpdateDisplay = VoidGUI.updateDisplay
function VoidGUI.updateDisplay()
_origUpdateDisplay()
local place = getCurrentPlace()
if place then
if barMove then barMove.BackgroundColor3 = getConfidenceColor(place.confidence.movement) end
if barUI then barUI.BackgroundColor3 = getConfidenceColor(place.confidence.ui) end
if barSeq then barSeq.BackgroundColor3 = getConfidenceColor(place.confidence.sequences) end
end
end
-- ============================================================
-- SECTION 26: LOW CONFIDENCE WARNING
-- ============================================================
local _origStartGhost = GhostController.startGhost
function GhostController.startGhost()
local place = getCurrentPlace()
if place then
local avg = (place.confidence.movement + place.confidence.ui + place.confidence.sequences) / 3
if avg < 20 then
VoidGUI.notify("⚠ Very low confidence (" .. math.floor(avg) .. "%). Train more!")
task.wait(1.5)
elseif avg < 40 then
VoidGUI.notify("⚠ Low confidence (" .. math.floor(avg) .. "%). AI may be rough.")
end
end
return _origStartGhost()
end
-- ============================================================
-- SECTION 27: CHAT COMMANDS
-- ============================================================
local CHAT_PREFIX = "/void"
local CHAT_COMMANDS = {
learn = function() AIState.isLearning = true; Observer.startSession(); VoidGUI.notify("Learning started") end,
stop = function() AIState.isLearning = false; Observer.endSession(); VoidGUI.notify("Learning stopped") end,
ghost = function() GhostController.startGhost() end,
lend = function() GhostController.startLend() end,
reclaim = function() GhostController.reclaim() end,
pause = function()
AIState.isPaused = not AIState.isPaused
VoidGUI.notify(AIState.isPaused and "Paused" or "Resumed")
end,
stealth = function()
Config.stealthMode = not Config.stealthMode
AIState.isStealth = Config.stealthMode
saveConfig()
if Config.stealthMode then VoidGUI.minimize() else VoidGUI.maximize() end
end,
safe = function() Observer.tagCurrentZone("safe"); VoidGUI.notify("Zone: SAFE") end,
danger = function() Observer.tagCurrentZone("danger"); VoidGUI.notify("Zone: DANGER") end,
exit = function() Observer.tagCurrentZone("exit"); VoidGUI.notify("Zone: EXIT") end,
item = function() Observer.tagCurrentZone("item_spawn"); VoidGUI.notify("Zone: ITEM") end,
save = function() saveBrain(); VoidGUI.notify("Brain saved!") end,
llm = function()
local canUse, reason = LLMController.canUseLLM()
if canUse then AIState.mode = "llm"; VoidGUI.notify("LLM mode ON")
else VoidGUI.notify("LLM blocked: " .. (reason or "?")) end
end,
status = function()
local place = getCurrentPlace()
VoidGUI.notify(string.format("Mode:%s Sess:%d Move:%d%% UI:%d%%",
AIState.mode,
place and #place.sessions or 0,
place and place.confidence.movement or 0,
place and place.confidence.ui or 0))
end,
help = function()
VoidGUI.notify("Cmds: learn stop ghost lend reclaim pause stealth safe danger exit item save llm status")
end,
}
pcall(function()
LocalPlayer.Chatted:Connect(function(msg)
local parts = msg:lower():split(" ")
if parts[1] ~= CHAT_PREFIX then return end
local cmd = parts[2]
if cmd and CHAT_COMMANDS[cmd] then
CHAT_COMMANDS[cmd]()
VoidGUI.updateDisplay()
end
end)
end)
-- ============================================================
-- SECTION 28: MENTAL MAP VISUALIZER
-- ============================================================
local function buildMentalMapView(parent, yPos)
local place = getCurrentPlace()
if not place then return end
local TAG_COLORS = {
visited = Color3.fromRGB(50, 50, 90),
safe = Color3.fromRGB(87, 242, 135),
danger = Color3.fromRGB(237, 66, 69),
item_spawn = Color3.fromRGB(254, 231, 92),
exit = Color3.fromRGB(88, 101, 242),
}
local mapFrame = Instance.new("Frame")
mapFrame.Size = UDim2.new(1, -12, 0, 110)
mapFrame.Position = UDim2.new(0, 6, 0, yPos)
mapFrame.BackgroundColor3 = Color3.fromRGB(8, 8, 14)
mapFrame.BorderSizePixel = 0
mapFrame.ClipsDescendants = true
mapFrame.Parent = parent
local mc = Instance.new("UICorner"); mc.CornerRadius = UDim.new(0,6); mc.Parent = mapFrame
makeLabel(mapFrame, "🗺 Mental Map", UDim2.new(1,0,0,14), UDim2.new(0,4,0,2), 9, COLORS.textDim)
local landmarks = place.mentalMap.landmarks or {}
local minX, maxX, minZ, maxZ = math.huge, -math.huge, math.huge, -math.huge
for _, lm in pairs(landmarks) do
if lm.pos.x < minX then minX = lm.pos.x end
if lm.pos.x > maxX then maxX = lm.pos.x end
if lm.pos.z < minZ then minZ = lm.pos.z end
if lm.pos.z > maxZ then maxZ = lm.pos.z end
end
local rangeX = math.max(1, maxX - minX)
local rangeZ = math.max(1, maxZ - minZ)
local dotCount = 0
for _, lm in pairs(landmarks) do
if dotCount >= 150 then break end
dotCount += 1
local nx = (lm.pos.x - minX) / rangeX
local nz = (lm.pos.z - minZ) / rangeZ
local dot = Instance.new("Frame")
dot.Size = UDim2.new(0, 4, 0, 4)
dot.Position = UDim2.new(nx * 0.9 + 0.05, -2, nz * 0.7 + 0.15, -2)
dot.BackgroundColor3 = TAG_COLORS[lm.tag] or TAG_COLORS.visited
dot.BorderSizePixel = 0
dot.Parent = mapFrame
local dc = Instance.new("UICorner"); dc.CornerRadius = UDim.new(1,0); dc.Parent = dot
end
-- Player position dot
local char = LocalPlayer.Character
if char then
local hrp = char:FindFirstChild("HumanoidRootPart")
if hrp then
local nx = rangeX > 1 and (hrp.Position.X - minX) / rangeX or 0.5
local nz = rangeZ > 1 and (hrp.Position.Z - minZ) / rangeZ or 0.5
local pd = Instance.new("Frame")
pd.Size = UDim2.new(0, 7, 0, 7)
pd.Position = UDim2.new(math.clamp(nx*0.9+0.05,0,1), -3, math.clamp(nz*0.7+0.15,0,1), -3)
pd.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
pd.BorderSizePixel = 0
pd.ZIndex = 5
pd.Parent = mapFrame
local pdc = Instance.new("UICorner"); pdc.CornerRadius = UDim.new(1,0); pdc.Parent = pd
end
end
-- Legend row
local legendData = {{"safe","Safe"},{"danger","Danger"},{"exit","Exit"},{"item_spawn","Item"}}
local lx = 4
for _, li in ipairs(legendData) do
local ldot = Instance.new("Frame")
ldot.Size = UDim2.new(0,5,0,5); ldot.Position = UDim2.new(0,lx,1,-14)
ldot.BackgroundColor3 = TAG_COLORS[li[1]]; ldot.BorderSizePixel = 0; ldot.Parent = mapFrame
local lc = Instance.new("UICorner"); lc.CornerRadius = UDim.new(1,0); lc.Parent = ldot
makeLabel(mapFrame, li[2], UDim2.new(0,28,0,10), UDim2.new(0,lx+7,1,-16), 8, COLORS.textDim)
lx += 40
end
end
-- ============================================================
-- SECTION 29: BRAIN EXPORT
-- ============================================================
local function exportBrainSummary()
local place = getCurrentPlace()
if not place then VoidGUI.notify("No brain loaded"); return end
local lines = {
"=== VoidAI Brain Export ===",
"Universe: " .. getUniverseId(),
"Place: " .. getPlaceId() .. " (" .. (place.placeName or "?") .. ")",
"Sessions: " .. #place.sessions,
"Total Actions: " .. (place.totalActions or 0),
string.format("Confidence - Move:%d%% UI:%d%% Seq:%d%%",
place.confidence.movement, place.confidence.ui, place.confidence.sequences),
"",
"--- Top UI Buttons ---",
}
local sorted = {}
for name, count in pairs(place.topUIButtons or {}) do
table.insert(sorted, name .. ": " .. count .. "x")
end
table.sort(sorted)
for _, s in ipairs(sorted) do table.insert(lines, " " .. s) end
table.insert(lines, "")
table.insert(lines, "--- Mental Map Landmarks ---")
local lmCount = 0
for _, lm in pairs(place.mentalMap.landmarks or {}) do
lmCount += 1
table.insert(lines, string.format(" [%s] (%.0f,%.0f,%.0f) visits:%d",
lm.tag, lm.pos.x, lm.pos.y, lm.pos.z, lm.visits))
end
if lmCount == 0 then table.insert(lines, " (none yet)") end
table.insert(lines, "")
table.insert(lines, "--- Known Patterns ---")
if #SequenceDetector.knownPatterns > 0 then
for i, pat in ipairs(SequenceDetector.knownPatterns) do
if i > 10 then break end
table.insert(lines, string.format(" Pattern %d (x%d): %s", i, pat.count, pat.seq))
end
else
table.insert(lines, " (none — train 2+ sessions first)")
end
table.insert(lines, "")
table.insert(lines, "Exported: " .. os.date and os.date("%Y-%m-%d %H:%M:%S") or tostring(os.time()))
local path = BRAIN_FOLDER .. "/export_" .. getPlaceId() .. ".txt"
Exec.writeFile(path, table.concat(lines, "\n"))
VoidGUI.notify("Exported to " .. path)
end
-- ============================================================
-- SECTION 30: HORROR GAME AUTO-DETECT
-- ============================================================
local HORROR_KEYWORDS = {
"doors","horror","scary","haunted","dark","nightmare",
"paranormal","escape","survival","entity","monster","backrooms",
}
local function detectAndConfigureHorror()
local place = getCurrentPlace()
if not place then return end
local gname = (place.placeName or ""):lower()
local isHorror = false
for _, kw in ipairs(HORROR_KEYWORDS) do
if gname:find(kw) then isHorror = true; break end
end
if not isHorror then return end
VoidGUI.notify("🚨 Horror game detected — threat detection enhanced")
-- Pre-tag safe objects in workspace
for _, obj in ipairs(workspace:GetDescendants()) do
if obj:IsA("BasePart") then
for _, safe in ipairs(BASE_KNOWLEDGE.horrorDefaults.safeObjects) do
if obj.Name:lower():find(safe:lower()) then
local posKey = math.floor(obj.Position.X/10) .. "_" .. math.floor(obj.Position.Z/10)
if not place.mentalMap.landmarks[posKey] then
place.mentalMap.landmarks[posKey] = {
pos = {x=obj.Position.X, y=obj.Position.Y, z=obj.Position.Z},
tag = "safe",
visits= 1,
note = "auto:" .. obj.Name,
}
end
end
end
end
end
end
-- ============================================================
-- SECTION 31: SCHEDULE COUNTDOWN
-- ============================================================
task.spawn(function()
while true do
task.wait(10)
if Config.scheduleEnabled and not AIState.scheduleActive then
local startSec = (Config.scheduleStartMin or 0) * 60
local remaining = startSec - AIState.scheduleTimer
if remaining > 0 and remaining < 120 then
VoidGUI.notify(string.format("⏱ AI starts in %.0f sec", remaining))
end
end
end
end)
-- ============================================================
-- SECTION 32: SAFETY HOOKS
-- ============================================================
Players.LocalPlayer.AncestryChanged:Connect(function()
if not Players.LocalPlayer.Parent then
if AIState.isLearning and AIState.currentSession then
Observer.endSession()
end
saveBrain()
saveConfig()
end
end)
LocalPlayer.CharacterRemoving:Connect(function()
if AIState.mode ~= "idle" and AIState.mode ~= "observing" then
GhostController.reclaim()
VoidGUI.notify("Character removed — control reclaimed")
end
end)
-- ============================================================
-- SECTION 19: INITIALIZATION (FINAL)
-- ============================================================
local function init()
-- Setup folders
Exec.makeFolder(BRAIN_FOLDER)
-- Load config first
loadConfig()
-- Apply saved config
if Config.stealthMode then AIState.isStealth = true end
if Config.activePersona then AIState.persona = Config.activePersona end
if Config.keybinds then
for action, keyName in pairs(Config.keybinds) do
if KEY_MAP[keyName] then
Keybinds.binds[action] = KEY_MAP[keyName]
end
end
end
-- Load brain
loadBrain()
-- Build GUI
if not AIState.isStealth then
VoidGUI.build()
VoidGUI.updateDisplay()
else
-- Stealth: build but hide, mini button only
VoidGUI.build()
if mainFrame then mainFrame.Visible = false end
if miniButton then miniButton.Visible = true end
end
-- Build threat HUD after GUI exists
task.spawn(function()
task.wait(0.3)
buildThreatHUD()
end)
-- Run horror detection after brain loads
task.spawn(function()
task.wait(4)
detectAndConfigureHorror()
end)
-- Run sequence analysis on startup if brain already has sessions
task.spawn(function()
task.wait(3)
SequenceDetector.analyze()
end)
-- Add mental map to stats tab after GUI builds
task.spawn(function()
task.wait(0.5)
if screenGui then
local panel = screenGui:FindFirstChild("VoidAIPanel")
if panel then
local scrollFrames = {}
for _, child in ipairs(panel:GetChildren()) do
if child:IsA("ScrollingFrame") then
table.insert(scrollFrames, child)
end
end
-- Stats tab = index 2, Settings tab = index 4
local statsFrame = scrollFrames[2]
local settingsFrame = scrollFrames[4]
if statsFrame then
buildMentalMapView(statsFrame, 330)
statsFrame.CanvasSize = UDim2.new(0,0,0,460)
end
if settingsFrame then
local exportBtn = makeButton(settingsFrame,
"📤 Export Brain Summary",
UDim2.new(1,-12,0,28),
UDim2.new(0,6,0,500),
COLORS.panel)
exportBtn.MouseButton1Click:Connect(exportBrainSummary)
settingsFrame.CanvasSize = UDim2.new(0,0,0,560)
end
end
end
end)
-- Live display update loop (every second)
task.spawn(function()
while true do
task.wait(1)
VoidGUI.updateDisplay()
end
end)
-- Welcome notification
task.wait(0.5)
if not AIState.isStealth then
local platform = isMobile and "Mobile 📱" or "PC 🖥"
local brainStatus = Exec.isFile(getBrainPath()) and "Brain loaded ✓" or "New brain created"
VoidGUI.notify("VoidAI v1.0 — " .. platform .. " — " .. brainStatus)
end
-- Print to output for executor console
print("[VoidAI] Loaded successfully")
print("[VoidAI] Platform: " .. (isMobile and "Mobile" or "PC"))
print("[VoidAI] Brain path: " .. getBrainPath())
print("[VoidAI] Universe: " .. getUniverseId() .. " | Place: " .. getPlaceId())
print("[VoidAI] Chat commands: /void help")
if isPC then
print("[VoidAI] Keybinds: G=Learn H=Lend J=GUI F=Reclaim")
end
end
-- ============================================================
-- SECTION 33: ADAPTIVE THINKING MODE
-- ============================================================
--[[
Adaptive Thinking Mode (ATM):
- Activates automatically when player is AFK (no input for N seconds)
- OR manually toggled from GUI / chat command
- AI watches ALL other players in the server simultaneously
- Learns their movement paths, sequences, clustering patterns
- Cross-references: 3+ players took same path = high-confidence route
- Tags player clusters as "safe", player flee-zones as "danger"
- All observed data is weighted at 60% trust vs 100% for self-recorded
- Stops instantly the moment user touches any input
- Brain data from ATM is clearly marked so you can see what came from it
]]
local AdaptiveThinking = {}
-- State
AdaptiveThinking.active = false
AdaptiveThinking.manualOverride= false -- user forced it on via GUI
AdaptiveThinking.afkTimer = 0
AdaptiveThinking.afkThreshold = 30 -- seconds of no input = AFK
AdaptiveThinking.TRUST_WEIGHT = 0.6 -- observed data trust vs 1.0 for self
AdaptiveThinking.tickAccum = 0
AdaptiveThinking.sampleRate = 0.4 -- seconds between player samples (adaptive)
AdaptiveThinking.sessionStart = 0
-- Per-player tracking table: stores last known positions + action hints
AdaptiveThinking.playerTrackers = {}
-- Observed session accumulator (separate from self-recorded sessions)
AdaptiveThinking.observedSession = nil
-- Path frequency map: posKey -> count across all observed players
AdaptiveThinking.pathFrequency = {}
-- ---- AFK DETECTION ----
local lastInputTime = os.time()
if not isMobile then
UserInputService.InputBegan:Connect(function(input, processed)
-- Any real input = not AFK
local t = input.UserInputType
if t == Enum.UserInputType.Keyboard
or t == Enum.UserInputType.MouseButton1
or t == Enum.UserInputType.MouseButton2
or t == Enum.UserInputType.MouseMovement then
lastInputTime = os.time()
-- If ATM was running in auto mode, stop it
if AdaptiveThinking.active and not AdaptiveThinking.manualOverride then
AdaptiveThinking.stop("Input detected — AFK mode off")
end
end
end)
UserInputService.InputChanged:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseMovement then
lastInputTime = os.time()
if AdaptiveThinking.active and not AdaptiveThinking.manualOverride then
AdaptiveThinking.stop("Mouse moved — AFK mode off")
end
end
end)
else
-- Mobile: any touch = not AFK
UserInputService.TouchStarted:Connect(function()
lastInputTime = os.time()
if AdaptiveThinking.active and not AdaptiveThinking.manualOverride then
AdaptiveThinking.stop("Touch detected — AFK mode off")
end
end)
end
-- ---- CORE FUNCTIONS ----
function AdaptiveThinking.start(reason)
if AdaptiveThinking.active then return end
AdaptiveThinking.active = true
AdaptiveThinking.sessionStart = os.time()
AdaptiveThinking.pathFrequency= {}
AdaptiveThinking.playerTrackers = {}
-- Create a fresh observed session
AdaptiveThinking.observedSession = {
id = os.time(),
type = "adaptive_observed", -- marked as ATM data
trustWeight = AdaptiveThinking.TRUST_WEIGHT,
actions = {},
movements = {},
sequences = {},
playerPaths = {}, -- per-player path logs
startTime = os.time(),
endTime = nil,
playerCount = #Players:GetPlayers() - 1, -- exclude self
source = "adaptive_thinking",
}
VoidGUI.notify("🧠 Adaptive Thinking ON — watching " ..
tostring(#Players:GetPlayers() - 1) .. " players")
print("[VoidAI:ATM] Started — reason: " .. (reason or "manual"))
end
function AdaptiveThinking.stop(reason)
if not AdaptiveThinking.active then return end
AdaptiveThinking.active = false
AdaptiveThinking.manualOverride= false
-- Finalize and merge observed session into brain
if AdaptiveThinking.observedSession then
AdaptiveThinking.observedSession.endTime = os.time()
AdaptiveThinking.mergeIntoBrain()
end
AdaptiveThinking.observedSession = nil
VoidGUI.notify("🧠 Adaptive Thinking OFF — brain updated")
print("[VoidAI:ATM] Stopped — reason: " .. (reason or "manual"))
VoidGUI.updateDisplay()
end
function AdaptiveThinking.toggle()
if AdaptiveThinking.active then
AdaptiveThinking.manualOverride = false
AdaptiveThinking.stop("manual toggle")
else
AdaptiveThinking.manualOverride = true
AdaptiveThinking.start("manual toggle")
end
end
-- ---- PLAYER SAMPLING ----
function AdaptiveThinking.samplePlayers()
if not AdaptiveThinking.active then return end
local place = getCurrentPlace()
if not place then return end
local session = AdaptiveThinking.observedSession
if not session then return end
local allPlayers = Players:GetPlayers()
local activeSamples = 0
for _, p in ipairs(allPlayers) do
if p == LocalPlayer then continue end
if not p.Character then continue end
local hrp = p.Character:FindFirstChild("HumanoidRootPart")
local hum = p.Character:FindFirstChildOfClass("Humanoid")
if not hrp or not hum then continue end
local pos = hrp.Position
local pid = p.UserId
activeSamples += 1
-- Initialize tracker for this player
if not AdaptiveThinking.playerTrackers[pid] then
AdaptiveThinking.playerTrackers[pid] = {
name = p.Name,
lastPos = pos,
lastMoveT = os.time(),
pathPoints = {},
isRunning = false,
fleeCount = 0,
}
session.playerPaths[tostring(pid)] = {
name = p.Name,
points = {},
}
end
local tracker = AdaptiveThinking.playerTrackers[pid]
local delta = (pos - tracker.lastPos).Magnitude
-- Only record if player actually moved (>2 studs, same threshold as self-observer)
if delta >= 2 then
tracker.lastPos = pos
tracker.lastMoveT= os.time()
-- Log point to this player's path
local point = {
pos = {x = pos.X, y = pos.Y, z = pos.Z},
t = os.time() - AdaptiveThinking.sessionStart,
spd = hum.WalkSpeed,
}
table.insert(tracker.pathPoints, point)
if session.playerPaths[tostring(pid)] then
table.insert(session.playerPaths[tostring(pid)].points, point)
end
-- Update path frequency map for this grid cell
local posKey = math.floor(pos.X/10) .. "_" .. math.floor(pos.Z/10)
AdaptiveThinking.pathFrequency[posKey] =
(AdaptiveThinking.pathFrequency[posKey] or 0) + 1
-- Add to observed session as a weighted movement action
table.insert(session.actions, {
type = "move",
source = "observed_" .. p.Name,
trustWeight = AdaptiveThinking.TRUST_WEIGHT,
data = point,
})
table.insert(session.movements, point)
end
-- Detect fleeing (high walkspeed = running)
local isFleeing = hum.WalkSpeed > 20
if isFleeing and not tracker.isRunning then
tracker.isRunning = true
tracker.fleeCount += 1
-- Record flee event
table.insert(session.actions, {
type = "flee_observed",
source = p.Name,
data = {
pos = {x = pos.X, y = pos.Y, z = pos.Z},
t = os.time() - AdaptiveThinking.sessionStart,
}
})
-- If 3+ players flee from roughly same area = strong danger signal
local nearbyFleeing = 0
for _, p2 in ipairs(allPlayers) do
if p2 ~= LocalPlayer and p2 ~= p and p2.Character then
local hrp2 = p2.Character:FindFirstChild("HumanoidRootPart")
local hum2 = p2.Character:FindFirstChildOfClass("Humanoid")
if hrp2 and hum2 and hum2.WalkSpeed > 20 then
if (hrp2.Position - pos).Magnitude < 60 then
nearbyFleeing += 1
end
end
end
end
if nearbyFleeing >= 2 then
-- Tag flee origin as danger
local fleeKey = math.floor(pos.X/10) .. "_" .. math.floor(pos.Z/10)
place.mentalMap.landmarks[fleeKey] = {
pos = {x = pos.X, y = pos.Y, z = pos.Z},
tag = "danger",
visits = nearbyFleeing,
note = "atm:mass_flee",
}
print("[VoidAI:ATM] Mass flee detected — tagged danger at " .. fleeKey)
end
elseif not isFleeing then
tracker.isRunning = false
end
end
-- Update sample rate based on player count (more players = slower sample to avoid lag)
if activeSamples > 10 then
AdaptiveThinking.sampleRate = 0.8
elseif activeSamples > 5 then
AdaptiveThinking.sampleRate = 0.5
else
AdaptiveThinking.sampleRate = 0.3
end
end
-- ---- CROSS-REFERENCE & ZONE TAGGING ----
function AdaptiveThinking.processPathFrequency()
if not AdaptiveThinking.active then return end
local place = getCurrentPlace()
if not place then return end
for posKey, count in pairs(AdaptiveThinking.pathFrequency) do
local parts = posKey:split("_")
if #parts >= 2 then
local wx = tonumber(parts[1]) * 10
local wz = tonumber(parts[2]) * 10
if not place.mentalMap.landmarks[posKey] then
place.mentalMap.landmarks[posKey] = {
pos = {x = wx, y = 0, z = wz},
tag = "visited",
visits = 0,
note = "atm",
}
end
local lm = place.mentalMap.landmarks[posKey]
-- Increment visits by weighted count
lm.visits = (lm.visits or 0) + math.floor(count * AdaptiveThinking.TRUST_WEIGHT)
-- Cross-reference: 3+ distinct players passed here = likely safe/important route
if count >= 3 and lm.tag == "visited" then
lm.tag = "safe"
lm.note = "atm:high_traffic"
end
end
end
end
-- ---- MERGE INTO BRAIN ----
function AdaptiveThinking.mergeIntoBrain()
local place = getCurrentPlace()
if not place or not AdaptiveThinking.observedSession then return end
local session = AdaptiveThinking.observedSession
-- Process path frequency → zone tags
AdaptiveThinking.processPathFrequency()
-- Only add observed session if it has meaningful data
local actionCount = #session.actions
if actionCount < 10 then
print("[VoidAI:ATM] Observed session too sparse (" .. actionCount .. " actions) — discarded")
return
end
-- Add to place sessions with trust weight metadata
table.insert(place.sessions, session)
place.totalActions = (place.totalActions or 0) + actionCount
-- Recalculate confidence, but dampen ATM contribution
-- ATM sessions count as 0.6x a normal session for confidence
local atmSessions = 0
local selfSessions = 0
for _, s in ipairs(place.sessions) do
if s.type == "adaptive_observed" then
atmSessions += 1
else
selfSessions += 1
end
end
-- Weighted confidence: self sessions worth 1.0, ATM sessions worth 0.6
local weightedCount = selfSessions + atmSessions * AdaptiveThinking.TRUST_WEIGHT
if weightedCount > 0 then
local totalMoves, totalUI, totalSeq = 0, 0, 0
for _, s in ipairs(place.sessions) do
local w = (s.type == "adaptive_observed") and AdaptiveThinking.TRUST_WEIGHT or 1.0
totalMoves = totalMoves + #(s.movements or {}) * w
totalUI = totalUI + #(s.uiClicks or {}) * w
totalSeq = totalSeq + #(s.sequences or {}) * w
end
place.confidence.movement = math.min(100, math.floor(math.log(totalMoves + 1) * 15))
place.confidence.ui = math.min(100, math.floor(math.log(totalUI + 1) * 20))
place.confidence.sequences = math.min(100, math.floor(math.log(totalSeq + 1) * 25))
end
-- Run sequence analysis on the new data
SequenceDetector.analyze()
saveBrain()
print(string.format("[VoidAI:ATM] Merged — %d actions from %d players | Conf: Move%d%% UI%d%%",
actionCount,
session.playerCount,
place.confidence.movement,
place.confidence.ui))
end
-- ---- MAIN HEARTBEAT HOOK ----
RunService.Heartbeat:Connect(function(dt)
-- AFK timer (only auto-trigger if not manual override)
if not AdaptiveThinking.manualOverride then
local idleTime = os.time() - lastInputTime
if idleTime >= AdaptiveThinking.afkThreshold and not AdaptiveThinking.active then
-- Auto-start ATM if player appears to be AFK
-- Only if not already in another active AI mode
if AIState.mode == "idle" or AIState.mode == "observing" then
AdaptiveThinking.start("AFK auto-trigger (" .. idleTime .. "s idle)")
end
end
end
-- Sample players while ATM is active
if AdaptiveThinking.active then
AdaptiveThinking.tickAccum += dt
if AdaptiveThinking.tickAccum >= AdaptiveThinking.sampleRate then
AdaptiveThinking.tickAccum = 0
AdaptiveThinking.samplePlayers()
-- Process path frequency every 15 seconds
local elapsed = os.time() - AdaptiveThinking.sessionStart
if elapsed > 0 and elapsed % 15 == 0 then
AdaptiveThinking.processPathFrequency()
end
end
end
end)
-- ---- GUI INTEGRATION ----
-- Add ATM button and status to main tab after GUI builds
task.spawn(function()
task.wait(1)
if not screenGui then return end
local panel = screenGui:FindFirstChild("VoidAIPanel")
if not panel then return end
-- Find main tab ScrollingFrame (first one)
local scrollFrames = {}
for _, child in ipairs(panel:GetChildren()) do
if child:IsA("ScrollingFrame") then
table.insert(scrollFrames, child)
end
end
local mainScroll = scrollFrames[1]
if not mainScroll then return end
-- ATM toggle button
local atmBtn = makeButton(mainScroll,
"🧠 Adaptive Thinking: OFF",
UDim2.new(1,-12,0,28),
UDim2.new(0,6,0,310),
COLORS.panel)
atmBtn.TextSize = 11
-- AFK timer label
local atmStatus = makeLabel(mainScroll,
"AFK: 0s / 30s — 0 players watched",
UDim2.new(1,-12,0,14),
UDim2.new(0,6,0,342),
9, COLORS.textDim)
atmBtn.MouseButton1Click:Connect(function()
AdaptiveThinking.toggle()
if AdaptiveThinking.active then
atmBtn.Text = "🧠 Adaptive Thinking: ON"
atmBtn.BackgroundColor3 = COLORS.accent
else
atmBtn.Text = "🧠 Adaptive Thinking: OFF"
atmBtn.BackgroundColor3 = COLORS.panel
end
end)
-- Live status updater for ATM button
task.spawn(function()
while true do
task.wait(1)
local idleTime = os.time() - lastInputTime
local pCount = math.max(0, #Players:GetPlayers() - 1)
if AdaptiveThinking.active then
local elapsed = os.time() - AdaptiveThinking.sessionStart
local actions = AdaptiveThinking.observedSession
and #AdaptiveThinking.observedSession.actions or 0
atmStatus.Text = string.format(
"ATM: %ds active | %d players | %d actions learned",
elapsed, pCount, actions)
atmStatus.TextColor3 = COLORS.success
atmBtn.Text = "🧠 Adaptive Thinking: ON"
atmBtn.BackgroundColor3 = COLORS.accent
else
local threshold = AdaptiveThinking.afkThreshold
local remaining = math.max(0, threshold - idleTime)
atmStatus.Text = string.format(
"AFK: %ds idle | auto-starts in %ds | %d players",
math.floor(idleTime), math.floor(remaining), pCount)
atmStatus.TextColor3 = COLORS.textDim
atmBtn.Text = "🧠 Adaptive Thinking: OFF"
atmBtn.BackgroundColor3 = COLORS.panel
end
end
end)
-- Expand canvas to fit new button
mainScroll.CanvasSize = UDim2.new(0,0,0,580)
end)
-- ---- CHAT COMMAND ----
CHAT_COMMANDS["atm"] = function()
AdaptiveThinking.toggle()
VoidGUI.notify(AdaptiveThinking.active and
"🧠 ATM ON — watching players" or
"🧠 ATM OFF — brain saved")
end
CHAT_COMMANDS["afk"] = function()
-- Set AFK threshold to 0 to force immediate ATM trigger
lastInputTime = 0
VoidGUI.notify("AFK forced — ATM will start shortly")
end
-- ---- STATS TAB INTEGRATION ----
-- Patch stats display to show ATM session count separately
local _origBuildStats = VoidGUI.buildStatsTab
function VoidGUI.buildStatsTab(container)
_origBuildStats(container)
-- Add ATM-specific stat rows
local place = getCurrentPlace()
if not place then return end
local atmCount = 0
local selfCount = 0
for _, s in ipairs(place.sessions) do
if s.type == "adaptive_observed" then
atmCount += 1
else
selfCount += 1
end
end
makeLabel(container,
"Self Sessions",
UDim2.new(0.6,0,0,16),
UDim2.new(0,6,0,195), 11, COLORS.textDim)
makeLabel(container,
tostring(selfCount),
UDim2.new(0.4,0,0,16),
UDim2.new(0.6,0,0,195), 11, COLORS.success)
makeLabel(container,
"ATM Sessions (0.6x)",
UDim2.new(0.6,0,0,16),
UDim2.new(0,6,0,213), 11, COLORS.textDim)
makeLabel(container,
tostring(atmCount),
UDim2.new(0.4,0,0,16),
UDim2.new(0.6,0,0,213), 11, COLORS.accent)
end
-- ============================================================
-- SECTION 34: EXTENDED ADAPTIVE THINKING (EAT)
-- ============================================================
--[[
Extended Adaptive Thinking (EAT):
Runs while the USER plays normally — no lending, no control taken.
Learns from BOTH the user's own actions AND other players simultaneously.
Layered with AI-mimicking systems:
- Pattern Reinforcement (dual-source confirmation)
- Attention Focus (prioritize active players)
- Action Chunking / Macro system
- Forgetting Curve (session time decay)
- Next-Action Prediction Engine
- Working Memory Buffer (short-term vs long-term)
- Behavior Clustering (path generalization)
]]
local EAT = {}
-- ---- STATE ----
EAT.active = false
EAT.sessionStart = 0
EAT.tickAccum = 0
EAT.sampleRate = 0.25 -- base sample rate, adapts by FPS + player count
-- ---- WORKING MEMORY BUFFER (short-term) ----
-- Holds last N actions seen by user. Checked before long-term brain.
EAT.workingMemory = {} -- circular buffer
EAT.WM_CAPACITY = 20 -- max entries
-- ---- MACRO / CHUNK SYSTEM ----
EAT.macros = {} -- named action chunks
EAT.currentChunk = {} -- current chunk being built
EAT.CHUNK_MIN_LEN = 3 -- minimum actions to form a chunk
EAT.CHUNK_MAX_LEN = 8
EAT.chunkLabels = {
["move|move|move|ui_click"] = "approach_and_interact",
["sprint_start|move|move|jump"] = "parkour_sequence",
["move|ui_click|move|move"] = "click_and_move",
["jump|move|move|move"] = "jump_traverse",
["sprint_start|move|move|move"] = "sprint_path",
["ui_click|ui_click|move"] = "multi_interact",
["move|move|jump|move|move"] = "gap_cross",
["ui_click|move|ui_click"] = "ui_navigate",
["move|move|move|move|move"] = "long_path",
["sprint_start|sprint_end|ui_click"] = "sprint_to_objective",
}
-- ---- ATTENTION SCORER ----
-- Each player gets a score 0-100 representing how worth-watching they are
EAT.attentionScores = {} -- [userId] = score
EAT.attentionHistory= {} -- [userId] = {lastPos, lastActionCount, lastUpdateTime}
-- ---- FORGETTING CURVE ----
-- Returns weight multiplier [0.0 - 1.0] based on session age in seconds
local FORGETTING_CURVE = {
{days=0, weight=1.00},
{days=3, weight=0.80},
{days=7, weight=0.60},
{days=14, weight=0.30},
{days=30, weight=0.10},
{days=999, weight=0.05},
}
local SECONDS_PER_DAY = 86400
function EAT.getTimeDecayWeight(sessionTimestamp)
if not sessionTimestamp then return 1.0 end
local ageSeconds = os.time() - sessionTimestamp
local ageDays = ageSeconds / SECONDS_PER_DAY
local weight = 0.05
for i = 1, #FORGETTING_CURVE - 1 do
local a = FORGETTING_CURVE[i]
local b = FORGETTING_CURVE[i + 1]
if ageDays >= a.days and ageDays < b.days then
-- Linear interpolation between curve points
local t = (ageDays - a.days) / (b.days - a.days)
weight = a.weight + (b.weight - a.weight) * t
break
end
end
return math.max(0.05, weight)
end
-- ---- PATTERN REINFORCEMENT ----
-- Tracks which sequences were seen from BOTH self AND observed players
EAT.reinforcedPatterns = {} -- sequenceKey -> {selfCount, observedCount, totalWeight}
function EAT.reinforcePattern(seqKey, source)
if not EAT.reinforcedPatterns[seqKey] then
EAT.reinforcedPatterns[seqKey] = {
selfCount = 0,
observedCount = 0,
totalWeight = 0,
}
end
local pat = EAT.reinforcedPatterns[seqKey]
if source == "self" then
pat.selfCount += 1
else
pat.observedCount += 1
end
-- Weight doubles when BOTH self and observed confirm it
if pat.selfCount > 0 and pat.observedCount > 0 then
pat.totalWeight = (pat.selfCount + pat.observedCount) * 2.0 -- dual-source bonus
else
pat.totalWeight = pat.selfCount * 1.0 + pat.observedCount * 0.6
end
end
function EAT.getPatternWeight(seqKey)
local pat = EAT.reinforcedPatterns[seqKey]
if not pat then return 0 end
return pat.totalWeight
end
-- ---- WORKING MEMORY ----
function EAT.pushWorkingMemory(action)
table.insert(EAT.workingMemory, action)
if #EAT.workingMemory > EAT.WM_CAPACITY then
table.remove(EAT.workingMemory, 1) -- drop oldest
end
end
function EAT.getWorkingMemoryKey()
-- Builds a sequence key from recent working memory entries
local key = ""
local start = math.max(1, #EAT.workingMemory - 4)
for i = start, #EAT.workingMemory do
key = key .. (EAT.workingMemory[i].type or "?") .. "|"
end
return key
end
-- ---- ACTION CHUNKING ----
function EAT.pushChunk(actionType)
table.insert(EAT.currentChunk, actionType)
if #EAT.currentChunk > EAT.CHUNK_MAX_LEN then
table.remove(EAT.currentChunk, 1)
end
-- Check if current chunk matches any label
EAT.detectMacro()
end
function EAT.detectMacro()
if #EAT.currentChunk < EAT.CHUNK_MIN_LEN then return nil end
-- Try matching from longest to shortest window
for len = math.min(#EAT.currentChunk, EAT.CHUNK_MAX_LEN), EAT.CHUNK_MIN_LEN, -1 do
local key = ""
local startIdx = #EAT.currentChunk - len + 1
for i = startIdx, #EAT.currentChunk do
key = key .. EAT.currentChunk[i] .. "|"
end
-- Remove trailing |
key = key:sub(1, -2)
local label = EAT.chunkLabels[key]
if label then
-- Register or reinforce this macro
if not EAT.macros[label] then
EAT.macros[label] = {
label = label,
key = key,
count = 0,
actions = {},
lastSeen = 0,
}
end
EAT.macros[label].count += 1
EAT.macros[label].lastSeen = os.time()
-- Save actual action data snapshot for replay
local place = getCurrentPlace()
if place and AIState.currentSession then
local total = #AIState.currentSession.actions
local slice = {}
for i = math.max(1, total - len + 1), total do
table.insert(slice, AIState.currentSession.actions[i])
end
EAT.macros[label].actions = slice
end
print("[VoidAI:EAT] Macro detected: " .. label .. " (x" .. EAT.macros[label].count .. ")")
return label
end
end
return nil
end
function EAT.saveMacros()
-- Persist macros into brain file
local place = getCurrentPlace()
if not place then return end
place.macros = EAT.macros
end
function EAT.loadMacros()
local place = getCurrentPlace()
if not place then return end
if place.macros then
EAT.macros = place.macros
local count = 0
for _ in pairs(EAT.macros) do count += 1 end
if count > 0 then
print("[VoidAI:EAT] Loaded " .. count .. " macros from brain")
end
end
end
-- ---- ATTENTION FOCUS ----
function EAT.updateAttentionScore(player)
local pid = player.UserId
local char = player.Character
if not char then return 0 end
local hrp = char:FindFirstChild("HumanoidRootPart")
local hum = char:FindFirstChildOfClass("Humanoid")
if not hrp or not hum then return 0 end
if not EAT.attentionHistory[pid] then
EAT.attentionHistory[pid] = {
lastPos = hrp.Position,
lastActionCount = 0,
lastUpdateTime = os.time(),
score = 50,
}
end
local hist = EAT.attentionHistory[pid]
local timeDelta= os.time() - hist.lastUpdateTime
if timeDelta < 0.5 then return hist.score end
local score = 0
-- Movement activity (0-40 pts)
local moveDelta = (hrp.Position - hist.lastPos).Magnitude
score += math.min(40, moveDelta * 2)
-- Walk speed (running = interesting, 0-20 pts)
if hum.WalkSpeed > 20 then score += 20
elseif hum.WalkSpeed > 14 then score += 10 end
-- Health change detection (combat/damage = interesting, 0-20 pts)
if hum.Health < hum.MaxHealth * 0.9 then score += 20
elseif hum.Health < hum.MaxHealth then score += 10 end
-- Idle penalty
if moveDelta < 1 and hum.WalkSpeed <= 0 then
score = math.max(0, score - 30)
end
-- Smooth score with exponential moving average
hist.score = hist.score * 0.7 + score * 0.3
hist.lastPos = hrp.Position
hist.lastUpdateTime= os.time()
EAT.attentionScores[pid] = hist.score
return hist.score
end
function EAT.getSortedPlayers()
-- Returns players sorted by attention score descending
local scored = {}
for _, p in ipairs(Players:GetPlayers()) do
if p ~= LocalPlayer then
local score = EAT.updateAttentionScore(p)
table.insert(scored, {player = p, score = score})
end
end
table.sort(scored, function(a, b) return a.score > b.score end)
return scored
end
-- ---- BEHAVIOR CLUSTERING ----
-- Groups similar movement positions into cluster centroids
-- So AI generalizes paths instead of memorizing exact coordinates
EAT.clusters = {} -- list of {centroid, members, tag}
EAT.CLUSTER_RADIUS = 15 -- studs — positions within this distance merge
function EAT.addToCluster(pos, tag)
-- Find nearest existing cluster
local nearest = nil
local nearestDist= math.huge
for i, cluster in ipairs(EAT.clusters) do
local c = cluster.centroid
local dist= (Vector3.new(c.x,c.y,c.z) - pos).Magnitude
if dist < nearestDist then
nearestDist = dist
nearest = i
end
end
if nearest and nearestDist <= EAT.CLUSTER_RADIUS then
-- Merge into existing cluster — update centroid
local cluster = EAT.clusters[nearest]
local n = #cluster.members + 1
cluster.centroid = {
x = (cluster.centroid.x * (n-1) + pos.X) / n,
y = (cluster.centroid.y * (n-1) + pos.Y) / n,
z = (cluster.centroid.z * (n-1) + pos.Z) / n,
}
table.insert(cluster.members, {x=pos.X, y=pos.Y, z=pos.Z})
if tag and tag ~= "visited" then cluster.tag = tag end
else
-- Create new cluster
table.insert(EAT.clusters, {
centroid = {x=pos.X, y=pos.Y, z=pos.Z},
members = {{x=pos.X, y=pos.Y, z=pos.Z}},
tag = tag or "visited",
weight = 1,
})
end
end
function EAT.getNearestCluster(pos)
local nearest = nil
local nearestDist= math.huge
for _, cluster in ipairs(EAT.clusters) do
local c = cluster.centroid
local dist= (Vector3.new(c.x,c.y,c.z) - pos).Magnitude
if dist < nearestDist then
nearestDist = dist
nearest = cluster
end
end
return nearest, nearestDist
end
function EAT.saveClusters()
local place = getCurrentPlace()
if not place then return end
-- Cap at 500 clusters to avoid bloat
if #EAT.clusters > 500 then
-- Keep only the most-membered clusters
table.sort(EAT.clusters, function(a,b)
return #a.members > #b.members
end)
local trimmed = {}
for i = 1, 500 do trimmed[i] = EAT.clusters[i] end
EAT.clusters = trimmed
end
place.clusters = EAT.clusters
end
function EAT.loadClusters()
local place = getCurrentPlace()
if not place then return end
if place.clusters then
EAT.clusters = place.clusters
print("[VoidAI:EAT] Loaded " .. #EAT.clusters .. " behavior clusters")
end
end
-- ---- NEXT-ACTION PREDICTION ENGINE ----
-- Scores candidate actions based on:
-- 1. Working memory context (recent actions)
-- 2. Reinforced pattern weights
-- 3. Cluster proximity
-- 4. Forgetting-curve-weighted session history
EAT.lastPrediction = nil
EAT.predictionScores= {}
local CANDIDATE_ACTIONS = {
"move", "jump", "sprint_start", "ui_click",
"hide", "flee", "interact", "wait",
}
function EAT.predictNextAction()
local place = getCurrentPlace()
if not place then return nil end
local wmKey = EAT.getWorkingMemoryKey()
local scores = {}
for _, candidate in ipairs(CANDIDATE_ACTIONS) do
local score = 0
-- 1. Working memory match: does recent history predict this next?
local testKey = wmKey .. candidate
for seqKey, pat in pairs(EAT.reinforcedPatterns) do
if seqKey:find(testKey, 1, true) then
score += EAT.getPatternWeight(seqKey) * 10
end
end
-- 2. Session history: how often does this action follow current context?
for _, session in ipairs(place.sessions) do
local decay = EAT.getTimeDecayWeight(session.startTime or session.id)
local trust = (session.type == "adaptive_observed") and 0.6 or 1.0
local actions= session.actions or {}
for i = 2, #actions do
if actions[i-1].type == (EAT.workingMemory[#EAT.workingMemory] or {}).type then
if actions[i].type == candidate then
score += decay * trust * 5
end
end
end
end
-- 3. Threat modifier: if threat detected, boost survival actions
if AIState.threatDetected then
if candidate == "hide" or candidate == "flee" then
score += 50
end
if candidate == "ui_click" or candidate == "wait" then
score -= 20
end
end
-- 4. Persona modifier
if AIState.persona == "Aggressive" then
if candidate == "sprint_start" or candidate == "move" then score += 10 end
if candidate == "hide" or candidate == "wait" then score -= 15 end
elseif AIState.persona == "Passive" then
if candidate == "wait" or candidate == "ui_click" then score += 10 end
if candidate == "sprint_start" then score -= 10 end
end
scores[candidate] = math.max(0, score)
end
-- Find highest scoring candidate
local bestAction = "move"
local bestScore = -1
for action, score in pairs(scores) do
if score > bestScore then
bestScore = score
bestAction = action
end
end
EAT.lastPrediction = bestAction
EAT.predictionScores = scores
return bestAction, bestScore, scores
end
-- ---- FORGETTING CURVE APPLICATION ----
-- Recalculates confidence using time-decayed session weights
-- Called periodically so confidence reflects recent relevance
function EAT.applyForgettingCurve()
local place = getCurrentPlace()
if not place or #place.sessions == 0 then return end
local totalMoves, totalUI, totalSeq = 0, 0, 0
for _, session in ipairs(place.sessions) do
local decay = EAT.getTimeDecayWeight(session.startTime or session.id)
local trust = (session.type == "adaptive_observed")
and AdaptiveThinking.TRUST_WEIGHT or 1.0
local w = decay * trust
totalMoves = totalMoves + #(session.movements or {}) * w
totalUI = totalUI + #(session.uiClicks or {}) * w
totalSeq = totalSeq + #(session.sequences or {}) * w
end
place.confidence.movement = math.min(100, math.floor(math.log(totalMoves + 1) * 15))
place.confidence.ui = math.min(100, math.floor(math.log(totalUI + 1) * 20))
place.confidence.sequences = math.min(100, math.floor(math.log(totalSeq + 1) * 25))
end
-- ---- CORE: OBSERVE SELF TICK ----
-- Runs while EAT is active AND user is actively playing
function EAT.observeSelf()
local char = LocalPlayer.Character
if not char then return end
local hrp = char:FindFirstChild("HumanoidRootPart")
local hum = char:FindFirstChildOfClass("Humanoid")
if not hrp or not hum then return end
local pos = hrp.Position
-- Record movement to working memory
local moveDelta = (pos - Observer.lastPosition).Magnitude
if moveDelta >= 2 then
local action = {
type = "move",
source = "self",
data = {pos={x=pos.X,y=pos.Y,z=pos.Z}, t=os.time()-AIState.sessionStart},
}
EAT.pushWorkingMemory(action)
EAT.pushChunk("move")
-- Add to behavior clusters
EAT.addToCluster(pos, "visited")
-- Reinforce this as a self-observed pattern
local wmKey = EAT.getWorkingMemoryKey()
EAT.reinforcePattern(wmKey, "self")
end
end
-- ---- CORE: OBSERVE OTHERS TICK ----
-- Attention-weighted player sampling
function EAT.observeOthers()
local place = getCurrentPlace()
if not place then return end
local sortedPlayers = EAT.getSortedPlayers()
-- Top 30% of players get 3x more samples, bottom 20% are skipped
local total = #sortedPlayers
if total == 0 then return end
for rank, entry in ipairs(sortedPlayers) do
local p = entry.player
local score = entry.score
-- Skip idle players entirely (score < 15)
if score < 15 then continue end
-- Sub-sample low-attention players (50% chance)
local normalized = rank / total -- 0=highest attention, 1=lowest
if normalized > 0.7 then
if math.random() > 0.5 then continue end
end
local char = p.Character
if not char then continue end
local hrp = char:FindFirstChild("HumanoidRootPart")
local hum = char:FindFirstChildOfClass("Humanoid")
if not hrp or not hum then continue end
local pos = hrp.Position
-- Track this player's previous position
local pid = p.UserId
if not AdaptiveThinking.playerTrackers[pid] then
AdaptiveThinking.playerTrackers[pid] = {
name = p.Name,
lastPos = pos,
lastMoveT = os.time(),
pathPoints= {},
isRunning = false,
fleeCount = 0,
}
end
local tracker = AdaptiveThinking.playerTrackers[pid]
local moveDelta= (pos - tracker.lastPos).Magnitude
if moveDelta < 2 then continue end
tracker.lastPos = pos
-- Build action from observation
local observedAction = {
type = "move",
source = "observed_" .. p.Name,
trustWeight = AdaptiveThinking.TRUST_WEIGHT,
data = {
pos = {x=pos.X, y=pos.Y, z=pos.Z},
t = os.time() - EAT.sessionStart,
spd = hum.WalkSpeed,
},
}
-- Push to working memory with lower trust
EAT.pushWorkingMemory(observedAction)
-- Pattern reinforcement — observed source
local wmKey = EAT.getWorkingMemoryKey()
EAT.reinforcePattern(wmKey, "observed")
-- Add to clusters
EAT.addToCluster(pos, "visited")
-- Detect fleeing
if hum.WalkSpeed > 20 and not tracker.isRunning then
tracker.isRunning = true
EAT.addToCluster(pos, "danger")
elseif hum.WalkSpeed <= 16 then
tracker.isRunning = false
end
-- Check if same path was also taken by self recently
-- If so, reinforce double
local selfNearby = false
if LocalPlayer.Character then
local selfHRP = LocalPlayer.Character:FindFirstChild("HumanoidRootPart")
if selfHRP and (selfHRP.Position - pos).Magnitude < EAT.CLUSTER_RADIUS then
selfNearby = true
end
end
if selfNearby then
EAT.reinforcePattern(wmKey, "self") -- dual-source reinforce
end
end
end
-- ---- MAIN EAT TICK (plugged into RunService) ----
RunService.Heartbeat:Connect(function(dt)
if not EAT.active then return end
EAT.tickAccum += dt
-- Adapt sample rate same as ATM
local playerCount = #Players:GetPlayers() - 1
if FPS > 50 and playerCount <= 5 then
EAT.sampleRate = 0.2
elseif FPS > 30 then
EAT.sampleRate = 0.35
else
EAT.sampleRate = 0.6
end
if EAT.tickAccum < EAT.sampleRate then return end
EAT.tickAccum = 0
-- Observe self
EAT.observeSelf()
-- Observe others
EAT.observeOthers()
-- Run prediction every 2 seconds (low priority)
if AIState.tickCounter % 8 == 0 then
EAT.predictNextAction()
end
-- Apply forgetting curve every 30 seconds
if AIState.tickCounter % 120 == 0 then
EAT.applyForgettingCurve()
end
-- Auto-save macros + clusters every 60 seconds
if AIState.tickCounter % 240 == 0 then
EAT.saveMacros()
EAT.saveClusters()
saveBrain()
end
end)
-- ---- PUBLIC API ----
function EAT.start()
if EAT.active then return end
EAT.active = true
EAT.sessionStart = os.time()
EAT.workingMemory = {}
EAT.currentChunk = {}
AdaptiveThinking.playerTrackers = {}
-- Load existing macros + clusters from brain
EAT.loadMacros()
EAT.loadClusters()
-- Make sure observer session is also running (self-learning)
if not AIState.isLearning then
AIState.isLearning = true
Observer.startSession()
end
VoidGUI.notify("⚡ Extended Adaptive Thinking ON")
print("[VoidAI:EAT] Started — dual-source learning active")
end
function EAT.stop()
if not EAT.active then return end
EAT.active = false
-- Finalize
EAT.saveMacros()
EAT.saveClusters()
EAT.applyForgettingCurve()
saveBrain()
-- Print summary
local macroCount = 0
local clusterCount = #EAT.clusters
local patCount = 0
for _ in pairs(EAT.macros) do macroCount += 1 end
for _ in pairs(EAT.reinforcedPatterns) do patCount += 1 end
VoidGUI.notify(string.format("⚡ EAT OFF — %d macros | %d clusters | %d patterns",
macroCount, clusterCount, patCount))
print(string.format("[VoidAI:EAT] Stopped — Macros:%d Clusters:%d Patterns:%d",
macroCount, clusterCount, patCount))
end
function EAT.toggle()
if EAT.active then EAT.stop() else EAT.start() end
end
-- ---- GHOST/LEND INTEGRATION ----
-- GhostController checks EAT prediction before doing linear replay
local _eatWrappedStepReplay = GhostController.stepReplay
function GhostController.stepReplay()
-- If EAT prediction is available and confident, use it
if EAT.lastPrediction and EAT.predictionScores then
local bestScore = EAT.predictionScores[EAT.lastPrediction] or 0
if bestScore > 20 then -- confidence threshold
local char = LocalPlayer.Character
local hum = char and char:FindFirstChildOfClass("Humanoid")
local hrp = char and char:FindFirstChild("HumanoidRootPart")
if EAT.lastPrediction == "jump" and hum then
hum.Jump = true
EAT.pushChunk("jump")
return
elseif EAT.lastPrediction == "sprint_start" and hum then
hum.WalkSpeed = 24
EAT.pushChunk("sprint_start")
return
elseif EAT.lastPrediction == "hide" then
GhostController.runSurvivalPattern()
return
elseif EAT.lastPrediction == "flee" then
GhostController.runSurvivalPattern()
return
elseif EAT.lastPrediction == "move" then
-- Use nearest cluster centroid as target
if hrp then
local cluster, dist = EAT.getNearestCluster(hrp.Position)
if cluster and dist > 5 then
local target = Vector3.new(
cluster.centroid.x,
cluster.centroid.y,
cluster.centroid.z
)
if hum then hum:MoveTo(target) end
EAT.pushChunk("move")
return
end
end
end
end
end
-- Fallback: check macro library first
local wmKey = EAT.getWorkingMemoryKey()
for label, macro in pairs(EAT.macros) do
if macro.count >= 2 and #macro.actions > 0 then
-- Find a macro whose key is predicted by current context
if wmKey:find(macro.key:sub(1,6), 1, true) then
-- Replay macro actions
task.spawn(function()
for _, action in ipairs(macro.actions) do
if AIState.mode ~= "ghost" and AIState.mode ~= "lend" then break end
if AIState.isPaused then break end
local char = LocalPlayer.Character
local hum = char and char:FindFirstChildOfClass("Humanoid")
if action.type == "move" and action.data then
GhostController.executeMove(action.data)
elseif action.type == "ui_click" and action.data then
GhostController.executeUIClick(action.data)
elseif action.type == "jump" and hum then
hum.Jump = true
end
task.wait(0.15)
end
end)
return
end
end
end
-- Final fallback: linear replay
_eatWrappedStepReplay()
end
-- ---- GUI INTEGRATION ----
task.spawn(function()
task.wait(1.2)
if not screenGui then return end
local panel = screenGui:FindFirstChild("VoidAIPanel")
if not panel then return end
local scrollFrames = {}
for _, child in ipairs(panel:GetChildren()) do
if child:IsA("ScrollingFrame") then
table.insert(scrollFrames, child)
end
end
local mainScroll = scrollFrames[1]
local settingsScroll = scrollFrames[4]
if mainScroll then
-- EAT toggle button (below ATM button)
local eatBtn = makeButton(mainScroll,
"⚡ Ext. Adaptive Thinking: OFF",
UDim2.new(1,-12,0,28),
UDim2.new(0,6,0,356),
COLORS.panel)
eatBtn.TextSize = 11
-- Prediction display
local predLabel = makeLabel(mainScroll,
"Prediction: —",
UDim2.new(1,-12,0,14),
UDim2.new(0,6,0,388),
9, COLORS.textDim)
-- Macro count
local macroLabel = makeLabel(mainScroll,
"Macros: 0 | Clusters: 0 | Patterns: 0",
UDim2.new(1,-12,0,14),
UDim2.new(0,6,0,404),
9, COLORS.textDim)
eatBtn.MouseButton1Click:Connect(function()
EAT.toggle()
if EAT.active then
eatBtn.Text = "⚡ Ext. Adaptive Thinking: ON"
eatBtn.BackgroundColor3 = COLORS.accentHot
else
eatBtn.Text = "⚡ Ext. Adaptive Thinking: OFF"
eatBtn.BackgroundColor3 = COLORS.panel
end
end)
-- Live update loop for EAT status
task.spawn(function()
while true do
task.wait(1)
if not mainScroll or not mainScroll.Parent then break end
-- Update button state
if EAT.active then
eatBtn.Text = "⚡ Ext. Adaptive Thinking: ON"
eatBtn.BackgroundColor3 = COLORS.accentHot
else
eatBtn.Text = "⚡ Ext. Adaptive Thinking: OFF"
eatBtn.BackgroundColor3 = COLORS.panel
end
-- Update prediction label
if EAT.lastPrediction then
local score = EAT.predictionScores and
EAT.predictionScores[EAT.lastPrediction] or 0
predLabel.Text = string.format("Prediction: %s (%.0f pts)",
EAT.lastPrediction, score)
predLabel.TextColor3 = score > 20 and COLORS.success or COLORS.textDim
else
predLabel.Text = "Prediction: — (not enough data)"
predLabel.TextColor3 = COLORS.textDim
end
-- Macro / cluster / pattern counts
local macroCount = 0
local patternCount = 0
for _ in pairs(EAT.macros) do macroCount += 1 end
for _ in pairs(EAT.reinforcedPatterns) do patternCount += 1 end
macroLabel.Text = string.format(
"Macros:%d | Clusters:%d | Patterns:%d",
macroCount, #EAT.clusters, patternCount)
end
end)
mainScroll.CanvasSize = UDim2.new(0,0,0,650)
end
-- Add EAT stats to settings tab
if settingsScroll then
makeLabel(settingsScroll,
"EAT Settings",
UDim2.new(1,-12,0,14),
UDim2.new(0,6,0,540),
11, COLORS.accent).Font = Enum.Font.GothamBold
-- AFK threshold slider (text input)
makeLabel(settingsScroll,
"AFK Threshold (sec)",
UDim2.new(0.55,0,0,22),
UDim2.new(0,6,0,558),
10, COLORS.textDim)
local afkBox = Instance.new("TextBox")
afkBox.Size = UDim2.new(0.35,0,0,22)
afkBox.Position = UDim2.new(0.6,0,0,558)
afkBox.BackgroundColor3 = COLORS.panel
afkBox.TextColor3 = COLORS.text
afkBox.Text = tostring(AdaptiveThinking.afkThreshold)
afkBox.Font = Enum.Font.GothamBold
afkBox.TextSize = 11
afkBox.BorderSizePixel = 0
afkBox.Parent = settingsScroll
local afkc = Instance.new("UICorner"); afkc.CornerRadius = UDim.new(0,4); afkc.Parent = afkBox
afkBox.FocusLost:Connect(function()
local val = tonumber(afkBox.Text)
if val and val >= 5 and val <= 300 then
AdaptiveThinking.afkThreshold = val
VoidGUI.notify("AFK threshold: " .. val .. "s")
else
afkBox.Text = tostring(AdaptiveThinking.afkThreshold)
end
end)
-- Cluster radius input
makeLabel(settingsScroll,
"Cluster Radius (studs)",
UDim2.new(0.55,0,0,22),
UDim2.new(0,6,0,584),
10, COLORS.textDim)
local clusterBox = Instance.new("TextBox")
clusterBox.Size = UDim2.new(0.35,0,0,22)
clusterBox.Position = UDim2.new(0.6,0,0,584)
clusterBox.BackgroundColor3 = COLORS.panel
clusterBox.TextColor3 = COLORS.text
clusterBox.Text = tostring(EAT.CLUSTER_RADIUS)
clusterBox.Font = Enum.Font.GothamBold
clusterBox.TextSize = 11
clusterBox.BorderSizePixel = 0
clusterBox.Parent = settingsScroll
local clc = Instance.new("UICorner"); clc.CornerRadius = UDim.new(0,4); clc.Parent = clusterBox
clusterBox.FocusLost:Connect(function()
local val = tonumber(clusterBox.Text)
if val and val >= 5 and val <= 100 then
EAT.CLUSTER_RADIUS = val
else
clusterBox.Text = tostring(EAT.CLUSTER_RADIUS)
end
end)
-- Clear clusters button
local clearBtn = makeButton(settingsScroll,
"🗑 Clear Clusters",
UDim2.new(1,-12,0,26),
UDim2.new(0,6,0,610),
COLORS.danger)
clearBtn.MouseButton1Click:Connect(function()
EAT.clusters = {}
local place = getCurrentPlace()
if place then place.clusters = {} end
VoidGUI.notify("Clusters cleared")
end)
settingsScroll.CanvasSize = UDim2.new(0,0,0,660)
end
end)
-- ---- CHAT COMMANDS ----
CHAT_COMMANDS["eat"] = function() EAT.toggle() end
CHAT_COMMANDS["macros"] = function()
local count = 0
local names = {}
for label, macro in pairs(EAT.macros) do
count += 1
table.insert(names, label .. "(x" .. macro.count .. ")")
end
VoidGUI.notify("Macros (" .. count .. "): " .. table.concat(names, ", "):sub(1, 80))
end
CHAT_COMMANDS["predict"] = function()
local action, score = EAT.predictNextAction()
VoidGUI.notify("Prediction: " .. (action or "none") .. " (" .. math.floor(score or 0) .. " pts)")
end
CHAT_COMMANDS["forget"] = function()
EAT.applyForgettingCurve()
VoidGUI.notify("Forgetting curve applied — confidence recalculated")
end
-- ---- INIT HOOK ----
-- Load EAT data when brain loads (runs after main init)
task.spawn(function()
task.wait(2)
EAT.loadMacros()
EAT.loadClusters()
local macroCount = 0
for _ in pairs(EAT.macros) do macroCount += 1 end
if macroCount > 0 then
print("[VoidAI:EAT] " .. macroCount .. " macros ready | " .. #EAT.clusters .. " clusters loaded")
end
end)
-- ============================================================
-- SECTION 35: ANOMALY DETECTION
-- ============================================================
--[[
Watches for unusual events the AI hasn't seen before.
Flags them as "anomalies" and stores them separately.
Over time, anomalies that repeat get promoted to normal patterns.
Mimics how a real AI handles out-of-distribution inputs.
]]
local AnomalyDetector = {}
AnomalyDetector.anomalies = {} -- {event, count, firstSeen, lastSeen}
AnomalyDetector.PROMOTE_COUNT= 3 -- seen N times = no longer anomaly
function AnomalyDetector.check(actionType, context)
local place = getCurrentPlace()
if not place or #place.sessions < 2 then return false end
-- Check if this action type has ever appeared in any session
local seen = false
for _, session in ipairs(place.sessions) do
for _, action in ipairs(session.actions or {}) do
if action.type == actionType then
seen = true
break
end
end
if seen then break end
end
if not seen then
-- New anomaly
local key = actionType .. "|" .. (context or "")
if not AnomalyDetector.anomalies[key] then
AnomalyDetector.anomalies[key] = {
event = actionType,
context = context,
count = 0,
firstSeen = os.time(),
lastSeen = os.time(),
promoted = false,
}
end
local a = AnomalyDetector.anomalies[key]
a.count += 1
a.lastSeen = os.time()
if a.count >= AnomalyDetector.PROMOTE_COUNT and not a.promoted then
a.promoted = true
print("[VoidAI:Anomaly] Promoted to pattern: " .. key)
VoidGUI.notify("🔍 New pattern learned: " .. actionType)
end
return true
end
return false
end
function AnomalyDetector.getUnpromoted()
local list = {}
for _, a in pairs(AnomalyDetector.anomalies) do
if not a.promoted then table.insert(list, a) end
end
table.sort(list, function(a,b) return a.count > b.count end)
return list
end
-- ============================================================
-- SECTION 36: EMOTION / URGENCY STATE
-- ============================================================
--[[
The AI maintains an internal urgency level (0-100).
Low urgency = slow careful decisions.
High urgency = fast aggressive decisions.
Driven by: threat detection, player health, nearby fleeing players,
time pressure signals (timers in UI), and persona.
Mimics an emotional state layer found in modern RL agents.
]]
local UrgencySystem = {}
UrgencySystem.level = 0 -- 0 = calm, 100 = panic
UrgencySystem.history = {} -- rolling history for smoothing
UrgencySystem.MAX_HIST = 10
function UrgencySystem.update()
local rawUrgency = 0
-- Threat detected = massive urgency spike
if AIState.threatDetected then
rawUrgency += 60
end
-- Player health
local char = LocalPlayer.Character
if char then
local hum = char:FindFirstChildOfClass("Humanoid")
if hum and hum.MaxHealth > 0 then
local healthRatio = hum.Health / hum.MaxHealth
if healthRatio < 0.3 then
rawUrgency += 40
elseif healthRatio < 0.6 then
rawUrgency += 20
end
end
end
-- Nearby players fleeing
local fleeCount = 0
for _, p in ipairs(Players:GetPlayers()) do
if p ~= LocalPlayer and p.Character then
local h = p.Character:FindFirstChildOfClass("Humanoid")
if h and h.WalkSpeed > 20 then fleeCount += 1 end
end
end
rawUrgency += math.min(30, fleeCount * 10)
-- Persona modifier
if AIState.persona == "Aggressive" then
rawUrgency = rawUrgency * 1.3
elseif AIState.persona == "Passive" then
rawUrgency = rawUrgency * 0.6
end
rawUrgency = math.min(100, rawUrgency)
-- Smooth with rolling average
table.insert(UrgencySystem.history, rawUrgency)
if #UrgencySystem.history > UrgencySystem.MAX_HIST then
table.remove(UrgencySystem.history, 1)
end
local sum = 0
for _, v in ipairs(UrgencySystem.history) do sum += v end
UrgencySystem.level = sum / #UrgencySystem.history
end
function UrgencySystem.getLabel()
if UrgencySystem.level >= 75 then return "PANIC", COLORS.danger
elseif UrgencySystem.level >= 50 then return "HIGH", COLORS.warning
elseif UrgencySystem.level >= 25 then return "MED", COLORS.accent
else return "CALM", COLORS.success
end
end
-- Wire urgency into replay speed — higher urgency = faster action cadence
local function getReplayInterval()
local base = 0.2
local urgencyFactor = 1 - (UrgencySystem.level / 100) * 0.7
return math.max(0.05, base * urgencyFactor)
end
-- Update urgency every second in heartbeat
local lastUrgencyTick = 0
RunService.Heartbeat:Connect(function(dt)
lastUrgencyTick += dt
if lastUrgencyTick >= 1 then
lastUrgencyTick = 0
UrgencySystem.update()
end
end)
-- ============================================================
-- SECTION 37: GOAL INFERENCE ENGINE
-- ============================================================
--[[
Tries to infer what the player's current goal is
based on observed actions + mental map context.
Goals feed into the prediction engine and LLM prompts.
Mimics goal-directed planning in modern AI agents.
]]
local GoalEngine = {}
GoalEngine.currentGoal = "explore" -- default
GoalEngine.goalHistory = {}
GoalEngine.goalScores = {}
local GOAL_SIGNALS = {
explore = {"move", "move", "move", "move"},
combat = {"sprint_start", "jump", "move", "sprint_start"},
interact_ui = {"ui_click", "ui_click", "move"},
collect = {"move", "ui_click", "move", "ui_click"},
escape = {"sprint_start", "move", "jump", "sprint_start", "move"},
idle = {"wait", "wait", "wait"},
}
function GoalEngine.infer()
if #EAT.workingMemory < 4 then return GoalEngine.currentGoal end
-- Build recent action string
local recent = {}
local start = math.max(1, #EAT.workingMemory - 5)
for i = start, #EAT.workingMemory do
table.insert(recent, EAT.workingMemory[i].type or "?")
end
GoalEngine.goalScores = {}
for goal, signals in pairs(GOAL_SIGNALS) do
local score = 0
for i, sig in ipairs(signals) do
if recent[i] == sig then score += 1 end
end
GoalEngine.goalScores[goal] = score / #signals
end
-- Threat override
if AIState.threatDetected then
GoalEngine.goalScores["escape"] = 1.0
end
-- Pick highest
local bestGoal = GoalEngine.currentGoal
local bestScore = 0
for goal, score in pairs(GoalEngine.goalScores) do
if score > bestScore then
bestScore = score
bestGoal = goal
end
end
if bestGoal ~= GoalEngine.currentGoal then
table.insert(GoalEngine.goalHistory, {
from = GoalEngine.currentGoal,
to = bestGoal,
t = os.time(),
})
GoalEngine.currentGoal = bestGoal
print("[VoidAI:Goal] Inferred goal: " .. bestGoal)
end
return GoalEngine.currentGoal
end
-- Patch LLM prompt to include goal
local _origBuildPrompt = LLMController.buildPrompt
function LLMController.buildPrompt(gameState)
local base = _origBuildPrompt(gameState)
if not base then return nil end
local urgLabel = UrgencySystem.getLabel()
local goal = GoalEngine.infer()
local anomalies= AnomalyDetector.getUnpromoted()
local anomStr = ""
for i, a in ipairs(anomalies) do
if i > 3 then break end
anomStr = anomStr .. a.event .. "(x" .. a.count .. ") "
end
return base
.. string.format("\nInferred goal: %s", goal)
.. string.format("\nUrgency level: %s (%.0f/100)", urgLabel, UrgencySystem.level)
.. string.format("\nUnexplained events: %s", anomStr ~= "" and anomStr or "none")
.. string.format("\nActive macros: %d | Behavior clusters: %d",
(function() local c=0; for _ in pairs(EAT.macros) do c+=1 end; return c end)(),
#EAT.clusters)
end
-- ============================================================
-- SECTION 38: SELF-ASSESSMENT REPORT
-- ============================================================
--[[
Periodically generates a plain-text self-assessment of the AI's
current knowledge state. Useful for the user to understand what
the AI knows and what it still needs to learn.
Also used as context when switching from Local AI to LLM mode.
]]
local SelfAssessment = {}
function SelfAssessment.generate()
local place = getCurrentPlace()
local urgLabel = UrgencySystem.getLabel()
local goal = GoalEngine.currentGoal
local macroCount, patternCount = 0, 0
for _ in pairs(EAT.macros) do macroCount += 1 end
for _ in pairs(EAT.reinforcedPatterns) do patternCount += 1 end
local anomalies = AnomalyDetector.getUnpromoted()
local weakAreas = {}
local strongAreas= {}
if place then
if place.confidence.movement < 40 then table.insert(weakAreas, "movement")
else table.insert(strongAreas, "movement") end
if place.confidence.ui < 40 then table.insert(weakAreas, "UI interaction")
else table.insert(strongAreas, "UI interaction") end
if place.confidence.sequences < 40 then table.insert(weakAreas, "action sequences")
else table.insert(strongAreas, "action sequences") end
end
local lines = {
"=== VoidAI Self-Assessment ===",
"Game: " .. (place and place.placeName or "Unknown"),
"Sessions: " .. (place and #place.sessions or 0),
"Goal: " .. goal,
"Urgency: " .. urgLabel .. string.format(" (%.0f/100)", UrgencySystem.level),
"",
"KNOWLEDGE:",
" Strong: " .. (#strongAreas > 0 and table.concat(strongAreas, ", ") or "none yet"),
" Weak: " .. (#weakAreas > 0 and table.concat(weakAreas, ", ") or "none"),
"",
"LEARNED STRUCTURES:",
" Macros: " .. macroCount,
" Clusters: " .. #EAT.clusters,
" Patterns: " .. patternCount,
" Anomalies:" .. #anomalies .. " unresolved",
"",
"RECOMMENDATIONS:",
}
if #weakAreas > 0 then
table.insert(lines, " → Train more: " .. table.concat(weakAreas, ", "))
end
if macroCount < 3 then
table.insert(lines, " → Play longer to build macro library")
end
if #EAT.clusters < 10 then
table.insert(lines, " → Explore more areas to build mental map")
end
if place and #place.sessions >= 5 and macroCount >= 3 then
table.insert(lines, " ✓ AI is ready for LLM-assisted play")
end
table.insert(lines, "")
table.insert(lines, "Generated: " .. tostring(os.time()))
local report = table.concat(lines, "\n")
-- Save to file
local path = BRAIN_FOLDER .. "/assessment_" .. getPlaceId() .. ".txt"
Exec.writeFile(path, report)
return report
end
-- Add to chat commands
CHAT_COMMANDS["assess"] = function()
local report = SelfAssessment.generate()
-- Show first 3 lines as notification
local firstLines = report:split("\n")
local preview = ""
for i = 1, math.min(3, #firstLines) do
preview = preview .. firstLines[i] .. " | "
end
VoidGUI.notify(preview)
print(report)
end
-- ============================================================
-- SECTION 39: REPLAY SPEED CONTROL + LOOP MODES
-- ============================================================
--[[
Gives the user fine control over how the AI replays sessions.
Modes:
- Normal: plays actions at recorded pace
- Fast: 2x speed
- Cautious: 0.5x speed, more pauses
- Loop: repeats best session forever
- Once: plays once then stops
Also integrates urgency — high urgency overrides to Fast mode.
]]
local ReplayControl = {}
ReplayControl.speed = 1.0 -- multiplier
ReplayControl.loopMode = "loop" -- "loop" | "once"
ReplayControl.paused = false
local SPEED_PRESETS = {
Cautious = 0.5,
Normal = 1.0,
Fast = 2.0,
Panic = 4.0, -- auto-set by urgency
}
function ReplayControl.setSpeed(preset)
ReplayControl.speed = SPEED_PRESETS[preset] or 1.0
VoidGUI.notify("Replay speed: " .. preset)
end
function ReplayControl.getInterval()
local base = 0.2
local urgency = UrgencySystem.level
-- High urgency overrides to faster replay
local urgencyMod = 1 - (urgency / 100) * 0.7
local finalInterval = (base / ReplayControl.speed) * urgencyMod
return math.max(0.04, finalInterval)
end
-- Patch main heartbeat ghost/lend step to use ReplayControl interval
local replayAccum = 0
RunService.Heartbeat:Connect(function(dt)
if AIState.mode ~= "ghost" and AIState.mode ~= "lend" then
replayAccum = 0
return
end
if AIState.isPaused then return end
replayAccum += dt
if replayAccum >= ReplayControl.getInterval() then
replayAccum = 0
GhostController.stepReplay()
end
end)
-- ============================================================
-- SECTION 40: MULTI-BRAIN SUPPORT
-- ============================================================
--[[
Allows saving and loading multiple named brain profiles
for the same game. Useful if you want a "grind" brain
and a "exploration" brain for the same experience.
Named brains are stored as universe_ID_profilename.json
]]
local MultiBrain = {}
MultiBrain.profiles = {}
function MultiBrain.listProfiles()
local files = Exec.listFiles(BRAIN_FOLDER)
local profiles = {}
local prefix = "universe_" .. getUniverseId()
for _, f in ipairs(files) do
if f:find(prefix) then
-- Extract profile name from filename
local name = f:match("universe_%d+_?(.-)%.json") or "default"
table.insert(profiles, {file = f, name = name == "" and "default" or name})
end
end
return profiles
end
function MultiBrain.saveAs(profileName)
local safeName = profileName:gsub("[^%w%-_]", "")
if safeName == "" then safeName = "default" end
local path = BRAIN_FOLDER .. "/universe_" .. getUniverseId() .. "_" .. safeName .. ".json"
local ok, encoded = pcall(HttpService.JSONEncode, HttpService, Brain)
if ok then
Exec.writeFile(path, encoded)
VoidGUI.notify("Brain saved as: " .. safeName)
end
end
function MultiBrain.loadProfile(profileName)
local safeName = profileName:gsub("[^%w%-_]", "")
local path = BRAIN_FOLDER .. "/universe_" .. getUniverseId() .. "_" .. safeName .. ".json"
if not Exec.isFile(path) then
VoidGUI.notify("Profile not found: " .. safeName)
return false
end
local raw = Exec.readFile(path)
if not raw then return false end
local ok, decoded = pcall(HttpService.JSONDecode, HttpService, raw)
if ok and decoded then
Brain = decoded
Brain.baseKnowledge = BASE_KNOWLEDGE
EAT.loadMacros()
EAT.loadClusters()
VoidGUI.notify("Loaded brain: " .. safeName)
VoidGUI.updateDisplay()
return true
end
return false
end
-- Chat commands for multi-brain
CHAT_COMMANDS["saveas"] = function()
-- /void saveas profilename
-- Parse not possible from single command, use notify to guide
VoidGUI.notify("Type /void saveas_<name> to save a profile")
end
-- Dynamic command for /void saveas_grind, /void saveas_explore etc.
local _origChatHandler = nil
pcall(function()
LocalPlayer.Chatted:Connect(function(msg)
local lower = msg:lower()
local parts = lower:split(" ")
if parts[1] ~= CHAT_PREFIX then return end
local cmd = parts[2] or ""
-- Handle saveas_ prefix
if cmd:sub(1, 7) == "saveas_" then
local profileName = cmd:sub(8)
if profileName and profileName ~= "" then
MultiBrain.saveAs(profileName)
end
elseif cmd:sub(1, 5) == "load_" then
local profileName = cmd:sub(6)
if profileName and profileName ~= "" then
MultiBrain.loadProfile(profileName)
end
end
end)
end)
-- ============================================================
-- SECTION 41: PERFORMANCE PROFILER
-- ============================================================
--[[
Tracks how long each VoidAI subsystem takes per frame.
Displayed in stats tab. Helps identify bottlenecks on low-end devices.
Auto-disables expensive subsystems if they exceed budget.
]]
local Profiler = {}
Profiler.budgetMs = 2.0 -- max ms per frame allowed for VoidAI
Profiler.times = {
observer = 0,
eat = 0,
atm = 0,
threat = 0,
urgency = 0,
total = 0,
}
Profiler.warnings = {}
Profiler.enabled = true
local function profiledRun(label, fn)
if not Profiler.enabled then fn(); return end
local start = os.clock()
local ok, err = pcall(fn)
local elapsed = (os.clock() - start) * 1000 -- ms
Profiler.times[label] = Profiler.times[label] * 0.8 + elapsed * 0.2 -- EMA
if elapsed > Profiler.budgetMs then
table.insert(Profiler.warnings, {
label = label,
ms = elapsed,
time = os.time(),
})
-- Cap warnings list
if #Profiler.warnings > 20 then
table.remove(Profiler.warnings, 1)
end
end
if not ok then
warn("[VoidAI:Profiler] Error in " .. label .. ": " .. tostring(err))
end
end
-- ============================================================
-- SECTION 42: FINAL GUI POLISH — URGENCY + GOAL + PREDICTION
-- ============================================================
task.spawn(function()
task.wait(1.5)
if not screenGui then return end
local panel = screenGui:FindFirstChild("VoidAIPanel")
if not panel then return end
local scrollFrames = {}
for _, child in ipairs(panel:GetChildren()) do
if child:IsA("ScrollingFrame") then
table.insert(scrollFrames, child)
end
end
local mainScroll = scrollFrames[1]
local statsScroll = scrollFrames[2]
-- Add urgency + goal bar to main tab
if mainScroll then
local urgencyLabel = makeLabel(mainScroll,
"Urgency: CALM | Goal: explore",
UDim2.new(1,-12,0,14),
UDim2.new(0,6,0,422),
9, COLORS.success)
task.spawn(function()
while true do
task.wait(1)
if not urgencyLabel or not urgencyLabel.Parent then break end
local label, color = UrgencySystem.getLabel()
local goal = GoalEngine.currentGoal
urgencyLabel.Text = string.format(
"Urgency: %s (%.0f) | Goal: %s",
label, UrgencySystem.level, goal)
urgencyLabel.TextColor3 = color
end
end)
mainScroll.CanvasSize = UDim2.new(0,0,0,700)
end
-- Add profiler readout to stats tab
if statsScroll then
makeLabel(statsScroll,
"⚡ Subsystem Timing (ms avg)",
UDim2.new(1,-12,0,14),
UDim2.new(0,6,0,440),
11, COLORS.accent).Font = Enum.Font.GothamBold
local profLabels = {}
local subsystems = {"observer","eat","atm","threat","urgency","total"}
for i, sys in ipairs(subsystems) do
local lbl = makeLabel(statsScroll,
sys .. ": 0.0ms",
UDim2.new(1,-12,0,13),
UDim2.new(0,6,0,456 + (i-1)*14),
9, COLORS.textDim)
profLabels[sys] = lbl
end
task.spawn(function()
while true do
task.wait(1)
for sys, lbl in pairs(profLabels) do
if lbl and lbl.Parent then
local ms = Profiler.times[sys] or 0
local color = ms > 1.5 and COLORS.danger
or ms > 0.8 and COLORS.warning
or COLORS.success
lbl.Text = string.format("%s: %.2fms", sys, ms)
lbl.TextColor3 = color
end
end
end
end)
statsScroll.CanvasSize = UDim2.new(0,0,0,580)
end
-- Add self-assessment button to stats tab
if statsScroll then
local assessBtn = makeButton(statsScroll,
"📋 Generate Self-Assessment",
UDim2.new(1,-12,0,28),
UDim2.new(0,6,0,548),
COLORS.panel)
assessBtn.MouseButton1Click:Connect(function()
local report = SelfAssessment.generate()
local lines = report:split("\n")
VoidGUI.notify(lines[1] .. " — see console")
print(report)
end)
statsScroll.CanvasSize = UDim2.new(0,0,0,620)
end
-- Replay speed buttons in settings tab
local settingsScroll = scrollFrames[4]
if settingsScroll then
makeLabel(settingsScroll,
"Replay Speed",
UDim2.new(1,-12,0,14),
UDim2.new(0,6,0,640),
11, COLORS.textDim)
local speedPresets = {"Cautious","Normal","Fast"}
for i, preset in ipairs(speedPresets) do
local spBtn = makeButton(settingsScroll,
preset,
UDim2.new(1/3,-5,0,24),
UDim2.new((i-1)/3, 3, 0, 658),
ReplayControl.speed == SPEED_PRESETS[preset] and COLORS.accent or COLORS.panel)
spBtn.TextSize = 10
spBtn.MouseButton1Click:Connect(function()
ReplayControl.setSpeed(preset)
end)
end
-- Loop mode toggle
local loopBtn = makeButton(settingsScroll,
"Loop: " .. ReplayControl.loopMode,
UDim2.new(1,-12,0,26),
UDim2.new(0,6,0,688),
COLORS.panel)
loopBtn.TextSize = 10
loopBtn.MouseButton1Click:Connect(function()
ReplayControl.loopMode = ReplayControl.loopMode == "loop" and "once" or "loop"
loopBtn.Text = "Loop: " .. ReplayControl.loopMode
end)
settingsScroll.CanvasSize = UDim2.new(0,0,0,740)
end
end)
-- ============================================================
-- SECTION 43: LOOP MODE ENFORCEMENT
-- ============================================================
-- Patch GhostController to respect loopMode = "once"
local _baseStepForLoop = GhostController.stepReplay
local hasPlayedOnce = false
function GhostController.stepReplayFinal()
if ReplayControl.loopMode == "once" then
if not GhostController.replaySession then return end
local actions = GhostController.replaySession.actions
if GhostController.replayIndex > #(actions or {}) then
if not hasPlayedOnce then
hasPlayedOnce = true
GhostController.reclaim()
VoidGUI.notify("Replay complete (once mode)")
end
return
end
end
hasPlayedOnce = false
_baseStepForLoop()
end
GhostController.stepReplay = GhostController.stepReplayFinal
-- ============================================================
-- SECTION 44: COMPLETE CHAT COMMAND REFERENCE
-- ============================================================
-- Add remaining missing commands and a full help output
CHAT_COMMANDS["help"] = function()
local cmds = {
"--- VoidAI Commands (/void <cmd>) ---",
"learn / stop — toggle observer mode",
"ghost / lend — toggle ghost / lend mode",
"llm — toggle LLM mode",
"eat — toggle Extended Adaptive Thinking",
"atm — toggle Adaptive Thinking (AFK mode)",
"afk — force AFK trigger",
"reclaim — take back control",
"pause — pause AI",
"safe/danger/exit/item — tag current zone",
"save — save brain now",
"assess — generate self-assessment",
"predict — show next action prediction",
"forget — apply forgetting curve",
"macros — list learned macros",
"status — show current AI status",
"stealth — toggle stealth mode",
"saveas_<name> — save brain profile",
"load_<name> — load brain profile",
"help — show this list",
}
for _, line in ipairs(cmds) do print("[VoidAI] " .. line) end
VoidGUI.notify("Full command list printed to console")
end
CHAT_COMMANDS["speed"] = function()
local speeds = {"Cautious (0.5x)","Normal (1x)","Fast (2x)"}
VoidGUI.notify("Speeds: " .. table.concat(speeds, " | ") .. " — use /void speed_<name>")
end
-- Dynamic speed command
pcall(function()
LocalPlayer.Chatted:Connect(function(msg)
local lower = msg:lower()
local parts = lower:split(" ")
if parts[1] ~= CHAT_PREFIX then return end
local cmd = parts[2] or ""
if cmd:sub(1,6) == "speed_" then
local preset = cmd:sub(7)
local map = {cautious="Cautious", normal="Normal", fast="Fast"}
if map[preset] then
ReplayControl.setSpeed(map[preset])
end
end
end)
end)
-- ============================================================
-- SECTION 45: STARTUP SUMMARY
-- ============================================================
local function printStartupSummary()
local place = getCurrentPlace()
local mc, patc = 0, 0
for _ in pairs(EAT.macros) do mc += 1 end
for _ in pairs(EAT.reinforcedPatterns) do patc += 1 end
local function pad(s, n)
s = tostring(s):sub(1, n)
while #s < n do s = s .. " " end
return s
end
print("╔════════════════════════════════════╗")
print("║ VoidAI v1.0 — Ready ║")
print("╠════════════════════════════════════╣")
print("║ Platform : " .. pad(isMobile and "Mobile" or "PC", 25) .. "║")
print("║ Universe : " .. pad(getUniverseId(), 25) .. "║")
print("║ Place : " .. pad(getPlaceId(), 25) .. "║")
if place then
print("║ Game : " .. pad((place.placeName or "Unknown"), 25) .. "║")
print("║ Sessions : " .. pad(#place.sessions, 25) .. "║")
print("║ Conf Mov : " .. pad(place.confidence.movement .. "%", 25) .. "║")
print("║ Conf UI : " .. pad(place.confidence.ui .. "%", 25) .. "║")
print("║ Conf Seq : " .. pad(place.confidence.sequences .. "%", 25) .. "║")
end
print("║ Macros : " .. pad(mc, 25) .. "║")
print("║ Clusters : " .. pad(#EAT.clusters, 25) .. "║")
print("║ Patterns : " .. pad(patc, 25) .. "║")
print("╠════════════════════════════════════╣")
if isPC then
print("║ Keybinds: G=Learn H=Lend ║")
print("║ J=GUI F=Reclaim ║")
else
print("║ Mobile: Tap floating buttons ║")
print("║ ✋ = Reclaim control ║")
end
print("╠════════════════════════════════════╣")
print("║ Chat commands: /void help ║")
print("╚════════════════════════════════════╝")
end
-- ============================================================
-- SECTION 46: CONSOLIDATED FINAL HEARTBEAT
-- ============================================================
-- Single authoritative heartbeat — replaces fragmented ones above.
-- All per-frame logic is profiled and rate-limited here.
local _fb_learn = 0
local _fb_threat = 0
local _fb_urgency = 0
local _fb_save = 0
local _fb_goal = 0
local _fb_afk = 0
local _fb_eat = 0
local _fb_atm = 0
RunService.Heartbeat:Connect(function(dt)
updateFPS(dt)
AIState.tickCounter += 1
updateSchedule(dt)
-- AFK check every 5s
_fb_afk += dt
if _fb_afk >= 5 then
_fb_afk = 0
if not AdaptiveThinking.manualOverride then
local idle = os.time() - lastInputTime
if idle >= AdaptiveThinking.afkThreshold
and not AdaptiveThinking.active
and (AIState.mode == "idle" or AIState.mode == "observing") then
AdaptiveThinking.start("AFK auto (" .. idle .. "s)")
end
end
end
-- Threat detection every 0.5s (always runs regardless of FPS)
_fb_threat += dt
if _fb_threat >= 0.5 then
_fb_threat = 0
local ok2 = pcall(function()
local isThreat, tName = ThreatDetector.scan()
if isThreat and not AIState.threatDetected then
ThreatDetector.respondToThreat(tName)
end
if AIState.threatCooldown > 0 then
AIState.threatCooldown -= 0.5
if AIState.threatCooldown <= 0 then
AIState.threatDetected = false
end
end
end)
if not ok2 then end -- silent fail on threat scan errors
end
-- Observer learning (adaptive cooldown)
_fb_learn += dt
if _fb_learn >= LearnCooldown then
_fb_learn = 0
if AIState.isLearning and AIState.currentSession then
pcall(function()
local char = LocalPlayer.Character
if not char then return end
local hrp = char:FindFirstChild("HumanoidRootPart")
if not hrp then return end
Observer.recordMovement(hrp.Position)
-- Feed EAT if active
if EAT.active then
EAT.pushChunk("move")
EAT.reinforcePattern(EAT.getWorkingMemoryKey(), "self")
EAT.addToCluster(hrp.Position, "visited")
end
-- Anomaly check
if #AIState.currentSession.actions > 0 then
local last = AIState.currentSession.actions[#AIState.currentSession.actions]
AnomalyDetector.check(last.type or "unknown", GoalEngine.currentGoal)
end
end)
end
end
-- EAT sampling (adaptive per FPS + player count)
if EAT.active then
_fb_eat += dt
local pcount = math.max(1, #Players:GetPlayers() - 1)
local eatRate = (FPS > 50 and pcount <= 5) and 0.2
or (FPS > 30) and 0.35
or 0.6
if _fb_eat >= eatRate then
_fb_eat = 0
pcall(EAT.observeSelf)
pcall(EAT.observeOthers)
end
end
-- ATM sampling
if AdaptiveThinking.active then
_fb_atm += dt
if _fb_atm >= AdaptiveThinking.sampleRate then
_fb_atm = 0
pcall(AdaptiveThinking.samplePlayers)
end
end
-- Urgency update every 1s
_fb_urgency += dt
if _fb_urgency >= 1 then
_fb_urgency = 0
pcall(UrgencySystem.update)
end
-- Goal inference + prediction every 3s
_fb_goal += dt
if _fb_goal >= 3 then
_fb_goal = 0
pcall(GoalEngine.infer)
if EAT.active then
pcall(EAT.predictNextAction)
end
end
-- LLM step
if AIState.mode == "llm" and not AIState.isPaused then
AIState.llmCooldown = (AIState.llmCooldown or 0) + dt
if AIState.llmCooldown >= LLMController.callCooldown then
AIState.llmCooldown = 0
pcall(LLMController.callLLM)
end
end
-- Batch save + forgetting curve + sequence analysis every 30s
_fb_save += dt
if _fb_save >= 30 then
_fb_save = 0
if AIState.isLearning or EAT.active or AdaptiveThinking.active then
pcall(EAT.saveMacros)
pcall(EAT.saveClusters)
pcall(saveBrain)
end
-- Every 5 minutes: forgetting curve + sequence analysis
if AIState.tickCounter % 10 == 0 then
pcall(EAT.applyForgettingCurve)
pcall(SequenceDetector.analyze)
end
end
end)
-- ============================================================
-- SECTION 47: BRAIN VERSION MIGRATION
-- ============================================================
local CURRENT_VERSION = "1.0"
local function migrateBrain()
if not Brain then return end
if Brain.version == CURRENT_VERSION then return end
Brain.version = CURRENT_VERSION
for _, place in pairs(Brain.places or {}) do
if not place.macros then place.macros = {} end
if not place.clusters then place.clusters = {} end
if not place.topUIButtons then place.topUIButtons = {} end
if not place.totalActions then place.totalActions = 0 end
if not place.totalPlaytime then place.totalPlaytime = 0 end
if not place.confidence then
place.confidence = { movement=0, ui=0, sequences=0 }
end
if not place.mentalMap then
place.mentalMap = {
landmarks={}, safeZones={}, dangerZones={},
itemSpawns={}, exits={},
}
end
for _, session in ipairs(place.sessions or {}) do
if not session.type then session.type = "self" end
if not session.priority then session.priority = 1 end
if not session.starred then session.starred = false end
if not session.startTime then session.startTime = session.id end
if not session.movements then session.movements = {} end
if not session.uiClicks then session.uiClicks = {} end
if not session.sequences then session.sequences = {} end
if not session.actions then session.actions = {} end
end
end
saveBrain()
print("[VoidAI] Brain migrated to v" .. CURRENT_VERSION)
end
-- ============================================================
-- SECTION 48: MOBILE FINAL WIRING
-- ============================================================
if isMobile then
task.spawn(function()
task.wait(3)
local function hookThumbstick()
local pgui = LocalPlayer:FindFirstChild("PlayerGui")
if not pgui then return end
for _, gui in ipairs(pgui:GetChildren()) do
local ts = gui:FindFirstChild("DynamicThumbstick", true)
or gui:FindFirstChild("Thumbstick", true)
if ts then
RunService.Heartbeat:Connect(function()
if not AIState.isLearning then return end
if AIState.tickCounter % 3 ~= 0 then return end
local char = LocalPlayer.Character
if not char then return end
local hrp = char:FindFirstChild("HumanoidRootPart")
if hrp then Observer.recordMovement(hrp.Position) end
end)
return true
end
end
return false
end
if not hookThumbstick() then
PlayerGui.ChildAdded:Connect(function()
task.wait(1)
hookThumbstick()
end)
end
end)
end
-- ============================================================
-- ENTRY POINT
-- ============================================================
local ok, err = pcall(function()
-- Core setup
Exec.makeFolder(BRAIN_FOLDER)
loadConfig()
-- Apply config to live state
if Config.stealthMode then AIState.isStealth = true end
if Config.activePersona then AIState.persona = Config.activePersona end
if Config.keybinds then
for action, keyName in pairs(Config.keybinds) do
if KEY_MAP[keyName] then
Keybinds.binds[action] = KEY_MAP[keyName]
end
end
end
-- Load + migrate brain
loadBrain()
migrateBrain()
-- Build GUI
VoidGUI.build()
if AIState.isStealth then
if mainFrame then mainFrame.Visible = false end
if miniButton then miniButton.Visible = true end
end
-- Deferred post-init (let game fully load first)
task.spawn(function()
task.wait(2)
-- Load EAT persistent data
EAT.loadMacros()
EAT.loadClusters()
-- Startup analysis passes
EAT.applyForgettingCurve()
SequenceDetector.analyze()
GoalEngine.infer()
-- Build threat HUD
buildThreatHUD()
-- Horror game detection + safe object pre-tagging
detectAndConfigureHorror()
-- Live display update loop
task.spawn(function()
while true do
task.wait(1)
VoidGUI.updateDisplay()
end
end)
-- Welcome notification
if not AIState.isStealth then
local place = getCurrentPlace()
local isNewBrain= (not place) or (#place.sessions == 0)
if isNewBrain then
VoidGUI.notify("VoidAI ready — press G (or tap 🧠) to start learning!")
else
local mc = 0
for _ in pairs(EAT.macros) do mc += 1 end
VoidGUI.notify(string.format(
"VoidAI ready — %d sessions | %d macros | %d clusters",
place and #place.sessions or 0, mc, #EAT.clusters))
end
end
-- Print full console summary
printStartupSummary()
end)
end)
if not ok then
warn("[VoidAI] Critical init error: " .. tostring(err))
pcall(function()
Exec.makeFolder(BRAIN_FOLDER)
loadBrain()
VoidGUI.build()
VoidGUI.notify("⚠ VoidAI: init error — check console")
print("[VoidAI] Recovery mode. Error was: " .. tostring(err))
end)
end
-- ============================================================
-- END OF VOIDAI v1.0
-- ============================================================
--[[
SYSTEM INDEX:
01 Services + platform detection
02 Executor compatibility (Synapse/KRNL/Fluxus/Arceus/Delta/Hydrogen/Codex)
03 Base knowledge (embedded WASD + mobile defaults + horror keywords)
04 Brain file system (per-universe JSON, per-place entries, cumulative)
05 Config system (persisted settings)
06 Adaptive learning cooldown + FPS monitor
07 AI state machine
08 Observer (movement, UI clicks, jump, sprint — event-driven)
09 Threat detection + survival response
10 Ghost controller (client-side AI playback)
11 Lend controller (AI takes full input control)
12 LLM controller (Claude / ChatGPT / Gemini with brain context)
13 Place transition continuity (survives teleports in same universe)
14 UI click hooks (event-driven, zero polling cost)
15 Keybind system (PC, fully rebindable via GUI)
16 Schedule mode (auto start/stop AI at set times)
17 GUI system (4 tabs, minimize to draggable floating button)
18 Threat HUD (pulsing indicator)
19 Sequence detector (cross-session pattern mining)
20 Smart pattern-aware replay
21 Jump + sprint event tracking
22 Other player passive observer
23 Confidence color coding (green/yellow/red bars)
24 Low confidence warnings before ghost/lend
25 Chat command system (/void <cmd>) — 20+ commands
26 Mental map visualizer (top-down dot map in Stats tab)
27 Brain export to readable .txt file
28 Horror game auto-detect + safe object pre-tagging
29 Schedule countdown notifications
30 Safety hooks (BindToClose, CharacterRemoving, respawn)
31 Adaptive Thinking Mode (ATM) — AFK multi-player observer
32 Extended Adaptive Thinking (EAT) — dual-source live learning
33 Pattern reinforcement (dual-source weight doubling)
34 Attention focus (per-player activity scoring)
35 Action chunking / macro system (10 built-in macro labels)
36 Forgetting curve (logarithmic time-decay confidence)
37 Next-action prediction engine (scored candidates)
38 Working memory buffer (short-term vs long-term split)
39 Behavior clustering (path generalization via centroid merge)
40 Anomaly detection + pattern promotion
41 Urgency / emotion state (0-100, CALM/MED/HIGH/PANIC)
42 Goal inference engine (explore/combat/interact/escape/etc)
43 Self-assessment report generator
44 Replay speed control (Cautious/Normal/Fast) + loop modes
45 Multi-brain profile support (saveas_/load_ commands)
46 Performance profiler (per-subsystem ms EMA tracking)
47 Consolidated heartbeat (single connection, all systems)
48 Mobile thumbstick wiring
49 Brain version migration
]]



if you try it out thanks a lot!