local Players=game:GetService('Players') local RunService=game:GetService('RunService') local RS=game:GetService('ReplicatedStorage') local SS=game:GetService('ServerStorage') local TextService=game:GetService('TextService') local TextChatService=game:GetService('TextChatService') local Safety=require(script.Parent.ChatSafety).new(TextService,TextChatService) local Config=require(RS.Shared.Config) local Tokenizer=require(script.Parent.Tokenizer) local Engine=require(script.Parent.Inference) local remote=Instance.new('RemoteEvent');remote.Name='Club';remote.Parent=RS local terminals=workspace:WaitForChild('Lab'):WaitForChild('Terminals') local rooms={};local ready=false;local loading=0;local tokenizer local jobs={};local waiting={};local rate={} local inputChannel=Instance.new('TextChannel');inputChannel.Name='LamplightInput';inputChannel.Parent=TextChatService local displayChannel=Instance.new('TextChannel');displayChannel.Name='LamplightDisplay';displayChannel.Parent=TextChatService local function fire(player,kind,data) if player and player.Parent then remote:FireClient(player,kind,data) end end local function status(room,text) room.status=text;room.model:SetAttribute('Status',text);room.model:SetAttribute('Busy',room.busy) if room.owner then fire(room.owner,'status',{status=text,terminal=room.id}) end end local function sendView(room,recipient) local revision=room.viewRevision;local owner=room.owner;local history=table.clone(room.displayHistory) task.spawn(function() local allowed=owner and Safety:canView(owner.UserId,recipient.UserId) if room.viewRevision~=revision or room.owner~=owner then return end fire(recipient,'view',{terminal=room.id,revision=revision,history=if allowed then history else {}}) end) end local function broadcast(room) room.viewRevision=(room.viewRevision or 0)+1 room.model:SetAttribute('Transcript',nil) room.model:SetAttribute('RequestVersion',room.version) room.model:SetAttribute('Busy',room.busy) room.model:SetAttribute('Owner',if room.owner then room.owner.DisplayName else '') room.model:SetAttribute('OwnerId',if room.owner then room.owner.UserId else 0) room.model:SetAttribute('Status',room.status) local pp=room.model.Interact.ProximityPrompt pp.ActionText=if room.owner then 'In use' else 'Sit at terminal' for _,recipient in Players:GetPlayers() do sendView(room,recipient) end end local function near(player,room,distance) local root=player.Character and player.Character:FindFirstChild('HumanoidRootPart') return root and (root.Position-room.model.Interact.Position).Magnitude<=distance end local function cancel(room) room.version+=1;room.busy=false end local function release(room) if room.releasing then return end room.releasing=true local owner=room.owner local character=owner and owner.Character local humanoid=character and character:FindFirstChildOfClass('Humanoid') local wasSeated=humanoid and humanoid.SeatPart==room.seat local exitCFrame=if wasSeated then room.seatHome*CFrame.new(0,2,5) else nil cancel(room);room.owner=nil;room.history={};room.displayHistory={};room.publicPrompt=nil;room.publicAnswer=nil -- Remove the seat-to-avatar weld before moving the avatar, preserving the chair assembly. local weld=room.seat:FindFirstChild('SeatWeld');if weld then weld:Destroy() end room.seat.Disabled=true if wasSeated then humanoid.Sit=false humanoid:ChangeState(Enum.HumanoidStateType.GettingUp) task.spawn(function() -- Let the seat detach before moving the avatar; never teleport the chair's assembly. RunService.Heartbeat:Wait();RunService.Heartbeat:Wait() local root=character:FindFirstChild('HumanoidRootPart') if root and not humanoid.SeatPart and not room.seat:FindFirstChild('SeatWeld') then root.CFrame=exitCFrame root.AssemblyLinearVelocity=Vector3.zero;root.AssemblyAngularVelocity=Vector3.zero end end) end status(room,if ready then 'READY' else 'WARMING UP');broadcast(room);fire(owner,'released',{terminal=room.id}) task.delay(1,function() room.releasing=false;room.seat.Disabled=false end) end local function claim(player,room) if room.releasing then return end if not near(player,room,Config.ClaimDistance) then return end local humanoid=player.Character and player.Character:FindFirstChildOfClass('Humanoid') if not humanoid or humanoid.Health<=0 then return end if room.seat.Occupant and room.seat.Occupant~=humanoid then return end if humanoid.SeatPart and humanoid.SeatPart~=room.seat then return end if room.owner and room.owner~=player then fire(player,'notice',{text='This terminal is in use by '..room.owner.DisplayName..'. You can read its public monitor.'});return end for _,other in rooms do if other~=room and other.owner==player then release(other) end end room.owner=player;room.lastActive=os.clock();broadcast(room) if humanoid.SeatPart~=room.seat then room.seat:Sit(humanoid) end task.delay(.5,function() if room.owner==player and room.seat.Occupant~=humanoid then release(room) end end) fire(player,'open',{terminal=room.id,status=room.status,ready=ready,busy=room.busy}) end for i=1,4 do local model=terminals:WaitForChild('Terminal'..i) local room={id=i,model=model,seat=model:WaitForChild('Chair seat'),status='WARMING UP',history={},displayHistory={},version=0,busy=false,lastActive=0} room.seatHome=room.seat.CFrame rooms[i]=room model.Interact.ProximityPrompt.Triggered:Connect(function(player) claim(player,room) end) room.seat:GetPropertyChangedSignal('Occupant'):Connect(function() local humanoid=room.seat.Occupant if humanoid then local player=Players:GetPlayerFromCharacter(humanoid.Parent) if player and room.owner~=player then claim(player,room) end elseif room.owner and not room.releasing then release(room) end end) broadcast(room) end -- Only a completed TextChatService submission can create a model request. inputChannel.ShouldDeliverCallback=function(message,recipient) local source=message.TextSource if not source or recipient.UserId~=source.UserId then return false end local player=Players:GetPlayerByUserId(source.UserId) local id,version=message.Metadata:match('^Lamplight:(%d):(%d+):[%w%-]+$') id=tonumber(id);version=tonumber(version) local room=id and rooms[id] if not room or room.owner~=player or room.version~=version or room.busy or #message.Text>Config.MaxPromptCharacters*4 or not near(player,room,Config.ReleaseDistance) then return false end Safety:record(player.UserId,message.MessageId,id,room.version,message.Text,os.clock()) return true end local function addChatUser(player) task.spawn(function() local ok,source=pcall(function() return inputChannel:AddUserAsync(player.UserId) end) if not ok or not source then fire(player,'notice',{text='Roblox chat is unavailable for this account.'}) end end) end Players.PlayerAdded:Connect(addChatUser) for _,player in Players:GetPlayers() do addChatUser(player) end local function enqueue(player,room,text) if not ready then fire(player,'notice',{text='The model is warming up. Please wait a moment.'});return end if room.busy then return end if not player.Character or room.seat.Occupant~=player.Character:FindFirstChildOfClass('Humanoid') then return end if type(text)~='string' or #text>Config.MaxPromptCharacters*4 or not utf8.len(text) or utf8.len(text)>Config.MaxPromptCharacters or not text:match('%S') then return end room.busy=true;room.lastActive=os.clock();room.version+=1;local version=room.version status(room,'CHECKING MESSAGE') task.spawn(function() local allowed=Safety:canView(player.UserId,player.UserId) local safe,reason if allowed then safe,reason=Safety:filter(text,player.UserId) end if room.version~=version or room.owner~=player then return end if not safe then room.busy=false;status(room,'MESSAGE WITHHELD');broadcast(room);fire(player,'notice',{terminal=room.id,text=if reason=='professional' then Safety.professionalNotice elseif reason=='blocked' then 'This message was blocked by Roblox filtering. Please try a different message.' else 'Roblox chat or filtering is unavailable. Your message was not sent.'});return end local messages={{role='system',content=Config.SystemPrompt}} for _,m in room.history do table.insert(messages,m) end table.insert(messages,{role='user',content=safe}) local ids=tokenizer:chat(messages) while #ids>Config.MaxContext-Config.MaxOutput and #messages>2 do table.remove(messages,2);if #messages>2 then table.remove(messages,2) end;ids=tokenizer:chat(messages) end if #ids>Config.MaxContext-Config.MaxOutput then room.busy=false;status(room,'MESSAGE TOO LONG');broadcast(room);fire(player,'notice',{text='That message uses too many tokens. Please shorten it.'});return end room.publicPrompt=safe;room.publicAnswer='';table.insert(room.displayHistory,{role='user',content=safe}) fire(player,'accepted',{text=safe});status(room,'WAITING FOR A TURN');broadcast(room) table.insert(waiting,{room=room,player=player,version=version,messages=messages,ids=ids,index=1,output={},rng=Random.new(),started=os.clock(),generated=0}) end) end remote.OnServerEvent:Connect(function(player,action,data) if type(action)~='string' or type(data)~='table' then return end if action=='hello' then fire(player,'welcome',{ready=ready,loading=loading});for _,room in rooms do sendView(room,player) end;return end local id=data.terminal;if type(id)~='number' or id%1~=0 then return end local room=rooms[id];if not room then return end if action=='claim' then claim(player,room);return end if room.owner~=player then return end if action=='release' then release(room);room.displayHistory={};return end if not near(player,room,Config.ReleaseDistance) then release(room);return end room.lastActive=os.clock() if action=='stop' then cancel(room);status(room,'STOPPED');broadcast(room);fire(player,'done',{cancelled=true});return end if action=='new' then cancel(room);room.history={};room.displayHistory={};room.publicPrompt=nil;room.publicAnswer=nil;status(room,'READY');broadcast(room);fire(player,'cleared',{});return end if action=='ask' then local text=Safety:take(player.UserId,data.messageId,id,room.version,os.clock()) if not text then fire(player,'notice',{terminal=id,text='Your message was not accepted by Roblox chat. Please send it again.'});return end local now=os.clock();if now-(rate[player.UserId] or 0)<2 then fire(player,'notice',{text='Please wait a moment before sending another message.'});return end;rate[player.UserId]=now;enqueue(player,room,text) end end) Players.PlayerRemoving:Connect(function(player) for _,room in rooms do if room.owner==player then release(room);room.displayHistory={} end end rate[player.UserId]=nil;Safety.receipts[player.UserId]=nil end) task.spawn(function() while true do task.wait(5) for _,room in rooms do if room.owner and (not near(room.owner,room,Config.ReleaseDistance) or (not room.busy and os.clock()-room.lastActive>Config.IdleSeconds)) then release(room);room.displayHistory={} elseif room.owner then for _,recipient in Players:GetPlayers() do sendView(room,recipient) end end end end end) local function valid(job) return job.room.version==job.version and job.room.owner==job.player and job.player.Parent~=nil end local function finish(job) local room=job.room status(room,'PREPARING PUBLIC RESPONSE') task.spawn(function() local raw=tokenizer:decode(job.output) -- A byte-level tokenizer may stop in the middle of a UTF-8 code point. while #raw>0 and not utf8.len(raw) do raw=raw:sub(1,-2) end local safe,reason=Safety:filter(raw,job.player.UserId,true) if not valid(job) then return end if not safe then safe=if reason=='professional' then Safety.professionalNotice elseif reason=='blocked' then 'This response was blocked by Roblox filtering. Please try a different message.' else 'The response could not be checked by Roblox filtering and was not displayed. Please try again.' end room.publicAnswer=safe;room.history=job.messages;table.remove(room.history,1);table.insert(room.history,{role='assistant',content=safe}) table.insert(room.displayHistory,{role='assistant',content=safe});while #room.displayHistory>12 do table.remove(room.displayHistory,1) end room.busy=false;room.lastActive=os.clock();status(room,'READY');broadcast(room) fire(job.player,'done',{tokens=#job.output,seconds=os.clock()-job.started}) print(string.format('[Lamplight] Terminal %d: %d tokens in %.2fs',room.id,#job.output,os.clock()-job.started)) end) end task.spawn(function() local ok,err=pcall(function() tokenizer=Tokenizer.new(SS:WaitForChild('TokenizerData')) Engine.load(SS:WaitForChild('ModelData'),function(p) loading=p;RS:SetAttribute('ModelLoading',p) end) end) if not ok then warn('[Lamplight] Model load failed: '..tostring(err));for _,r in rooms do status(r,'MODEL UNAVAILABLE');broadcast(r) end;return end ready=true;RS:SetAttribute('ModelReady',true) for _,r in rooms do status(r,'READY');broadcast(r) end remote:FireAllClients('ready',{}) print('[Lamplight] SmolLM2 ready. Pure Luau. '..Engine.manifest.weightBytes..' weight bytes.') while true do while #jobs0 do local j=table.remove(waiting,1) if valid(j) then j.session=Engine.newSession(Config.MaxContext);table.insert(jobs,j) end end if #jobs==0 then task.wait(.1) else for index=#jobs,1,-1 do local j=jobs[index] if not valid(j) then table.remove(jobs,index) else local okStep,errorStep=pcall(function() local prefill=j.index<=#j.ids local id=if prefill then j.ids[j.index] else j.output[#j.output] local projected=not prefill or j.index==#j.ids local logits=Engine.forward(j.session,id,projected,function() return not valid(j) end) j.index+=1 if projected then local nextId=Engine.sample(logits,Config.Temperature,j.rng,j.output) if nextId==2 or j.session.position>=Config.MaxContext-1 then j.finished=true else table.insert(j.output,nextId) end if #j.output>=Config.MaxOutput then j.finished=true end status(j.room,'WRITING · '..#j.output..' TOKENS') else status(j.room,'READING · '..(j.index-1)..' / '..#j.ids) end local display=j.room.model.Screen.TerminalDisplay;display.State.Text=j.room.status if os.clock()-j.started>240 then error('Response time limit reached') end end) if not okStep then table.remove(jobs,index) if valid(j) then j.room.busy=false;status(j.room,'TRY AGAIN');fire(j.player,'done',{text='The model took too long or encountered an error. Try a shorter message.'});warn('[Lamplight] '..tostring(errorStep)) end elseif j.finished then table.remove(jobs,index);finish(j) end end end end end end)