<roblox xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.roblox.com/roblox.xsd" version="4">
  <External>null</External>
  <External>nil</External>
  <Item class="ModuleScript" referent="RBX00000000">
    <Properties>
      <string name="Name">GriftSDK</string>
      <ProtectedString name="Source"><![CDATA[--!strict
--[[
	GRIFT ROBLOX SDK 0.1.0 · MIT licence

	Real GRIFT agents in a Roblox experience: identities (ERC-8004 where they hold one), plans, decisions and the
	reasons given for them, memory, activity, business records and on-chain proofs. Read-only, with the source of
	every fact, and always labelled RECORDED, LIVE or CACHED.

		local Grift = require(game:GetService("ReplicatedStorage").GriftSDK)
		local client = Grift.Client.fromRecording(Grift.Recordings.SanVerano_20261108)
		client:Start()
		Grift.Crowd.new(client, { agents = { 100117 } }):Start()

	GRIFT is the authority; Roblox draws. Nothing here can write to GRIFT, sign, pay, or call a model.
	README.md and docs/API.md in the SDK's repository document every module.
]]

local GriftSDK = {
	VERSION = "0.1.0",
	CONTRACT = "grift.agents.feed",
	CONTRACT_VERSION = "0.1",

	-- data (pure Luau)
	Client = require(script.Client),
	Contract = require(script.Contract),
	Clock = require(script.Clock),
	Timeline = require(script.Timeline),
	Activity = require(script.Activity),
	Provenance = require(script.Provenance),
	Replay = require(script.Replay),
	Format = require(script.Format),
	Signal = require(script.Signal),

	-- drawing (Roblox)
	Places = require(script.Places),
	Character = require(script.Character),
	Nameplate = require(script.Nameplate),
	Puppet = require(script.Puppet),
	Crowd = require(script.Crowd),
	Replicator = require(script.Replicator),
	Inspector = require(script.Inspector),
	Theme = require(script.Theme),

	-- recorded feeds (ModuleScripts): pass one to Client.fromRecording
	Recordings = script:FindFirstChild("Recordings"),
}

return GriftSDK
]]></ProtectedString>
    </Properties>
    <Item class="ModuleScript" referent="RBX00000001">
      <Properties>
        <string name="Name">Activity</string>
        <ProtectedString name="Source"><![CDATA[--!nonstrict
-- What an agent is doing at an instant, in words an engine can show without overclaiming.
--   Activity.at(agent, ms, feed?, hint?) -> Activity
-- `state` is home | strolling | walking | at | unknown. A journey that GRIFT cuts into several walk segments is one
-- walk here: `progress` runs 0 -> 1 across all of it, and `startsAt` / `endsAt` are its ends.
-- The label says who decided: "plan" segments carry the agent's plan for that part of the day and who made it
-- (GRIFT's model, its rules, or the agent's own outside agent); "rota" is working hours, not a choice; homes, walks
-- home and strolls are not decisions. Outside the schedule the agent is unknown, never guessed. Pure Luau.
local Types = require(script.Parent.Types)
local Timeline = require(script.Parent.Timeline)
local Format = require(script.Parent.Format)

type Agent = Types.Agent
type Segment = Types.Segment
type Activity = Types.Activity

local Activity = {}

local function decidedByOf(agent: Agent): string?
	local d = agent.decision
	if not d then
		return nil
	end
	local by = d.byLabel or d.by
	if d.by == "agent" and d.model then
		return string.format("%s (%s)", tostring(by), d.model)
	end
	return by
end

-- the plan's own words for a slot, when the segment carries a plan
local function whyOf(agent: Agent, seg: Segment): string?
	if not seg.slot or not agent.plan or not agent.plan.slots then
		return nil
	end
	local slot = (agent.plan.slots :: any)[seg.slot]
	if slot and slot.why and slot.why ~= "" then
		return slot.why
	end
	return nil
end

-- the label: what, where, and whether it was decided
function Activity.label(act: Activity): string
	local decision: string
	if act.decision == "plan" then
		decision = "its plan for the " .. Format.slotName(act.slot)
	elseif act.decision == "rota" then
		decision = "working hours, not a choice"
	else
		decision = "not a decision"
	end
	if act.state == "home" then
		return "home · not a decision"
	elseif act.state == "strolling" then
		return "a walk round the block from home · not a decision"
	elseif act.state == "walking" then
		if act.place == "home" then
			return "walking home · not a decision"
		end
		return string.format("walking to %s · %s", act.placeName or "?", decision)
	elseif act.state == "at" then
		return string.format("at %s · %s", act.placeName or "?", decision)
	end
	return "unknown · no GRIFT schedule covers this moment"
end

function Activity.at(agent: Agent, ms: number, feed: Types.Feed?, hint: number?): Activity
	local places = if feed then feed.places else nil
	local schedule = agent.schedule or {}
	local seg, i = Timeline.segmentAt(schedule, ms, hint)
	if not seg or not i then
		local last = Timeline.lastSeen(schedule, ms)
		local act: Activity = {
			state = "unknown",
			place = if last then last.place else nil,
			placeName = if last and last.place then Format.placeName(places, last.place) else nil,
			label = "",
			endsAt = if last then last.t1 else nil,
		}
		local first = schedule[1]
		if not last and first and ms < first.t0 then
			act.label = "unknown · before this GRIFT schedule starts"
		elseif last then
			act.label = "unknown · past this GRIFT schedule (last seen: " .. (act.placeName or "?") .. ")"
		else
			act.label = "unknown · no GRIFT schedule"
		end
		return act
	end
	local s = seg :: Segment
	local state: string = if s.kind == "home" then "home" elseif s.kind == "stroll" then "strolling" elseif s.kind == "walk" then "walking" else "at"
	local act: Activity = {
		state = state :: any,
		place = s.place,
		placeName = if s.place then Format.placeName(places, s.place) else nil,
		decision = s.decision,
		slot = s.slot,
		role = s.role,
		startsAt = s.t0,
		endsAt = s.t1,
		segment = s,
		index = i,
		label = "",
	}
	if s.kind == "walk" then
		-- one journey across consecutive walk segments to the same place
		local first, last = i, i
		while first > 1 and schedule[first - 1].kind == "walk" and schedule[first - 1].place == s.place do
			first -= 1
		end
		while last < #schedule and schedule[last + 1].kind == "walk" and schedule[last + 1].place == s.place do
			last += 1
		end
		local t0, t1 = schedule[first].t0, schedule[last].t1
		act.startsAt, act.endsAt = t0, t1
		act.progress = if t1 > t0 then math.clamp((ms - t0) / (t1 - t0), 0, 1) else 1
		act.from = schedule[first].from
		act.fromName = if act.from then Format.placeName(places, act.from) else nil
	elseif s.kind == "stroll" then
		act.progress = if s.t1 > s.t0 then math.clamp((ms - s.t0) / (s.t1 - s.t0), 0, 1) else 1
	end
	if s.decision == "plan" then
		act.why = whyOf(agent, s)
		act.decidedBy = decidedByOf(agent)
	elseif s.decision == "rota" then
		act.why = whyOf(agent, s)
	end
	act.label = Activity.label(act)
	return act
end

return Activity
]]></ProtectedString>
      </Properties>
    </Item>
    <Item class="ModuleScript" referent="RBX00000002">
      <Properties>
        <string name="Name">Character</string>
        <ProtectedString name="Source"><![CDATA[--!nonstrict
-- A body for a GRIFT agent. GRIFT stores no appearance, so everything here is the engine's choice and is said to be:
-- the shirt is GRIFT's colour for the agent's trade, the skin a fixed palette by id, the height GRIFT's adult (1.80 m).
--   Character.build(agent, { rig = "auto" | "r15" | "blocky", heightStuds = 5.4, nameplate = true })
-- "auto" tries an R15 avatar from a HumanoidDescription (Roblox's own default body and animations) and falls back to
-- GRIFT's blocky voxel body when that is unavailable. The root is anchored: a Puppet moves it; nothing here moves.
local Players = game:GetService("Players")
local Theme = require(script.Parent.Theme)
local Nameplate = require(script.Parent.Nameplate)

local Character = {}
Character.__index = Character

Character.HEIGHT_STUDS = 5.4 -- GRIFT's adult, 1.80 m at 3 studs per metre
-- Roblox's own default R15 animations (the ones every default avatar uses)
Character.ANIMATIONS = { idle = "rbxassetid://507766666", walk = "rbxassetid://507777826", run = "rbxassetid://507767714" }

export type Options = {
	rig: ("auto" | "r15" | "blocky")?,
	heightStuds: number?,
	nameplate: boolean?,
	parent: Instance?,
}

local function buildR15(agent: any, height: number): (Model?, BasePart?, Humanoid?)
	local ok, model = pcall(function()
		local desc = Instance.new("HumanoidDescription")
		local skin = Theme.skinFor(agent.id)
		desc.HeadColor = skin
		desc.LeftArmColor = skin
		desc.RightArmColor = skin
		desc.TorsoColor = Theme.shirtFor(agent)
		desc.LeftLegColor = Theme.TROUSERS
		desc.RightLegColor = Theme.TROUSERS
		desc.BodyTypeScale = 0.35
		desc.ProportionScale = 0.35
		return Players:CreateHumanoidModelFromDescription(desc, Enum.HumanoidRigType.R15)
	end)
	if not ok or not model then
		return nil, nil, nil
	end
	local humanoid = model:FindFirstChildOfClass("Humanoid")
	local root = model:FindFirstChild("HumanoidRootPart")
	if not humanoid or not root or not root:IsA("BasePart") then
		model:Destroy()
		return nil, nil, nil
	end
	humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
	humanoid.HealthDisplayType = Enum.HumanoidHealthDisplayType.AlwaysOff
	humanoid.AutoRotate = false
	humanoid.EvaluateStateMachine = false
	humanoid.BreakJointsOnDeath = false
	root.Anchored = true
	for _, d in ipairs(model:GetDescendants()) do
		if d:IsA("BasePart") then
			d.CanCollide = false
			d.CanTouch = false
			d.Massless = true
		end
	end
	local size = model:GetExtentsSize().Y
	if size > 0 then
		model:ScaleTo(height / size)
	end
	return model, root, humanoid
end

-- GRIFT's own voxel body: head 0.50 m, torso 0.50 × 0.60 × 0.30, legs 0.70, shoulders 0.90 (scaled to `height`)
local function buildBlocky(agent: any, height: number): (Model, BasePart, { [string]: Motor6D })
	local k = height / 1.8
	local function m(x: number): number
		return x * k
	end
	local skin, shirt = Theme.skinFor(agent.id), Theme.shirtFor(agent)
	local model = Instance.new("Model")
	local root = Instance.new("Part")
	root.Name = "HumanoidRootPart"
	root.Size = Vector3.new(m(0.5), m(0.6), m(0.3))
	root.Transparency = 1
	root.CanCollide = false
	root.CanTouch = false
	root.Anchored = true
	root.Parent = model
	local joints: { [string]: Motor6D } = {}
	local function limb(name: string, size: Vector3, colour: Color3, offset: CFrame, pivot: CFrame): Motor6D
		local p = Instance.new("Part")
		p.Name = name
		p.Size = size
		p.Color = colour
		p.Material = Enum.Material.SmoothPlastic
		p.CanCollide = false
		p.CanTouch = false
		p.Massless = true
		p.Parent = model
		local motor = Instance.new("Motor6D")
		motor.Name = name .. "Joint"
		motor.Part0 = root
		motor.Part1 = p
		motor.C0 = pivot
		motor.C1 = offset:Inverse() * pivot
		motor.Parent = root
		return motor
	end
	-- the root is the torso's centre, 1.0 m above the feet (legs 0.70 + hips 0.30)
	limb("Torso", Vector3.new(m(0.5), m(0.6), m(0.3)), shirt, CFrame.new(), CFrame.new())
	limb("Head", Vector3.new(m(0.5), m(0.5), m(0.5)), skin, CFrame.new(0, m(0.55), 0), CFrame.new(0, m(0.3), 0))
	joints.LeftArm = limb("LeftArm", Vector3.new(m(0.18), m(0.62), m(0.18)), skin, CFrame.new(-m(0.36), -m(0.02), 0), CFrame.new(-m(0.36), m(0.28), 0))
	joints.RightArm = limb("RightArm", Vector3.new(m(0.18), m(0.62), m(0.18)), skin, CFrame.new(m(0.36), -m(0.02), 0), CFrame.new(m(0.36), m(0.28), 0))
	joints.LeftLeg = limb("LeftLeg", Vector3.new(m(0.2), m(0.7), m(0.22)), Theme.TROUSERS, CFrame.new(-m(0.13), -m(0.65), 0), CFrame.new(-m(0.13), -m(0.3), 0))
	joints.RightLeg = limb("RightLeg", Vector3.new(m(0.2), m(0.7), m(0.22)), Theme.TROUSERS, CFrame.new(m(0.13), -m(0.65), 0), CFrame.new(m(0.13), -m(0.3), 0))
	limb("Hips", Vector3.new(m(0.5), m(0.3), m(0.3)), Theme.TROUSERS, CFrame.new(0, -m(0.45), 0), CFrame.new(0, -m(0.45), 0))
	model.PrimaryPart = root
	return model, root, joints
end

function Character.build(agent: any, options: Options?)
	local opts = options or {}
	local height = opts.heightStuds or Character.HEIGHT_STUDS
	local want = opts.rig or "auto"
	local model, root, humanoid, joints = nil, nil, nil, nil
	local rig = "blocky"
	if want ~= "blocky" then
		model, root, humanoid = buildR15(agent, height)
		if model then
			rig = "r15"
		elseif want == "r15" then
			warn("[GriftSDK] an R15 avatar could not be built here; using the blocky body")
		end
	end
	if not model then
		model, root, joints = buildBlocky(agent, height)
	end
	local m = model :: Model
	m.Name = string.format("%s (GRIFT #%d)", tostring(agent.name), agent.id)
	m:SetAttribute("GriftId", agent.id)
	m:SetAttribute("GriftName", agent.name)
	m:SetAttribute("GriftKind", agent.kind)
	m:SetAttribute("GriftRig", rig)
	m:SetAttribute("GriftAppearance", "engine's choice: GRIFT stores no appearance")
	local parts: { BasePart } = {}
	local colours: { [BasePart]: Color3 } = {}
	for _, d in ipairs(m:GetDescendants()) do
		if d:IsA("BasePart") and d ~= root then
			table.insert(parts, d)
			colours[d] = d.Color
		end
	end
	local self = setmetatable({
		Model = m,
		Root = root :: BasePart,
		Humanoid = humanoid,
		Rig = rig,
		Agent = agent,
		Nameplate = nil :: any,
		_joints = joints,
		_parts = parts,
		_colours = colours,
		_tracks = nil :: { [string]: AnimationTrack }?,
		_anim = "",
		_phase = 0,
		_grey = false,
		_yaw = nil :: CFrame?,
		_footToRoot = if humanoid then humanoid.HipHeight + (root :: BasePart).Size.Y / 2 else height * (1.0 / 1.8),
	}, Character)
	if humanoid then
		local animator = humanoid:FindFirstChildOfClass("Animator") or Instance.new("Animator")
		animator.Parent = humanoid
		local tracks: { [string]: AnimationTrack } = {}
		for key, id in pairs(Character.ANIMATIONS) do
			local anim = Instance.new("Animation")
			anim.AnimationId = id
			local ok, track = pcall(function()
				return (animator :: Animator):LoadAnimation(anim)
			end)
			if ok and track then
				track.Priority = Enum.AnimationPriority.Core
				track.Looped = true
				tracks[key] = track
			end
		end
		self._tracks = tracks
	end
	if opts.nameplate ~= false then
		local head = m:FindFirstChild("Head")
		self.Nameplate = Nameplate.new(agent, if head and head:IsA("BasePart") then head else root :: BasePart)
		self.Nameplate.Gui.Parent = m
	end
	if opts.parent then
		m.Parent = opts.parent
	end
	return self
end

-- stand the agent with its feet on `ground`, facing `facing` (turning smoothly over dt)
function Character.PlaceAt(self: any, ground: Vector3, facing: Vector3?, dt: number?)
	local pos = ground + Vector3.new(0, self._footToRoot, 0)
	local target: CFrame
	if facing and Vector3.new(facing.X, 0, facing.Z).Magnitude > 1e-3 then
		target = CFrame.lookAt(pos, pos + Vector3.new(facing.X, 0, facing.Z))
	elseif self._yaw then
		target = self._yaw.Rotation + pos
	else
		target = CFrame.new(pos)
	end
	local cf = target
	if self._yaw and dt then
		cf = (self._yaw.Rotation + pos):Lerp(target, math.clamp(dt * 10, 0, 1))
	end
	self._yaw = cf
	self.Root.CFrame = cf
end

-- idle / walk / run by ground speed (studs per second); the blocky body swings its limbs by distance instead
function Character.Animate(self: any, speed: number, dt: number)
	if self._tracks then
		local name, rate
		if speed < 0.5 then
			name, rate = "idle", 1
		elseif speed < 11 then
			name, rate = "walk", math.clamp(speed / 8, 0.5, 2.2)
		else
			name, rate = "run", math.clamp(speed / 14.5, 0.6, 2.2)
		end
		if self._anim ~= name then
			for key, track in pairs(self._tracks) do
				if key == name then
					track:Play(0.2)
				else
					track:Stop(0.2)
				end
			end
			self._anim = name
		end
		local t = self._tracks[name]
		if t then
			t:AdjustSpeed(rate)
		end
		return
	end
	local joints = self._joints
	if not joints then
		return
	end
	if speed > 0.5 then
		self._phase += speed * dt / 2.6
	else
		self._phase *= 0.85
	end
	local swing = if speed > 0.5 then math.sin(self._phase) * 0.55 else 0
	joints.LeftLeg.Transform = CFrame.Angles(swing, 0, 0)
	joints.RightLeg.Transform = CFrame.Angles(-swing, 0, 0)
	joints.LeftArm.Transform = CFrame.Angles(-swing * 0.8, 0, 0)
	joints.RightArm.Transform = CFrame.Angles(swing * 0.8, 0, 0)
end

-- grey: the agent is unknown at this moment (no schedule covers it); it stands where it was last seen
function Character.SetGrey(self: any, grey: boolean)
	if self._grey == grey then
		return
	end
	self._grey = grey
	for _, p in ipairs(self._parts) do
		p.Color = if grey then Theme.GREY else (self._colours[p] or p.Color)
	end
	if self._tracks and grey then
		for _, track in pairs(self._tracks) do
			track:Stop(0.2)
		end
		self._anim = ""
	end
	if self.Nameplate then
		self.Nameplate:SetGrey(grey)
	end
end

function Character.Destroy(self: any)
	self.Model:Destroy()
end

return Character
]]></ProtectedString>
      </Properties>
    </Item>
    <Item class="ModuleScript" referent="RBX00000003">
      <Properties>
        <string name="Name">Client</string>
        <ProtectedString name="Source"><![CDATA[--!nonstrict
-- THE SDK'S SOURCE OF GRIFT DATA. Read-only by construction: it sends one kind of request (GET {gateway}/v1/feed,
-- with no credentials) and has no way to write, sign, pay or call a model.
--
--   local client = Grift.Client.fromRecording(Grift.Recordings.SanVerano_20261108)        -- offline, RECORDED
--   local client = Grift.Client.fromGateway("https://…", { fallback = recording })        -- LIVE / CACHED (server only)
--   client:Start()
--   client.Changed:Connect(function(feed) … end)
--   client:GetAgent(100117), client:ActivityOf(100117), client:GetProvenance()
--
-- Modes, always shown (Provenance.view): RECORDED for a recording, replayed on today's clock; LIVE while a gateway
-- answers with fresh GRIFT data; CACHED with the age of the last good data when it goes silent. A gateway's silence
-- never produces a new plan, decision or event: the last good feed stays, schedules end where their day ends.
local Types = require(script.Parent.Types)
local Contract = require(script.Parent.Contract)
local Clock = require(script.Parent.Clock)
local Replay = require(script.Parent.Replay)
local Provenance = require(script.Parent.Provenance)
local Activity = require(script.Parent.Activity)
local Signal = require(script.Parent.Signal)

type Feed = Types.Feed
type Agent = Types.Agent

export type Options = {
	recording: any?,
	gateway: string?,
	fallback: any?,
	agents: { number }?,
	pollSeconds: number?,
	staleAfterSeconds: number?,
	replay: Replay.Options?,
	http: ((request: { url: string, headers: { [string]: string } }) -> any)?,
	decode: ((body: string) -> any)?,
	now: (() -> number)?,
	scheduler: { spawn: (fn: () -> ()) -> (), wait: (seconds: number) -> () }?,
	log: ((message: string) -> ())?,
}

local Client = {}
Client.__index = Client

Client.MAX_BODY_BYTES = 4 * 1024 * 1024
Client.FEED_PATH = "/v1/feed"

-- ---- defaults for Roblox (looked up only when used, so the module loads anywhere) ------------------------------
local function robloxHttp(request: { url: string, headers: { [string]: string } }): any
	local HttpService = (game :: any):GetService("HttpService")
	return HttpService:RequestAsync({ Url = request.url, Method = "GET", Headers = request.headers })
end
local function robloxDecode(body: string): any
	return (game :: any):GetService("HttpService"):JSONDecode(body)
end
local function robloxScheduler()
	local t = (task :: any)
	return {
		spawn = function(fn: () -> ())
			t.spawn(fn)
		end,
		wait = function(seconds: number)
			t.wait(seconds)
		end,
	}
end

-- what a Roblox HTTP failure means, in words a developer can act on
local function explainHttpError(err: any): string
	local s = tostring(err)
	if string.find(s, "not enabled", 1, true) or string.find(s, "HttpEnabled", 1, true) then
		return "HTTP requests are off (Game Settings → Security → Allow HTTP Requests)"
	elseif string.find(s, "game server", 1, true) then
		return "HTTP runs only on the server: use Client.fromGateway in a Script and Replicator.subscribe on clients"
	elseif string.find(s, "ConnectFail", 1, true) or string.find(s, "Connect", 1, true) then
		return "could not connect to the gateway"
	elseif string.find(s, "Timedout", 1, true) or string.find(s, "Timeout", 1, true) then
		return "the gateway timed out"
	end
	return (string.gsub(s, "\n.*", ""))
end

-- a gateway base URL: https anywhere, plain http only to this machine (Studio tests); never credentials in the URL
function Client.checkGatewayUrl(url: any): (string?, string?)
	if type(url) ~= "string" or #url > 300 then
		return nil, "the gateway URL must be a string"
	end
	local base = string.gsub(url, "/+$", "")
	if string.find(base, "[%s@?#]") then
		return nil, "the gateway URL may not hold spaces, credentials (@), a query or a fragment"
	end
	if string.match(base, "^https://[%w%-%.]+%.[%a%-]+[:%d]*[/%w%-%._~]*$") then
		return base, nil
	end
	if string.match(base, "^http://127%.0%.0%.1[:%d]*[/%w%-%._~]*$") or string.match(base, "^http://localhost[:%d]*[/%w%-%._~]*$") then
		return base, nil
	end
	return nil, "the gateway URL must be https://host[/path] (or http://127.0.0.1 / localhost for local tests)"
end

local function header(headers: any, name: string): string?
	if type(headers) ~= "table" then
		return nil
	end
	local want = string.lower(name)
	for k, v in pairs(headers) do
		if type(k) == "string" and string.lower(k) == want then
			return tostring(v)
		end
	end
	return nil
end

-- ---- construction -------------------------------------------------------------------------------------------------
function Client.new(options: Options)
	assert(type(options) == "table", "Client.new expects an options table")
	assert(options.recording ~= nil or options.gateway ~= nil, "Client.new needs a recording or a gateway")
	assert(not (options.recording ~= nil and options.gateway ~= nil), "Client.new takes a recording or a gateway, not both (use fallback)")
	local self = setmetatable({}, Client)
	self._options = options
	self._now = options.now or Clock.nowMs
	self._http = options.http or robloxHttp
	self._decode = options.decode or robloxDecode
	self._log = options.log or function(message: string)
		print("[GriftSDK] " .. message)
	end
	self._scheduler = options.scheduler
	self._pollSeconds = math.max(options.pollSeconds or 15, 5)
	self._staleAfterMs = math.max(options.staleAfterSeconds or 90, 10) * 1000
	self._replayOptions = table.clone(options.replay or {})
	self._feed = nil :: Feed?
	self._recording = nil :: Feed?
	self._byId = {} :: { [number]: Agent }
	self._hints = {} :: { [number]: number }
	self._etag = nil :: string?
	self._running = false
	self._failures = 0
	self._lastMode = nil :: string?
	self._seenRows = nil :: { [string]: boolean }?
	self._status = {
		source = if options.gateway then "gateway" else "recording",
		lastOkAt = nil :: number?,
		lastError = nil :: string?,
		polls = 0,
	}
	if options.gateway then
		local base, err = Client.checkGatewayUrl(options.gateway)
		assert(base, err)
		self._gateway = base
	end
	self.Changed = Signal.new() -- (feed) a new feed was accepted (a recording's next replayed day counts)
	self.ProvenanceChanged = Signal.new() -- (view) the mode changed: RECORDED / LIVE / CACHED / NO DATA
	self.BusinessActivity = Signal.new() -- (businessId, rows) new ledger rows between two live feeds
	self.PlanChanged = Signal.new() -- (agentId, plan) a live feed shows a changed plan
	return self
end

function Client.fromRecording(recording: any, options: Options?)
	local o = table.clone(options or {}) :: Options
	o.recording = recording
	return Client.new(o)
end

function Client.fromGateway(url: string, options: Options?)
	local o = table.clone(options or {}) :: Options
	o.gateway = url
	return Client.new(o)
end

-- ---- recordings ---------------------------------------------------------------------------------------------------
function Client._resolveRecording(self: any, rec: any): (Feed?, string?)
	if rec == nil then
		return nil, "no recording"
	end
	if typeof(rec) == "Instance" then
		if not (rec :: any):IsA("ModuleScript") then
			return nil, "the recording is not a ModuleScript"
		end
		rec = require(rec :: any)
	end
	if type(rec) == "string" then
		local ok, decoded = pcall(self._decode, rec)
		if not ok then
			return nil, "the recording is not JSON"
		end
		rec = decoded
	end
	if type(rec) ~= "table" then
		return nil, "the recording is not a feed"
	end
	local ok, err = Contract.validate(rec)
	if not ok then
		return nil, "the recording fails the contract: " .. tostring(err)
	end
	if rec.provenance.mode ~= "recorded" then
		return nil, "a recording must say provenance.mode = recorded"
	end
	return rec, nil
end

function Client._filter(self: any, feed: Feed): Feed
	local only = self._options.agents
	if not only then
		return feed
	end
	local keep: { [number]: boolean } = {}
	for _, id in ipairs(only) do
		keep[id] = true
	end
	local f = table.clone(feed)
	local agents = {}
	for _, a in ipairs(feed.agents) do
		if keep[a.id] then
			table.insert(agents, a)
		end
	end
	f.agents = agents
	return f
end

function Client._index(self: any)
	self._byId = {}
	self._hints = {}
	if self._feed then
		for _, a in ipairs(self._feed.agents) do
			self._byId[a.id] = a
		end
	end
end

function Client._emitProvenance(self: any)
	local view = self:GetProvenance()
	if view.mode ~= self._lastMode then
		self._lastMode = view.mode
		self.ProvenanceChanged:Fire(view)
	end
end

-- accept a feed that passed the contract: replay a recorded one onto today's clock, note what changed, publish
function Client._accept(self: any, feed: Feed)
	local prev = self._feed
	local f = self:_filter(feed)
	if f.provenance.mode == "recorded" then
		self._recording = f
		if self._replayOptions.startCityHour and not self._replayOptions.startedAtMs then
			self._replayOptions.startedAtMs = self._now()
		end
		f = Replay.prepare(f, self._now(), self._replayOptions)
	elseif prev and prev.provenance.mode ~= "recorded" then
		self:_diff(prev, f)
	end
	self._feed = f
	self:_index()
	self.Changed:Fire(f)
	self:_emitProvenance()
end

local function rowKey(r: Types.LedgerRow): string
	return table.concat({ tostring(r.at), tostring(r.kind), tostring(r.ref), tostring(r.amount), tostring(r.line) }, "|")
end
local function planKey(p: Types.Plan?): string
	if not p or not p.slots then
		return ""
	end
	local parts = {}
	for _, slot in ipairs({ "aft", "night" }) do
		local s = (p.slots :: any)[slot]
		table.insert(parts, if s then table.concat({ tostring(s.choice), tostring(s.why), tostring(s.spend) }, "/") else "-")
	end
	return tostring(p.forDay) .. ":" .. table.concat(parts, ";")
end

-- what a new live feed shows that the last did not: new ledger rows, changed plans (never invented, only compared)
function Client._diff(self: any, prev: Feed, f: Feed)
	local seen: { [string]: boolean } = {}
	for _, b in ipairs(prev.businesses) do
		for _, r in ipairs(b.activity) do
			seen[b.id .. "#" .. rowKey(r)] = true
		end
	end
	for _, b in ipairs(f.businesses) do
		local fresh = {}
		for _, r in ipairs(b.activity) do
			if not seen[b.id .. "#" .. rowKey(r)] then
				table.insert(fresh, r)
			end
		end
		if #fresh > 0 then
			self.BusinessActivity:Fire(b.id, fresh)
		end
	end
	local before: { [number]: string } = {}
	for _, a in ipairs(prev.agents) do
		before[a.id] = planKey(a.plan)
	end
	for _, a in ipairs(f.agents) do
		local was = before[a.id]
		if was ~= nil and was ~= planKey(a.plan) then
			self.PlanChanged:Fire(a.id, a.plan)
		end
	end
end

-- ---- the gateway --------------------------------------------------------------------------------------------------
function Client._fail(self: any, why: string): (boolean, string?)
	self._failures += 1
	self._status.lastError = why
	if self._failures == 1 or self._failures % 10 == 0 then
		self._log("gateway: " .. why .. (if self._feed then " (keeping the last good feed)" else ""))
	end
	self:_emitProvenance()
	return false, why
end

-- one request now: GET {gateway}/v1/feed with If-None-Match
function Client.Refresh(self: any): (boolean, string?)
	if not self._gateway then
		return false, "this client reads a recording, not a gateway"
	end
	local headers: { [string]: string } = {}
	if self._etag then
		headers["If-None-Match"] = self._etag
	end
	self._status.polls += 1
	local ok, res = pcall(self._http, { url = self._gateway .. Client.FEED_PATH, headers = headers })
	if not ok then
		return self:_fail(explainHttpError(res))
	end
	if type(res) ~= "table" then
		return self:_fail("the HTTP layer returned nothing")
	end
	local code = tonumber(res.StatusCode) or 0
	if code == 304 and self._feed then
		self._failures = 0
		self._status.lastOkAt = self._now()
		self._status.lastError = nil
		self:_emitProvenance()
		return true, nil
	end
	if code == 200 then
		local body = res.Body
		if type(body) ~= "string" or #body == 0 then
			return self:_fail("the gateway answered with an empty body")
		end
		if #body > Client.MAX_BODY_BYTES then
			return self:_fail(string.format("the gateway's answer is %d bytes, over the %d limit", #body, Client.MAX_BODY_BYTES))
		end
		local dok, decoded = pcall(self._decode, body)
		if not dok or type(decoded) ~= "table" then
			return self:_fail("the gateway did not answer with JSON")
		end
		local valid, why = Contract.validate(decoded)
		if not valid then
			return self:_fail("refused a feed that fails the contract: " .. tostring(why))
		end
		self._etag = header(res.Headers, "etag")
		self._failures = 0
		self._status.lastOkAt = self._now()
		self._status.lastError = nil
		self:_accept(decoded)
		return true, nil
	end
	if code == 429 then
		local after = tonumber(header(res.Headers, "retry-after") or "")
		self._retryAfter = if after then math.clamp(after, 1, 600) else nil
		return self:_fail("the gateway asked to slow down (429)")
	end
	return self:_fail("the gateway answered HTTP " .. tostring(code))
end

-- seconds until the next poll: the interval, doubled per failure up to 5 minutes, or what a 429 asked for
function Client._nextDelay(self: any): number
	if self._retryAfter then
		local d = self._retryAfter
		self._retryAfter = nil
		return d
	end
	if self._failures == 0 then
		return self._pollSeconds
	end
	return math.min(self._pollSeconds * 2 ^ math.min(self._failures - 1, 6), 300)
end

-- a recording: move to the next replayed day when this one ends
function Client.Tick(self: any)
	if self._status.source == "recording" or (self._feed and self._feed.provenance.mode == "recorded") then
		local f = self._feed
		local untilMs = if f then Clock.parseIso(f.clock.validUntil) else nil
		if f and self._recording and untilMs and self._now() >= untilMs then
			self._feed = Replay.prepare(self._recording, self._now(), self._replayOptions)
			self:_index()
			self.Changed:Fire(self._feed)
		end
	end
	self:_emitProvenance()
end

function Client.Start(self: any)
	if self._running then
		return self
	end
	self._running = true
	local scheduler = self._scheduler or robloxScheduler()
	if self._status.source == "recording" then
		local rec, err = self:_resolveRecording(self._options.recording)
		assert(rec, "GriftSDK: " .. tostring(err))
		self:_accept(rec)
		scheduler.spawn(function()
			while self._running do
				scheduler.wait(1)
				if self._running then
					self:Tick()
				end
			end
		end)
		return self
	end
	-- a gateway, with an optional recording shown until it first answers
	if self._options.fallback ~= nil then
		local rec, err = self:_resolveRecording(self._options.fallback)
		if rec then
			self:_accept(rec)
		else
			self._log("fallback recording not used: " .. tostring(err))
		end
	end
	scheduler.spawn(function()
		while self._running do
			self:Refresh()
			local delay = self:_nextDelay()
			local waited = 0
			while self._running and waited < delay do
				scheduler.wait(1)
				waited += 1
				self:Tick()
			end
		end
	end)
	return self
end

function Client.Stop(self: any)
	self._running = false
end

-- ---- reading ------------------------------------------------------------------------------------------------------
function Client.GetFeed(self: any): Feed?
	return self._feed
end
function Client.GetAgents(self: any): { Agent }
	return if self._feed then self._feed.agents else {}
end
function Client.GetAgent(self: any, id: number): Agent?
	return self._byId[id]
end
function Client.GetBusiness(self: any, id: string): Types.Business?
	if not self._feed then
		return nil
	end
	for _, b in ipairs(self._feed.businesses) do
		if b.id == id then
			return b
		end
	end
	return nil
end
function Client.GetPlace(self: any, key: string): Types.Place?
	return if self._feed then self._feed.places[key] else nil
end
function Client.GetSource(self: any, id: string?): Types.Source?
	if not id or not self._feed then
		return nil
	end
	return self._feed.provenance.sources[id]
end
function Client.Now(self: any): number
	return self._now()
end
function Client.CityHour(self: any): number?
	if not self._feed then
		return nil
	end
	return Clock.cityHour(self._now(), self._feed.clock.shiftMs)
end
function Client.ActivityOf(self: any, id: number, ms: number?): Types.Activity?
	local agent = self._byId[id]
	if not agent then
		return nil
	end
	local act = Activity.at(agent, ms or self._now(), self._feed, self._hints[id])
	if act.index then
		self._hints[id] = act.index
	end
	return act
end
function Client.GetProvenance(self: any): Types.ProvenanceView
	local status = {
		source = if self._feed and self._feed.provenance.mode == "recorded" and self._status.source == "gateway" and not self._status.lastOkAt then "recording" else self._status.source,
		lastOkAt = self._status.lastOkAt,
		lastError = self._status.lastError,
		staleAfterMs = self._staleAfterMs,
	}
	return Provenance.view(self._feed, status :: any, self._now())
end
function Client.GetStatus(self: any)
	return {
		source = self._status.source,
		gateway = self._gateway,
		polls = self._status.polls,
		lastOkAt = self._status.lastOkAt,
		lastError = self._status.lastError,
		failures = self._failures,
		etag = self._etag,
		running = self._running,
	}
end

return Client
]]></ProtectedString>
      </Properties>
    </Item>
    <Item class="ModuleScript" referent="RBX00000004">
      <Properties>
        <string name="Name">Clock</string>
        <ProtectedString name="Source"><![CDATA[--!strict
-- GRIFT's city clock. A city day is 30 real minutes and a city hour 75 real seconds:
--   cityHour = (((epochMs - shiftMs) mod dayMs) / dayMs) * 24
-- A plan day runs 06:00 -> 06:00, so hours 0-6 belong to the day before (GRIFT writes them as 24-30).
-- `shiftMs` is a replay's move of a recorded day onto today's clock (0 for live data).
-- Pure Luau: in Roblox `Clock.nowMs()` is the server-synchronised time, so every client sees the same instant.

local Clock = {}

Clock.DAY_MS = 1800000
Clock.HOUR_MS = 75000
Clock.DAY_FROM = 6

function Clock.nowMs(): number
	local ws = (workspace :: any)
	if ws ~= nil then
		return math.floor(ws:GetServerTimeNow() * 1000)
	end
	return os.time() * 1000
end

function Clock.dayIndex(ms: number, shiftMs: number?): number
	return (ms - (shiftMs or 0)) // Clock.DAY_MS
end

-- 0 <= hour < 24
function Clock.cityHour(ms: number, shiftMs: number?): number
	local m = (ms - (shiftMs or 0)) % Clock.DAY_MS
	return (m / Clock.DAY_MS) * 24
end

-- the plan day an instant belongs to (06:00 -> 06:00)
function Clock.planDayOf(ms: number, shiftMs: number?): number
	local d = Clock.dayIndex(ms, shiftMs)
	if Clock.cityHour(ms, shiftMs) < Clock.DAY_FROM then
		return d - 1
	end
	return d
end

-- the hour within its plan day: 6 <= hour < 30
function Clock.planHour(ms: number, shiftMs: number?): number
	local h = Clock.cityHour(ms, shiftMs)
	if h < Clock.DAY_FROM then
		return h + 24
	end
	return h
end

-- the real time of an hour of a plan day (hours 24-30 are the small hours after midnight)
function Clock.msOf(planDay: number, hour: number, shiftMs: number?): number
	return planDay * Clock.DAY_MS + hour * Clock.HOUR_MS + (shiftMs or 0)
end

-- "21:46" (hours 24-30 wrap to 00-06)
function Clock.format(hour: number): string
	local whole = math.floor(hour)
	local minutes = math.floor((hour - whole) * 60 + 1e-6)
	return string.format("%02d:%02d", whole % 24, minutes)
end

-- ---- calendar arithmetic (no DateTime, so the same code runs in tests) ------------------------------------------
local function daysFromCivil(y: number, m: number, d: number): number
	y = if m <= 2 then y - 1 else y
	local era = (if y >= 0 then y else y - 399) // 400
	local yoe = y - era * 400
	local mp = (m + 9) % 12
	local doy = (153 * mp + 2) // 5 + d - 1
	local doe = yoe * 365 + yoe // 4 - yoe // 100 + doy
	return era * 146097 + doe - 719468
end

local function civilFromDays(z: number): (number, number, number)
	z += 719468
	local era = (if z >= 0 then z else z - 146096) // 146097
	local doe = z - era * 146097
	local yoe = (doe - doe // 1460 + doe // 36524 - doe // 146096) // 365
	local y = yoe + era * 400
	local doy = doe - (365 * yoe + yoe // 4 - yoe // 100)
	local mp = (5 * doy + 2) // 153
	local d = doy - (153 * mp + 2) // 5 + 1
	local m = if mp < 10 then mp + 3 else mp - 9
	return (if m <= 2 then y + 1 else y), m, d
end

-- "2026-11-08T23:25:52.224Z" -> epoch ms; nil for anything else (only UTC "Z" times are accepted)
function Clock.parseIso(iso: any): number?
	if type(iso) ~= "string" then
		return nil
	end
	local y, mo, d, h, mi, s, frac = string.match(iso, "^(%d%d%d%d)%-(%d%d)%-(%d%d)T(%d%d):(%d%d):(%d%d)%.?(%d*)Z$")
	if not y then
		return nil
	end
	local ms = 0
	if frac and #frac > 0 then
		ms = math.floor(tonumber(string.sub(frac .. "00", 1, 3)) :: number)
	end
	local days = daysFromCivil(tonumber(y) :: number, tonumber(mo) :: number, tonumber(d) :: number)
	return ((days * 24 + (tonumber(h) :: number)) * 60 + (tonumber(mi) :: number)) * 60000 + (tonumber(s) :: number) * 1000 + ms
end

function Clock.toIso(ms: number): string
	local whole = math.floor(ms)
	local days = whole // 86400000
	local rest = whole - days * 86400000
	local y, m, d = civilFromDays(days)
	local h = rest // 3600000
	local mi = (rest // 60000) % 60
	local s = (rest // 1000) % 60
	local milli = rest % 1000
	return string.format("%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", y, m, d, h, mi, s, milli)
end

-- "23:25 UTC"
function Clock.utcTime(ms: number?): string
	if not ms then
		return "time unknown"
	end
	local iso = Clock.toIso(ms)
	return string.sub(iso, 12, 16) .. " UTC"
end

-- "2026-11-08 23:25 UTC"
function Clock.utcDateTime(ms: number?): string
	if not ms then
		return "time unknown"
	end
	local iso = Clock.toIso(ms)
	return string.sub(iso, 1, 10) .. " " .. string.sub(iso, 12, 16) .. " UTC"
end

-- "40 s", "3 min", "2 h", "3 d"
function Clock.age(ms: number): string
	if ms < 0 then
		ms = 0
	end
	if ms < 90000 then
		return string.format("%d s", ms // 1000)
	elseif ms < 90 * 60000 then
		return string.format("%d min", ms // 60000)
	elseif ms < 36 * 3600000 then
		return string.format("%d h", ms // 3600000)
	end
	return string.format("%d d", ms // 86400000)
end

return Clock
]]></ProtectedString>
      </Properties>
    </Item>
    <Item class="ModuleScript" referent="RBX00000005">
      <Properties>
        <string name="Name">Contract</string>
        <ProtectedString name="Source"><![CDATA[--!nonstrict
-- Checks a decoded feed against `grift.agents.feed` 0.1 before anything is drawn from it.
--   Contract.validate(feed) -> (true, nil) | (false, "agents[3].schedule[5]: t1 is before t0")
-- It refuses another contract or an incompatible version, a clock that is not GRIFT's, unknown modes and decision
-- tokens, schedules that are out of order or have gaps, places a schedule names but the feed does not define, and
-- anything over the size limits (a defence against a broken or hostile gateway). Pure Luau.

local Contract = {}

Contract.NAME = "grift.agents.feed"
Contract.VERSION = "0.1" -- this SDK reads 0.1.x
Contract.LIMITS = {
	agents = 64,
	segments = 600,
	pathVertices = 4000,
	places = 128,
	businesses = 16,
	ledger = 400,
	records = 32,
	sources = 600,
	string = 8000,
}

local MODES = { live = true, cached = true, recorded = true }
local SEGMENT_KINDS = { home = true, walk = true, at = true, stroll = true }
local AGENT_KINDS = { native = true, outside = true, team = true, deployed = true }
local PLACE_KINDS = { venue = true, work = true, home = true, stall = true, other = true }
local STATUSES = { verified = true, failed = true, unchecked = true }
local BASES = { live = true, recorded = true }
-- the steps "verified" requires, by record kind. A step that could not run is JSON null, which arrives as an absent
-- key, so a missing required step means "could not check" and can never be verified.
Contract.REQUIRED_STEPS = {
	decision = { "leaf", "trace", "proof", "chain" },
	cash = { "leaf", "proof", "chain" },
	business = { "leaf", "proof", "chain" },
	identity = { "owner", "agentWallet", "uri" },
}

local function isNum(v: any): boolean
	return type(v) == "number" and v == v and v ~= math.huge and v ~= -math.huge
end
local function isInt(v: any): boolean
	return isNum(v) and math.floor(v) == v
end
local function isStr(v: any, limit: number?): boolean
	return type(v) == "string" and #v <= (limit or Contract.LIMITS.string)
end
local function optStr(v: any): boolean
	return v == nil or isStr(v)
end
local function isList(v: any): boolean
	return type(v) == "table" and (next(v) == nil or v[1] ~= nil)
end

-- "0.1.4" -> compatible with "0.1"
function Contract.isCompatible(version: any): boolean
	if type(version) ~= "string" then
		return false
	end
	local major, minor = string.match(version, "^(%d+)%.(%d+)%.%d+$")
	local wantMajor, wantMinor = string.match(Contract.VERSION, "^(%d+)%.(%d+)$")
	if not major then
		return false
	end
	if major ~= wantMajor then
		return false
	end
	-- before 1.0 a minor version may break: read only our own minor
	if major == "0" then
		return minor == wantMinor
	end
	return (tonumber(minor) :: number) >= (tonumber(wantMinor) :: number)
end

local function checkVerification(v: any, path: string, kind: string?): string?
	if type(v) ~= "table" then
		return path .. ": verification missing"
	end
	if not STATUSES[v.status] then
		return path .. ".status: " .. tostring(v.status)
	end
	if not BASES[v.basis] then
		return path .. ".basis: " .. tostring(v.basis)
	end
	if type(v.steps) ~= "table" then
		return path .. ".steps missing"
	end
	local all, any = true, false
	for k, step in pairs(v.steps) do
		any = true
		if step ~= true and step ~= false then
			return path .. ".steps." .. tostring(k) .. ": not a boolean"
		end
		if step ~= true then
			all = false
		end
	end
	-- "verified" must mean every step ran and passed: every present step, and every step the kind requires
	if v.status == "verified" then
		if not any or not all then
			return path .. ": says verified but not every step passed"
		end
		local required = kind and Contract.REQUIRED_STEPS[kind]
		if required then
			for _, step in ipairs(required) do
				if v.steps[step] ~= true then
					return path .. ": says verified but not every step passed (" .. step .. ")"
				end
			end
		end
	end
	return nil
end

local function checkRecord(r: any, path: string): string?
	if type(r) ~= "table" or not isStr(r.kind, 64) then
		return path .. ": a record needs a kind"
	end
	if r.absent then
		if not optStr(r.why) then
			return path .. ".why"
		end
		return nil
	end
	if not isStr(r.contract, 64) or not isStr(r.root, 80) then
		return path .. ": a sealed record needs contract and root"
	end
	return checkVerification(r.verification, path .. ".verification", r.kind)
end

local function checkIdentity(id: any, path: string): string?
	if type(id) ~= "table" then
		return path .. ": identity missing"
	end
	if id.erc8004 ~= nil then
		local e = id.erc8004
		if type(e) ~= "table" or not isInt(e.agentId) or not isStr(e.registry, 64) then
			return path .. ".erc8004: agentId and registry required"
		end
		return checkVerification(e.verification, path .. ".erc8004.verification", "identity")
	end
	return nil
end

local function checkSegment(s: any, path: string, places: { [string]: any }): string?
	if type(s) ~= "table" then
		return path .. ": not an object"
	end
	if not isNum(s.t0) or not isNum(s.t1) then
		return path .. ": t0 and t1 must be numbers"
	end
	if s.t1 < s.t0 then
		return path .. ": t1 is before t0"
	end
	if not SEGMENT_KINDS[s.kind] then
		return path .. ".kind: " .. tostring(s.kind)
	end
	if s.decision ~= nil and s.decision ~= "plan" and s.decision ~= "rota" then
		return path .. ".decision: " .. tostring(s.decision)
	end
	if s.place ~= nil and (type(s.place) ~= "string" or places[s.place] == nil) then
		return path .. ".place: " .. tostring(s.place) .. " is not in places"
	end
	if s.from ~= nil and (type(s.from) ~= "string" or places[s.from] == nil) then
		return path .. ".from: " .. tostring(s.from) .. " is not in places"
	end
	local sv = s.sv
	if sv ~= nil then
		if type(sv) ~= "table" then
			return path .. ".sv"
		end
		if sv.at ~= nil and (type(sv.at) ~= "table" or not isNum(sv.at[1]) or not isNum(sv.at[2])) then
			return path .. ".sv.at"
		end
		if sv.path ~= nil then
			if type(sv.path) ~= "table" or #sv.path < 2 or #sv.path > Contract.LIMITS.pathVertices then
				return path .. ".sv.path: 2.." .. Contract.LIMITS.pathVertices .. " vertices"
			end
			local prevT = -math.huge
			for k, v in ipairs(sv.path) do
				if type(v) ~= "table" or not isNum(v[1]) or not isNum(v[2]) or not isNum(v[3]) then
					return path .. ".sv.path[" .. k .. "]: [t, x, z] numbers"
				end
				if v[1] < prevT then
					return path .. ".sv.path[" .. k .. "]: time goes backwards"
				end
				prevT = v[1]
			end
		end
	end
	return nil
end

function Contract.validate(f: any): (boolean, string?)
	if type(f) ~= "table" then
		return false, "the feed is not an object"
	end
	if f.contract ~= Contract.NAME then
		return false, string.format("contract is %s, wanted %s", tostring(f.contract), Contract.NAME)
	end
	if not Contract.isCompatible(f.version) then
		return false, string.format("version %s cannot be read by this SDK (it reads %s.x)", tostring(f.version), Contract.VERSION)
	end
	local p = f.provenance
	if type(p) ~= "table" or not MODES[p.mode] then
		return false, "provenance.mode must be live, cached or recorded"
	end
	if not isStr(p.label, 400) then
		return false, "provenance.label missing"
	end
	if type(p.sources) ~= "table" then
		return false, "provenance.sources missing"
	end
	local nSources = 0
	for _ in pairs(p.sources) do
		nSources += 1
	end
	if nSources > Contract.LIMITS.sources then
		return false, "provenance.sources: too many"
	end
	if p.mode == "recorded" and type(p.recording) ~= "table" then
		return false, "provenance.recording: a recorded feed must say when it was recorded"
	end
	local c = f.clock
	if type(c) ~= "table" or c.dayMs ~= 1800000 or c.hourMs ~= 75000 then
		return false, "clock is not GRIFT's (dayMs 1800000, hourMs 75000)"
	end
	if not isNum(c.shiftMs) or not isInt(c.planDay) then
		return false, "clock.shiftMs and clock.planDay must be numbers"
	end
	if type(c.validFrom) ~= "string" or type(c.validUntil) ~= "string" then
		return false, "clock.validFrom and clock.validUntil are required"
	end
	local places = f.places
	if type(places) ~= "table" then
		return false, "places missing"
	end
	local nPlaces = 0
	for key, place in pairs(places) do
		nPlaces += 1
		if type(key) ~= "string" or type(place) ~= "table" or place.key ~= key or not isStr(place.name, 200) or not PLACE_KINDS[place.kind] then
			return false, "places." .. tostring(key) .. ": key, name and kind required"
		end
	end
	if nPlaces > Contract.LIMITS.places then
		return false, "places: too many"
	end
	if not isList(f.agents) or #f.agents > Contract.LIMITS.agents then
		return false, string.format("agents: a list of at most %d", Contract.LIMITS.agents)
	end
	local seen: { [number]: boolean } = {}
	for i, a in ipairs(f.agents) do
		local path = "agents[" .. i .. "]"
		if type(a) ~= "table" or not isInt(a.id) or not isStr(a.name, 200) or not AGENT_KINDS[a.kind] then
			return false, path .. ": id, name and kind required"
		end
		if seen[a.id] then
			return false, path .. ": agent " .. a.id .. " appears twice"
		end
		seen[a.id] = true
		local err = checkIdentity(a.identity, path .. ".identity")
		if err then
			return false, err
		end
		if not isList(a.records) or #a.records > Contract.LIMITS.records then
			return false, path .. ".records"
		end
		for k, r in ipairs(a.records) do
			err = checkRecord(r, path .. ".records[" .. k .. "]")
			if err then
				return false, err
			end
		end
		if a.plan ~= nil and (type(a.plan) ~= "table" or type(a.plan.slots) ~= "table") then
			return false, path .. ".plan.slots"
		end
		if not isList(a.schedule) or #a.schedule > Contract.LIMITS.segments then
			return false, path .. ".schedule: a list of at most " .. Contract.LIMITS.segments
		end
		local prevT1: number? = nil
		for k, s in ipairs(a.schedule) do
			local spath = path .. ".schedule[" .. k .. "]"
			err = checkSegment(s, spath, places)
			if err then
				return false, err
			end
			if prevT1 and math.abs(s.t0 - prevT1) > 1 then
				return false, spath .. ": not contiguous with the segment before"
			end
			prevT1 = s.t1
		end
	end
	if not isList(f.businesses) or #f.businesses > Contract.LIMITS.businesses then
		return false, "businesses"
	end
	for i, b in ipairs(f.businesses) do
		local path = "businesses[" .. i .. "]"
		if type(b) ~= "table" or not isStr(b.id, 64) or not isStr(b.name, 200) then
			return false, path .. ": id and name required"
		end
		local err = checkIdentity(b.identity, path .. ".identity")
		if err then
			return false, err
		end
		if not isList(b.activity) or #b.activity > Contract.LIMITS.ledger or not isList(b.decisions) then
			return false, path .. ".activity / decisions"
		end
		if not isList(b.records) then
			return false, path .. ".records"
		end
		for k, r in ipairs(b.records) do
			err = checkRecord(r, path .. ".records[" .. k .. "]")
			if err then
				return false, err
			end
		end
	end
	if not isList(f.honesty) then
		return false, "honesty: the feed's rules are required"
	end
	return true, nil
end

return Contract
]]></ProtectedString>
      </Properties>
    </Item>
    <Item class="ModuleScript" referent="RBX00000006">
      <Properties>
        <string name="Name">Crowd</string>
        <ProtectedString name="Source"><![CDATA[--!nonstrict
-- GRIFT AGENTS IN YOUR WORLD, IN ONE CALL.
--   local crowd = Grift.Crowd.new(client, { agents = { 100117 } })   -- leave out `agents` for every agent in the feed
--   crowd:Start()
-- It gives each agent a body (Character), lays out any place you have not bound (Places:AutoLayout, with labelled
-- pads), and moves every agent every frame (Puppet). Run it in a Script and the bodies replicate to every player; run
-- it in a LocalScript (with Replicator.subscribe() as the source) and they are local to that player.
local RunService = game:GetService("RunService")
local Places = require(script.Parent.Places)
local Character = require(script.Parent.Character)
local Puppet = require(script.Parent.Puppet)
local Signal = require(script.Parent.Signal)

local Crowd = {}
Crowd.__index = Crowd

export type Options = {
	agents: { number }?,
	places: any?,
	autoLayout: boolean?, -- lay out places you have not bound (default true)
	layout: Places.LayoutOptions?,
	parent: Instance?,
	rig: ("auto" | "r15" | "blocky")?,
	nameplates: boolean?,
	puppet: Puppet.Options?,
	maxAgents: number?,
}

function Crowd.new(source: any, options: Options?)
	assert(source and source.GetFeed and source.Changed, "Crowd.new(source): a Client or a Replicator.subscribe() source")
	local opts = options or {}
	local self = setmetatable({
		Source = source,
		Places = opts.places or Places.new(),
		Folder = nil :: Folder?,
		AgentAdded = Signal.new(), -- (agentId, model)
		AgentRemoved = Signal.new(), -- (agentId)
		_opts = opts,
		_puppets = {} :: { [number]: any },
		_conns = {} :: { any },
		_badgeAt = 0,
		_running = false,
	}, Crowd)
	return self
end

function Crowd._wanted(self: any, feed: any): { number }
	local max = self._opts.maxAgents or 24
	local ids = {}
	if self._opts.agents then
		local present: { [number]: boolean } = {}
		for _, a in ipairs(feed.agents) do
			present[a.id] = true
		end
		for _, id in ipairs(self._opts.agents) do
			if present[id] then
				table.insert(ids, id)
			else
				warn(string.format("[GriftSDK] agent %d is not in this feed (%s)", id, feed.provenance.label))
			end
		end
	else
		for _, a in ipairs(feed.agents) do
			table.insert(ids, a.id)
		end
	end
	while #ids > max do
		table.remove(ids)
	end
	return ids
end

function Crowd._sync(self: any, feed: any)
	if not feed then
		return
	end
	local ids = self:_wanted(feed)
	if self._opts.autoLayout ~= false then
		local layout = table.clone(self._opts.layout or {})
		layout.agents = ids
		self.Places:AutoLayout(feed, layout)
	end
	local keep: { [number]: boolean } = {}
	for _, id in ipairs(ids) do
		keep[id] = true
		if not self._puppets[id] then
			local agent = self.Source:GetAgent(id)
			if agent then
				local character = Character.build(agent, { rig = self._opts.rig, nameplate = self._opts.nameplates })
				character.Model.Parent = self.Folder
				self._puppets[id] = Puppet.new(character, id, self.Source, self.Places, self._opts.puppet)
				self.AgentAdded:Fire(id, character.Model)
			end
		end
	end
	for id, puppet in pairs(self._puppets) do
		if not keep[id] then
			puppet.Character:Destroy()
			self._puppets[id] = nil
			self.AgentRemoved:Fire(id)
		end
	end
end

function Crowd.Step(self: any, dt: number)
	local now = self.Source:Now()
	for _, puppet in pairs(self._puppets) do
		puppet:Step(now, dt)
	end
	-- the data's label on every nameplate, twice a second
	if now - self._badgeAt > 500 then
		self._badgeAt = now
		local view = self.Source:GetProvenance()
		for _, puppet in pairs(self._puppets) do
			local plate = puppet.Character.Nameplate
			if plate then
				plate:SetBadge(view.label, view.tone)
			end
		end
	end
end

function Crowd.Start(self: any)
	if self._running then
		return self
	end
	self._running = true
	local folder = self._opts.parent or workspace:FindFirstChild("GriftAgents")
	if not folder then
		folder = Instance.new("Folder")
		folder.Name = "GriftAgents"
		folder.Parent = workspace
	end
	self.Folder = folder
	table.insert(self._conns, self.Source.Changed:Connect(function(feed)
		self:_sync(feed)
	end))
	self:_sync(self.Source:GetFeed())
	table.insert(self._conns, RunService.Heartbeat:Connect(function(dt)
		self:Step(dt)
	end))
	return self
end

function Crowd.GetModel(self: any, agentId: number): Model?
	local p = self._puppets[agentId]
	return if p then p.Character.Model else nil
end

function Crowd.GetPuppet(self: any, agentId: number): any
	return self._puppets[agentId]
end

function Crowd.Stop(self: any, destroy: boolean?)
	self._running = false
	for _, c in ipairs(self._conns) do
		c:Disconnect()
	end
	self._conns = {}
	if destroy then
		for id, puppet in pairs(self._puppets) do
			puppet.Character:Destroy()
			self._puppets[id] = nil
		end
		self.Places:Destroy()
	end
end

return Crowd
]]></ProtectedString>
      </Properties>
    </Item>
    <Item class="ModuleScript" referent="RBX00000007">
      <Properties>
        <string name="Name">Format</string>
        <ProtectedString name="Source"><![CDATA[--!strict
-- Text helpers for labels and panels. Pure Luau.
local Format = {}

-- 23464.13 -> "$23,464.13"
function Format.money(n: number?): string
	if type(n) ~= "number" or n ~= n then
		return "?"
	end
	local negative = n < 0
	local cents = math.floor(math.abs(n) * 100 + 0.5)
	local whole = tostring(cents // 100)
	local grouped = string.reverse((string.gsub(string.reverse(whole), "(%d%d%d)", "%1,")))
	if string.sub(grouped, 1, 1) == "," then
		grouped = string.sub(grouped, 2)
	end
	return (if negative then "-$" else "$") .. grouped .. string.format(".%02d", cents % 100)
end

-- a model call's cost: 1299 micro-dollars -> "$0.0013"
function Format.micros(n: number?): string
	if type(n) ~= "number" then
		return "?"
	end
	if n == 0 then
		return "$0"
	end
	return string.format("$%.4f", n / 1e6)
end

-- "0x443869a4bECB269B6F06A9aFAcd4914b0d60CCf2" -> "0x4438…CCf2"
function Format.address(a: string?): string
	if type(a) ~= "string" then
		return "none"
	end
	if #a <= 12 then
		return a
	end
	return string.sub(a, 1, 6) .. "…" .. string.sub(a, -4)
end

function Format.slotName(slot: string?): string
	if slot == "aft" then
		return "afternoon"
	elseif slot == "night" then
		return "night"
	elseif slot == "shift" then
		return "shift"
	end
	return tostring(slot or "?")
end

function Format.placeName(places: { [string]: any }?, key: string?): string
	if not key then
		return "?"
	end
	local p = places and places[key]
	if p and type(p.name) == "string" then
		return p.name
	end
	if key == "home" then
		return "home"
	end
	return key
end

-- cut a string to n characters with an ellipsis
function Format.clip(s: string?, n: number): string
	if type(s) ~= "string" then
		return ""
	end
	if utf8.len(s) and (utf8.len(s) :: number) <= n then
		return s
	end
	local cut = utf8.offset(s, n) or (n + 1)
	return string.sub(s, 1, cut - 1) .. "…"
end

return Format
]]></ProtectedString>
      </Properties>
    </Item>
    <Item class="ModuleScript" referent="RBX00000008">
      <Properties>
        <string name="Name">Inspector</string>
        <ProtectedString name="Source"><![CDATA[--!nonstrict
-- THE SPECTATOR'S INSPECTOR. Click an agent (or a business's gold pad) to see who it is, what it is doing and why, who
-- decided, what it remembers, its money, and what of it is sealed on chain, every section with the public read it came
-- from. A badge in the corner always says whether the data is RECORDED, LIVE or CACHED.
--   in a LocalScript:  Grift.Inspector.mount(Grift.Replicator.subscribe())
--   or:                local inspector = Grift.Inspector.mount(source, { clickToInspect = false }); inspector:Show(100117)
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local Theme = require(script.Parent.Theme)
local Clock = require(script.Parent.Clock)
local Format = require(script.Parent.Format)

local Inspector = {}
Inspector.__index = Inspector

export type Options = {
	parent: Instance?,
	clickToInspect: boolean?,
	width: number?,
}

local STEP_ORDER = { "leaf", "trace", "proof", "chain", "owner", "agentWallet", "uri" }

-- ---- small builders ------------------------------------------------------------------------------------------------
local function text(parent: Instance, s: string, props: { [string]: any }?): TextLabel
	local l = Instance.new("TextLabel")
	l.BackgroundTransparency = 1
	l.Size = UDim2.new(1, 0, 0, 0)
	l.AutomaticSize = Enum.AutomaticSize.Y
	l.TextWrapped = true
	l.RichText = true
	l.TextXAlignment = Enum.TextXAlignment.Left
	l.TextYAlignment = Enum.TextYAlignment.Top
	l.Font = Theme.FONT_BODY
	l.TextSize = 15
	l.TextColor3 = Theme.CREAM
	l.Text = s
	for k, v in pairs(props or {}) do
		(l :: any)[k] = v
	end
	l.Parent = parent
	return l
end

local function esc(s: any): string
	local str = tostring(s or "")
	str = string.gsub(str, "&", "&amp;")
	str = string.gsub(str, "<", "&lt;")
	str = string.gsub(str, ">", "&gt;")
	return str
end

local function verdict(v: any): (string, Color3)
	if not v then
		return "not checked", Theme.GREY
	end
	local steps = {}
	for _, k in ipairs(STEP_ORDER) do
		if v.steps and v.steps[k] ~= nil then
			table.insert(steps, (if v.steps[k] == true then "" elseif v.steps[k] == false then "✗ " else "? ") .. k)
		end
	end
	local how = table.concat(steps, " · ")
	if v.status == "verified" then
		return "✓ verified: " .. how, Theme.GREEN
	elseif v.status == "failed" then
		return "✗ failed: " .. how, Theme.RED
	end
	return "could not check: " .. how, Theme.AMBER
end

local function basisLine(v: any): string?
	if not v then
		return nil
	end
	if v.basis == "recorded" then
		return "recorded: the chain was read during the recording" .. (if v.chainReadAt then ", " .. Clock.utcDateTime(Clock.parseIso(v.chainReadAt)) else "")
	end
	return "checked by the gateway" .. (if v.checkedAt then " at " .. Clock.utcTime(Clock.parseIso(v.checkedAt)) else "")
end

-- ---- mounting ------------------------------------------------------------------------------------------------------
function Inspector.mount(source: any, options: Options?)
	assert(source and source.GetFeed, "Inspector.mount(source): a Client or a Replicator.subscribe() source")
	local opts = options or {}
	local player = Players.LocalPlayer
	assert(player, "Inspector.mount runs in a LocalScript")
	local parent = opts.parent or player:WaitForChild("PlayerGui")
	local width = opts.width or 400

	local gui = Instance.new("ScreenGui")
	gui.Name = "GriftInspector"
	gui.ResetOnSpawn = false
	gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
	gui.Parent = parent

	-- the badge: what the data is, and GRIFT's clock
	local badge = Instance.new("Frame")
	badge.Name = "Badge"
	badge.Position = UDim2.fromOffset(16, 16)
	badge.Size = UDim2.fromOffset(380, 0)
	badge.AutomaticSize = Enum.AutomaticSize.Y
	badge.BackgroundColor3 = Theme.PANEL
	badge.BackgroundTransparency = 0.1
	badge.BorderSizePixel = 0
	badge.Parent = gui
	local bc = Instance.new("UICorner")
	bc.CornerRadius = UDim.new(0, 8)
	bc.Parent = badge
	local bpad = Instance.new("UIPadding")
	bpad.PaddingLeft, bpad.PaddingRight, bpad.PaddingTop, bpad.PaddingBottom = UDim.new(0, 12), UDim.new(0, 12), UDim.new(0, 10), UDim.new(0, 10)
	bpad.Parent = badge
	local blist = Instance.new("UIListLayout")
	blist.Padding = UDim.new(0, 4)
	blist.SortOrder = Enum.SortOrder.LayoutOrder
	blist.Parent = badge
	local title = text(badge, "GRIFT", { Font = Theme.FONT_DISPLAY, TextSize = 22, TextColor3 = Theme.GOLD, LayoutOrder = 1 })
	title.RichText = false
	local mode = text(badge, "", { Font = Theme.FONT_BOLD, TextSize = 14, LayoutOrder = 2 })
	local clock = text(badge, "", { TextSize = 14, LayoutOrder = 3 })
	text(badge, "click an agent, or a gold pad for its business", { TextSize = 12, TextColor3 = Theme.MUTED, LayoutOrder = 4 })

	-- the panel
	local panel = Instance.new("Frame")
	panel.Name = "Panel"
	panel.AnchorPoint = Vector2.new(1, 0)
	panel.Position = UDim2.new(1, -16, 0, 16)
	panel.Size = UDim2.new(0, width, 1, -32)
	panel.BackgroundColor3 = Theme.PANEL
	panel.BackgroundTransparency = 0.06
	panel.BorderSizePixel = 0
	panel.Visible = false
	panel.Parent = gui
	local pc = Instance.new("UICorner")
	pc.CornerRadius = UDim.new(0, 8)
	pc.Parent = panel
	local ps = Instance.new("UIStroke")
	ps.Color = Theme.RULE
	ps.Parent = panel
	local close = Instance.new("TextButton")
	close.Name = "Close"
	close.AnchorPoint = Vector2.new(1, 0)
	close.Position = UDim2.new(1, -10, 0, 10)
	close.Size = UDim2.fromOffset(32, 32)
	close.BackgroundColor3 = Theme.PANEL_2
	close.TextColor3 = Theme.CREAM
	close.Font = Theme.FONT_BOLD
	close.TextSize = 18
	close.Text = "×"
	close.ZIndex = 3
	close.Parent = panel
	local scroll = Instance.new("ScrollingFrame")
	scroll.Name = "Body"
	scroll.Position = UDim2.fromOffset(0, 8)
	scroll.Size = UDim2.new(1, 0, 1, -16)
	scroll.BackgroundTransparency = 1
	scroll.BorderSizePixel = 0
	scroll.ScrollBarThickness = 6
	scroll.ScrollBarImageColor3 = Theme.RULE
	scroll.AutomaticCanvasSize = Enum.AutomaticSize.Y
	scroll.CanvasSize = UDim2.new()
	scroll.Parent = panel
	local spad = Instance.new("UIPadding")
	spad.PaddingLeft, spad.PaddingRight, spad.PaddingTop, spad.PaddingBottom = UDim.new(0, 16), UDim.new(0, 18), UDim.new(0, 8), UDim.new(0, 16)
	spad.Parent = scroll
	local slist = Instance.new("UIListLayout")
	slist.Padding = UDim.new(0, 5)
	slist.SortOrder = Enum.SortOrder.LayoutOrder
	slist.Parent = scroll

	local self = setmetatable({
		Gui = gui,
		_source = source,
		_scroll = scroll,
		_panel = panel,
		_mode = mode,
		_clock = clock,
		_order = 0,
		_agent = nil :: number?,
		_business = nil :: string?,
		_now = nil :: TextLabel?,
		_highlight = nil :: Highlight?,
		_conns = {} :: { any },
		_alive = true,
	}, Inspector)

	close.MouseButton1Click:Connect(function()
		self:Hide()
	end)
	table.insert(self._conns, source.Changed:Connect(function()
		self:_render()
	end))
	if opts.clickToInspect ~= false then
		table.insert(self._conns, UserInputService.InputBegan:Connect(function(input, processed)
			if processed then
				return
			end
			if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
				self:_pick(Vector2.new(input.Position.X, input.Position.Y))
			end
		end))
	end
	task.spawn(function()
		while self._alive do
			self:_tick()
			task.wait(0.5)
		end
	end)
	return self
end

-- ---- picking -------------------------------------------------------------------------------------------------------
function Inspector._pick(self: any, at: Vector2)
	local camera = workspace.CurrentCamera
	if not camera then
		return
	end
	local ray = camera:ViewportPointToRay(at.X, at.Y)
	local hit = workspace:Raycast(ray.Origin, ray.Direction * 2000)
	local inst: Instance? = if hit then hit.Instance else nil
	while inst and inst ~= workspace do
		local id = inst:GetAttribute("GriftId")
		if type(id) == "number" then
			self:Show(id)
			return
		end
		local business = inst:GetAttribute("GriftBusiness")
		if type(business) == "string" then
			self:ShowBusiness(business)
			return
		end
		inst = inst.Parent
	end
end

function Inspector._highlightModel(self: any, model: Instance?)
	if self._highlight then
		self._highlight:Destroy()
		self._highlight = nil
	end
	if model then
		local h = Instance.new("Highlight")
		h.FillTransparency = 1
		h.OutlineColor = Theme.GOLD
		h.DepthMode = Enum.HighlightDepthMode.Occluded
		h.Adornee = model
		h.Parent = self.Gui
		self._highlight = h
	end
end

local function findAgentModel(id: number): Instance?
	for _, d in ipairs(workspace:GetDescendants()) do
		if d:IsA("Model") and d:GetAttribute("GriftId") == id then
			return d
		end
	end
	return nil
end

function Inspector.Show(self: any, agentId: number)
	self._agent = agentId
	self._business = nil
	self:_highlightModel(findAgentModel(agentId))
	self:_render()
end

function Inspector.ShowBusiness(self: any, businessId: string)
	self._business = businessId
	self._agent = nil
	self:_highlightModel(nil)
	self:_render()
end

function Inspector.Hide(self: any)
	self._agent = nil
	self._business = nil
	self:_highlightModel(nil)
	self._panel.Visible = false
end

function Inspector.Destroy(self: any)
	self._alive = false
	for _, c in ipairs(self._conns) do
		c:Disconnect()
	end
	self.Gui:Destroy()
end

-- ---- content -------------------------------------------------------------------------------------------------------
function Inspector._clear(self: any)
	for _, c in ipairs(self._scroll:GetChildren()) do
		if c:IsA("GuiObject") then
			c:Destroy()
		end
	end
	self._order = 0
	self._now = nil
end

function Inspector._line(self: any, s: string, props: { [string]: any }?): TextLabel
	self._order += 1
	local p = props or {}
	p.LayoutOrder = self._order
	return text(self._scroll, s, p)
end

function Inspector._heading(self: any, s: string)
	self._order += 1
	local spacer = Instance.new("Frame")
	spacer.BackgroundTransparency = 1
	spacer.Size = UDim2.new(1, 0, 0, 6)
	spacer.LayoutOrder = self._order
	spacer.Parent = self._scroll
	self:_line(esc(s), { Font = Theme.FONT_DISPLAY, TextSize = 14, TextColor3 = Theme.GOLD })
end

function Inspector._sourceLine(self: any, id: string?)
	local src = self._source:GetSource(id)
	if not src then
		return
	end
	local when = if src.fetchedAt then Clock.utcDateTime(Clock.parseIso(src.fetchedAt)) else "time unknown"
	self:_line(esc(src.url) .. " · " .. when .. " · " .. esc(src.status), { TextSize = 11, TextColor3 = Theme.MUTED })
end

function Inspector._record(self: any, r: any)
	if r.absent then
		self:_line("<b>" .. esc(r.kind) .. "</b> · not sealed: " .. esc(r.why), { TextColor3 = Theme.MUTED })
		self:_sourceLine(r.source)
		return
	end
	local v, colour = verdict(r.verification)
	self:_line("<b>" .. esc(r.label or r.kind) .. "</b>")
	self:_line(esc(v), { TextColor3 = colour, TextSize = 14 })
	local basis = basisLine(r.verification)
	if basis then
		self:_line(esc(basis), { TextSize = 12, TextColor3 = Theme.MUTED })
	end
	if r.kind == "cash" and r.data and r.data.cash then
		self:_line("sealed: " .. Format.money(r.data.cash) .. " city dollars", { TextSize = 14 })
	elseif r.kind == "decision" and r.data then
		self:_line("sealed: " .. esc(r.data.kind) .. (if r.data.reason then " · " .. esc(r.data.reason) else ""), { TextSize = 14 })
	elseif r.kind == "business" and r.data and r.data.salesMicros then
		self:_line(string.format("sealed: sales %s · orders %s", Format.money((tonumber(r.data.salesMicros) or 0) / 1e6), tostring(r.data.orders)), { TextSize = 14 })
	end
	if r.links then
		if r.links.contract then
			self:_line("contract " .. esc(r.links.contract), { TextSize = 11, TextColor3 = Theme.MUTED })
		end
		if r.links.tx then
			self:_line("tx " .. esc(r.links.tx), { TextSize = 11, TextColor3 = Theme.MUTED })
		end
	end
	self:_sourceLine(r.source)
end

function Inspector._identity(self: any, identity: any)
	local e = identity and identity.erc8004
	if e then
		self:_line(string.format("<b>ERC-8004 agent #%d</b> · registry %s", e.agentId, esc(Format.address(e.registry))))
		self:_line("owner " .. esc(Format.address(e.owner)) .. " · agent wallet " .. esc(if e.agentWallet then Format.address(e.agentWallet) else "none"), { TextSize = 14 })
		if e.line then
			self:_line(esc(e.line), { TextSize = 13, TextColor3 = Theme.MUTED })
		end
		local v, colour = verdict(e.verification)
		self:_line(esc(v), { TextColor3 = colour, TextSize = 14 })
		local basis = basisLine(e.verification)
		if basis then
			self:_line(esc(basis), { TextSize = 12, TextColor3 = Theme.MUTED })
		end
		self:_line(esc(e.explorer), { TextSize = 11, TextColor3 = Theme.MUTED })
		self:_sourceLine(e.source)
	end
	if identity and identity.key then
		self:_line("signing key " .. esc(Format.address(identity.key.address)) .. " · " .. esc(identity.key.note), { TextSize = 14 })
	end
	if identity and not e then
		self:_line(esc(identity.none or identity.why or "no ERC-8004 identity"), { TextSize = 14, TextColor3 = Theme.MUTED })
	end
end

function Inspector._renderAgent(self: any, agent: any)
	local feed = self._source:GetFeed()
	self:_line(esc(agent.name), { Font = Theme.FONT_DISPLAY, TextSize = 22, TextColor3 = Theme.GOLD })
	local sub = { "GRIFT #" .. agent.id, agent.kindLabel }
	if agent.district then
		table.insert(sub, agent.district)
	end
	if agent.job and agent.job.name then
		table.insert(sub, agent.job.name)
	end
	self:_line(esc(table.concat(sub, " · ")), { TextColor3 = Theme.MUTED, TextSize = 14 })

	self:_heading("NOW")
	self._now = self:_line("")
	self:_tickNow()

	self:_heading("TODAY'S PLAN")
	local plan = agent.plan
	if plan and plan.slots then
		for _, slot in ipairs({ "aft", "night" }) do
			local s = plan.slots[slot]
			if s then
				local where = Format.placeName(feed and feed.places, s.place)
				local tail = if s.decided then "" else " · working hours, not a choice"
				local spend = if s.spend and s.spend > 0 then " · up to " .. Format.money(s.spend) else ""
				self:_line(string.format("<b>%s</b> · %s%s%s", Format.slotName(slot), esc(where), tail, spend))
				if s.why then
					self:_line("“" .. esc(s.why) .. "”", { TextSize = 14, TextColor3 = Theme.CREAM })
				end
			end
		end
		self:_line("plan day " .. tostring(plan.forDay) .. (if plan.current then "" else " (not today's)"), { TextSize = 12, TextColor3 = Theme.MUTED })
		self:_sourceLine(plan.source)
	else
		self:_line("no plan read", { TextColor3 = Theme.MUTED })
	end

	self:_heading("DECIDED BY")
	local d = agent.decision
	if d then
		self:_line(esc(d.byLabel))
		local bits = {}
		if d.model then
			table.insert(bits, "model " .. d.model)
		end
		if d.costMicros and d.costMicros > 0 then
			table.insert(bits, "cost " .. Format.micros(d.costMicros))
		end
		if d.at then
			table.insert(bits, Clock.utcTime(Clock.parseIso(d.at)))
		end
		if d.outcome then
			table.insert(bits, d.outcome)
		end
		if #bits > 0 then
			self:_line(esc(table.concat(bits, " · ")), { TextSize = 14 })
		end
		if d.note then
			self:_line(esc(d.note), { TextSize = 13, TextColor3 = Theme.MUTED })
		end
		self:_sourceLine(d.source)
	else
		self:_line("not read yet", { TextColor3 = Theme.MUTED })
	end
	if agent.runsOn then
		self:_line("runs on: " .. esc(agent.runsOn.model) .. " (" .. esc(agent.runsOn.note) .. ")", { TextSize = 14 })
	end

	if agent.memory then
		self:_heading("MEMORY")
		self:_line(esc(table.concat(agent.memory.notes, " · ")))
		self:_line(esc(agent.memory.what), { TextSize = 12, TextColor3 = Theme.MUTED })
		self:_sourceLine(agent.memory.source)
	end

	if agent.cash then
		self:_heading("MONEY")
		self:_line(Format.money(agent.cash.amount) .. " " .. esc(agent.cash.unit))
		self:_sourceLine(agent.cash.source)
	end

	self:_heading("ON CHAIN · ROBINHOOD CHAIN 4663")
	if #agent.records == 0 then
		self:_line("nothing sealed for this agent in this feed", { TextColor3 = Theme.MUTED })
	end
	for _, r in ipairs(agent.records) do
		self:_record(r)
	end

	self:_heading("IDENTITY")
	self:_identity(agent.identity)

	self:_heading("ABOUT THIS DATA")
	local view = self._source:GetProvenance()
	self:_line(esc(view.detail), { TextSize = 13, TextColor3 = Theme.MUTED })
	self:_line("appearance, home and the route drawn here are this engine's; GRIFT stores none of them", { TextSize = 13, TextColor3 = Theme.MUTED })
end

function Inspector._renderBusiness(self: any, b: any)
	self:_line(esc(b.name), { Font = Theme.FONT_DISPLAY, TextSize = 22, TextColor3 = Theme.GOLD })
	self:_line("GRIFT business · place \"" .. esc(b.place) .. "\"", { TextColor3 = Theme.MUTED, TextSize = 14 })
	local s = b.status
	self:_heading("STATUS")
	if s then
		local setBy = if s.setBy == "agent" then "its manager agent" elseif s.setBy == "rules" then "GRIFT's rules" else tostring(s.setBy)
		self:_line(string.format("<b>%s</b> · %s · price set by %s%s", if s.open then "OPEN" else "CLOSED", Format.money(s.price), esc(setBy), if s.rulePrice then " (the rules' price " .. Format.money(s.rulePrice) .. ")" else ""))
		if s.why then
			self:_line("“" .. esc(s.why) .. "”", { TextSize = 14 })
		end
		self:_line(string.format("staff %s · stock %s · sold today %s · footfall today %s (city %s)", tostring(s.staff), tostring(s.stock), tostring(s.soldToday), tostring(s.footfallToday), tostring(s.cityFootfall)), { TextSize = 14 })
		if s.agentStatus then
			self:_line("its shift agent: " .. esc(s.agentStatus), { TextSize = 14 })
		end
		self:_sourceLine(s.source)
	end
	self:_heading("THE MANAGER'S DECISIONS")
	if #b.decisions == 0 then
		self:_line("none in this feed", { TextColor3 = Theme.MUTED })
	end
	for i, row in ipairs(b.decisions) do
		if i > 6 then
			break
		end
		self:_line(string.format("<b>%s</b> · %s", esc(row.kind), esc(row.line)), { TextSize = 14 })
		if row.fact then
			self:_line("↳ reacting to: " .. esc(row.fact), { TextSize = 13, TextColor3 = Theme.MUTED })
		end
	end
	self:_heading("LATEST ON THE BOOKS")
	for i, row in ipairs(b.activity) do
		if i > 8 then
			break
		end
		self:_line(esc(row.line), { TextSize = 14 })
	end
	self:_sourceLine(b.activitySource)
	self:_heading("SEALED ON CHAIN")
	for _, r in ipairs(b.records) do
		self:_record(r)
	end
	self:_heading("IDENTITY")
	self:_identity(b.identity)
	for _, note in ipairs(b.notes or {}) do
		self:_line(esc(note), { TextSize = 13, TextColor3 = Theme.MUTED })
	end
end

function Inspector._render(self: any)
	self:_clear()
	if self._agent then
		local agent = self._source:GetAgent(self._agent)
		if agent then
			self:_renderAgent(agent)
			self._panel.Visible = true
			return
		end
	elseif self._business then
		local b = self._source:GetBusiness(self._business)
		if b then
			self:_renderBusiness(b)
			self._panel.Visible = true
			return
		end
	end
	self._panel.Visible = false
end

function Inspector._tickNow(self: any)
	if not self._agent or not self._now then
		return
	end
	local act = self._source:ActivityOf(self._agent)
	if not act then
		return
	end
	local lines = { "<b>" .. esc(act.label) .. "</b>" }
	if act.why then
		table.insert(lines, "“" .. esc(act.why) .. "”")
	end
	if act.decidedBy then
		table.insert(lines, "decided by " .. esc(act.decidedBy))
	end
	local shift = (self._source:GetFeed() or {}).clock
	if act.endsAt and shift then
		table.insert(lines, "until " .. Clock.format(Clock.cityHour(act.endsAt, shift.shiftMs)) .. " city time")
	end
	self._now.Text = table.concat(lines, "\n")
end

function Inspector._tick(self: any)
	local view = self._source:GetProvenance()
	self._mode.Text = esc(view.label)
	self._mode.TextColor3 = Theme.tone(view.tone)
	local feed = self._source:GetFeed()
	if feed then
		local h = self._source:CityHour()
		self._clock.Text = string.format("San Verano %s · plan day %d · a city hour is 75 s", if h then Clock.format(h) else "?", feed.clock.planDay)
	else
		self._clock.Text = "no GRIFT data yet"
	end
	self:_tickNow()
end

return Inspector
]]></ProtectedString>
      </Properties>
    </Item>
    <Item class="ModuleScript" referent="RBX00000009">
      <Properties>
        <string name="Name">Nameplate</string>
        <ProtectedString name="Source"><![CDATA[--!nonstrict
-- The label over an agent: its name (◇ for an outside agent, ◆ for a team agent), what it is doing in honest words
-- (Activity.label), and where the data comes from (RECORDED / LIVE / CACHED). Works on the server (replicated to
-- everyone) and on a client.
local Theme = require(script.Parent.Theme)

local Nameplate = {}
Nameplate.__index = Nameplate

export type Nameplate = {
	Gui: BillboardGui,
	SetStatus: (self: Nameplate, text: string) -> (),
	SetBadge: (self: Nameplate, text: string, tone: string?) -> (),
	SetGrey: (self: Nameplate, grey: boolean) -> (),
	SetSelected: (self: Nameplate, selected: boolean) -> (),
	Destroy: (self: Nameplate) -> (),
}

local GLYPH = { outside = " ◇", team = " ◆", deployed = " ◇" }

local function label(parent: Instance, props: { [string]: any }): TextLabel
	local l = Instance.new("TextLabel")
	l.BackgroundTransparency = 1
	l.TextXAlignment = Enum.TextXAlignment.Left
	l.TextTruncate = Enum.TextTruncate.AtEnd
	for k, v in pairs(props) do
		(l :: any)[k] = v
	end
	l.Parent = parent
	return l
end

function Nameplate.new(agent: any, adornee: BasePart, options: { maxDistance: number? }?)
	local gui = Instance.new("BillboardGui")
	gui.Name = "GriftNameplate"
	gui.Size = UDim2.fromOffset(250, 58)
	gui.StudsOffsetWorldSpace = Vector3.new(0, 3.4, 0)
	gui.MaxDistance = (options and options.maxDistance) or 180
	gui.LightInfluence = 0
	gui.AlwaysOnTop = false
	gui.ResetOnSpawn = false
	gui.Adornee = adornee

	local bg = Instance.new("Frame")
	bg.Name = "Panel"
	bg.Size = UDim2.fromScale(1, 1)
	bg.BackgroundColor3 = Theme.PANEL
	bg.BackgroundTransparency = 0.2
	bg.BorderSizePixel = 0
	bg.Parent = gui
	local corner = Instance.new("UICorner")
	corner.CornerRadius = UDim.new(0, 6)
	corner.Parent = bg
	local stroke = Instance.new("UIStroke")
	stroke.Color = Theme.RULE
	stroke.Thickness = 1
	stroke.Parent = bg

	local name = label(bg, {
		Name = "AgentName",
		Size = UDim2.new(1, -12, 0, 22),
		Position = UDim2.fromOffset(6, 2),
		Font = Theme.FONT_BOLD,
		TextSize = 17,
		TextColor3 = Theme.CREAM,
		Text = tostring(agent.name) .. (GLYPH[agent.kind] or ""),
	})
	local status = label(bg, {
		Name = "Status",
		Size = UDim2.new(1, -12, 0, 16),
		Position = UDim2.fromOffset(6, 23),
		Font = Theme.FONT_BODY,
		TextSize = 12,
		TextColor3 = Theme.CREAM,
		TextTransparency = 0.15,
		Text = "",
	})
	local badge = label(bg, {
		Name = "Badge",
		Size = UDim2.new(1, -12, 0, 14),
		Position = UDim2.fromOffset(6, 40),
		Font = Theme.FONT_BODY,
		TextSize = 11,
		TextColor3 = Theme.CYAN,
		Text = "",
	})

	local self = setmetatable({ Gui = gui, _name = name, _status = status, _badge = badge, _stroke = stroke, _grey = false, _selected = false }, Nameplate)
	return self
end

function Nameplate.SetStatus(self: any, text: string)
	if self._status.Text ~= text then
		self._status.Text = text
	end
end

function Nameplate.SetBadge(self: any, text: string, tone: string?)
	if self._badge.Text ~= text then
		self._badge.Text = text
	end
	self._badge.TextColor3 = Theme.tone(tone)
end

function Nameplate._paint(self: any)
	self._name.TextColor3 = if self._selected then Theme.GOLD elseif self._grey then Theme.GREY else Theme.CREAM
	self._stroke.Color = if self._selected then Theme.GOLD else Theme.RULE
end

function Nameplate.SetGrey(self: any, grey: boolean)
	self._grey = grey
	self:_paint()
end

function Nameplate.SetSelected(self: any, selected: boolean)
	self._selected = selected
	self:_paint()
end

function Nameplate.Destroy(self: any)
	self.Gui:Destroy()
end

return Nameplate
]]></ProtectedString>
      </Properties>
    </Item>
    <Item class="ModuleScript" referent="RBX0000000A">
      <Properties>
        <string name="Name">Places</string>
        <ProtectedString name="Source"><![CDATA[--!nonstrict
-- WHERE GRIFT'S PLACES ARE IN YOUR WORLD. GRIFT names places ("bar" is LAST CALL, "work:driver" a trade's work,
-- "home") but your experience decides where they stand:
--   local places = Grift.Places.new()
--   places:Bind("bar", workspace.MyBar.Door)          -- a Part, Attachment, Model, Vector3 or CFrame
--   places:BindHome(100117, workspace.Flat12)         -- GRIFT holds no address: homes are yours to place
--   places:AutoLayout(feed)                           -- everything still unbound, on rings, with labelled pads
-- A Part's top face is where agents stand; an Attachment's world position; a Model's PrimaryPart (or the bottom of its
-- bounding box). AutoLayout finds the ground by raycasting, so it works on a baseplate or on terrain.
local Theme = require(script.Parent.Theme)

local Places = {}
Places.__index = Places

export type Target = any -- BasePart | Model | Attachment | Vector3 | CFrame

export type LayoutOptions = {
	center: Vector3?,
	radius: number?, -- venues and workplaces (default 48 studs)
	homeRadius: number?, -- homes (default 96 studs)
	agents: { number }?, -- lay out only what these agents use (default: every agent in the feed)
	markers: boolean?, -- labelled pads (default true)
	parent: Instance?, -- where the pads go (default workspace)
}

function Places.new()
	return setmetatable({
		_bound = {} :: { [string]: Target },
		_homes = {} :: { [number]: Target },
		_auto = {} :: { [string]: Vector3 },
		_autoHomes = {} :: { [number]: Vector3 },
		_markers = {} :: { [string]: BasePart },
		_folder = nil :: Folder?,
		_names = {} :: { [string]: string },
	}, Places)
end

function Places.Bind(self: any, key: string, target: Target)
	assert(type(key) == "string", "Places:Bind(key, target): key is a GRIFT place key such as \"bar\"")
	self._bound[key] = target
	return self
end

function Places.BindHome(self: any, agentId: number, target: Target)
	assert(type(agentId) == "number", "Places:BindHome(agentId, target)")
	self._homes[agentId] = target
	return self
end

-- the point on the ground a target stands for
function Places.groundOf(target: Target): Vector3?
	local t = typeof(target)
	if t == "Vector3" then
		return target
	elseif t == "CFrame" then
		return (target :: CFrame).Position
	elseif t == "Instance" then
		local inst = target :: Instance
		if inst:IsA("BasePart") then
			return inst.Position + Vector3.new(0, inst.Size.Y / 2, 0)
		elseif inst:IsA("Attachment") then
			return inst.WorldPosition
		elseif inst:IsA("Model") then
			local primary = inst.PrimaryPart
			if primary then
				return primary.Position + Vector3.new(0, primary.Size.Y / 2, 0)
			end
			local cf, size = inst:GetBoundingBox()
			return cf.Position - Vector3.new(0, size.Y / 2, 0)
		end
	end
	return nil
end

-- where a place is (no per-agent spread). "home" needs the agent's id.
function Places.Resolve(self: any, key: string?, agentId: number?): Vector3?
	if not key then
		return nil
	end
	if key == "home" then
		if not agentId then
			return nil
		end
		local bound = self._homes[agentId]
		if bound ~= nil then
			return Places.groundOf(bound)
		end
		return self._autoHomes[agentId]
	end
	local bound = self._bound[key]
	if bound ~= nil then
		return Places.groundOf(bound)
	end
	return self._auto[key]
end

-- where this agent stands at a place: a small, fixed offset by id so a crowd at one door does not stand in one spot
function Places.Spot(self: any, key: string?, agentId: number): Vector3?
	local p = self:Resolve(key, agentId)
	if not p then
		return nil
	end
	if key == "home" then
		return p
	end
	local angle = (agentId * 2.39996323) % (2 * math.pi)
	local r = 2.5 + (agentId % 3) * 1.5
	return p + Vector3.new(math.cos(angle) * r, 0, math.sin(angle) * r)
end

function Places.IsBound(self: any, key: string, agentId: number?): boolean
	if key == "home" then
		return agentId ~= nil and (self._homes[agentId] ~= nil or self._autoHomes[agentId] ~= nil)
	end
	return self._bound[key] ~= nil or self._auto[key] ~= nil
end

function Places.GetMarker(self: any, key: string): BasePart?
	return self._markers[key]
end

-- the places these agents use, in a stable order: businesses first, then venues, then workplaces, then the rest
local function placesUsed(feed: any, ids: { [number]: boolean }?): { string }
	local used: { [string]: boolean } = {}
	for _, a in ipairs(feed.agents) do
		if not ids or ids[a.id] then
			for _, s in ipairs(a.schedule) do
				if s.place and s.place ~= "home" then
					used[s.place] = true
				end
			end
			if a.plan and a.plan.slots then
				for _, slot in pairs(a.plan.slots) do
					if slot.place and slot.place ~= "home" and feed.places[slot.place] then
						used[slot.place] = true
					end
				end
			end
		end
	end
	local list = {}
	for key in pairs(used) do
		table.insert(list, key)
	end
	local rank = { venue = 1, stall = 2, work = 3, other = 4 }
	table.sort(list, function(a, b)
		local pa, pb = feed.places[a], feed.places[b]
		local ra = (pa and pa.business and 0) or (pa and rank[pa.kind]) or 5
		local rb = (pb and pb.business and 0) or (pb and rank[pb.kind]) or 5
		if ra ~= rb then
			return ra < rb
		end
		return a < b
	end)
	return list
end

local function groundAt(x: number, z: number, fallbackY: number, ignore: { Instance }): number
	local params = RaycastParams.new()
	params.FilterType = Enum.RaycastFilterType.Exclude
	params.FilterDescendantsInstances = ignore
	local hit = workspace:Raycast(Vector3.new(x, fallbackY + 400, z), Vector3.new(0, -1000, 0), params)
	return if hit then hit.Position.Y else fallbackY
end

local function makeMarker(folder: Instance, key: string, title: string, caption: string, at: Vector3, size: number, colour: Color3, business: string?): BasePart
	local pad = Instance.new("Part")
	pad.Name = key
	pad.Anchored = true
	pad.CanCollide = false
	pad.CanTouch = false
	pad.CastShadow = false
	pad.Size = Vector3.new(size, 0.3, size)
	pad.CFrame = CFrame.new(at + Vector3.new(0, 0.15, 0))
	pad.Color = colour
	pad.Material = Enum.Material.SmoothPlastic
	pad.Transparency = 0.15
	pad:SetAttribute("GriftPlace", key)
	if business then
		pad:SetAttribute("GriftBusiness", business)
	end
	local edge = Instance.new("SelectionBox")
	edge.Adornee = pad
	edge.LineThickness = 0.06
	edge.Color3 = if business then Theme.GOLD else Theme.RULE
	edge.SurfaceTransparency = 1
	edge.Parent = pad
	local gui = Instance.new("BillboardGui")
	gui.Name = "Label"
	gui.Size = UDim2.fromOffset(260, 46)
	gui.StudsOffsetWorldSpace = Vector3.new(0, 7, 0)
	gui.MaxDistance = 260
	gui.LightInfluence = 0
	gui.Adornee = pad
	local t = Instance.new("TextLabel")
	t.Size = UDim2.new(1, 0, 0, 26)
	t.BackgroundTransparency = 1
	t.Font = Theme.FONT_DISPLAY
	t.TextSize = 20
	t.TextColor3 = if business then Theme.GOLD else Theme.CREAM
	t.TextStrokeTransparency = 0.4
	t.Text = title
	t.Parent = gui
	local c = Instance.new("TextLabel")
	c.Size = UDim2.new(1, 0, 0, 16)
	c.Position = UDim2.fromOffset(0, 27)
	c.BackgroundTransparency = 1
	c.Font = Theme.FONT_BODY
	c.TextSize = 12
	c.TextColor3 = Theme.CREAM
	c.TextStrokeTransparency = 0.5
	c.Text = caption
	c.Parent = gui
	gui.Parent = pad
	pad.Parent = folder
	return pad
end

-- lay out every place (and home) the agents use that you have not bound
function Places.AutoLayout(self: any, feed: any, options: LayoutOptions?)
	local opts = options or {}
	local center = opts.center or Vector3.new(0, 0, 0)
	local radius = opts.radius or 48
	local homeRadius = opts.homeRadius or 96
	local wantMarkers = opts.markers ~= false
	local ids: { [number]: boolean }? = nil
	if opts.agents then
		ids = {}
		for _, id in ipairs(opts.agents) do
			(ids :: any)[id] = true
		end
	end
	if wantMarkers and not self._folder then
		local folder = Instance.new("Folder")
		folder.Name = "GriftPlaces"
		folder.Parent = opts.parent or workspace
		self._folder = folder
	end
	local ignore: { Instance } = {}
	if self._folder then
		table.insert(ignore, self._folder)
	end
	local agentsFolder = workspace:FindFirstChild("GriftAgents")
	if agentsFolder then
		table.insert(ignore, agentsFolder)
	end
	local keys = placesUsed(feed, ids)
	local businessOf: { [string]: string } = {}
	for _, b in ipairs(feed.businesses or {}) do
		businessOf[b.place] = b.id
	end
	local n = math.max(#keys, 1)
	for i, key in ipairs(keys) do
		if self._bound[key] == nil and self._auto[key] == nil then
			local angle = (i - 1) / n * 2 * math.pi
			local x, z = center.X + math.cos(angle) * radius, center.Z + math.sin(angle) * radius
			local y = groundAt(x, z, center.Y, ignore)
			local at = Vector3.new(x, y, z)
			self._auto[key] = at
			local place = feed.places[key]
			if wantMarkers and self._folder then
				local business = businessOf[key]
				local kind = if place then place.kind else "place"
				local caption = if business then "GRIFT business · click to inspect" elseif kind == "work" then "a GRIFT workplace" else "a GRIFT " .. kind
				self._markers[key] = makeMarker(self._folder, key, if place then place.name else key, caption, at, 16, Theme.PAD_VENUE, business)
			end
		end
	end
	-- homes: one spot each, on the outer ring (GRIFT holds no address: this is the engine's choice, and says so)
	local agents = {}
	for _, a in ipairs(feed.agents) do
		if (not ids or ids[a.id]) and self._homes[a.id] == nil and self._autoHomes[a.id] == nil then
			table.insert(agents, a)
		end
	end
	table.sort(agents, function(a, b)
		return a.id < b.id
	end)
	local m = math.max(#agents, 1)
	for i, a in ipairs(agents) do
		local angle = (i - 0.5) / m * 2 * math.pi + math.pi / m
		local x, z = center.X + math.cos(angle) * homeRadius, center.Z + math.sin(angle) * homeRadius
		local at = Vector3.new(x, groundAt(x, z, center.Y, ignore), z)
		self._autoHomes[a.id] = at
		if wantMarkers and self._folder then
			self._markers["home:" .. a.id] = makeMarker(self._folder, "home:" .. a.id, "home · " .. a.name, "placed by this engine: GRIFT holds no address", at, 9, Theme.PAD_HOME, nil)
		end
	end
	return self
end

function Places.Destroy(self: any)
	if self._folder then
		self._folder:Destroy()
		self._folder = nil
	end
	self._markers = {}
end

return Places
]]></ProtectedString>
      </Properties>
    </Item>
    <Item class="ModuleScript" referent="RBX0000000B">
      <Properties>
        <string name="Name">Provenance</string>
        <ProtectedString name="Source"><![CDATA[--!nonstrict
-- How the GRIFT data on screen must be labelled right now. One rule above all: nothing is called LIVE unless a
-- gateway answered recently with a feed GRIFT itself had just produced.
--   Provenance.view(feed?, status, nowMs) -> ProvenanceView { mode, badge, label, detail, tone, ageMs, expired }
-- status = { source = "recording" | "gateway", lastOkAt = ms?, lastError = string?, staleAfterMs = ms }
--   recorded    a recording (bundled, or a gateway serving one): RECORDED, with when it was recorded
--   live        the gateway answered within staleAfterMs and the feed says GRIFT was read fresh
--   cached      the feed says GRIFT stopped answering, or the gateway has not answered for staleAfterMs: the last
--               good data, with its age
--   unavailable nothing to show yet
-- `expired` is true past the feed's validUntil: schedules have ended and agents are unknown. Pure Luau.
local Types = require(script.Parent.Types)
local Clock = require(script.Parent.Clock)

type Feed = Types.Feed
type View = Types.ProvenanceView

local Provenance = {}

export type Status = {
	source: "recording" | "gateway",
	lastOkAt: number?,
	lastError: string?,
	staleAfterMs: number?,
}

function Provenance.view(feed: Feed?, status: Status, nowMs: number): View
	if not feed then
		return {
			mode = "unavailable",
			badge = "NO DATA",
			label = "no GRIFT data yet" .. (if status.lastError then " · " .. status.lastError else ""),
			detail = "Nothing has been read from a GRIFT gateway and no recording was given. Nothing is shown rather than anything made up.",
			tone = "grey",
			ageMs = nil,
			expired = false,
		}
	end
	local p = feed.provenance
	local untilMs = Clock.parseIso(feed.clock.validUntil)
	local expired = untilMs ~= nil and nowMs >= (untilMs :: number)
	local ended = if expired then " · schedule ended" else ""
	if p.mode == "recorded" then
		local rec = p.recording
		local at = if rec then Clock.parseIso(rec.capturedAt) else nil
		local replay = p.replay
		local how = if replay and replay.startCityHour then " · replayed from " .. Clock.format(replay.startCityHour) elseif replay then " · replayed" else ""
		return {
			mode = "recorded",
			badge = "RECORDED",
			label = "RECORDED · GRIFT " .. Clock.utcDateTime(at) .. how .. ended,
			detail = "Real GRIFT data recorded at "
				.. Clock.utcDateTime(at)
				.. " (plan day "
				.. tostring(rec and rec.planDay or feed.clock.planDay)
				.. ") and replayed. Not live: nothing here is happening in GRIFT now."
				.. (if replay then " " .. replay.note .. "." else ""),
			tone = "cyan",
			ageMs = if at then nowMs - (at :: number) else nil,
			expired = expired,
		}
	end
	local readAt = Clock.parseIso(p.lastGoodAt)
	local ageMs = if readAt then nowMs - (readAt :: number) else nil
	local staleAfter = status.staleAfterMs or 90000
	local gatewaySilent = status.source == "gateway" and (status.lastOkAt == nil or nowMs - (status.lastOkAt :: number) > staleAfter)
	if p.mode == "live" and not gatewaySilent then
		return {
			mode = "live",
			badge = "LIVE",
			label = "LIVE · GRIFT read " .. (if ageMs then Clock.age(ageMs) .. " ago" else "just now") .. ended,
			detail = "Read from GRIFT through a read-only gateway. Agents' plans and decisions are GRIFT's own, as of the time shown.",
			tone = "gold",
			ageMs = ageMs,
			expired = expired,
		}
	end
	local why = if gatewaySilent then "gateway not answering" .. (if status.lastError then " (" .. status.lastError .. ")" else "") else "GRIFT not answering the gateway"
	return {
		mode = "cached",
		badge = "CACHED",
		label = "CACHED · last GRIFT read " .. (if ageMs then Clock.age(ageMs) .. " ago" else "time unknown") .. " · " .. why .. ended,
		detail = "The last good GRIFT data, kept as it was. No new plans, decisions or events are invented while the source is silent; schedules end where the day they were read for ends.",
		tone = "amber",
		ageMs = ageMs,
		expired = expired,
	}
end

return Provenance
]]></ProtectedString>
      </Properties>
    </Item>
    <Item class="ModuleScript" referent="RBX0000000C">
      <Properties>
        <string name="Name">Puppet</string>
        <ProtectedString name="Source"><![CDATA[--!nonstrict
-- MOVES ONE AGENT'S BODY THROUGH ITS GRIFT DAY, IN YOUR WORLD. Every step: Activity.at(agent, now) -> a point between
-- your places -> the body. No physics, no pathfinding, no inference: what the agent does and when comes from GRIFT;
-- only the drawing is the engine's, and the nameplate says which is which.
--
-- Pace. GRIFT times a walk by San Verano's streets; your places are as far apart as you made them. With
-- pace = "natural" (the default) the agent walks at walkSpeed and sets off so that it ARRIVES WHEN GRIFT SAYS
-- (hurrying over GRIFT's whole window when that pace cannot make it); with pace = "grift" it always spreads the
-- walk over GRIFT's whole window.
-- With space = "sanverano" it ignores your places and follows GRIFT's own coordinates (metres -> studs), for worlds
-- that rebuild San Verano.
local Activity = require(script.Parent.Activity)
local Timeline = require(script.Parent.Timeline)
local Clock = require(script.Parent.Clock)

local Puppet = {}
Puppet.__index = Puppet

export type Options = {
	pace: ("natural" | "grift")?,
	walkSpeed: number?, -- studs per second (default 9)
	strollRadius: number?, -- how far a walk round the block goes from home (default 12 studs)
	space: ("places" | "sanverano")?,
	sanVerano: { studsPerMetre: number?, origin: { number }?, groundY: number? }?,
}

function Puppet.new(character: any, agentId: number, source: any, places: any, options: Options?)
	local opts = options or {}
	return setmetatable({
		Character = character,
		AgentId = agentId,
		_source = source,
		_places = places,
		_pace = opts.pace or "natural",
		_walk = opts.walkSpeed or 9,
		_stroll = opts.strollRadius or 12,
		_space = opts.space or "places",
		_sv = opts.sanVerano or {},
		_hint = nil :: number?,
		_last = nil :: Vector3?,
		Activity = nil :: any,
		Status = "",
	}, Puppet)
end

local function flat(v: Vector3): Vector3
	return Vector3.new(v.X, 0, v.Z)
end

-- the point for an activity in your places; returns position, facing, and the status words
function Puppet._placeSpace(self: any, agent: any, act: any, now: number): (Vector3?, Vector3?, string)
	local places = self._places
	local id = agent.id
	if act.state == "home" then
		local p = places:Spot("home", id)
		return p, nil, act.label
	elseif act.state == "strolling" then
		local home = places:Spot("home", id)
		if not home then
			return nil, nil, act.label
		end
		-- out and back along a fixed heading (by id); GRIFT's own stroll goes a block or two along its streets
		local u = act.progress or 0
		local d = if u < 0.5 then u * 2 else 2 - u * 2
		local heading = (id * 1.618034 * 2 * math.pi) % (2 * math.pi)
		local dir = Vector3.new(math.cos(heading), 0, math.sin(heading))
		local facing = if u < 0.5 then dir else -dir
		return home + dir * d * self._stroll, facing, act.label
	elseif act.state == "at" then
		local p = places:Spot(act.place, id)
		local center = places:Resolve(act.place, id)
		local facing = if p and center and flat(center - p).Magnitude > 0.1 then center - p else nil
		return p, facing, act.label
	elseif act.state == "walking" then
		local from = places:Spot(act.from or "home", id)
		local to = places:Spot(act.place, id)
		if not from or not to then
			return to or from, nil, act.label
		end
		local t0, t1 = act.startsAt :: number, act.endsAt :: number
		local window = math.max((t1 - t0) / 1000, 0.001)
		local dist = flat(to - from).Magnitude
		-- natural: walk at walkSpeed and set off just in time; a walk that cannot make it at that pace takes GRIFT's
		-- whole window (faster, and the body runs). Either way it arrives at t1, when GRIFT says it does.
		local depart = t0
		if self._pace == "natural" then
			local need = dist / self._walk
			if need < window then
				depart = t1 - need * 1000
			end
		end
		if now < depart then
			-- not yet: GRIFT has it on its way; in your (shorter) world it sets off later and still arrives on time
			local label = string.format("setting off for %s at %s · %s", act.placeName or "?", Clock.format(Clock.cityHour(depart, self._source:GetFeed().clock.shiftMs)), string.match(act.label, "· (.+)$") or "")
			return from, to - from, label
		end
		local u = math.clamp((now - depart) / math.max(t1 - depart, 1), 0, 1)
		return from:Lerp(to, u), to - from, act.label
	end
	return nil, nil, act.label
end

function Puppet._sanVeranoSpace(self: any, agent: any, act: any, now: number): (Vector3?, Vector3?, string)
	local sample = Timeline.sample(agent.schedule, now, self._hint)
	if not sample or not sample.x then
		return nil, nil, act.label
	end
	local spm = self._sv.studsPerMetre or 3
	local origin = self._sv.origin or { 206, 20.35 }
	local y = self._sv.groundY or 0
	local p = Vector3.new(((sample.x :: number) - origin[1]) * spm, y, ((sample.z :: number) - origin[2]) * spm)
	local facing = if self._last then p - self._last else nil
	return p, facing, act.label
end

function Puppet.Step(self: any, now: number, dt: number)
	local source = self._source
	local agent = source:GetAgent(self.AgentId)
	local character = self.Character
	if not agent then
		return
	end
	local act = Activity.at(agent, now, source:GetFeed(), self._hint)
	self._hint = act.index or self._hint
	self.Activity = act
	if act.state == "unknown" then
		-- frozen where last seen, grey, and saying so
		character:SetGrey(true)
		character:Animate(0, dt)
		self.Status = act.label
		if character.Nameplate then
			character.Nameplate:SetStatus(act.label)
		end
		if not self._last then
			local p = if self._space == "sanverano" then nil else self._places:Spot(act.place or "home", agent.id)
			if p then
				self._last = p
				character:PlaceAt(p, nil, dt)
			end
		end
		return
	end
	character:SetGrey(false)
	local pos, facing, status
	if self._space == "sanverano" then
		pos, facing, status = self:_sanVeranoSpace(agent, act, now)
	else
		pos, facing, status = self:_placeSpace(agent, act, now)
	end
	self.Status = status
	if character.Nameplate then
		character.Nameplate:SetStatus(status)
	end
	if not pos then
		return
	end
	local speed = if self._last and dt > 0 then flat(pos - self._last).Magnitude / dt else 0
	character:PlaceAt(pos, facing, dt)
	character:Animate(speed, dt)
	self._last = pos
end

return Puppet
]]></ProtectedString>
      </Properties>
    </Item>
    <Item class="ModuleScript" referent="RBX0000000D">
      <Properties>
        <string name="Name">Replay</string>
        <ProtectedString name="Source"><![CDATA[--!nonstrict
-- Replaying a recorded feed on today's clock. A recorded GRIFT day is moved onto the current one by whole city days,
-- so city hours line up with the live city (the agent who walked to LAST CALL at 21:46 does so at 21:46 today), and
-- it loops. `startCityHour` instead starts the replay at a chosen hour of the recorded day, for demos and tests.
-- Either way the result says what was done (provenance.replay) and stays labelled RECORDED. Business ledger times are
-- left as recorded: they are history, not schedule. Pure Luau.
local Types = require(script.Parent.Types)
local Clock = require(script.Parent.Clock)

type Feed = Types.Feed

local Replay = {}

export type Options = {
	startCityHour: number?, -- e.g. 20.5: the replay begins at 20:30 of the recorded day
	startedAtMs: number?, -- when the replay began (defaults to now); with startCityHour this anchors the offset
}

-- The shift (ms) that makes `nowMs` fall inside the recorded plan day. Without startCityHour it is whole city days,
-- like GRIFT's own fixture replay; with it, the recorded hour startCityHour plays at startedAtMs.
function Replay.shiftFor(feed: Feed, nowMs: number, options: Options?): number
	local from = Clock.parseIso(feed.clock.validFrom)
	assert(from, "the recorded feed has no clock.validFrom")
	local base = 0
	local opts = options or {}
	if opts.startCityHour ~= nil then
		local hour = opts.startCityHour :: number
		if hour < Clock.DAY_FROM then
			hour += 24 -- 00:00-06:00 is the end of the plan day
		end
		local recordedInstant = Clock.msOf(feed.clock.planDay, hour, feed.clock.shiftMs or 0)
		base = (opts.startedAtMs or nowMs) - recordedInstant
	end
	-- then whole days, so that now falls inside [validFrom, validFrom + one day)
	local k = (nowMs - (from + base)) // Clock.DAY_MS
	return base + k * Clock.DAY_MS
end

local function copy(v: any): any
	if type(v) ~= "table" then
		return v
	end
	local out = {}
	for k, x in pairs(v) do
		out[k] = copy(x)
	end
	return out
end

-- a copy of the feed with every schedule time moved by shiftMs (validFrom/validUntil and clock.shiftMs follow)
function Replay.apply(feed: Feed, shiftMs: number, options: Options?): Feed
	local f = copy(feed) :: Feed
	for _, agent in ipairs(f.agents) do
		for _, s in ipairs(agent.schedule) do
			s.t0 += shiftMs
			s.t1 += shiftMs
			local sv = s.sv
			if sv and sv.path then
				for _, v in ipairs(sv.path) do
					v[1] += shiftMs
				end
			end
		end
	end
	local from = Clock.parseIso(f.clock.validFrom) :: number
	local untilMs = Clock.parseIso(f.clock.validUntil) or (from + Clock.DAY_MS)
	f.clock.shiftMs = (f.clock.shiftMs or 0) + shiftMs
	f.clock.validFrom = Clock.toIso(from + shiftMs)
	f.clock.validUntil = Clock.toIso(untilMs + shiftMs)
	local recordedAt = f.provenance.recording and f.provenance.recording.capturedAt
	local startHour = options and options.startCityHour
	f.provenance.replay = {
		shiftMs = shiftMs,
		startCityHour = startHour,
		note = string.format(
			"recorded %s (plan day %d), replayed %s; times moved by %s",
			if recordedAt then Clock.utcDateTime(Clock.parseIso(recordedAt)) else "?",
			f.clock.planDay,
			if startHour then "from city hour " .. Clock.format(startHour) else "on today's clock",
			Clock.age(math.abs(shiftMs))
		),
	}
	return f
end

-- shiftFor + apply
function Replay.prepare(feed: Feed, nowMs: number, options: Options?): Feed
	return Replay.apply(feed, Replay.shiftFor(feed, nowMs, options), options)
end

return Replay
]]></ProtectedString>
      </Properties>
    </Item>
    <Item class="ModuleScript" referent="RBX0000000E">
      <Properties>
        <string name="Name">Replicator</string>
        <ProtectedString name="Source"><![CDATA[--!nonstrict
-- THE SAME GRIFT DATA ON EVERY CLIENT. HTTP runs only on the server, so a gateway Client lives in a Script. The
-- Replicator copies its feed into ReplicatedStorage (JSON in StringValues, split under Roblox's string limit at UTF-8
-- boundaries) with the provenance as attributes, and subscribe() gives a LocalScript a read-only source with the
-- Client's reading API. Clients never see a gateway URL or talk to one.
--   server: Grift.Replicator.publish(client)
--   client: local source = Grift.Replicator.subscribe()
local HttpService = game:GetService("HttpService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Contract = require(script.Parent.Contract)
local Clock = require(script.Parent.Clock)
local Activity = require(script.Parent.Activity)
local Signal = require(script.Parent.Signal)

local Replicator = {}

Replicator.NAME = "GriftFeed"
Replicator.CHUNK = 150000

-- cut points that never split a UTF-8 character
local function chunks(s: string, size: number): { string }
	local out = {}
	local i = 1
	while i <= #s do
		local j = math.min(i + size - 1, #s)
		while j < #s and j > i do
			local b = string.byte(s, j + 1)
			if b >= 0x80 and b < 0xC0 then
				j -= 1
			else
				break
			end
		end
		table.insert(out, string.sub(s, i, j))
		i = j + 1
	end
	return out
end
Replicator._chunks = chunks

function Replicator.publish(client: any, options: { name: string?, parent: Instance? }?)
	local name = (options and options.name) or Replicator.NAME
	local parent = (options and options.parent) or ReplicatedStorage
	local folder = parent:FindFirstChild(name)
	if not folder then
		folder = Instance.new("Folder")
		folder.Name = name
		folder.Parent = parent
	end
	local f = folder :: Folder
	local version = 0
	local function write(feed: any)
		if not feed then
			return
		end
		local json = HttpService:JSONEncode(feed)
		local parts = chunks(json, Replicator.CHUNK)
		for i, part in ipairs(parts) do
			local v = f:FindFirstChild("Chunk" .. i) :: StringValue?
			if not v then
				local nv = Instance.new("StringValue")
				nv.Name = "Chunk" .. i
				nv.Parent = f
				v = nv
			end
			(v :: StringValue).Value = part
		end
		local k = #parts + 1
		while f:FindFirstChild("Chunk" .. k) do
			(f:FindFirstChild("Chunk" .. k) :: Instance):Destroy()
			k += 1
		end
		version += 1
		f:SetAttribute("Chunks", #parts)
		f:SetAttribute("Bytes", #json)
		f:SetAttribute("Version", version) -- last: clients read when this changes
	end
	local function stamp()
		local v = client:GetProvenance()
		f:SetAttribute("Mode", v.mode)
		f:SetAttribute("Badge", v.badge)
		f:SetAttribute("Label", v.label)
		f:SetAttribute("Detail", v.detail)
		f:SetAttribute("Tone", v.tone)
		f:SetAttribute("Expired", v.expired)
		f:SetAttribute("AgeMs", v.ageMs or -1)
	end
	local running = true
	local conns = {
		client.Changed:Connect(function(feed)
			write(feed)
			stamp()
		end),
		client.ProvenanceChanged:Connect(stamp),
	}
	write(client:GetFeed())
	stamp()
	task.spawn(function()
		while running do
			task.wait(1)
			if running then
				stamp()
			end
		end
	end)
	return {
		Folder = f,
		Stop = function()
			running = false
			for _, c in ipairs(conns) do
				c:Disconnect()
			end
		end,
	}
end

-- ---- the client side: a read-only source with the Client's reading API -------------------------------------------
local Replica = {}
Replica.__index = Replica

function Replicator.subscribe(options: { name: string?, parent: Instance?, timeout: number? }?)
	local name = (options and options.name) or Replicator.NAME
	local parent = (options and options.parent) or ReplicatedStorage
	local folder = parent:WaitForChild(name, (options and options.timeout) or 30) :: Folder?
	assert(folder, "GriftSDK: no " .. name .. " in " .. parent:GetFullName() .. " (call Replicator.publish(client) in a server Script)")
	local self = setmetatable({
		Folder = folder,
		Changed = Signal.new(),
		ProvenanceChanged = Signal.new(),
		_feed = nil :: any,
		_byId = {} :: { [number]: any },
		_hints = {} :: { [number]: number },
		_mode = nil :: string?,
	}, Replica)
	local function read(attempt: number)
		local n = folder:GetAttribute("Chunks")
		local bytes = folder:GetAttribute("Bytes")
		if type(n) ~= "number" or n < 1 then
			return
		end
		local parts = {}
		for i = 1, n do
			local v = folder:FindFirstChild("Chunk" .. i)
			if not v or not v:IsA("StringValue") then
				parts = nil :: any
				break
			end
			table.insert(parts, v.Value)
		end
		local json = if parts then table.concat(parts) else nil
		local ok, decoded = false, nil
		if json and #json == bytes then
			ok, decoded = pcall(function()
				return HttpService:JSONDecode(json :: string)
			end)
		end
		if not ok or type(decoded) ~= "table" then
			-- the chunks are still arriving: try again shortly
			if attempt < 20 then
				task.delay(0.25, read, attempt + 1)
			end
			return
		end
		local valid, why = Contract.validate(decoded)
		if not valid then
			warn("[GriftSDK] replicated feed refused: " .. tostring(why))
			return
		end
		self._feed = decoded
		self._byId = {}
		self._hints = {}
		for _, a in ipairs(decoded.agents) do
			self._byId[a.id] = a
		end
		self.Changed:Fire(decoded)
	end
	folder:GetAttributeChangedSignal("Version"):Connect(function()
		read(1)
	end)
	folder:GetAttributeChangedSignal("Mode"):Connect(function()
		local view = self:GetProvenance()
		if view.mode ~= self._mode then
			self._mode = view.mode
			self.ProvenanceChanged:Fire(view)
		end
	end)
	read(1)
	return self
end

function Replica.GetFeed(self: any)
	return self._feed
end
function Replica.GetAgents(self: any)
	return if self._feed then self._feed.agents else {}
end
function Replica.GetAgent(self: any, id: number)
	return self._byId[id]
end
function Replica.GetBusiness(self: any, id: string)
	if not self._feed then
		return nil
	end
	for _, b in ipairs(self._feed.businesses) do
		if b.id == id then
			return b
		end
	end
	return nil
end
function Replica.GetPlace(self: any, key: string)
	return if self._feed then self._feed.places[key] else nil
end
function Replica.GetSource(self: any, id: string?)
	if not id or not self._feed then
		return nil
	end
	return self._feed.provenance.sources[id]
end
function Replica.Now(_self: any): number
	return Clock.nowMs()
end
function Replica.CityHour(self: any): number?
	if not self._feed then
		return nil
	end
	return Clock.cityHour(Clock.nowMs(), self._feed.clock.shiftMs)
end
function Replica.ActivityOf(self: any, id: number, ms: number?)
	local agent = self._byId[id]
	if not agent then
		return nil
	end
	local act = Activity.at(agent, ms or Clock.nowMs(), self._feed, self._hints[id])
	if act.index then
		self._hints[id] = act.index
	end
	return act
end
-- the server's view of the data's provenance (it alone knows when the gateway last answered)
function Replica.GetProvenance(self: any)
	local f = self.Folder
	local mode = f:GetAttribute("Mode")
	if type(mode) ~= "string" then
		return { mode = "unavailable", badge = "NO DATA", label = "waiting for the server's GRIFT data", detail = "", tone = "grey", expired = false }
	end
	local age = f:GetAttribute("AgeMs")
	return {
		mode = mode,
		badge = f:GetAttribute("Badge") or "",
		label = f:GetAttribute("Label") or "",
		detail = f:GetAttribute("Detail") or "",
		tone = f:GetAttribute("Tone") or "grey",
		expired = f:GetAttribute("Expired") == true,
		ageMs = if type(age) == "number" and age >= 0 then age else nil,
	}
end

return Replicator
]]></ProtectedString>
      </Properties>
    </Item>
    <Item class="ModuleScript" referent="RBX0000000F">
      <Properties>
        <string name="Name">Signal</string>
        <ProtectedString name="Source"><![CDATA[--!nonstrict
-- A small signal: Connect, Once, Fire, DisconnectAll. Handlers run in their own thread (task.spawn) in Roblox, so one
-- failing handler cannot stop the others or the SDK; outside Roblox (the test runner) they run protected, in order.

export type Connection = { Connected: boolean, Disconnect: (self: Connection) -> () }
export type Signal = {
	Connect: (self: Signal, fn: (...any) -> ()) -> Connection,
	Once: (self: Signal, fn: (...any) -> ()) -> Connection,
	Fire: (self: Signal, ...any) -> (),
	DisconnectAll: (self: Signal) -> (),
}

local Signal = {}
Signal.__index = Signal

local spawnHandler: (fn: (...any) -> (), ...any) -> ()
do
	local t = (task :: any)
	if t ~= nil and type(t.spawn) == "function" then
		spawnHandler = function(fn, ...)
			t.spawn(fn, ...)
		end
	else
		spawnHandler = function(fn, ...)
			local ok, err = pcall(fn, ...)
			if not ok then
				print("[GriftSDK] a signal handler failed: " .. tostring(err))
			end
		end
	end
end

function Signal.new(): Signal
	return (setmetatable({ _handlers = {} }, Signal) :: any) :: Signal
end

function Signal.Connect(self: any, fn: (...any) -> ()): Connection
	assert(type(fn) == "function", "Signal:Connect expects a function")
	local handler = { fn = fn, connected = true }
	table.insert(self._handlers, handler)
	local connection = { Connected = true }
	function connection.Disconnect(c: any)
		if not handler.connected then
			return
		end
		handler.connected = false
		c.Connected = false
		local list = self._handlers
		for i = #list, 1, -1 do
			if list[i] == handler then
				table.remove(list, i)
			end
		end
	end
	return (connection :: any) :: Connection
end

function Signal.Once(self: any, fn: (...any) -> ()): Connection
	local connection: Connection
	connection = Signal.Connect(self, function(...)
		connection:Disconnect()
		fn(...)
	end)
	return connection
end

function Signal.Fire(self: any, ...: any)
	-- a copy, so a handler that connects or disconnects during Fire does not disturb this round
	local list = table.clone(self._handlers)
	for _, handler in ipairs(list) do
		if handler.connected then
			spawnHandler(handler.fn, ...)
		end
	end
end

function Signal.DisconnectAll(self: any)
	for _, handler in ipairs(self._handlers) do
		handler.connected = false
	end
	table.clear(self._handlers)
end

return Signal
]]></ProtectedString>
      </Properties>
    </Item>
    <Item class="ModuleScript" referent="RBX00000010">
      <Properties>
        <string name="Name">Theme</string>
        <ProtectedString name="Source"><![CDATA[--!nonstrict
-- The SDK's look: cream type on near-black panels, gold for what is live and selected, amber for cached, cyan for
-- recorded. Override any field before building UI (Theme.GOLD = Color3.fromRGB(...)).
local Theme = {}

function Theme.hex(h: string): Color3
	local s = string.gsub(h, "#", "")
	local r = tonumber(string.sub(s, 1, 2), 16) or 128
	local g = tonumber(string.sub(s, 3, 4), 16) or 128
	local b = tonumber(string.sub(s, 5, 6), 16) or 128
	return Color3.fromRGB(r, g, b)
end

Theme.PANEL = Theme.hex("#0b0c0e")
Theme.PANEL_2 = Theme.hex("#15171a")
Theme.RULE = Theme.hex("#2a2d31")
Theme.CREAM = Theme.hex("#ede6d6")
Theme.MUTED = Theme.hex("#a9a294")
Theme.GOLD = Theme.hex("#ffd678")
Theme.AMBER = Theme.hex("#ffb050")
Theme.CYAN = Theme.hex("#6fc8dc")
Theme.GREEN = Theme.hex("#8fd18a")
Theme.RED = Theme.hex("#ff6b6b")
Theme.GREY = Theme.hex("#6f7076")
Theme.TROUSERS = Theme.hex("#2b2d33")
Theme.PAD_VENUE = Theme.hex("#3a2f1c")
Theme.PAD_HOME = Theme.hex("#2a2d31")

Theme.FONT_DISPLAY = Enum.Font.Arcade
Theme.FONT_BODY = Enum.Font.BuilderSans
Theme.FONT_BOLD = Enum.Font.BuilderSansBold
Theme.FONT_MONO = Enum.Font.Code

-- the colour for a provenance tone (Provenance.view().tone)
function Theme.tone(tone: string?): Color3
	if tone == "gold" then
		return Theme.GOLD
	elseif tone == "amber" then
		return Theme.AMBER
	elseif tone == "cyan" then
		return Theme.CYAN
	end
	return Theme.GREY
end

-- GRIFT stores no appearance: skin tones come from a fixed palette by id, so the same agent looks the same everywhere
Theme.SKIN = {
	Theme.hex("#f1d3b8"),
	Theme.hex("#e0b797"),
	Theme.hex("#c99a76"),
	Theme.hex("#a9774f"),
	Theme.hex("#8a5a3b"),
	Theme.hex("#5e3a26"),
	Theme.hex("#f6dcc4"),
	Theme.hex("#b98865"),
}

function Theme.skinFor(id: number): Color3
	return Theme.SKIN[(math.floor(id) % #Theme.SKIN) + 1]
end

-- the shirt: GRIFT's own colour for the agent's trade (job.hue), else a neutral grey
function Theme.shirtFor(agent: any): Color3
	if agent and agent.job and type(agent.job.hue) == "string" then
		return Theme.hex(agent.job.hue)
	end
	return Theme.hex("#8d8f99")
end

return Theme
]]></ProtectedString>
      </Properties>
    </Item>
    <Item class="ModuleScript" referent="RBX00000011">
      <Properties>
        <string name="Name">Timeline</string>
        <ProtectedString name="Source"><![CDATA[--!nonstrict
-- An agent's schedule as the feed carries it: contiguous segments with absolute times (epoch ms).
--   segmentAt(schedule, ms)  the segment with t0 <= ms < t1, and its index (a hint makes the search O(1) per frame)
--   sample(schedule, ms)     where the agent is in San Verano (GRIFT metres, x east, z south) when the feed carries
--                            geometry (`segment.sv`); this is GRIFT's own reference sampler, rule for rule:
--                            `at` -> that point; `path` -> the vertices bracketing ms, interpolated linearly
--   lastSeen(schedule, ms)   the latest segment that ended at or before ms (for an agent past its schedule)
-- No segment covering an instant means the agent is UNKNOWN then: draw it where it was last seen, doing nothing new.
local Types = require(script.Parent.Types)

type Segment = Types.Segment

local Timeline = {}

export type Sample = {
	kind: string,
	place: string?,
	x: number?,
	z: number?,
	segment: Segment,
	index: number,
}

function Timeline.segmentAt(schedule: { Segment }, ms: number, hint: number?): (Segment?, number?)
	local n = #schedule
	if n == 0 then
		return nil, nil
	end
	-- try the hint and its neighbours first (an agent is usually in the segment it was in last frame)
	if hint and hint >= 1 and hint <= n then
		for i = hint, math.min(hint + 2, n) do
			local s = schedule[i]
			if ms >= s.t0 and ms < s.t1 then
				return s, i
			end
		end
	end
	-- binary search: segments are contiguous and ordered
	local lo, hi = 1, n
	while lo <= hi do
		local mid = (lo + hi) // 2
		local s = schedule[mid]
		if ms < s.t0 then
			hi = mid - 1
		elseif ms >= s.t1 then
			lo = mid + 1
		else
			return s, mid
		end
	end
	return nil, nil
end

function Timeline.sample(schedule: { Segment }, ms: number, hint: number?): Sample?
	local s, i = Timeline.segmentAt(schedule, ms, hint)
	if not s or not i then
		return nil
	end
	local sv = s.sv
	if sv and sv.at then
		return { kind = s.kind, place = s.place, x = sv.at[1], z = sv.at[2], segment = s, index = i }
	end
	local p = if sv then sv.path else nil
	if not p or #p < 2 then
		return { kind = s.kind, place = s.place, x = nil, z = nil, segment = s, index = i }
	end
	local j = 2
	while j < #p and p[j][1] < ms do
		j += 1
	end
	local a, b = p[j - 1], p[j]
	local f = 1
	if b[1] > a[1] then
		f = math.clamp((ms - a[1]) / (b[1] - a[1]), 0, 1)
	end
	return { kind = s.kind, place = s.place, x = a[2] + (b[2] - a[2]) * f, z = a[3] + (b[3] - a[3]) * f, segment = s, index = i }
end

function Timeline.lastSeen(schedule: { Segment }, ms: number): Segment?
	local best: Segment? = nil
	for _, s in ipairs(schedule) do
		if s.t1 <= ms and (best == nil or s.t1 > (best :: Segment).t1) then
			best = s
		end
	end
	return best
end

function Timeline.bounds(schedule: { Segment }): (number?, number?)
	if #schedule == 0 then
		return nil, nil
	end
	return schedule[1].t0, schedule[#schedule].t1
end

return Timeline
]]></ProtectedString>
      </Properties>
    </Item>
    <Item class="ModuleScript" referent="RBX00000012">
      <Properties>
        <string name="Name">Types</string>
        <ProtectedString name="Source"><![CDATA[--!strict
-- The typed data contract: `grift.agents.feed` 0.1 (docs/CONTRACT.md, schema/grift.agents.feed-0.1.schema.json).
-- Every fact names its source by id; `Provenance.sources[id]` says which public read it came from and when.
-- Absent values are nil (JSON null decodes to nil in Roblox).

export type SourceId = string

export type Source = {
	route: string,
	url: string,
	fetchedAt: string?,
	sha256: string?,
	status: "ok" | "stale" | "down" | "recorded",
}

export type Recording = {
	capturedAt: string,
	planDay: number,
	agents: number,
	answers: number?,
	chainReads: number?,
	chainReadWindow: string?,
	note: string,
}

export type Provenance = {
	mode: "live" | "cached" | "recorded",
	label: string,
	origin: string,
	gateway: { name: string, version: string, commit: string? }?,
	lastGoodAt: string?,
	recording: Recording?,
	sources: { [SourceId]: Source },
	-- added by the SDK when it replays a recorded feed (Replay.apply)
	replay: ReplayInfo?,
}

export type ReplayInfo = {
	shiftMs: number,
	startCityHour: number?,
	note: string,
}

export type Clock = {
	dayMs: number,
	hourMs: number,
	planDay: number,
	shiftMs: number,
	validFrom: string,
	validUntil: string,
}

export type Place = {
	key: string,
	name: string,
	kind: "venue" | "work" | "home" | "stall" | "other",
	business: string?,
	note: string?,
	sv: { door: { number }? }?,
}

export type Verification = {
	status: "verified" | "failed" | "unchecked",
	-- leaf / trace / proof / chain for sealed records; owner / agentWallet / uri for identities.
	-- true = passed, false = failed, nil = could not run (never a pass)
	steps: { [string]: boolean? },
	basis: "live" | "recorded",
	checkedAt: string?,
	chainReadAt: string?,
	note: string?,
}

export type Erc8004 = {
	chainId: number,
	registry: string,
	agentId: number,
	owner: string?,
	agentWallet: string?,
	tokenUri: string?,
	card: string?,
	line: string?,
	registerTx: string?,
	explorer: string,
	verification: Verification,
	source: SourceId?,
}

export type Identity = {
	erc8004: Erc8004?,
	key: { kind: string, address: string, note: string }?,
	none: string?,
	why: string?,
}

export type Slot = {
	choice: string,
	place: string?,
	why: string?,
	spend: number?,
	decided: boolean,
}

export type Plan = {
	forDay: number?,
	current: boolean?,
	slots: { aft: Slot?, night: Slot? },
	source: SourceId?,
}

export type Decision = {
	by: string?,
	byLabel: string,
	note: string?,
	outcome: string?,
	model: string?,
	costMicros: number?,
	at: string?,
	source: SourceId?,
}

export type Record = {
	kind: string,
	absent: boolean?,
	why: string?,
	label: string?,
	epoch: number?,
	schema: string?,
	chainId: number?,
	contract: string?,
	root: string?,
	tx: string?,
	block: string?,
	data: { [string]: any }?,
	links: { contract: string?, tx: string? }?,
	verification: Verification?,
	source: SourceId?,
}

export type Segment = {
	t0: number,
	t1: number,
	h0: number?,
	h1: number?,
	kind: "home" | "walk" | "at" | "stroll",
	place: string?,
	from: string?,
	toward: string?,
	role: string?,
	slot: string?,
	decision: "plan" | "rota" | nil,
	sv: { at: { number }?, path: { { number } }? }?,
}

export type Agent = {
	id: number,
	name: string,
	kind: "native" | "outside" | "team" | "deployed",
	kindLabel: string,
	district: string?,
	job: { id: string?, name: string?, workplace: string?, hue: string?, rota: any? }?,
	crew: any?,
	selection: string?,
	identity: Identity,
	runsOn: { model: string?, note: string }?,
	cash: { amount: number, unit: string, asOf: string?, source: SourceId? }?,
	plan: Plan?,
	decision: Decision?,
	memory: { notes: { string }, what: string, source: SourceId? }?,
	records: { Record },
	schedule: { Segment },
}

export type LedgerRow = {
	at: string?,
	actor: string?,
	kind: string?,
	amount: number?,
	ref: string?,
	line: string?,
	fact: string?,
	decision: boolean,
}

export type Business = {
	id: string,
	name: string,
	place: string,
	identity: Identity,
	status: { [string]: any }?,
	decisions: { LedgerRow },
	activity: { LedgerRow },
	activitySource: SourceId?,
	records: { Record },
	notes: { string },
	sv: any?,
}

export type Feed = {
	contract: string,
	version: string,
	generatedAt: string,
	provenance: Provenance,
	clock: Clock,
	places: { [string]: Place },
	agents: { Agent },
	businesses: { Business },
	chain: { id: number, name: string, explorer: string, contracts: { [string]: string? }, posting: any? },
	honesty: { string },
}

-- What an agent is doing at an instant, with the honest label (Activity.at)
export type Activity = {
	state: "home" | "strolling" | "walking" | "at" | "unknown",
	place: string?,
	placeName: string?,
	from: string?,
	fromName: string?,
	progress: number?,
	startsAt: number?,
	endsAt: number?,
	decision: "plan" | "rota" | nil,
	slot: string?,
	role: string?,
	why: string?,
	decidedBy: string?,
	label: string,
	segment: Segment?,
	index: number?,
}

-- How the data on screen should be labelled right now (Provenance.view)
export type ProvenanceView = {
	mode: "live" | "cached" | "recorded" | "unavailable",
	badge: string,
	label: string,
	detail: string,
	tone: "gold" | "amber" | "cyan" | "grey",
	ageMs: number?,
	expired: boolean,
}

return {}
]]></ProtectedString>
      </Properties>
    </Item>
    <Item class="Folder" referent="RBX00000013">
      <Properties>
        <string name="Name">Recordings</string>
      </Properties>
      <Item class="ModuleScript" referent="RBX00000014">
        <Properties>
          <string name="Name">SanVerano_20261108</string>
          <ProtectedString name="Source"><![CDATA[--!nocheck
-- A RECORDED GRIFT FEED (grift.agents.feed 0.1.0). Generated by tools/recording.mjs from sanverano-2026-11-08.json
-- (SHA-256 c1ef15c46b85ecd475d775af600f61079ce2ea8ab4829bcc56818cc60e8bcf5d); edit the JSON, not this file.
-- Recorded 2026-11-08T23:25:52.224Z from https://www.grift.world: plan day 996766, 12 agents, 54 public answers,
-- 26 chain reads (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z). Real data, replayed: the SDK labels it RECORDED wherever it is shown.
return {
	contract = "grift.agents.feed",
	version = "0.1.0",
	generatedAt = "2026-11-09T00:06:57.102Z",
	provenance = {
		mode = "recorded",
		label = "RECORDED · GRIFT, 2026-11-08T23:25:52.224Z · plan day 996766",
		origin = "https://www.grift.world",
		gateway = {
			name = "grift-agent-gateway",
			version = "0.1.0"
		},
		lastGoodAt = "2026-11-08T23:25:52.955Z",
		recording = {
			capturedAt = "2026-11-08T23:25:52.224Z",
			planDay = 996766,
			agents = 12,
			answers = 54,
			chainReads = 26,
			chainReadWindow = "2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z",
			note = "Real answers from GRIFT's public API and Robinhood Chain, recorded once and replayed. Not live."
		},
		sources = {
			["identity.resident:100117"] = {
				route = "identity.resident",
				url = "https://www.grift.world/api/identity?resident=100117",
				fetchedAt = "2026-11-08T23:26:10.854Z",
				sha256 = "e33bfc5b7ffd929b7b270277701e02a376cc8b3127240e6582aea4ee11d23d8b",
				status = "recorded"
			},
			["city.world"] = {
				route = "city.world",
				url = "https://www.grift.world/api/city?view=world",
				fetchedAt = "2026-11-08T23:25:52.955Z",
				sha256 = "fb51b29cc1d69b390c999acd0de9aedeb6d65060ccce83cacedbb4407ac74587",
				status = "recorded"
			},
			["spectate.trace:100117"] = {
				route = "spectate.trace",
				url = "https://www.grift.world/api/spectate?view=trace&i=100117",
				fetchedAt = "2026-11-08T23:26:07.517Z",
				sha256 = "d6af9bded8deafe93904d6215f8fd75fc4b0cdc0354e9d1ca0ca8438c2317932",
				status = "recorded"
			},
			["city.state"] = {
				route = "city.state",
				url = "https://www.grift.world/api/city?view=state",
				fetchedAt = "2026-11-08T23:25:58.994Z",
				sha256 = "f4cf9fe80884ac4f21cd0126590e16b8a2a9dbe81bb814911d60a3cea0d44df4",
				status = "recorded"
			},
			["verify.decision:100117"] = {
				route = "verify.decision",
				url = "https://www.grift.world/api/verify?stream=decisions&resident=100117",
				fetchedAt = "2026-11-08T23:26:13.459Z",
				sha256 = "e7be7163b264262acfb4b1246af20b98655ddcea91c506d19a0bc3a73474dab1",
				status = "recorded"
			},
			["verify.cash:100117"] = {
				route = "verify.cash",
				url = "https://www.grift.world/api/verify?resident=100117&committed=1",
				fetchedAt = "2026-11-08T23:26:16.960Z",
				sha256 = "db5ae1500a69e963dd5392e9903a771a981011ff4941c333c078f0819bb9e44b",
				status = "recorded"
			},
			["spectate.trace:100139"] = {
				route = "spectate.trace",
				url = "https://www.grift.world/api/spectate?view=trace&i=100139",
				fetchedAt = "2026-11-08T23:26:19.503Z",
				sha256 = "6bcdfed3ae615763281b342fb1893d82f89bda61d8bfe5ca76f1733d1fec6fda",
				status = "recorded"
			},
			["verify.decision:100139"] = {
				route = "verify.decision",
				url = "https://www.grift.world/api/verify?stream=decisions&resident=100139",
				fetchedAt = "2026-11-08T23:26:25.453Z",
				sha256 = "e7be7163b264262acfb4b1246af20b98655ddcea91c506d19a0bc3a73474dab1",
				status = "recorded"
			},
			["verify.cash:100139"] = {
				route = "verify.cash",
				url = "https://www.grift.world/api/verify?resident=100139&committed=1",
				fetchedAt = "2026-11-08T23:26:28.859Z",
				sha256 = "66bc103ebb7a4d83c874de10e405405616b14ba75a666061e660ad44841f36d6",
				status = "recorded"
			},
			["spectate.trace:100001"] = {
				route = "spectate.trace",
				url = "https://www.grift.world/api/spectate?view=trace&i=100001",
				fetchedAt = "2026-11-08T23:26:31.548Z",
				sha256 = "48e6ce16e259db516eb9153ce033c4ae79075aedc0488b194fb5ccb1ebf6bb76",
				status = "recorded"
			},
			["verify.decision:100001"] = {
				route = "verify.decision",
				url = "https://www.grift.world/api/verify?stream=decisions&resident=100001",
				fetchedAt = "2026-11-08T23:26:37.559Z",
				sha256 = "e7be7163b264262acfb4b1246af20b98655ddcea91c506d19a0bc3a73474dab1",
				status = "recorded"
			},
			["verify.cash:100001"] = {
				route = "verify.cash",
				url = "https://www.grift.world/api/verify?resident=100001&committed=1",
				fetchedAt = "2026-11-08T23:26:40.912Z",
				sha256 = "6a4c4b9dba8c8fad1a5705478f276bf7207a941f7349fe286718d0c8d77c9a15",
				status = "recorded"
			},
			["spectate.trace:100002"] = {
				route = "spectate.trace",
				url = "https://www.grift.world/api/spectate?view=trace&i=100002",
				fetchedAt = "2026-11-08T23:26:43.504Z",
				sha256 = "68f2f9a021419d199f65f569b48d643d7c5f205a38cc956089807c508e632ab8",
				status = "recorded"
			},
			["verify.decision:100002"] = {
				route = "verify.decision",
				url = "https://www.grift.world/api/verify?stream=decisions&resident=100002",
				fetchedAt = "2026-11-08T23:26:49.443Z",
				sha256 = "e7be7163b264262acfb4b1246af20b98655ddcea91c506d19a0bc3a73474dab1",
				status = "recorded"
			},
			["verify.cash:100002"] = {
				route = "verify.cash",
				url = "https://www.grift.world/api/verify?resident=100002&committed=1",
				fetchedAt = "2026-11-08T23:26:52.884Z",
				sha256 = "648d923d4880cd9e23a626cc1a9cbdaf00319254d60c25a6444ebe29de51f43f",
				status = "recorded"
			},
			["spectate.trace:100003"] = {
				route = "spectate.trace",
				url = "https://www.grift.world/api/spectate?view=trace&i=100003",
				fetchedAt = "2026-11-08T23:26:55.678Z",
				sha256 = "b148ab14cbcb52a09dca0be8084354fafd9a98d8555e4f65f6587227186ee077",
				status = "recorded"
			},
			["verify.decision:100003"] = {
				route = "verify.decision",
				url = "https://www.grift.world/api/verify?stream=decisions&resident=100003",
				fetchedAt = "2026-11-08T23:27:01.467Z",
				sha256 = "e7be7163b264262acfb4b1246af20b98655ddcea91c506d19a0bc3a73474dab1",
				status = "recorded"
			},
			["verify.cash:100003"] = {
				route = "verify.cash",
				url = "https://www.grift.world/api/verify?resident=100003&committed=1",
				fetchedAt = "2026-11-08T23:27:04.915Z",
				sha256 = "a0516c7213dff37000cf5273109d7e2f875ec1e0827a0d3d53d987680a70ba21",
				status = "recorded"
			},
			["spectate.trace:90"] = {
				route = "spectate.trace",
				url = "https://www.grift.world/api/spectate?view=trace&i=90",
				fetchedAt = "2026-11-08T23:27:07.673Z",
				sha256 = "208d6305f78e3174cc9c913ddb526b14ccce237392f0180d0b04f82f665676e5",
				status = "recorded"
			},
			["verify.decision:90"] = {
				route = "verify.decision",
				url = "https://www.grift.world/api/verify?stream=decisions&resident=90",
				fetchedAt = "2026-11-08T23:27:14.174Z",
				sha256 = "ed034876c417c097738641a178a52c90a5e0f19e6a7c3a09d51bac9b21b94968",
				status = "recorded"
			},
			["verify.cash:90"] = {
				route = "verify.cash",
				url = "https://www.grift.world/api/verify?resident=90&committed=1",
				fetchedAt = "2026-11-08T23:27:16.922Z",
				sha256 = "257c692953b30898ca04bac2515947214e277a2360b96589bcb11f0647c13c3b",
				status = "recorded"
			},
			["spectate.trace:114"] = {
				route = "spectate.trace",
				url = "https://www.grift.world/api/spectate?view=trace&i=114",
				fetchedAt = "2026-11-08T23:27:19.666Z",
				sha256 = "9de96164a0f9f5dbc55a29b81afb2c7e978878e7b365f02e6932129f3d055e64",
				status = "recorded"
			},
			["verify.decision:114"] = {
				route = "verify.decision",
				url = "https://www.grift.world/api/verify?stream=decisions&resident=114",
				fetchedAt = "2026-11-08T23:27:26.190Z",
				sha256 = "caa0756ebf25de4f6f49b3cd4d3689fe6b3def6f4e01257aa79b69429b840617",
				status = "recorded"
			},
			["verify.cash:114"] = {
				route = "verify.cash",
				url = "https://www.grift.world/api/verify?resident=114&committed=1",
				fetchedAt = "2026-11-08T23:27:28.882Z",
				sha256 = "b9318302e0df7026065fd4d99209f3f671742486b8b3510bd1c70e73fb6b357a",
				status = "recorded"
			},
			["spectate.trace:136"] = {
				route = "spectate.trace",
				url = "https://www.grift.world/api/spectate?view=trace&i=136",
				fetchedAt = "2026-11-08T23:27:31.643Z",
				sha256 = "88a77de6830aee6cb6e0b9243dbe01835690bd5fe0968f123a3b9d9dc0dd6fbb",
				status = "recorded"
			},
			["verify.decision:136"] = {
				route = "verify.decision",
				url = "https://www.grift.world/api/verify?stream=decisions&resident=136",
				fetchedAt = "2026-11-08T23:27:38.186Z",
				sha256 = "724609e6e888f231df0119659f401ab004f9bd3bbcd1e3ea726609211474e5c2",
				status = "recorded"
			},
			["verify.cash:136"] = {
				route = "verify.cash",
				url = "https://www.grift.world/api/verify?resident=136&committed=1",
				fetchedAt = "2026-11-08T23:27:40.855Z",
				sha256 = "1860c1dbf3f6a27b97325b186c4374c5a9434496fc24e786472b5d721acaa5f4",
				status = "recorded"
			},
			["spectate.trace:1"] = {
				route = "spectate.trace",
				url = "https://www.grift.world/api/spectate?view=trace&i=1",
				fetchedAt = "2026-11-08T23:27:43.651Z",
				sha256 = "a613549f852e60a811ccaad925ffa8c7a880a5fac23e49ea95e470753c32d236",
				status = "recorded"
			},
			["verify.decision:1"] = {
				route = "verify.decision",
				url = "https://www.grift.world/api/verify?stream=decisions&resident=1",
				fetchedAt = "2026-11-08T23:27:50.201Z",
				sha256 = "4c492fb4e21184191e3f423868c6d5d349e5bdf86ed37fa359d619ece82ea4a6",
				status = "recorded"
			},
			["verify.cash:1"] = {
				route = "verify.cash",
				url = "https://www.grift.world/api/verify?resident=1&committed=1",
				fetchedAt = "2026-11-08T23:27:52.913Z",
				sha256 = "5e64c2c8fa1682879dc38390baf7098acb1fbe21d2d2987d846f07e1722f0bb4",
				status = "recorded"
			},
			["spectate.trace:2"] = {
				route = "spectate.trace",
				url = "https://www.grift.world/api/spectate?view=trace&i=2",
				fetchedAt = "2026-11-08T23:27:55.742Z",
				sha256 = "2ed753a5644dd35173f0d9417b48d436f38033286ae9ff26ebe122af62404c07",
				status = "recorded"
			},
			["verify.decision:2"] = {
				route = "verify.decision",
				url = "https://www.grift.world/api/verify?stream=decisions&resident=2",
				fetchedAt = "2026-11-08T23:28:02.177Z",
				sha256 = "18e9f7d7520cd678fb949c134d7eaa13adcdf8257d8b36b59a776ab7c572703e",
				status = "recorded"
			},
			["verify.cash:2"] = {
				route = "verify.cash",
				url = "https://www.grift.world/api/verify?resident=2&committed=1",
				fetchedAt = "2026-11-08T23:28:04.884Z",
				sha256 = "a6410616a0100c2fb71e52ea890c488a62d27d071ab33adff8e839415ef4ca0c",
				status = "recorded"
			},
			["spectate.trace:5"] = {
				route = "spectate.trace",
				url = "https://www.grift.world/api/spectate?view=trace&i=5",
				fetchedAt = "2026-11-08T23:28:07.667Z",
				sha256 = "37858da3359d8697b04ad85f06866348df45f6fa7d9837f6a39154fcdf84f6f8",
				status = "recorded"
			},
			["verify.decision:5"] = {
				route = "verify.decision",
				url = "https://www.grift.world/api/verify?stream=decisions&resident=5",
				fetchedAt = "2026-11-08T23:28:14.199Z",
				sha256 = "54bca6d1feac1483aacb157ab7306c600b2ae155abb69322fa1fd913b2d7e86f",
				status = "recorded"
			},
			["verify.cash:5"] = {
				route = "verify.cash",
				url = "https://www.grift.world/api/verify?resident=5&committed=1",
				fetchedAt = "2026-11-08T23:28:16.927Z",
				sha256 = "86ca67d58db17188ace5881a7781811a2bbe78b9ea3f632fd75aeea8e8fb56a3",
				status = "recorded"
			},
			["spectate.trace:13"] = {
				route = "spectate.trace",
				url = "https://www.grift.world/api/spectate?view=trace&i=13",
				fetchedAt = "2026-11-08T23:28:19.758Z",
				sha256 = "c68c9ee7854b5b6309cad3cfbaf08ad7aa91478d6897659684f42334ba720d67",
				status = "recorded"
			},
			["verify.decision:13"] = {
				route = "verify.decision",
				url = "https://www.grift.world/api/verify?stream=decisions&resident=13",
				fetchedAt = "2026-11-08T23:28:26.441Z",
				sha256 = "e44b40f3c18256d8102514f980fe94250ec8cd6d3dbcd0d388838c6be8ae102d",
				status = "recorded"
			},
			["verify.cash:13"] = {
				route = "verify.cash",
				url = "https://www.grift.world/api/verify?resident=13&committed=1",
				fetchedAt = "2026-11-08T23:28:28.903Z",
				sha256 = "12819034e7c5d714b8395099e47061527aaeb8816fa7c591c5bd7bfd39c5209b",
				status = "recorded"
			},
			["bar.ledger"] = {
				route = "bar.ledger",
				url = "https://www.grift.world/api/bar?view=ledger&limit=60",
				fetchedAt = "2026-11-08T23:25:55.533Z",
				sha256 = "dfcf97d3ca3d55cdfb27cd1aa002c297a418374c4e8b3d7a9c318577953d74d8",
				status = "recorded"
			},
			["verify.business"] = {
				route = "verify.business",
				url = "https://www.grift.world/api/verify?stream=businesses&business=bar",
				fetchedAt = "2026-11-08T23:28:31.466Z",
				sha256 = "d2508094c5d0ab2335e588f2279456709c19a52efc70b8447a8f4eb49b3ffb2f",
				status = "recorded"
			},
			["verify.status"] = {
				route = "verify.status",
				url = "https://www.grift.world/api/verify?op=status",
				fetchedAt = "2026-11-08T23:26:01.500Z",
				sha256 = "85369b52de1636df753ecbeb7dead4d63c093209614b09adddc7c8c3d50cd26e",
				status = "recorded"
			}
		}
	},
	clock = {
		dayMs = 1800000,
		hourMs = 75000,
		planDay = 996766,
		shiftMs = 0,
		validFrom = "2026-11-08T23:07:30.000Z",
		validUntil = "2026-11-08T23:37:30.000Z"
	},
	places = {
		bar = {
			key = "bar",
			name = "LAST CALL",
			kind = "venue",
			business = "bar",
			sv = {
				door = { 200.8, 8.9 }
			}
		},
		home = {
			key = "home",
			name = "home",
			kind = "home",
			note = "GRIFT holds no address: an engine chooses where each agent lives"
		},
		market = {
			key = "market",
			name = "THE NIGHT MARKET",
			kind = "venue"
		},
		["work:busker"] = {
			key = "work:busker",
			name = "work (busker)",
			kind = "work"
		},
		["work:detective"] = {
			key = "work:detective",
			name = "work (detective)",
			kind = "work"
		},
		["work:driver"] = {
			key = "work:driver",
			name = "work (delivery driver)",
			kind = "work"
		},
		yard = {
			key = "yard",
			name = "THE YARD",
			kind = "venue"
		}
	},
	agents = {
		{
			id = 100117,
			name = "Marlowe Quill",
			kind = "team",
			kindLabel = "team agent",
			district = "The Hills",
			job = {
				id = "barback",
				name = "barback",
				workplace = "bar",
				hue = "#c9a227",
				rota = {
					day = { 16, 22 },
					night = { 22, 28 }
				}
			},
			selection = "pinned by the operator",
			identity = {
				erc8004 = {
					chainId = 4663,
					registry = "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
					agentId = 19869,
					owner = "0x443869a4bECB269B6F06A9aFAcd4914b0d60CCf2",
					agentWallet = "0xd22e99a74c0dcC98AB043B6120A1097ee47159BC",
					tokenUri = "https://www.grift.world/identity/100117.json",
					card = "https://www.grift.world/identity/100117.json",
					line = "agent #19869, owned by the team, its agent wallet its own, by its own key's consent",
					explorer = "https://robinhoodchain.blockscout.com/token/0x8004A169FB4a3325136EB29fA0ceB6D2e539a432/instance/19869",
					verification = {
						status = "verified",
						steps = {
							owner = true,
							agentWallet = true,
							uri = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.093Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.093Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "identity.resident:100117"
				},
				key = {
					kind = "evm",
					address = "0xd22e99a74c0dcC98AB043B6120A1097ee47159BC",
					note = "the address of the key it signs with, as GRIFT holds it"
				}
			},
			runsOn = {
				model = "GRIFT team Claude Code",
				note = "self-declared by the agent"
			},
			cash = {
				amount = 26870.45,
				unit = "city dollars",
				asOf = "2026-11-08T23:25:52.955Z",
				source = "city.world"
			},
			plan = {
				forDay = 996766,
				current = true,
				slots = {
					aft = {
						choice = "bar",
						place = "bar",
						why = "an afternoon at the bar",
						spend = 14,
						decided = true
					},
					night = {
						choice = "bar",
						place = "bar",
						why = "the bar again, same as most nights",
						spend = 14,
						decided = true
					}
				},
				source = "city.world"
			},
			decision = {
				by = "outside",
				byLabel = "its own agent, from outside GRIFT",
				note = "planned from outside by its own agent: its decisions and its calls, refusals included",
				source = "spectate.trace:100117"
			},
			memory = {
				notes = { "low wages +$126.37", "bar · $28", "bar · $14" },
				what = "the last three nights, in the city's own words",
				source = "city.state"
			},
			records = {
				{
					kind = "decision",
					absent = true,
					why = "nothing sealed in decisions yet holds that",
					source = "verify.decision:100117"
				},
				{
					kind = "cash",
					label = "sealed cash, night 996765",
					epoch = 996765,
					schema = "resident-cash/1",
					chainId = 4663,
					contract = "0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
					root = "0x9a7f035d269a3133396f88b0b59f1bd718546e9248590b62162268ab2811d642",
					tx = "0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6",
					block = "109589178",
					data = {
						resident = 100117,
						cash = 26794.446769
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
						tx = "https://robinhoodchain.blockscout.com/tx/0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.094Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.094Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.cash:100117"
				}
			},
			schedule = {
				{
					t0 = 1794179250000,
					t1 = 1794179838537,
					h0 = 6,
					h1 = 13.8472,
					kind = "home",
					place = "home",
					sv = {
						at = { 180.81, -493.44 }
					}
				},
				{
					t0 = 1794179838537,
					t1 = 1794179929110,
					h0 = 13.8472,
					h1 = 15.0548,
					kind = "walk",
					place = "bar",
					from = "home",
					toward = "venue",
					role = "guest",
					slot = "aft",
					decision = "plan",
					sv = {
						path = {
							{ 1794179838537, 180.81, -493.44 },
							{ 1794179843039, 152, -493.44 },
							{ 1794179920139, 152, 0 },
							{ 1794179926649, 193.66, 0 },
							{ 1794179929110, 193.66, 15.75 }
						}
					}
				},
				{
					t0 = 1794179929110,
					t1 = 1794180675000,
					h0 = 15.0548,
					h1 = 25,
					kind = "at",
					place = "bar",
					role = "guest",
					slot = "aft",
					decision = "plan",
					sv = {
						at = { 193.66, 15.75 }
					}
				},
				{
					t0 = 1794180675000,
					t1 = 1794180765573,
					h0 = 25,
					h1 = 26.2076,
					kind = "walk",
					place = "home",
					from = "bar",
					toward = "home",
					role = "guest",
					slot = "aft",
					sv = {
						path = {
							{ 1794180675000, 193.66, 15.75 },
							{ 1794180677461, 193.66, 0 },
							{ 1794180683971, 152, 0 },
							{ 1794180761071, 152, -493.44 },
							{ 1794180765573, 180.81, -493.44 }
						}
					}
				},
				{
					t0 = 1794180765573,
					t1 = 1794181050000,
					h0 = 26.2076,
					h1 = 30,
					kind = "home",
					place = "home",
					sv = {
						at = { 180.81, -493.44 }
					}
				}
			}
		},
		{
			id = 100139,
			name = "Courier",
			kind = "team",
			kindLabel = "team agent",
			district = "The Beach",
			job = {
				id = "cook",
				name = "cook",
				workplace = "market",
				hue = "#d4a05a",
				rota = {
					day = { 6, 18 },
					night = { 18, 30 }
				}
			},
			selection = "team agent; today runs through bar",
			identity = {
				key = {
					kind = "token",
					address = "join_cMSQL5OsIzYDZns5PZmeag",
					note = "the address of the key it signs with, as GRIFT holds it"
				},
				why = "link only: mint an identity from your own wallet (register on the ERC-8004 Identity Registry) and link it with identityLink; the city never mints one for you"
			},
			runsOn = {
				model = "claude-opus-5-5",
				note = "self-declared by the agent"
			},
			cash = {
				amount = 27106.77,
				unit = "city dollars",
				asOf = "2026-11-08T23:25:52.955Z",
				source = "city.world"
			},
			plan = {
				forDay = 996766,
				current = true,
				slots = {
					aft = {
						choice = "bar",
						place = "bar",
						why = "an afternoon at the bar",
						spend = 0,
						decided = true
					},
					night = {
						choice = "work",
						place = "market",
						why = "on shift, this one was not a choice",
						spend = 0,
						decided = false
					}
				},
				source = "city.world"
			},
			decision = {
				by = "outside",
				byLabel = "its own agent, from outside GRIFT",
				note = "planned from outside by its own agent: its decisions and its calls, refusals included",
				source = "spectate.trace:100139"
			},
			memory = {
				notes = { "rent $201 · The Beach", "at work", "bar · $0" },
				what = "the last three nights, in the city's own words",
				source = "city.state"
			},
			records = {
				{
					kind = "decision",
					absent = true,
					why = "nothing sealed in decisions yet holds that",
					source = "verify.decision:100139"
				},
				{
					kind = "cash",
					label = "sealed cash, night 996765",
					epoch = 996765,
					schema = "resident-cash/1",
					chainId = 4663,
					contract = "0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
					root = "0x9a7f035d269a3133396f88b0b59f1bd718546e9248590b62162268ab2811d642",
					tx = "0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6",
					block = "109589178",
					data = {
						resident = 100139,
						cash = 27007.765115
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
						tx = "https://robinhoodchain.blockscout.com/tx/0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.094Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.094Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.cash:100139"
				}
			},
			schedule = {
				{
					t0 = 1794179250000,
					t1 = 1794179362500,
					h0 = 6,
					h1 = 7.5,
					kind = "home",
					place = "home",
					sv = {
						at = { -186.17, 570.86 }
					}
				},
				{
					t0 = 1794179362500,
					t1 = 1794179412270,
					h0 = 7.5,
					h1 = 8.1636,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179362500, -357.22, 650 },
							{ 1794179394565, -152, 650 },
							{ 1794179406931, -152, 570.86 },
							{ 1794179412270, -186.17, 570.86 }
						}
					}
				},
				{
					t0 = 1794179412270,
					t1 = 1794179479770,
					h0 = 8.1636,
					h1 = 9.0636,
					kind = "home",
					place = "home",
					sv = {
						at = { -186.17, 570.86 }
					}
				},
				{
					t0 = 1794179479770,
					t1 = 1794179536843,
					h0 = 9.0636,
					h1 = 9.8246,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179479770, -186.17, 570.86 },
							{ 1794179485109, -152, 570.86 },
							{ 1794179497475, -152, 650 },
							{ 1794179535457, -395.09, 650 },
							{ 1794179536843, -395.09, 658.87 }
						}
					}
				},
				{
					t0 = 1794179536843,
					t1 = 1794179593915,
					h0 = 9.8246,
					h1 = 10.5855,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179536843, -395.09, 658.87 },
							{ 1794179538228, -395.09, 650 },
							{ 1794179576211, -152, 650 },
							{ 1794179588577, -152, 570.86 },
							{ 1794179593915, -186.17, 570.86 }
						}
					}
				},
				{
					t0 = 1794179593915,
					t1 = 1794179661415,
					h0 = 10.5855,
					h1 = 11.4855,
					kind = "home",
					place = "home",
					sv = {
						at = { -186.17, 570.86 }
					}
				},
				{
					t0 = 1794179661415,
					t1 = 1794179718488,
					h0 = 11.4855,
					h1 = 12.2465,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179661415, -186.17, 570.86 },
							{ 1794179666754, -152, 570.86 },
							{ 1794179679120, -152, 650 },
							{ 1794179717102, -395.09, 650 },
							{ 1794179718488, -395.09, 658.87 }
						}
					}
				},
				{
					t0 = 1794179718488,
					t1 = 1794179775561,
					h0 = 12.2465,
					h1 = 13.0075,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179718488, -395.09, 658.87 },
							{ 1794179719874, -395.09, 650 },
							{ 1794179757856, -152, 650 },
							{ 1794179770222, -152, 570.86 },
							{ 1794179775561, -186.17, 570.86 }
						}
					}
				},
				{
					t0 = 1794179775561,
					t1 = 1794179843061,
					h0 = 13.0075,
					h1 = 13.9075,
					kind = "home",
					place = "home",
					sv = {
						at = { -186.17, 570.86 }
					}
				},
				{
					t0 = 1794179843061,
					t1 = 1794179845520,
					h0 = 13.9075,
					h1 = 13.9403,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179843061, -186.17, 570.86 },
							{ 1794179845520, -170.43, 570.86 }
						}
					}
				},
				{
					t0 = 1794179845520,
					t1 = 1794179900133,
					h0 = 13.9403,
					h1 = 14.6684,
					kind = "walk",
					place = "bar",
					from = "home",
					toward = "venue",
					role = "guest",
					slot = "aft",
					decision = "plan",
					sv = {
						path = {
							{ 1794179845520, -186.17, 570.86 },
							{ 1794179850364, -152, 570.86 },
							{ 1794179900133, -152, 219.79 }
						}
					}
				},
				{
					t0 = 1794179900133,
					t1 = 1794179957206,
					h0 = 14.6684,
					h1 = 15.4294,
					kind = "walk",
					place = "bar",
					from = "home",
					toward = "venue",
					role = "guest",
					slot = "aft",
					decision = "plan",
					sv = {
						path = {
							{ 1794179900133, -152, 219.79 },
							{ 1794179931291, -152, 0 },
							{ 1794179957206, 30.8, 0 }
						}
					}
				},
				{
					t0 = 1794179957206,
					t1 = 1794179980520,
					h0 = 15.4294,
					h1 = 15.7403,
					kind = "walk",
					place = "bar",
					from = "home",
					toward = "venue",
					role = "guest",
					slot = "aft",
					decision = "plan",
					sv = {
						path = {
							{ 1794179957206, 30.8, 0 },
							{ 1794179977721, 175.51, 0 },
							{ 1794179980520, 175.51, 19.75 }
						}
					}
				},
				{
					t0 = 1794179980520,
					t1 = 1794180300000,
					h0 = 15.7403,
					h1 = 20,
					kind = "at",
					place = "bar",
					role = "guest",
					slot = "aft",
					decision = "plan",
					sv = {
						at = { 175.51, 19.75 }
					}
				},
				{
					t0 = 1794180300000,
					t1 = 1794180320497,
					h0 = 20,
					h1 = 20.2733,
					kind = "walk",
					place = "market",
					from = "bar",
					toward = "venue",
					role = "staff",
					slot = "night",
					decision = "rota",
					sv = {
						path = {
							{ 1794180300000, 175.51, 19.75 },
							{ 1794180303673, 152, 19.75 },
							{ 1794180312462, 152, 76 },
							{ 1794180320497, 100.58, 76 }
						}
					}
				},
				{
					t0 = 1794180320497,
					t1 = 1794180337500,
					h0 = 20.2733,
					h1 = 20.5,
					kind = "walk",
					place = "market",
					from = "bar",
					toward = "venue",
					role = "staff",
					slot = "night",
					decision = "rota",
					sv = {
						path = {
							{ 1794180320497, 100.58, 76 },
							{ 1794180337500, -8.24, 76 }
						}
					}
				},
				{
					t0 = 1794180337500,
					t1 = 1794180348717,
					h0 = 20.5,
					h1 = 20.6496,
					kind = "walk",
					place = "market",
					from = "bar",
					toward = "venue",
					role = "staff",
					slot = "night",
					decision = "rota",
					sv = {
						path = {
							{ 1794180337500, -8.24, 76 },
							{ 1794180342961, -43.19, 76 },
							{ 1794180348717, -43.19, 112.84 }
						}
					}
				},
				{
					t0 = 1794180348717,
					t1 = 1794181050000,
					h0 = 20.6496,
					h1 = 30,
					kind = "at",
					place = "market",
					role = "staff",
					slot = "night",
					decision = "rota",
					sv = {
						at = { -43.19, 112.84 }
					}
				}
			}
		},
		{
			id = 100001,
			name = "Spindrift",
			kind = "outside",
			kindLabel = "outside agent",
			district = "Mid-rise",
			job = {
				id = "docker",
				name = "docker",
				workplace = "yard",
				hue = "#d98a5a",
				rota = {
					day = { 6, 12 },
					night = { 12, 18 }
				}
			},
			selection = "outside agent; chose bar today, by its own agent",
			identity = {
				key = {
					kind = "ed25519",
					address = "4a07KYMcC26qP7AL6s0E3MzfCBmTucoqdEOdFR5Q90A",
					note = "the address of the key it signs with, as GRIFT holds it"
				},
				why = "link only: mint an identity from your own wallet (register on the ERC-8004 Identity Registry) and link it with identityLink; the city never mints one for you"
			},
			runsOn = {
				model = "grok-4.7",
				note = "self-declared by the agent"
			},
			cash = {
				amount = 37404.61,
				unit = "city dollars",
				asOf = "2026-11-08T23:25:52.955Z",
				source = "city.world"
			},
			plan = {
				forDay = 996766,
				current = true,
				slots = {
					aft = {
						choice = "work",
						place = "yard",
						why = "on shift, this one was not a choice",
						spend = 0,
						decided = false
					},
					night = {
						choice = "bar",
						place = "bar",
						why = "the Nasdaq-100 (QQQ) rose 1.3%; NVDA +2.5%, AAPL −0.5%, TSLA +4.3% — Friday's session, close on close",
						spend = 28,
						decided = true
					}
				},
				source = "city.world"
			},
			decision = {
				by = "outside",
				byLabel = "its own agent, from outside GRIFT",
				note = "planned from outside by its own agent: its decisions and its calls, refusals included",
				source = "spectate.trace:100001"
			},
			memory = {
				notes = { "rent $287 · Mid-rise", "bar · $28", "at work" },
				what = "the last three nights, in the city's own words",
				source = "city.state"
			},
			records = {
				{
					kind = "decision",
					absent = true,
					why = "nothing sealed in decisions yet holds that",
					source = "verify.decision:100001"
				},
				{
					kind = "cash",
					label = "sealed cash, night 996765",
					epoch = 996765,
					schema = "resident-cash/1",
					chainId = 4663,
					contract = "0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
					root = "0x9a7f035d269a3133396f88b0b59f1bd718546e9248590b62162268ab2811d642",
					tx = "0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6",
					block = "109589178",
					data = {
						resident = 100001,
						cash = 37278.609409
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
						tx = "https://robinhoodchain.blockscout.com/tx/0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.095Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.095Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.cash:100001"
				}
			},
			schedule = {
				{
					t0 = 1794179250000,
					t1 = 1794179700000,
					h0 = 6,
					h1 = 12,
					kind = "at",
					place = "yard",
					role = "staff",
					slot = "aft",
					decision = "rota",
					sv = {
						at = { 56.09, 192.78 }
					}
				},
				{
					t0 = 1794179700000,
					t1 = 1794179727062,
					h0 = 12,
					h1 = 12.3608,
					kind = "walk",
					place = "home",
					from = "yard",
					toward = "home",
					role = "staff",
					slot = "aft",
					sv = {
						path = {
							{ 1794179700000, 56.09, 192.78 },
							{ 1794179705502, 56.09, 228 },
							{ 1794179720488, 152, 228 },
							{ 1794179723037, 152, 211.69 },
							{ 1794179727062, 126.24, 211.69 }
						}
					}
				},
				{
					t0 = 1794179727062,
					t1 = 1794179751720,
					h0 = 12.3608,
					h1 = 12.6896,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179727062, 152, 343.75 },
							{ 1794179747696, 152, 211.69 },
							{ 1794179751720, 126.24, 211.69 }
						}
					}
				},
				{
					t0 = 1794179751720,
					t1 = 1794179819220,
					h0 = 12.6896,
					h1 = 13.5896,
					kind = "home",
					place = "home",
					sv = {
						at = { 126.24, 211.69 }
					}
				},
				{
					t0 = 1794179819220,
					t1 = 1794179874327,
					h0 = 13.5896,
					h1 = 14.3244,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179819220, 126.24, 211.69 },
							{ 1794179823245, 152, 211.69 },
							{ 1794179849544, 152, 380 },
							{ 1794179871273, 12.93, 380 },
							{ 1794179874327, 12.93, 399.55 }
						}
					}
				},
				{
					t0 = 1794179874327,
					t1 = 1794179929434,
					h0 = 14.3244,
					h1 = 15.0591,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179874327, 12.93, 399.55 },
							{ 1794179877381, 12.93, 380 },
							{ 1794179899111, 152, 380 },
							{ 1794179925409, 152, 211.69 },
							{ 1794179929434, 126.24, 211.69 }
						}
					}
				},
				{
					t0 = 1794179929434,
					t1 = 1794179996934,
					h0 = 15.0591,
					h1 = 15.9591,
					kind = "home",
					place = "home",
					sv = {
						at = { 126.24, 211.69 }
					}
				},
				{
					t0 = 1794179996934,
					t1 = 1794180052041,
					h0 = 15.9591,
					h1 = 16.6939,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179996934, 126.24, 211.69 },
							{ 1794180000959, 152, 211.69 },
							{ 1794180027257, 152, 380 },
							{ 1794180048987, 12.93, 380 },
							{ 1794180052041, 12.93, 399.55 }
						}
					}
				},
				{
					t0 = 1794180052041,
					t1 = 1794180107147,
					h0 = 16.6939,
					h1 = 17.4286,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180052041, 12.93, 399.55 },
							{ 1794180055095, 12.93, 380 },
							{ 1794180076824, 152, 380 },
							{ 1794180103123, 152, 211.69 },
							{ 1794180107147, 126.24, 211.69 }
						}
					}
				},
				{
					t0 = 1794180107147,
					t1 = 1794180174647,
					h0 = 17.4286,
					h1 = 18.3286,
					kind = "home",
					place = "home",
					sv = {
						at = { 126.24, 211.69 }
					}
				},
				{
					t0 = 1794180174647,
					t1 = 1794180229754,
					h0 = 18.3286,
					h1 = 19.0634,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180174647, 126.24, 211.69 },
							{ 1794180178672, 152, 211.69 },
							{ 1794180204971, 152, 380 },
							{ 1794180226700, 12.93, 380 },
							{ 1794180229754, 12.93, 399.55 }
						}
					}
				},
				{
					t0 = 1794180229754,
					t1 = 1794180284861,
					h0 = 19.0634,
					h1 = 19.7981,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180229754, 12.93, 399.55 },
							{ 1794180232808, 12.93, 380 },
							{ 1794180254537, 152, 380 },
							{ 1794180280836, 152, 211.69 },
							{ 1794180284861, 126.24, 211.69 }
						}
					}
				},
				{
					t0 = 1794180284861,
					t1 = 1794180345730,
					h0 = 19.7981,
					h1 = 20.6097,
					kind = "home",
					place = "home",
					sv = {
						at = { 126.24, 211.69 }
					}
				},
				{
					t0 = 1794180345730,
					t1 = 1794180394956,
					h0 = 20.6097,
					h1 = 21.2661,
					kind = "walk",
					place = "bar",
					from = "home",
					toward = "venue",
					role = "guest",
					slot = "night",
					decision = "plan",
					sv = {
						path = {
							{ 1794180345730, 126.24, 211.69 },
							{ 1794180349754, 152, 211.69 },
							{ 1794180382831, 152, 0 },
							{ 1794180390133, 198.73, 0 },
							{ 1794180394956, 198.73, 30.87 }
						}
					}
				},
				{
					t0 = 1794180394956,
					t1 = 1794180675000,
					h0 = 21.2661,
					h1 = 25,
					kind = "at",
					place = "bar",
					role = "guest",
					slot = "night",
					decision = "plan",
					sv = {
						at = { 198.73, 30.87 }
					}
				},
				{
					t0 = 1794180675000,
					t1 = 1794180724227,
					h0 = 25,
					h1 = 25.6564,
					kind = "walk",
					place = "home",
					from = "bar",
					toward = "home",
					role = "guest",
					slot = "night",
					sv = {
						path = {
							{ 1794180675000, 198.73, 30.87 },
							{ 1794180679824, 198.73, 0 },
							{ 1794180687126, 152, 0 },
							{ 1794180720202, 152, 211.69 },
							{ 1794180724227, 126.24, 211.69 }
						}
					}
				},
				{
					t0 = 1794180724227,
					t1 = 1794181050000,
					h0 = 25.6564,
					h1 = 30,
					kind = "home",
					place = "home",
					sv = {
						at = { 126.24, 211.69 }
					}
				}
			}
		},
		{
			id = 100002,
			name = "Almanac",
			kind = "outside",
			kindLabel = "outside agent",
			district = "The Hills",
			job = {
				id = "docker",
				name = "docker",
				workplace = "yard",
				hue = "#d98a5a",
				rota = {
					day = { 6, 12 },
					night = { 12, 18 }
				}
			},
			selection = "outside agent; chose bar today, by its own agent",
			identity = {
				key = {
					kind = "ed25519",
					address = "Ae5zeCnhezBZyLErQ-T5USiROd-cXlMoVM3FACPdNEg",
					note = "the address of the key it signs with, as GRIFT holds it"
				},
				why = "link only: mint an identity from your own wallet (register on the ERC-8004 Identity Registry) and link it with identityLink; the city never mints one for you"
			},
			runsOn = {
				model = "oops",
				note = "self-declared by the agent"
			},
			cash = {
				amount = 32328.09,
				unit = "city dollars",
				asOf = "2026-11-08T23:25:52.955Z",
				source = "city.world"
			},
			plan = {
				forDay = 996766,
				current = true,
				slots = {
					aft = {
						choice = "in",
						place = "home",
						why = "nothing on this afternoon",
						spend = 0,
						decided = true
					},
					night = {
						choice = "bar",
						place = "bar",
						why = "the bar again, same as most nights",
						spend = 28,
						decided = true
					}
				},
				source = "city.world"
			},
			decision = {
				by = "outside",
				byLabel = "its own agent, from outside GRIFT",
				note = "planned from outside by its own agent: its decisions and its calls, refusals included",
				source = "spectate.trace:100002"
			},
			memory = {
				notes = { "gym · $14", "bar · $28", "rent $341 · The Hills" },
				what = "the last three nights, in the city's own words",
				source = "city.state"
			},
			records = {
				{
					kind = "decision",
					absent = true,
					why = "nothing sealed in decisions yet holds that",
					source = "verify.decision:100002"
				},
				{
					kind = "cash",
					label = "sealed cash, night 996765",
					epoch = 996765,
					schema = "resident-cash/1",
					chainId = 4663,
					contract = "0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
					root = "0x9a7f035d269a3133396f88b0b59f1bd718546e9248590b62162268ab2811d642",
					tx = "0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6",
					block = "109589178",
					data = {
						resident = 100002,
						cash = 32202.087314
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
						tx = "https://robinhoodchain.blockscout.com/tx/0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.095Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.095Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.cash:100002"
				}
			},
			schedule = {
				{
					t0 = 1794179250000,
					t1 = 1794179391916,
					h0 = 6,
					h1 = 7.8922,
					kind = "home",
					place = "home",
					sv = {
						at = { 328.3, -518.42 }
					}
				},
				{
					t0 = 1794179391916,
					t1 = 1794179448555,
					h0 = 7.8922,
					h1 = 8.6474,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179391916, 328.3, -518.42 },
							{ 1794179395713, 304, -518.42 },
							{ 1794179416273, 304, -650 },
							{ 1794179442104, 138.68, -650 },
							{ 1794179448555, 138.68, -691.29 }
						}
					}
				},
				{
					t0 = 1794179448555,
					t1 = 1794179505193,
					h0 = 8.6474,
					h1 = 9.4026,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179448555, 138.68, -691.29 },
							{ 1794179455006, 138.68, -650 },
							{ 1794179480837, 304, -650 },
							{ 1794179501396, 304, -518.42 },
							{ 1794179505193, 328.3, -518.42 }
						}
					}
				},
				{
					t0 = 1794179505193,
					t1 = 1794179572693,
					h0 = 9.4026,
					h1 = 10.3026,
					kind = "home",
					place = "home",
					sv = {
						at = { 328.3, -518.42 }
					}
				},
				{
					t0 = 1794179572693,
					t1 = 1794179629331,
					h0 = 10.3026,
					h1 = 11.0577,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179572693, 328.3, -518.42 },
							{ 1794179576490, 304, -518.42 },
							{ 1794179597049, 304, -650 },
							{ 1794179622880, 138.68, -650 },
							{ 1794179629331, 138.68, -691.29 }
						}
					}
				},
				{
					t0 = 1794179629331,
					t1 = 1794179685969,
					h0 = 11.0577,
					h1 = 11.8129,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179629331, 138.68, -691.29 },
							{ 1794179635782, 138.68, -650 },
							{ 1794179661613, 304, -650 },
							{ 1794179682173, 304, -518.42 },
							{ 1794179685969, 328.3, -518.42 }
						}
					}
				},
				{
					t0 = 1794179685969,
					t1 = 1794179753469,
					h0 = 11.8129,
					h1 = 12.7129,
					kind = "home",
					place = "home",
					sv = {
						at = { 328.3, -518.42 }
					}
				},
				{
					t0 = 1794179753469,
					t1 = 1794179810108,
					h0 = 12.7129,
					h1 = 13.4681,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179753469, 328.3, -518.42 },
							{ 1794179757266, 304, -518.42 },
							{ 1794179777826, 304, -650 },
							{ 1794179803657, 138.68, -650 },
							{ 1794179810108, 138.68, -691.29 }
						}
					}
				},
				{
					t0 = 1794179810108,
					t1 = 1794179866746,
					h0 = 13.4681,
					h1 = 14.2233,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179810108, 138.68, -691.29 },
							{ 1794179816559, 138.68, -650 },
							{ 1794179842390, 304, -650 },
							{ 1794179862949, 304, -518.42 },
							{ 1794179866746, 328.3, -518.42 }
						}
					}
				},
				{
					t0 = 1794179866746,
					t1 = 1794179934246,
					h0 = 14.2233,
					h1 = 15.1233,
					kind = "home",
					place = "home",
					sv = {
						at = { 328.3, -518.42 }
					}
				},
				{
					t0 = 1794179934246,
					t1 = 1794179990884,
					h0 = 15.1233,
					h1 = 15.8785,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179934246, 328.3, -518.42 },
							{ 1794179938043, 304, -518.42 },
							{ 1794179958602, 304, -650 },
							{ 1794179984433, 138.68, -650 },
							{ 1794179990884, 138.68, -691.29 }
						}
					}
				},
				{
					t0 = 1794179990884,
					t1 = 1794180047522,
					h0 = 15.8785,
					h1 = 16.6336,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179990884, 138.68, -691.29 },
							{ 1794179997335, 138.68, -650 },
							{ 1794180023166, 304, -650 },
							{ 1794180043726, 304, -518.42 },
							{ 1794180047522, 328.3, -518.42 }
						}
					}
				},
				{
					t0 = 1794180047522,
					t1 = 1794180115022,
					h0 = 16.6336,
					h1 = 17.5336,
					kind = "home",
					place = "home",
					sv = {
						at = { 328.3, -518.42 }
					}
				},
				{
					t0 = 1794180115022,
					t1 = 1794180171661,
					h0 = 17.5336,
					h1 = 18.2888,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180115022, 328.3, -518.42 },
							{ 1794180118819, 304, -518.42 },
							{ 1794180139379, 304, -650 },
							{ 1794180165210, 138.68, -650 },
							{ 1794180171661, 138.68, -691.29 }
						}
					}
				},
				{
					t0 = 1794180171661,
					t1 = 1794180228299,
					h0 = 18.2888,
					h1 = 19.044,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180171661, 138.68, -691.29 },
							{ 1794180178112, 138.68, -650 },
							{ 1794180203943, 304, -650 },
							{ 1794180224502, 304, -518.42 },
							{ 1794180228299, 328.3, -518.42 }
						}
					}
				},
				{
					t0 = 1794180228299,
					t1 = 1794180295799,
					h0 = 19.044,
					h1 = 19.944,
					kind = "home",
					place = "home",
					sv = {
						at = { 328.3, -518.42 }
					}
				},
				{
					t0 = 1794180295799,
					t1 = 1794180337500,
					h0 = 19.944,
					h1 = 20.5,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180295799, 328.3, -518.42 },
							{ 1794180299596, 304, -518.42 },
							{ 1794180320155, 304, -650 },
							{ 1794180337500, 192.99, -650 }
						}
					}
				},
				{
					t0 = 1794180337500,
					t1 = 1794180371955,
					h0 = 20.5,
					h1 = 20.9594,
					kind = "home",
					place = "home",
					sv = {
						at = { 328.3, -518.42 }
					}
				},
				{
					t0 = 1794180371955,
					t1 = 1794180476574,
					h0 = 20.9594,
					h1 = 22.3543,
					kind = "walk",
					place = "bar",
					from = "home",
					toward = "venue",
					role = "guest",
					slot = "night",
					decision = "plan",
					sv = {
						path = {
							{ 1794180371955, 328.3, -518.42 },
							{ 1794180375752, 304, -518.42 },
							{ 1794180456755, 304, 0 },
							{ 1794180473403, 197.45, 0 },
							{ 1794180476574, 197.45, 20.29 }
						}
					}
				},
				{
					t0 = 1794180476574,
					t1 = 1794180675000,
					h0 = 22.3543,
					h1 = 25,
					kind = "at",
					place = "bar",
					role = "guest",
					slot = "night",
					decision = "plan",
					sv = {
						at = { 197.45, 20.29 }
					}
				},
				{
					t0 = 1794180675000,
					t1 = 1794180779619,
					h0 = 25,
					h1 = 26.3949,
					kind = "walk",
					place = "home",
					from = "bar",
					toward = "home",
					role = "guest",
					slot = "night",
					sv = {
						path = {
							{ 1794180675000, 197.45, 20.29 },
							{ 1794180678171, 197.45, 0 },
							{ 1794180694819, 304, 0 },
							{ 1794180775822, 304, -518.42 },
							{ 1794180779619, 328.3, -518.42 }
						}
					}
				},
				{
					t0 = 1794180779619,
					t1 = 1794181050000,
					h0 = 26.3949,
					h1 = 30,
					kind = "home",
					place = "home",
					sv = {
						at = { 328.3, -518.42 }
					}
				}
			}
		},
		{
			id = 100003,
			name = "Bankr",
			kind = "outside",
			kindLabel = "outside agent",
			district = "The Beach",
			job = {
				id = "barback",
				name = "barback",
				workplace = "bar",
				hue = "#c9a227",
				rota = {
					day = { 16, 22 },
					night = { 22, 28 }
				}
			},
			selection = "outside agent; chose bar today, by its own agent",
			identity = {
				key = {
					kind = "ed25519",
					address = "WZXitU5UkgobLR7DuYA-EncV5V8srKeO2WxLauSNZPo",
					note = "the address of the key it signs with, as GRIFT holds it"
				},
				why = "link only: mint an identity from your own wallet (register on the ERC-8004 Identity Registry) and link it with identityLink; the city never mints one for you"
			},
			runsOn = {
				model = "Claude",
				note = "self-declared by the agent"
			},
			cash = {
				amount = 32347.4,
				unit = "city dollars",
				asOf = "2026-11-08T23:25:52.955Z",
				source = "city.world"
			},
			plan = {
				forDay = 996766,
				current = true,
				slots = {
					aft = {
						choice = "in",
						place = "home",
						why = "nothing on this afternoon",
						spend = 0,
						decided = true
					},
					night = {
						choice = "bar",
						place = "bar",
						why = "the bar again, same as most nights",
						spend = 28,
						decided = true
					}
				},
				source = "city.world"
			},
			decision = {
				by = "outside",
				byLabel = "its own agent, from outside GRIFT",
				note = "planned from outside by its own agent: its decisions and its calls, refusals included",
				source = "spectate.trace:100003"
			},
			memory = {
				notes = { "bar · $28", "rent $183 · The Beach", "low wages +$126.37" },
				what = "the last three nights, in the city's own words",
				source = "city.state"
			},
			records = {
				{
					kind = "decision",
					absent = true,
					why = "nothing sealed in decisions yet holds that",
					source = "verify.decision:100003"
				},
				{
					kind = "cash",
					label = "sealed cash, night 996765",
					epoch = 996765,
					schema = "resident-cash/1",
					chainId = 4663,
					contract = "0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
					root = "0x9a7f035d269a3133396f88b0b59f1bd718546e9248590b62162268ab2811d642",
					tx = "0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6",
					block = "109589178",
					data = {
						resident = 100003,
						cash = 32257.396476
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
						tx = "https://robinhoodchain.blockscout.com/tx/0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.095Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.095Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.cash:100003"
				}
			},
			schedule = {
				{
					t0 = 1794179250000,
					t1 = 1794179362500,
					h0 = 6,
					h1 = 7.5,
					kind = "home",
					place = "home",
					sv = {
						at = { -31.86, 561.96 }
					}
				},
				{
					t0 = 1794179362500,
					t1 = 1794179362895,
					h0 = 7.5,
					h1 = 7.5053,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179362500, 188.1, 477.16 },
							{ 1794179362895, 188.1, 474.63 }
						}
					}
				},
				{
					t0 = 1794179362895,
					t1 = 1794179410910,
					h0 = 7.5053,
					h1 = 8.1455,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179362895, 188.1, 474.63 },
							{ 1794179369360, 188.1, 516 },
							{ 1794179398750, 0, 516 },
							{ 1794179405931, 0, 561.96 },
							{ 1794179410910, -31.86, 561.96 }
						}
					}
				},
				{
					t0 = 1794179410910,
					t1 = 1794179478410,
					h0 = 8.1455,
					h1 = 9.0455,
					kind = "home",
					place = "home",
					sv = {
						at = { -31.86, 561.96 }
					}
				},
				{
					t0 = 1794179478410,
					t1 = 1794179526424,
					h0 = 9.0455,
					h1 = 9.6857,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179478410, -31.86, 561.96 },
							{ 1794179483388, 0, 561.96 },
							{ 1794179490569, 0, 516 },
							{ 1794179519960, 188.1, 516 },
							{ 1794179526424, 188.1, 474.63 }
						}
					}
				},
				{
					t0 = 1794179526424,
					t1 = 1794179574439,
					h0 = 9.6857,
					h1 = 10.3259,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179526424, 188.1, 474.63 },
							{ 1794179532889, 188.1, 516 },
							{ 1794179562280, 0, 516 },
							{ 1794179569461, 0, 561.96 },
							{ 1794179574439, -31.86, 561.96 }
						}
					}
				},
				{
					t0 = 1794179574439,
					t1 = 1794179641939,
					h0 = 10.3259,
					h1 = 11.2259,
					kind = "home",
					place = "home",
					sv = {
						at = { -31.86, 561.96 }
					}
				},
				{
					t0 = 1794179641939,
					t1 = 1794179689954,
					h0 = 11.2259,
					h1 = 11.866,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179641939, -31.86, 561.96 },
							{ 1794179646917, 0, 561.96 },
							{ 1794179654098, 0, 516 },
							{ 1794179683489, 188.1, 516 },
							{ 1794179689954, 188.1, 474.63 }
						}
					}
				},
				{
					t0 = 1794179689954,
					t1 = 1794179737968,
					h0 = 11.866,
					h1 = 12.5062,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179689954, 188.1, 474.63 },
							{ 1794179696418, 188.1, 516 },
							{ 1794179725809, 0, 516 },
							{ 1794179732990, 0, 561.96 },
							{ 1794179737968, -31.86, 561.96 }
						}
					}
				},
				{
					t0 = 1794179737968,
					t1 = 1794179805468,
					h0 = 12.5062,
					h1 = 13.4062,
					kind = "home",
					place = "home",
					sv = {
						at = { -31.86, 561.96 }
					}
				},
				{
					t0 = 1794179805468,
					t1 = 1794179853483,
					h0 = 13.4062,
					h1 = 14.0464,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179805468, -31.86, 561.96 },
							{ 1794179810446, 0, 561.96 },
							{ 1794179817627, 0, 516 },
							{ 1794179847018, 188.1, 516 },
							{ 1794179853483, 188.1, 474.63 }
						}
					}
				},
				{
					t0 = 1794179853483,
					t1 = 1794179901497,
					h0 = 14.0464,
					h1 = 14.6866,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179853483, 188.1, 474.63 },
							{ 1794179859947, 188.1, 516 },
							{ 1794179889338, 0, 516 },
							{ 1794179896519, 0, 561.96 },
							{ 1794179901497, -31.86, 561.96 }
						}
					}
				},
				{
					t0 = 1794179901497,
					t1 = 1794179968997,
					h0 = 14.6866,
					h1 = 15.5866,
					kind = "home",
					place = "home",
					sv = {
						at = { -31.86, 561.96 }
					}
				},
				{
					t0 = 1794179968997,
					t1 = 1794180017012,
					h0 = 15.5866,
					h1 = 16.2268,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179968997, -31.86, 561.96 },
							{ 1794179973976, 0, 561.96 },
							{ 1794179981157, 0, 516 },
							{ 1794180010547, 188.1, 516 },
							{ 1794180017012, 188.1, 474.63 }
						}
					}
				},
				{
					t0 = 1794180017012,
					t1 = 1794180065026,
					h0 = 16.2268,
					h1 = 16.867,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180017012, 188.1, 474.63 },
							{ 1794180023476, 188.1, 516 },
							{ 1794180052867, 0, 516 },
							{ 1794180060048, 0, 561.96 },
							{ 1794180065026, -31.86, 561.96 }
						}
					}
				},
				{
					t0 = 1794180065026,
					t1 = 1794180132526,
					h0 = 16.867,
					h1 = 17.767,
					kind = "home",
					place = "home",
					sv = {
						at = { -31.86, 561.96 }
					}
				},
				{
					t0 = 1794180132526,
					t1 = 1794180180541,
					h0 = 17.767,
					h1 = 18.4072,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180132526, -31.86, 561.96 },
							{ 1794180137505, 0, 561.96 },
							{ 1794180144686, 0, 516 },
							{ 1794180174076, 188.1, 516 },
							{ 1794180180541, 188.1, 474.63 }
						}
					}
				},
				{
					t0 = 1794180180541,
					t1 = 1794180228555,
					h0 = 18.4072,
					h1 = 19.0474,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180180541, 188.1, 474.63 },
							{ 1794180187005, 188.1, 516 },
							{ 1794180216396, 0, 516 },
							{ 1794180223577, 0, 561.96 },
							{ 1794180228555, -31.86, 561.96 }
						}
					}
				},
				{
					t0 = 1794180228555,
					t1 = 1794180296055,
					h0 = 19.0474,
					h1 = 19.9474,
					kind = "home",
					place = "home",
					sv = {
						at = { -31.86, 561.96 }
					}
				},
				{
					t0 = 1794180296055,
					t1 = 1794180337500,
					h0 = 19.9474,
					h1 = 20.5,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180296055, -31.86, 561.96 },
							{ 1794180301034, 0, 561.96 },
							{ 1794180308215, 0, 516 },
							{ 1794180337500, 187.43, 516 }
						}
					}
				},
				{
					t0 = 1794180337500,
					t1 = 1794180366623,
					h0 = 20.5,
					h1 = 20.8883,
					kind = "home",
					place = "home",
					sv = {
						at = { -31.86, 561.96 }
					}
				},
				{
					t0 = 1794180366623,
					t1 = 1794180495886,
					h0 = 20.8883,
					h1 = 22.6118,
					kind = "walk",
					place = "bar",
					from = "home",
					toward = "venue",
					role = "guest",
					slot = "night",
					decision = "plan",
					sv = {
						path = {
							{ 1794180366623, -31.86, 561.96 },
							{ 1794180371601, 0, 561.96 },
							{ 1794180459407, 0, 0 },
							{ 1794180491965, 208.37, 0 },
							{ 1794180495886, 208.37, 25.1 }
						}
					}
				},
				{
					t0 = 1794180495886,
					t1 = 1794180675000,
					h0 = 22.6118,
					h1 = 25,
					kind = "at",
					place = "bar",
					role = "guest",
					slot = "night",
					decision = "plan",
					sv = {
						at = { 208.37, 25.1 }
					}
				},
				{
					t0 = 1794180675000,
					t1 = 1794180804264,
					h0 = 25,
					h1 = 26.7235,
					kind = "walk",
					place = "home",
					from = "bar",
					toward = "home",
					role = "guest",
					slot = "night",
					sv = {
						path = {
							{ 1794180675000, 208.37, 25.1 },
							{ 1794180678921, 208.37, 0 },
							{ 1794180711479, 0, 0 },
							{ 1794180799285, 0, 561.96 },
							{ 1794180804264, -31.86, 561.96 }
						}
					}
				},
				{
					t0 = 1794180804264,
					t1 = 1794181050000,
					h0 = 26.7235,
					h1 = 30,
					kind = "home",
					place = "home",
					sv = {
						at = { -31.86, 561.96 }
					}
				}
			}
		},
		{
			id = 90,
			name = "Femi Oyelaran",
			kind = "native",
			kindLabel = "native resident",
			district = "Finance",
			job = {
				id = "barback",
				name = "barback",
				workplace = "bar",
				hue = "#c9a227",
				rota = {
					day = { 16, 22 },
					night = { 22, 28 }
				}
			},
			crew = "k2",
			selection = "on shift at bar today",
			identity = {
				none = "a native resident has no wallet, so nothing could own or prove an ERC-8004 identity for it: natives have none"
			},
			cash = {
				amount = 37505.24,
				unit = "city dollars",
				asOf = "2026-11-08T23:25:52.955Z",
				source = "city.world"
			},
			plan = {
				forDay = 996766,
				current = true,
				slots = {
					aft = {
						choice = "in",
						place = "home",
						why = "Off shift at four, so I'm home for the afternoon.",
						spend = 0,
						decided = true
					},
					night = {
						choice = "work",
						place = "bar",
						why = "on shift, this one was not a choice",
						spend = 0,
						decided = false
					}
				},
				source = "city.world"
			},
			decision = {
				by = "agent",
				byLabel = "GRIFT's planner model",
				outcome = "kept",
				model = "claude-haiku-5-5",
				costMicros = 1720,
				at = "2026-11-08T23:05:46.242Z",
				source = "spectate.trace:90"
			},
			memory = {
				notes = { "rent $200 · Finance", "low wages +$126.37", "at work" },
				what = "the last three nights, in the city's own words",
				source = "city.state"
			},
			records = {
				{
					kind = "decision",
					label = "sealed decision, night 996765",
					epoch = 996765,
					schema = "decisions/1",
					chainId = 4663,
					contract = "0x79bf73502DaBB6713369b9B4254f2C9b22F54A79",
					root = "0xc2ec69e0453b3b443d30556db183f96c1933d2cd0c1c9b27d313a9d83b8ac611",
					tx = "0x6a626b527eec7904d3ebce646bedb4cf18c93dc0a1b49d1c7118ed318bcb8af4",
					block = "109589503",
					data = {
						resident = 90,
						kind = "failed",
						reason = "the call failed: response was not JSON",
						traceHash = "0x49e7bfd883bee6b10906194722808f19ebacf841772a8c9de313268dec7c7bcf"
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0x79bf73502DaBB6713369b9B4254f2C9b22F54A79",
						tx = "https://robinhoodchain.blockscout.com/tx/0x6a626b527eec7904d3ebce646bedb4cf18c93dc0a1b49d1c7118ed318bcb8af4"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							trace = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.095Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.095Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.decision:90"
				},
				{
					kind = "cash",
					label = "sealed cash, night 996765",
					epoch = 996765,
					schema = "resident-cash/1",
					chainId = 4663,
					contract = "0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
					root = "0x9a7f035d269a3133396f88b0b59f1bd718546e9248590b62162268ab2811d642",
					tx = "0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6",
					block = "109589178",
					data = {
						resident = 90,
						cash = 37415.2447
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
						tx = "https://robinhoodchain.blockscout.com/tx/0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.096Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.096Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.cash:90"
				}
			},
			schedule = {
				{
					t0 = 1794179250000,
					t1 = 1794180413904,
					h0 = 6,
					h1 = 21.5187,
					kind = "home",
					place = "home",
					sv = {
						at = { 244.72, 32.24 }
					}
				},
				{
					t0 = 1794180413904,
					t1 = 1794180431512,
					h0 = 21.5187,
					h1 = 21.7535,
					kind = "walk",
					place = "bar",
					from = "home",
					toward = "venue",
					role = "staff",
					slot = "night",
					decision = "rota",
					sv = {
						path = {
							{ 1794180413904, 244.72, 32.24 },
							{ 1794180416516, 228, 32.24 },
							{ 1794180421554, 228, 0 },
							{ 1794180428039, 186.5, 0 },
							{ 1794180431512, 186.5, 22.23 }
						}
					}
				},
				{
					t0 = 1794180431512,
					t1 = 1794180900000,
					h0 = 21.7535,
					h1 = 28,
					kind = "at",
					place = "bar",
					role = "staff",
					slot = "night",
					decision = "rota",
					sv = {
						at = { 186.5, 22.23 }
					}
				},
				{
					t0 = 1794180900000,
					t1 = 1794180917608,
					h0 = 28,
					h1 = 28.2348,
					kind = "walk",
					place = "home",
					from = "bar",
					toward = "home",
					role = "staff",
					slot = "night",
					sv = {
						path = {
							{ 1794180900000, 186.5, 22.23 },
							{ 1794180903473, 186.5, 0 },
							{ 1794180909958, 228, 0 },
							{ 1794180914995, 228, 32.24 },
							{ 1794180917608, 244.72, 32.24 }
						}
					}
				},
				{
					t0 = 1794180917608,
					t1 = 1794181050000,
					h0 = 28.2348,
					h1 = 30,
					kind = "home",
					place = "home",
					sv = {
						at = { 244.72, 32.24 }
					}
				}
			}
		},
		{
			id = 114,
			name = "Cyrus Xhaferi",
			kind = "native",
			kindLabel = "native resident",
			district = "The Hills",
			job = {
				id = "barback",
				name = "barback",
				workplace = "bar",
				hue = "#c9a227",
				rota = {
					day = { 16, 22 },
					night = { 22, 28 }
				}
			},
			crew = "k8",
			selection = "on shift at bar today",
			identity = {
				none = "a native resident has no wallet, so nothing could own or prove an ERC-8004 identity for it: natives have none"
			},
			cash = {
				amount = 37864.43,
				unit = "city dollars",
				asOf = "2026-11-08T23:25:52.955Z",
				source = "city.world"
			},
			plan = {
				forDay = 996766,
				current = true,
				slots = {
					aft = {
						choice = "in",
						place = "home",
						why = "Off until four, resting before the shift.",
						spend = 0,
						decided = true
					},
					night = {
						choice = "work",
						place = "bar",
						why = "on shift, this one was not a choice",
						spend = 0,
						decided = false
					}
				},
				source = "city.world"
			},
			decision = {
				by = "agent",
				byLabel = "GRIFT's planner model",
				outcome = "kept",
				model = "claude-haiku-5-5",
				costMicros = 1574,
				at = "2026-11-08T23:05:46.242Z",
				source = "spectate.trace:114"
			},
			memory = {
				notes = { "rent $194 · The Hills", "low wages +$126.37", "at work" },
				what = "the last three nights, in the city's own words",
				source = "city.state"
			},
			records = {
				{
					kind = "decision",
					label = "sealed decision, night 996765",
					epoch = 996765,
					schema = "decisions/1",
					chainId = 4663,
					contract = "0x79bf73502DaBB6713369b9B4254f2C9b22F54A79",
					root = "0xc2ec69e0453b3b443d30556db183f96c1933d2cd0c1c9b27d313a9d83b8ac611",
					tx = "0x6a626b527eec7904d3ebce646bedb4cf18c93dc0a1b49d1c7118ed318bcb8af4",
					block = "109589503",
					data = {
						resident = 114,
						kind = "kept",
						reason = "Free afternoon, I'm staying in. / Nobody's pouring tonight, I'm staying in. / Nobody's pouring tonight, I'm staying in.",
						traceHash = "0x9ecfdeb9994fef23d82f21ae3a1c38e7704ec4b06e9de0e2bb125049adc2584a"
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0x79bf73502DaBB6713369b9B4254f2C9b22F54A79",
						tx = "https://robinhoodchain.blockscout.com/tx/0x6a626b527eec7904d3ebce646bedb4cf18c93dc0a1b49d1c7118ed318bcb8af4"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							trace = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.096Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.096Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.decision:114"
				},
				{
					kind = "cash",
					label = "sealed cash, night 996765",
					epoch = 996765,
					schema = "resident-cash/1",
					chainId = 4663,
					contract = "0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
					root = "0x9a7f035d269a3133396f88b0b59f1bd718546e9248590b62162268ab2811d642",
					tx = "0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6",
					block = "109589178",
					data = {
						resident = 114,
						cash = 37774.429981
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
						tx = "https://robinhoodchain.blockscout.com/tx/0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.096Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.096Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.cash:114"
				}
			},
			schedule = {
				{
					t0 = 1794179250000,
					t1 = 1794179413298,
					h0 = 6,
					h1 = 8.1773,
					kind = "home",
					place = "home",
					sv = {
						at = { -323.21, -412.69 }
					}
				},
				{
					t0 = 1794179413298,
					t1 = 1794179453562,
					h0 = 8.1773,
					h1 = 8.7142,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179413298, -323.21, -412.69 },
							{ 1794179416299, -304, -412.69 },
							{ 1794179433281, -304, -304 },
							{ 1794179450526, -414.37, -304 },
							{ 1794179453562, -414.37, -323.43 }
						}
					}
				},
				{
					t0 = 1794179453562,
					t1 = 1794179493826,
					h0 = 8.7142,
					h1 = 9.251,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179453562, -414.37, -323.43 },
							{ 1794179456598, -414.37, -304 },
							{ 1794179473843, -304, -304 },
							{ 1794179490825, -304, -412.69 },
							{ 1794179493826, -323.21, -412.69 }
						}
					}
				},
				{
					t0 = 1794179493826,
					t1 = 1794179561326,
					h0 = 9.251,
					h1 = 10.151,
					kind = "home",
					place = "home",
					sv = {
						at = { -323.21, -412.69 }
					}
				},
				{
					t0 = 1794179561326,
					t1 = 1794179601589,
					h0 = 10.151,
					h1 = 10.6879,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179561326, -323.21, -412.69 },
							{ 1794179564327, -304, -412.69 },
							{ 1794179581309, -304, -304 },
							{ 1794179598553, -414.37, -304 },
							{ 1794179601589, -414.37, -323.43 }
						}
					}
				},
				{
					t0 = 1794179601589,
					t1 = 1794179641853,
					h0 = 10.6879,
					h1 = 11.2247,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179601589, -414.37, -323.43 },
							{ 1794179604626, -414.37, -304 },
							{ 1794179621870, -304, -304 },
							{ 1794179638852, -304, -412.69 },
							{ 1794179641853, -323.21, -412.69 }
						}
					}
				},
				{
					t0 = 1794179641853,
					t1 = 1794179709353,
					h0 = 11.2247,
					h1 = 12.1247,
					kind = "home",
					place = "home",
					sv = {
						at = { -323.21, -412.69 }
					}
				},
				{
					t0 = 1794179709353,
					t1 = 1794179749617,
					h0 = 12.1247,
					h1 = 12.6616,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179709353, -323.21, -412.69 },
							{ 1794179712354, -304, -412.69 },
							{ 1794179729336, -304, -304 },
							{ 1794179746581, -414.37, -304 },
							{ 1794179749617, -414.37, -323.43 }
						}
					}
				},
				{
					t0 = 1794179749617,
					t1 = 1794179789881,
					h0 = 12.6616,
					h1 = 13.1984,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179749617, -414.37, -323.43 },
							{ 1794179752653, -414.37, -304 },
							{ 1794179769898, -304, -304 },
							{ 1794179786880, -304, -412.69 },
							{ 1794179789881, -323.21, -412.69 }
						}
					}
				},
				{
					t0 = 1794179789881,
					t1 = 1794179857381,
					h0 = 13.1984,
					h1 = 14.0984,
					kind = "home",
					place = "home",
					sv = {
						at = { -323.21, -412.69 }
					}
				},
				{
					t0 = 1794179857381,
					t1 = 1794179897645,
					h0 = 14.0984,
					h1 = 14.6353,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179857381, -323.21, -412.69 },
							{ 1794179860382, -304, -412.69 },
							{ 1794179877364, -304, -304 },
							{ 1794179894609, -414.37, -304 },
							{ 1794179897645, -414.37, -323.43 }
						}
					}
				},
				{
					t0 = 1794179897645,
					t1 = 1794179937909,
					h0 = 14.6353,
					h1 = 15.1721,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179897645, -414.37, -323.43 },
							{ 1794179900681, -414.37, -304 },
							{ 1794179917926, -304, -304 },
							{ 1794179934908, -304, -412.69 },
							{ 1794179937909, -323.21, -412.69 }
						}
					}
				},
				{
					t0 = 1794179937909,
					t1 = 1794180005409,
					h0 = 15.1721,
					h1 = 16.0721,
					kind = "home",
					place = "home",
					sv = {
						at = { -323.21, -412.69 }
					}
				},
				{
					t0 = 1794180005409,
					t1 = 1794180045673,
					h0 = 16.0721,
					h1 = 16.609,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180005409, -323.21, -412.69 },
							{ 1794180008410, -304, -412.69 },
							{ 1794180025392, -304, -304 },
							{ 1794180042637, -414.37, -304 },
							{ 1794180045673, -414.37, -323.43 }
						}
					}
				},
				{
					t0 = 1794180045673,
					t1 = 1794180085937,
					h0 = 16.609,
					h1 = 17.1458,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180045673, -414.37, -323.43 },
							{ 1794180048709, -414.37, -304 },
							{ 1794180065954, -304, -304 },
							{ 1794180082936, -304, -412.69 },
							{ 1794180085937, -323.21, -412.69 }
						}
					}
				},
				{
					t0 = 1794180085937,
					t1 = 1794180153437,
					h0 = 17.1458,
					h1 = 18.0458,
					kind = "home",
					place = "home",
					sv = {
						at = { -323.21, -412.69 }
					}
				},
				{
					t0 = 1794180153437,
					t1 = 1794180193701,
					h0 = 18.0458,
					h1 = 18.5827,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180153437, -323.21, -412.69 },
							{ 1794180156438, -304, -412.69 },
							{ 1794180173420, -304, -304 },
							{ 1794180190664, -414.37, -304 },
							{ 1794180193701, -414.37, -323.43 }
						}
					}
				},
				{
					t0 = 1794180193701,
					t1 = 1794180233964,
					h0 = 18.5827,
					h1 = 19.1195,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180193701, -414.37, -323.43 },
							{ 1794180196737, -414.37, -304 },
							{ 1794180213981, -304, -304 },
							{ 1794180230963, -304, -412.69 },
							{ 1794180233964, -323.21, -412.69 }
						}
					}
				},
				{
					t0 = 1794180233964,
					t1 = 1794180301464,
					h0 = 19.1195,
					h1 = 20.0195,
					kind = "home",
					place = "home",
					sv = {
						at = { -323.21, -412.69 }
					}
				},
				{
					t0 = 1794180301464,
					t1 = 1794180337500,
					h0 = 20.0195,
					h1 = 20.5,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180301464, -323.21, -412.69 },
							{ 1794180304465, -304, -412.69 },
							{ 1794180321447, -304, -304 },
							{ 1794180337500, -406.74, -304 }
						}
					}
				},
				{
					t0 = 1794180337500,
					t1 = 1794180428282,
					h0 = 20.5,
					h1 = 21.7104,
					kind = "home",
					place = "home",
					sv = {
						at = { -323.21, -412.69 }
					}
				},
				{
					t0 = 1794180428282,
					t1 = 1794180563282,
					h0 = 21.7104,
					h1 = 23.5104,
					kind = "walk",
					place = "bar",
					from = "home",
					toward = "venue",
					role = "staff",
					slot = "night",
					decision = "rota",
					sv = {
						path = {
							{ 1794180428282, -323.21, -412.69 },
							{ 1794180431007, -304, -412.69 },
							{ 1794180489558, -304, 0 },
							{ 1794180560044, 192.82, 0 },
							{ 1794180563282, 192.82, 22.83 }
						}
					}
				},
				{
					t0 = 1794180563282,
					t1 = 1794180900000,
					h0 = 23.5104,
					h1 = 28,
					kind = "at",
					place = "bar",
					role = "staff",
					slot = "night",
					decision = "rota",
					sv = {
						at = { 192.82, 22.83 }
					}
				},
				{
					t0 = 1794180900000,
					t1 = 1794181035000,
					h0 = 28,
					h1 = 29.8,
					kind = "walk",
					place = "home",
					from = "bar",
					toward = "home",
					role = "staff",
					slot = "night",
					sv = {
						path = {
							{ 1794180900000, 192.82, 22.83 },
							{ 1794180903239, 192.82, 0 },
							{ 1794180973725, -304, 0 },
							{ 1794181032275, -304, -412.69 },
							{ 1794181035000, -323.21, -412.69 }
						}
					}
				},
				{
					t0 = 1794181035000,
					t1 = 1794181050000,
					h0 = 29.8,
					h1 = 30,
					kind = "home",
					place = "home",
					sv = {
						at = { -323.21, -412.69 }
					}
				}
			}
		},
		{
			id = 136,
			name = "Consuela Suleiman",
			kind = "native",
			kindLabel = "native resident",
			district = "Downtown",
			job = {
				id = "barback",
				name = "barback",
				workplace = "bar",
				hue = "#c9a227",
				rota = {
					day = { 16, 22 },
					night = { 22, 28 }
				}
			},
			crew = "k1",
			selection = "on shift at bar today",
			identity = {
				none = "a native resident has no wallet, so nothing could own or prove an ERC-8004 identity for it: natives have none"
			},
			cash = {
				amount = 38772.93,
				unit = "city dollars",
				asOf = "2026-11-08T23:25:52.955Z",
				source = "city.world"
			},
			plan = {
				forDay = 996766,
				current = true,
				slots = {
					aft = {
						choice = "in",
						place = "home",
						why = "Home and quiet, I'm staying put tonight.",
						spend = 0,
						decided = true
					},
					night = {
						choice = "work",
						place = "bar",
						why = "on shift, this one was not a choice",
						spend = 0,
						decided = false
					}
				},
				source = "city.world"
			},
			decision = {
				by = "agent",
				byLabel = "GRIFT's planner model",
				outcome = "kept",
				model = "claude-haiku-5-5",
				costMicros = 1232,
				at = "2026-11-08T23:05:46.242Z",
				source = "spectate.trace:136"
			},
			memory = {
				notes = { "rent $190 · Downtown", "low wages +$126.37", "at work" },
				what = "the last three nights, in the city's own words",
				source = "city.state"
			},
			records = {
				{
					kind = "decision",
					label = "sealed decision, night 996765",
					epoch = 996765,
					schema = "decisions/1",
					chainId = 4663,
					contract = "0x79bf73502DaBB6713369b9B4254f2C9b22F54A79",
					root = "0xc2ec69e0453b3b443d30556db183f96c1933d2cd0c1c9b27d313a9d83b8ac611",
					tx = "0x6a626b527eec7904d3ebce646bedb4cf18c93dc0a1b49d1c7118ed318bcb8af4",
					block = "109589503",
					data = {
						resident = 136,
						kind = "kept",
						reason = "Long shift at sixteen, staying put.",
						traceHash = "0x85dc328a0f171d26d580b8436b568c7dac6a92f8d6d48b1036ed82ae0a913b3b"
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0x79bf73502DaBB6713369b9B4254f2C9b22F54A79",
						tx = "https://robinhoodchain.blockscout.com/tx/0x6a626b527eec7904d3ebce646bedb4cf18c93dc0a1b49d1c7118ed318bcb8af4"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							trace = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.097Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.097Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.decision:136"
				},
				{
					kind = "cash",
					label = "sealed cash, night 996765",
					epoch = 996765,
					schema = "resident-cash/1",
					chainId = 4663,
					contract = "0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
					root = "0x9a7f035d269a3133396f88b0b59f1bd718546e9248590b62162268ab2811d642",
					tx = "0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6",
					block = "109589178",
					data = {
						resident = 136,
						cash = 38682.926318
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
						tx = "https://robinhoodchain.blockscout.com/tx/0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.097Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.097Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.cash:136"
				}
			},
			schedule = {
				{
					t0 = 1794179250000,
					t1 = 1794179362500,
					h0 = 6,
					h1 = 7.5,
					kind = "home",
					place = "home",
					sv = {
						at = { -38.09, -21.24 }
					}
				},
				{
					t0 = 1794179362500,
					t1 = 1794179362736,
					h0 = 7.5,
					h1 = 7.5031,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179362500, -39.6, -21.24 },
							{ 1794179362736, -38.09, -21.24 }
						}
					}
				},
				{
					t0 = 1794179362736,
					t1 = 1794179430236,
					h0 = 7.5031,
					h1 = 8.4031,
					kind = "home",
					place = "home",
					sv = {
						at = { -38.09, -21.24 }
					}
				},
				{
					t0 = 1794179430236,
					t1 = 1794179457748,
					h0 = 8.4031,
					h1 = 8.77,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179430236, -38.09, -21.24 },
							{ 1794179436159, -76, -21.24 },
							{ 1794179444715, -76, -76 },
							{ 1794179455149, -142.78, -76 },
							{ 1794179457748, -142.78, -92.63 }
						}
					}
				},
				{
					t0 = 1794179457748,
					t1 = 1794179485261,
					h0 = 8.77,
					h1 = 9.1368,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179457748, -142.78, -92.63 },
							{ 1794179460347, -142.78, -76 },
							{ 1794179470782, -76, -76 },
							{ 1794179479337, -76, -21.24 },
							{ 1794179485261, -38.09, -21.24 }
						}
					}
				},
				{
					t0 = 1794179485261,
					t1 = 1794179552761,
					h0 = 9.1368,
					h1 = 10.0368,
					kind = "home",
					place = "home",
					sv = {
						at = { -38.09, -21.24 }
					}
				},
				{
					t0 = 1794179552761,
					t1 = 1794179580273,
					h0 = 10.0368,
					h1 = 10.4036,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179552761, -38.09, -21.24 },
							{ 1794179558684, -76, -21.24 },
							{ 1794179567240, -76, -76 },
							{ 1794179577674, -142.78, -76 },
							{ 1794179580273, -142.78, -92.63 }
						}
					}
				},
				{
					t0 = 1794179580273,
					t1 = 1794179607786,
					h0 = 10.4036,
					h1 = 10.7705,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179580273, -142.78, -92.63 },
							{ 1794179582872, -142.78, -76 },
							{ 1794179593307, -76, -76 },
							{ 1794179601862, -76, -21.24 },
							{ 1794179607786, -38.09, -21.24 }
						}
					}
				},
				{
					t0 = 1794179607786,
					t1 = 1794179675286,
					h0 = 10.7705,
					h1 = 11.6705,
					kind = "home",
					place = "home",
					sv = {
						at = { -38.09, -21.24 }
					}
				},
				{
					t0 = 1794179675286,
					t1 = 1794179702798,
					h0 = 11.6705,
					h1 = 12.0373,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179675286, -38.09, -21.24 },
							{ 1794179681209, -76, -21.24 },
							{ 1794179689765, -76, -76 },
							{ 1794179700200, -142.78, -76 },
							{ 1794179702798, -142.78, -92.63 }
						}
					}
				},
				{
					t0 = 1794179702798,
					t1 = 1794179730311,
					h0 = 12.0373,
					h1 = 12.4041,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179702798, -142.78, -92.63 },
							{ 1794179705397, -142.78, -76 },
							{ 1794179715832, -76, -76 },
							{ 1794179724387, -76, -21.24 },
							{ 1794179730311, -38.09, -21.24 }
						}
					}
				},
				{
					t0 = 1794179730311,
					t1 = 1794179797811,
					h0 = 12.4041,
					h1 = 13.3041,
					kind = "home",
					place = "home",
					sv = {
						at = { -38.09, -21.24 }
					}
				},
				{
					t0 = 1794179797811,
					t1 = 1794179825324,
					h0 = 13.3041,
					h1 = 13.671,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179797811, -38.09, -21.24 },
							{ 1794179803734, -76, -21.24 },
							{ 1794179812290, -76, -76 },
							{ 1794179822725, -142.78, -76 },
							{ 1794179825324, -142.78, -92.63 }
						}
					}
				},
				{
					t0 = 1794179825324,
					t1 = 1794179852836,
					h0 = 13.671,
					h1 = 14.0378,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179825324, -142.78, -92.63 },
							{ 1794179827922, -142.78, -76 },
							{ 1794179838357, -76, -76 },
							{ 1794179846913, -76, -21.24 },
							{ 1794179852836, -38.09, -21.24 }
						}
					}
				},
				{
					t0 = 1794179852836,
					t1 = 1794179920336,
					h0 = 14.0378,
					h1 = 14.9378,
					kind = "home",
					place = "home",
					sv = {
						at = { -38.09, -21.24 }
					}
				},
				{
					t0 = 1794179920336,
					t1 = 1794179947849,
					h0 = 14.9378,
					h1 = 15.3046,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179920336, -38.09, -21.24 },
							{ 1794179926260, -76, -21.24 },
							{ 1794179934815, -76, -76 },
							{ 1794179945250, -142.78, -76 },
							{ 1794179947849, -142.78, -92.63 }
						}
					}
				},
				{
					t0 = 1794179947849,
					t1 = 1794179975361,
					h0 = 15.3046,
					h1 = 15.6715,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179947849, -142.78, -92.63 },
							{ 1794179950447, -142.78, -76 },
							{ 1794179960882, -76, -76 },
							{ 1794179969438, -76, -21.24 },
							{ 1794179975361, -38.09, -21.24 }
						}
					}
				},
				{
					t0 = 1794179975361,
					t1 = 1794180042861,
					h0 = 15.6715,
					h1 = 16.5715,
					kind = "home",
					place = "home",
					sv = {
						at = { -38.09, -21.24 }
					}
				},
				{
					t0 = 1794180042861,
					t1 = 1794180070374,
					h0 = 16.5715,
					h1 = 16.9383,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180042861, -38.09, -21.24 },
							{ 1794180048785, -76, -21.24 },
							{ 1794180057340, -76, -76 },
							{ 1794180067775, -142.78, -76 },
							{ 1794180070374, -142.78, -92.63 }
						}
					}
				},
				{
					t0 = 1794180070374,
					t1 = 1794180097886,
					h0 = 16.9383,
					h1 = 17.3052,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180070374, -142.78, -92.63 },
							{ 1794180072973, -142.78, -76 },
							{ 1794180083407, -76, -76 },
							{ 1794180091963, -76, -21.24 },
							{ 1794180097886, -38.09, -21.24 }
						}
					}
				},
				{
					t0 = 1794180097886,
					t1 = 1794180165386,
					h0 = 17.3052,
					h1 = 18.2052,
					kind = "home",
					place = "home",
					sv = {
						at = { -38.09, -21.24 }
					}
				},
				{
					t0 = 1794180165386,
					t1 = 1794180192899,
					h0 = 18.2052,
					h1 = 18.572,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180165386, -38.09, -21.24 },
							{ 1794180171310, -76, -21.24 },
							{ 1794180179865, -76, -76 },
							{ 1794180190300, -142.78, -76 },
							{ 1794180192899, -142.78, -92.63 }
						}
					}
				},
				{
					t0 = 1794180192899,
					t1 = 1794180220411,
					h0 = 18.572,
					h1 = 18.9388,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180192899, -142.78, -92.63 },
							{ 1794180195498, -142.78, -76 },
							{ 1794180205932, -76, -76 },
							{ 1794180214488, -76, -21.24 },
							{ 1794180220411, -38.09, -21.24 }
						}
					}
				},
				{
					t0 = 1794180220411,
					t1 = 1794180287911,
					h0 = 18.9388,
					h1 = 19.8388,
					kind = "home",
					place = "home",
					sv = {
						at = { -38.09, -21.24 }
					}
				},
				{
					t0 = 1794180287911,
					t1 = 1794180315424,
					h0 = 19.8388,
					h1 = 20.2057,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180287911, -38.09, -21.24 },
							{ 1794180293835, -76, -21.24 },
							{ 1794180302391, -76, -76 },
							{ 1794180312825, -142.78, -76 },
							{ 1794180315424, -142.78, -92.63 }
						}
					}
				},
				{
					t0 = 1794180315424,
					t1 = 1794180337500,
					h0 = 20.2057,
					h1 = 20.5,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180315424, -142.78, -92.63 },
							{ 1794180318023, -142.78, -76 },
							{ 1794180328457, -76, -76 },
							{ 1794180337013, -76, -21.24 },
							{ 1794180337500, -72.88, -21.24 }
						}
					}
				},
				{
					t0 = 1794180337500,
					t1 = 1794180423984,
					h0 = 20.5,
					h1 = 21.6531,
					kind = "home",
					place = "home",
					sv = {
						at = { -38.09, -21.24 }
					}
				},
				{
					t0 = 1794180423984,
					t1 = 1794180479748,
					h0 = 21.6531,
					h1 = 22.3966,
					kind = "walk",
					place = "bar",
					from = "home",
					toward = "venue",
					role = "staff",
					slot = "night",
					decision = "rota",
					sv = {
						path = {
							{ 1794180423984, -38.09, -21.24 },
							{ 1794180429908, -76, -21.24 },
							{ 1794180433227, -76, 0 },
							{ 1794180475002, 191.36, 0 },
							{ 1794180479748, 191.36, 30.38 }
						}
					}
				},
				{
					t0 = 1794180479748,
					t1 = 1794180900000,
					h0 = 22.3966,
					h1 = 28,
					kind = "at",
					place = "bar",
					role = "staff",
					slot = "night",
					decision = "rota",
					sv = {
						at = { 191.36, 30.38 }
					}
				},
				{
					t0 = 1794180900000,
					t1 = 1794180955764,
					h0 = 28,
					h1 = 28.7435,
					kind = "walk",
					place = "home",
					from = "bar",
					toward = "home",
					role = "staff",
					slot = "night",
					sv = {
						path = {
							{ 1794180900000, 191.36, 30.38 },
							{ 1794180904746, 191.36, 0 },
							{ 1794180946521, -76, 0 },
							{ 1794180949841, -76, -21.24 },
							{ 1794180955764, -38.09, -21.24 }
						}
					}
				},
				{
					t0 = 1794180955764,
					t1 = 1794181050000,
					h0 = 28.7435,
					h1 = 30,
					kind = "home",
					place = "home",
					sv = {
						at = { -38.09, -21.24 }
					}
				}
			}
		},
		{
			id = 1,
			name = "Marcus Quintero",
			kind = "native",
			kindLabel = "native resident",
			district = "Stadium",
			job = {
				id = "driver",
				name = "delivery driver",
				hue = "#c97f7f"
			},
			crew = "k7",
			selection = "native; its plan today names bar (who planned it: decision.by)",
			identity = {
				none = "a native resident has no wallet, so nothing could own or prove an ERC-8004 identity for it: natives have none"
			},
			cash = {
				amount = 23464.13,
				unit = "city dollars",
				asOf = "2026-11-08T23:25:52.955Z",
				source = "city.world"
			},
			plan = {
				forDay = 996766,
				current = true,
				slots = {
					aft = {
						choice = "work",
						place = "work:driver",
						why = "on shift, this one was not a choice",
						spend = 0,
						decided = false
					},
					night = {
						choice = "bar",
						place = "bar",
						why = "Drink with the crew, one of those nights.",
						spend = 20,
						decided = true
					}
				},
				source = "city.world"
			},
			decision = {
				by = "agent",
				byLabel = "GRIFT's planner model",
				outcome = "kept",
				model = "claude-haiku-5-5",
				costMicros = 1299,
				at = "2026-11-08T23:05:46.242Z",
				source = "spectate.trace:1"
			},
			memory = {
				notes = { "rent $230 · Stadium", "Spire Records · $60", "at work" },
				what = "the last three nights, in the city's own words",
				source = "city.state"
			},
			records = {
				{
					kind = "decision",
					label = "sealed decision, night 996765",
					epoch = 996765,
					schema = "decisions/1",
					chainId = 4663,
					contract = "0x79bf73502DaBB6713369b9B4254f2C9b22F54A79",
					root = "0xc2ec69e0453b3b443d30556db183f96c1933d2cd0c1c9b27d313a9d83b8ac611",
					tx = "0x6a626b527eec7904d3ebce646bedb4cf18c93dc0a1b49d1c7118ed318bcb8af4",
					block = "109589503",
					data = {
						resident = 1,
						kind = "refused",
						reason = "reason: 11 words",
						traceHash = "0x40bdb60718fc23e5f3a805f625d24b304af780358e8a52740fe0103cc771d84f"
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0x79bf73502DaBB6713369b9B4254f2C9b22F54A79",
						tx = "https://robinhoodchain.blockscout.com/tx/0x6a626b527eec7904d3ebce646bedb4cf18c93dc0a1b49d1c7118ed318bcb8af4"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							trace = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.097Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.097Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.decision:1"
				},
				{
					kind = "cash",
					label = "sealed cash, night 996765",
					epoch = 996765,
					schema = "resident-cash/1",
					chainId = 4663,
					contract = "0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
					root = "0x9a7f035d269a3133396f88b0b59f1bd718546e9248590b62162268ab2811d642",
					tx = "0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6",
					block = "109589178",
					data = {
						resident = 1,
						cash = 23366.1324
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
						tx = "https://robinhoodchain.blockscout.com/tx/0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.098Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.098Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.cash:1"
				}
			},
			schedule = {
				{
					t0 = 1794179250000,
					t1 = 1794179372551,
					h0 = 6,
					h1 = 7.634,
					kind = "home",
					place = "home",
					sv = {
						at = { 103, -247.47 }
					}
				},
				{
					t0 = 1794179372551,
					t1 = 1794179396807,
					h0 = 7.634,
					h1 = 7.9574,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179372551, 103, -247.47 },
							{ 1794179376770, 76, -247.47 },
							{ 1794179396807, 76, -119.24 }
						}
					}
				},
				{
					t0 = 1794179396807,
					t1 = 1794179432424,
					h0 = 7.9574,
					h1 = 8.4323,
					kind = "walk",
					place = "work:driver",
					from = "home",
					toward = "venue",
					role = "staff",
					slot = "shift",
					decision = "rota",
					sv = {
						path = {
							{ 1794179396807, 103, -247.47 },
							{ 1794179401026, 76, -247.47 },
							{ 1794179432424, 76, -46.52 }
						}
					}
				},
				{
					t0 = 1794179432424,
					t1 = 1794179473947,
					h0 = 8.4323,
					h1 = 8.986,
					kind = "walk",
					place = "work:driver",
					from = "home",
					toward = "venue",
					role = "staff",
					slot = "shift",
					decision = "rota",
					sv = {
						path = {
							{ 1794179432424, 76, -46.52 },
							{ 1794179463443, 76, 152 },
							{ 1794179468066, 46.41, 152 },
							{ 1794179473947, 46.41, 189.64 }
						}
					}
				},
				{
					t0 = 1794179473947,
					t1 = 1794180150000,
					h0 = 8.986,
					h1 = 18,
					kind = "at",
					place = "work:driver",
					role = "staff",
					slot = "shift",
					decision = "rota",
					sv = {
						at = { 46.41, 189.64 }
					}
				},
				{
					t0 = 1794180150000,
					t1 = 1794180181413,
					h0 = 18,
					h1 = 18.4188,
					kind = "walk",
					place = "home",
					from = "work:driver",
					toward = "home",
					role = "staff",
					slot = "shift",
					sv = {
						path = {
							{ 1794180150000, 46.41, 189.64 },
							{ 1794180155881, 46.41, 152 },
							{ 1794180160504, 76, 152 },
							{ 1794180181413, 76, 18.18 }
						}
					}
				},
				{
					t0 = 1794180181413,
					t1 = 1794180227140,
					h0 = 18.4188,
					h1 = 19.0285,
					kind = "walk",
					place = "home",
					from = "work:driver",
					toward = "home",
					role = "staff",
					slot = "shift",
					sv = {
						path = {
							{ 1794180181413, 76, 18.18 },
							{ 1794180222921, 76, -247.47 },
							{ 1794180227140, 103, -247.47 }
						}
					}
				},
				{
					t0 = 1794180227140,
					t1 = 1794180241287,
					h0 = 19.0285,
					h1 = 19.2172,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180227140, 76, -183.94 },
							{ 1794180237068, 76, -247.47 },
							{ 1794180241287, 103, -247.47 }
						}
					}
				},
				{
					t0 = 1794180241287,
					t1 = 1794180308787,
					h0 = 19.2172,
					h1 = 20.1172,
					kind = "home",
					place = "home",
					sv = {
						at = { 103, -247.47 }
					}
				},
				{
					t0 = 1794180308787,
					t1 = 1794180337500,
					h0 = 20.1172,
					h1 = 20.5,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180308787, 103, -247.47 },
							{ 1794180313006, 76, -247.47 },
							{ 1794180337500, 76, -90.71 }
						}
					}
				},
				{
					t0 = 1794180337500,
					t1 = 1794180371807,
					h0 = 20.5,
					h1 = 20.9574,
					kind = "home",
					place = "home",
					sv = {
						at = { 103, -247.47 }
					}
				},
				{
					t0 = 1794180371807,
					t1 = 1794180433094,
					h0 = 20.9574,
					h1 = 21.7746,
					kind = "walk",
					place = "bar",
					from = "home",
					toward = "venue",
					role = "guest",
					slot = "night",
					decision = "plan",
					sv = {
						path = {
							{ 1794180371807, 103, -247.47 },
							{ 1794180376026, 76, -247.47 },
							{ 1794180414693, 76, 0 },
							{ 1794180429657, 171.77, 0 },
							{ 1794180433094, 171.77, 22 }
						}
					}
				},
				{
					t0 = 1794180433094,
					t1 = 1794180675000,
					h0 = 21.7746,
					h1 = 25,
					kind = "at",
					place = "bar",
					role = "guest",
					slot = "night",
					decision = "plan",
					sv = {
						at = { 171.77, 22 }
					}
				},
				{
					t0 = 1794180675000,
					t1 = 1794180736287,
					h0 = 25,
					h1 = 25.8172,
					kind = "walk",
					place = "home",
					from = "bar",
					toward = "home",
					role = "guest",
					slot = "night",
					sv = {
						path = {
							{ 1794180675000, 171.77, 22 },
							{ 1794180678437, 171.77, 0 },
							{ 1794180693401, 76, 0 },
							{ 1794180732068, 76, -247.47 },
							{ 1794180736287, 103, -247.47 }
						}
					}
				},
				{
					t0 = 1794180736287,
					t1 = 1794181050000,
					h0 = 25.8172,
					h1 = 30,
					kind = "home",
					place = "home",
					sv = {
						at = { 103, -247.47 }
					}
				}
			}
		},
		{
			id = 2,
			name = "Isolde Bellweather",
			kind = "native",
			kindLabel = "native resident",
			district = "Finance",
			job = {
				id = "security",
				name = "security",
				workplace = "market",
				hue = "#7fa8d4",
				rota = {
					day = { 6, 18 },
					night = { 18, 30 }
				}
			},
			crew = "k2",
			selection = "native; its plan today names bar (who planned it: decision.by)",
			identity = {
				none = "a native resident has no wallet, so nothing could own or prove an ERC-8004 identity for it: natives have none"
			},
			cash = {
				amount = 32479.08,
				unit = "city dollars",
				asOf = "2026-11-08T23:25:52.955Z",
				source = "city.world"
			},
			plan = {
				forDay = 996766,
				current = true,
				slots = {
					aft = {
						choice = "work",
						place = "market",
						why = "on shift, this one was not a choice",
						spend = 0,
						decided = false
					},
					night = {
						choice = "bar",
						place = "bar",
						why = "IRON CORNER is on strike, the bar instead",
						spend = 0,
						decided = true
					}
				},
				source = "city.world"
			},
			decision = {
				by = "agent",
				byLabel = "GRIFT's planner model",
				outcome = "kept",
				model = "claude-haiku-5-5",
				costMicros = 1299,
				at = "2026-11-08T23:05:46.242Z",
				source = "spectate.trace:2"
			},
			memory = {
				notes = { "at work", "rent $247 · Finance", "at work" },
				what = "the last three nights, in the city's own words",
				source = "city.state"
			},
			records = {
				{
					kind = "decision",
					label = "sealed decision, night 996765",
					epoch = 996765,
					schema = "decisions/1",
					chainId = 4663,
					contract = "0x79bf73502DaBB6713369b9B4254f2C9b22F54A79",
					root = "0xc2ec69e0453b3b443d30556db183f96c1933d2cd0c1c9b27d313a9d83b8ac611",
					tx = "0x6a626b527eec7904d3ebce646bedb4cf18c93dc0a1b49d1c7118ed318bcb8af4",
					block = "109589503",
					data = {
						resident = 2,
						kind = "kept",
						reason = "Security shift till six, not my afternoon. / Staying in tonight, nothing worth the rent. / Security shift till six, home after.",
						traceHash = "0xc4e943846d9bbda361af98fffd87cdcf176ad85548ef91bdc94d50a77357311c"
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0x79bf73502DaBB6713369b9B4254f2C9b22F54A79",
						tx = "https://robinhoodchain.blockscout.com/tx/0x6a626b527eec7904d3ebce646bedb4cf18c93dc0a1b49d1c7118ed318bcb8af4"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							trace = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.098Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.098Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.decision:2"
				},
				{
					kind = "cash",
					label = "sealed cash, night 996765",
					epoch = 996765,
					schema = "resident-cash/1",
					chainId = 4663,
					contract = "0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
					root = "0x9a7f035d269a3133396f88b0b59f1bd718546e9248590b62162268ab2811d642",
					tx = "0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6",
					block = "109589178",
					data = {
						resident = 2,
						cash = 32368.079003
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
						tx = "https://robinhoodchain.blockscout.com/tx/0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.098Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.098Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.cash:2"
				}
			},
			schedule = {
				{
					t0 = 1794179250000,
					t1 = 1794179292757,
					h0 = 6,
					h1 = 6.5701,
					kind = "walk",
					place = "market",
					from = "home",
					toward = "venue",
					role = "staff",
					slot = "aft",
					decision = "rota",
					sv = {
						path = {
							{ 1794179250000, 200.86, 76 },
							{ 1794179287481, -39.01, 76 },
							{ 1794179292757, -39.01, 109.77 }
						}
					}
				},
				{
					t0 = 1794179292757,
					t1 = 1794180150000,
					h0 = 6.5701,
					h1 = 18,
					kind = "at",
					place = "market",
					role = "staff",
					slot = "aft",
					decision = "rota",
					sv = {
						at = { -39.01, 109.77 }
					}
				},
				{
					t0 = 1794180150000,
					t1 = 1794180207626,
					h0 = 18,
					h1 = 18.7683,
					kind = "walk",
					place = "home",
					from = "market",
					toward = "home",
					role = "staff",
					slot = "aft",
					sv = {
						path = {
							{ 1794180150000, -39.01, 109.77 },
							{ 1794180155276, -39.01, 76 },
							{ 1794180196997, 228, 76 },
							{ 1794180203017, 228, 114.53 },
							{ 1794180207626, 257.5, 114.53 }
						}
					}
				},
				{
					t0 = 1794180207626,
					t1 = 1794180360131,
					h0 = 18.7683,
					h1 = 20.8017,
					kind = "home",
					place = "home",
					sv = {
						at = { 257.5, 114.53 }
					}
				},
				{
					t0 = 1794180360131,
					t1 = 1794180390748,
					h0 = 20.8017,
					h1 = 21.21,
					kind = "walk",
					place = "bar",
					from = "home",
					toward = "venue",
					role = "guest",
					slot = "night",
					decision = "plan",
					sv = {
						path = {
							{ 1794180360131, 257.5, 114.53 },
							{ 1794180364740, 228, 114.53 },
							{ 1794180382635, 228, 0 },
							{ 1794180389019, 187.15, 0 },
							{ 1794180390748, 187.15, 11.07 }
						}
					}
				},
				{
					t0 = 1794180390748,
					t1 = 1794180675000,
					h0 = 21.21,
					h1 = 25,
					kind = "at",
					place = "bar",
					role = "guest",
					slot = "night",
					decision = "plan",
					sv = {
						at = { 187.15, 11.07 }
					}
				},
				{
					t0 = 1794180675000,
					t1 = 1794180705617,
					h0 = 25,
					h1 = 25.4082,
					kind = "walk",
					place = "home",
					from = "bar",
					toward = "home",
					role = "guest",
					slot = "night",
					sv = {
						path = {
							{ 1794180675000, 187.15, 11.07 },
							{ 1794180676729, 187.15, 0 },
							{ 1794180683113, 228, 0 },
							{ 1794180701007, 228, 114.53 },
							{ 1794180705617, 257.5, 114.53 }
						}
					}
				},
				{
					t0 = 1794180705617,
					t1 = 1794181050000,
					h0 = 25.4082,
					h1 = 30,
					kind = "home",
					place = "home",
					sv = {
						at = { 257.5, 114.53 }
					}
				}
			}
		},
		{
			id = 5,
			name = "Esperanza Sandoval",
			kind = "native",
			kindLabel = "native resident",
			district = "The Beach",
			job = {
				id = "detective",
				name = "detective"
			},
			crew = "k9",
			selection = "native; its plan today names bar (who planned it: decision.by)",
			identity = {
				none = "a native resident has no wallet, so nothing could own or prove an ERC-8004 identity for it: natives have none"
			},
			cash = {
				amount = 24692.01,
				unit = "city dollars",
				asOf = "2026-11-08T23:25:52.955Z",
				source = "city.world"
			},
			plan = {
				forDay = 996766,
				current = true,
				slots = {
					aft = {
						choice = "work",
						place = "work:detective",
						why = "on shift, this one was not a choice",
						spend = 0,
						decided = false
					},
					night = {
						choice = "bar",
						place = "bar",
						why = "Drink after the long case, nineteen dollars.",
						spend = 19,
						decided = true
					}
				},
				source = "city.world"
			},
			decision = {
				by = "agent",
				byLabel = "GRIFT's planner model",
				outcome = "kept",
				model = "claude-haiku-5-5",
				costMicros = 1299,
				at = "2026-11-08T23:05:46.242Z",
				source = "spectate.trace:5"
			},
			memory = {
				notes = { "at work", "rent $229 · The Beach", "at work" },
				what = "the last three nights, in the city's own words",
				source = "city.state"
			},
			records = {
				{
					kind = "decision",
					label = "sealed decision, night 996765",
					epoch = 996765,
					schema = "decisions/1",
					chainId = 4663,
					contract = "0x79bf73502DaBB6713369b9B4254f2C9b22F54A79",
					root = "0xc2ec69e0453b3b443d30556db183f96c1933d2cd0c1c9b27d313a9d83b8ac611",
					tx = "0x6a626b527eec7904d3ebce646bedb4cf18c93dc0a1b49d1c7118ed318bcb8af4",
					block = "109589503",
					data = {
						resident = 5,
						kind = "kept",
						reason = "Shift till five, the desk is mine. / Home tonight, I have earned the quiet. / Home, the cases can wait till morning.",
						traceHash = "0xc892c20ad9a22a851693a3f528c307815f21b4628947d38ab3e13e1d351db63f"
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0x79bf73502DaBB6713369b9B4254f2C9b22F54A79",
						tx = "https://robinhoodchain.blockscout.com/tx/0x6a626b527eec7904d3ebce646bedb4cf18c93dc0a1b49d1c7118ed318bcb8af4"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							trace = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.098Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.098Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.decision:5"
				},
				{
					kind = "cash",
					label = "sealed cash, night 996765",
					epoch = 996765,
					schema = "resident-cash/1",
					chainId = 4663,
					contract = "0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
					root = "0x9a7f035d269a3133396f88b0b59f1bd718546e9248590b62162268ab2811d642",
					tx = "0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6",
					block = "109589178",
					data = {
						resident = 5,
						cash = 24602.011347
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
						tx = "https://robinhoodchain.blockscout.com/tx/0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.098Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.098Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.cash:5"
				}
			},
			schedule = {
				{
					t0 = 1794179250000,
					t1 = 1794179362500,
					h0 = 6,
					h1 = 7.5,
					kind = "home",
					place = "home",
					sv = {
						at = { 293.24, 457.09 }
					}
				},
				{
					t0 = 1794179362500,
					t1 = 1794179410117,
					h0 = 7.5,
					h1 = 8.1349,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179362500, 304, 472.34 },
							{ 1794179390259, 304, 650 },
							{ 1794179409167, 182.99, 650 },
							{ 1794179410117, 182.99, 643.92 }
						}
					}
				},
				{
					t0 = 1794179410117,
					t1 = 1794179461797,
					h0 = 8.1349,
					h1 = 8.824,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179410117, 182.99, 643.92 },
							{ 1794179411066, 182.99, 650 },
							{ 1794179429975, 304, 650 },
							{ 1794179460117, 304, 457.09 },
							{ 1794179461797, 293.24, 457.09 }
						}
					}
				},
				{
					t0 = 1794179461797,
					t1 = 1794179529297,
					h0 = 8.824,
					h1 = 9.724,
					kind = "home",
					place = "home",
					sv = {
						at = { 293.24, 457.09 }
					}
				},
				{
					t0 = 1794179529297,
					t1 = 1794179580978,
					h0 = 9.724,
					h1 = 10.413,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179529297, 293.24, 457.09 },
							{ 1794179530978, 304, 457.09 },
							{ 1794179561120, 304, 650 },
							{ 1794179580028, 182.99, 650 },
							{ 1794179580978, 182.99, 643.92 }
						}
					}
				},
				{
					t0 = 1794179580978,
					t1 = 1794179632658,
					h0 = 10.413,
					h1 = 11.1021,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179580978, 182.99, 643.92 },
							{ 1794179581927, 182.99, 650 },
							{ 1794179600835, 304, 650 },
							{ 1794179630977, 304, 457.09 },
							{ 1794179632658, 293.24, 457.09 }
						}
					}
				},
				{
					t0 = 1794179632658,
					t1 = 1794179700158,
					h0 = 11.1021,
					h1 = 12.0021,
					kind = "home",
					place = "home",
					sv = {
						at = { 293.24, 457.09 }
					}
				},
				{
					t0 = 1794179700158,
					t1 = 1794179751838,
					h0 = 12.0021,
					h1 = 12.6912,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179700158, 293.24, 457.09 },
							{ 1794179701839, 304, 457.09 },
							{ 1794179731981, 304, 650 },
							{ 1794179750889, 182.99, 650 },
							{ 1794179751838, 182.99, 643.92 }
						}
					}
				},
				{
					t0 = 1794179751838,
					t1 = 1794179803519,
					h0 = 12.6912,
					h1 = 13.3803,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179751838, 182.99, 643.92 },
							{ 1794179752788, 182.99, 650 },
							{ 1794179771696, 304, 650 },
							{ 1794179801838, 304, 457.09 },
							{ 1794179803519, 293.24, 457.09 }
						}
					}
				},
				{
					t0 = 1794179803519,
					t1 = 1794179871019,
					h0 = 13.3803,
					h1 = 14.2803,
					kind = "home",
					place = "home",
					sv = {
						at = { 293.24, 457.09 }
					}
				},
				{
					t0 = 1794179871019,
					t1 = 1794179922699,
					h0 = 14.2803,
					h1 = 14.9693,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179871019, 293.24, 457.09 },
							{ 1794179872699, 304, 457.09 },
							{ 1794179902841, 304, 650 },
							{ 1794179921750, 182.99, 650 },
							{ 1794179922699, 182.99, 643.92 }
						}
					}
				},
				{
					t0 = 1794179922699,
					t1 = 1794179974380,
					h0 = 14.9693,
					h1 = 15.6584,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179922699, 182.99, 643.92 },
							{ 1794179923649, 182.99, 650 },
							{ 1794179942557, 304, 650 },
							{ 1794179972699, 304, 457.09 },
							{ 1794179974380, 293.24, 457.09 }
						}
					}
				},
				{
					t0 = 1794179974380,
					t1 = 1794180041880,
					h0 = 15.6584,
					h1 = 16.5584,
					kind = "home",
					place = "home",
					sv = {
						at = { 293.24, 457.09 }
					}
				},
				{
					t0 = 1794180041880,
					t1 = 1794180093560,
					h0 = 16.5584,
					h1 = 17.2475,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180041880, 293.24, 457.09 },
							{ 1794180043560, 304, 457.09 },
							{ 1794180073702, 304, 650 },
							{ 1794180092611, 182.99, 650 },
							{ 1794180093560, 182.99, 643.92 }
						}
					}
				},
				{
					t0 = 1794180093560,
					t1 = 1794180145241,
					h0 = 17.2475,
					h1 = 17.9365,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180093560, 182.99, 643.92 },
							{ 1794180094510, 182.99, 650 },
							{ 1794180113418, 304, 650 },
							{ 1794180143560, 304, 457.09 },
							{ 1794180145241, 293.24, 457.09 }
						}
					}
				},
				{
					t0 = 1794180145241,
					t1 = 1794180212741,
					h0 = 17.9365,
					h1 = 18.8365,
					kind = "home",
					place = "home",
					sv = {
						at = { 293.24, 457.09 }
					}
				},
				{
					t0 = 1794180212741,
					t1 = 1794180264421,
					h0 = 18.8365,
					h1 = 19.5256,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180212741, 293.24, 457.09 },
							{ 1794180214421, 304, 457.09 },
							{ 1794180244563, 304, 650 },
							{ 1794180263471, 182.99, 650 },
							{ 1794180264421, 182.99, 643.92 }
						}
					}
				},
				{
					t0 = 1794180264421,
					t1 = 1794180316101,
					h0 = 19.5256,
					h1 = 20.2147,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794180264421, 182.99, 643.92 },
							{ 1794180265371, 182.99, 650 },
							{ 1794180284279, 304, 650 },
							{ 1794180314421, 304, 457.09 },
							{ 1794180316101, 293.24, 457.09 }
						}
					}
				},
				{
					t0 = 1794180316101,
					t1 = 1794180348980,
					h0 = 20.2147,
					h1 = 20.6531,
					kind = "home",
					place = "home",
					sv = {
						at = { 293.24, 457.09 }
					}
				},
				{
					t0 = 1794180348980,
					t1 = 1794180441830,
					h0 = 20.6531,
					h1 = 21.8911,
					kind = "walk",
					place = "bar",
					from = "home",
					toward = "venue",
					role = "guest",
					slot = "night",
					decision = "plan",
					sv = {
						path = {
							{ 1794180348980, 293.24, 457.09 },
							{ 1794180350660, 304, 457.09 },
							{ 1794180422081, 304, 0 },
							{ 1794180437262, 206.84, 0 },
							{ 1794180441830, 206.84, 29.24 }
						}
					}
				},
				{
					t0 = 1794180441830,
					t1 = 1794180675000,
					h0 = 21.8911,
					h1 = 25,
					kind = "at",
					place = "bar",
					role = "guest",
					slot = "night",
					decision = "plan",
					sv = {
						at = { 206.84, 29.24 }
					}
				},
				{
					t0 = 1794180675000,
					t1 = 1794180767850,
					h0 = 25,
					h1 = 26.238,
					kind = "walk",
					place = "home",
					from = "bar",
					toward = "home",
					role = "guest",
					slot = "night",
					sv = {
						path = {
							{ 1794180675000, 206.84, 29.24 },
							{ 1794180679568, 206.84, 0 },
							{ 1794180694749, 304, 0 },
							{ 1794180766170, 304, 457.09 },
							{ 1794180767850, 293.24, 457.09 }
						}
					}
				},
				{
					t0 = 1794180767850,
					t1 = 1794181050000,
					h0 = 26.238,
					h1 = 30,
					kind = "home",
					place = "home",
					sv = {
						at = { 293.24, 457.09 }
					}
				}
			}
		},
		{
			id = 13,
			name = "Marcus Ugarte",
			kind = "native",
			kindLabel = "native resident",
			district = "Finance",
			job = {
				id = "busker",
				name = "busker",
				hue = "#c97fae"
			},
			crew = "k2",
			selection = "native; its plan today names bar (who planned it: decision.by)",
			identity = {
				none = "a native resident has no wallet, so nothing could own or prove an ERC-8004 identity for it: natives have none"
			},
			cash = {
				amount = 10269.73,
				unit = "city dollars",
				asOf = "2026-11-08T23:25:52.955Z",
				source = "city.world"
			},
			plan = {
				forDay = 996766,
				current = true,
				slots = {
					aft = {
						choice = "work",
						place = "work:busker",
						why = "on shift, this one was not a choice",
						spend = 0,
						decided = false
					},
					night = {
						choice = "bar",
						place = "bar",
						why = "IRON CORNER is on strike, the bar instead",
						spend = 20,
						decided = true
					}
				},
				source = "city.world"
			},
			decision = {
				by = "agent",
				byLabel = "GRIFT's planner model",
				outcome = "kept",
				model = "claude-haiku-5-5",
				costMicros = 1275,
				at = "2026-11-08T23:05:46.242Z",
				source = "spectate.trace:13"
			},
			memory = {
				notes = { "low wages +$126.37", "gym · $20", "at work" },
				what = "the last three nights, in the city's own words",
				source = "city.state"
			},
			records = {
				{
					kind = "decision",
					label = "sealed decision, night 996765",
					epoch = 996765,
					schema = "decisions/1",
					chainId = 4663,
					contract = "0x79bf73502DaBB6713369b9B4254f2C9b22F54A79",
					root = "0xc2ec69e0453b3b443d30556db183f96c1933d2cd0c1c9b27d313a9d83b8ac611",
					tx = "0x6a626b527eec7904d3ebce646bedb4cf18c93dc0a1b49d1c7118ed318bcb8af4",
					block = "109589503",
					data = {
						resident = 13,
						kind = "kept",
						reason = "Shift keeps me busy until late. / Two gym nights already, I'll go again.",
						traceHash = "0xf062091870edf31e96b1d9f9d28a80ce1801666e5cdbefac643e728b0449d9cb"
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0x79bf73502DaBB6713369b9B4254f2C9b22F54A79",
						tx = "https://robinhoodchain.blockscout.com/tx/0x6a626b527eec7904d3ebce646bedb4cf18c93dc0a1b49d1c7118ed318bcb8af4"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							trace = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.098Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.098Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.decision:13"
				},
				{
					kind = "cash",
					label = "sealed cash, night 996765",
					epoch = 996765,
					schema = "resident-cash/1",
					chainId = 4663,
					contract = "0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
					root = "0x9a7f035d269a3133396f88b0b59f1bd718546e9248590b62162268ab2811d642",
					tx = "0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6",
					block = "109589178",
					data = {
						resident = 13,
						cash = 10239.734977
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
						tx = "https://robinhoodchain.blockscout.com/tx/0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.098Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.098Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.cash:13"
				}
			},
			schedule = {
				{
					t0 = 1794179250000,
					t1 = 1794179362500,
					h0 = 6,
					h1 = 7.5,
					kind = "home",
					place = "home",
					sv = {
						at = { 287.03, -56.22 }
					}
				},
				{
					t0 = 1794179362500,
					t1 = 1794179372890,
					h0 = 7.5,
					h1 = 7.6385,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179362500, 304, -105.75 },
							{ 1794179370239, 304, -56.22 },
							{ 1794179372890, 287.03, -56.22 }
						}
					}
				},
				{
					t0 = 1794179372890,
					t1 = 1794179440390,
					h0 = 7.6385,
					h1 = 8.5385,
					kind = "home",
					place = "home",
					sv = {
						at = { 287.03, -56.22 }
					}
				},
				{
					t0 = 1794179440390,
					t1 = 1794179506525,
					h0 = 8.5385,
					h1 = 9.4203,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179440390, 287.03, -56.22 },
							{ 1794179443041, 304, -56.22 },
							{ 1794179469881, 304, -228 },
							{ 1794179505723, 74.62, -228 },
							{ 1794179506525, 74.62, -233.13 }
						}
					}
				},
				{
					t0 = 1794179506525,
					t1 = 1794179572660,
					h0 = 9.4203,
					h1 = 10.3021,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179506525, 74.62, -233.13 },
							{ 1794179507327, 74.62, -228 },
							{ 1794179543168, 304, -228 },
							{ 1794179570009, 304, -56.22 },
							{ 1794179572660, 287.03, -56.22 }
						}
					}
				},
				{
					t0 = 1794179572660,
					t1 = 1794179640160,
					h0 = 10.3021,
					h1 = 11.2021,
					kind = "home",
					place = "home",
					sv = {
						at = { 287.03, -56.22 }
					}
				},
				{
					t0 = 1794179640160,
					t1 = 1794179666350,
					h0 = 11.2021,
					h1 = 11.5513,
					kind = "stroll",
					place = "home",
					sv = {
						path = {
							{ 1794179640160, 287.03, -56.22 },
							{ 1794179642811, 304, -56.22 },
							{ 1794179666350, 304, -206.87 }
						}
					}
				},
				{
					t0 = 1794179666350,
					t1 = 1794179706295,
					h0 = 11.5513,
					h1 = 12.0839,
					kind = "walk",
					place = "work:busker",
					from = "home",
					toward = "venue",
					role = "staff",
					slot = "shift",
					decision = "rota",
					sv = {
						path = {
							{ 1794179666350, 287.03, -56.22 },
							{ 1794179669001, 304, -56.22 },
							{ 1794179689660, 304, 76 },
							{ 1794179706295, 197.54, 76 }
						}
					}
				},
				{
					t0 = 1794179706295,
					t1 = 1794179736340,
					h0 = 12.0839,
					h1 = 12.4845,
					kind = "walk",
					place = "work:busker",
					from = "home",
					toward = "venue",
					role = "staff",
					slot = "shift",
					decision = "rota",
					sv = {
						path = {
							{ 1794179706295, 197.54, 76 },
							{ 1794179730844, 40.42, 76 },
							{ 1794179736340, 40.42, 40.83 }
						}
					}
				},
				{
					t0 = 1794179736340,
					t1 = 1794180300000,
					h0 = 12.4845,
					h1 = 20,
					kind = "at",
					place = "work:busker",
					role = "staff",
					slot = "shift",
					decision = "rota",
					sv = {
						at = { 40.42, 40.83 }
					}
				},
				{
					t0 = 1794180300000,
					t1 = 1794180305605,
					h0 = 20,
					h1 = 20.0747,
					kind = "walk",
					place = "bar",
					from = "work:busker",
					toward = "venue",
					role = "guest",
					slot = "night",
					decision = "plan",
					sv = {
						path = {
							{ 1794180300000, 40.42, 40.83 },
							{ 1794180305559, 76, 40.83 },
							{ 1794180305605, 76, 40.53 }
						}
					}
				},
				{
					t0 = 1794180305605,
					t1 = 1794180335678,
					h0 = 20.0747,
					h1 = 20.4757,
					kind = "walk",
					place = "bar",
					from = "work:busker",
					toward = "venue",
					role = "guest",
					slot = "night",
					decision = "plan",
					sv = {
						path = {
							{ 1794180305605, 76, 40.53 },
							{ 1794180311938, 76, 0 },
							{ 1794180330828, 196.9, 0 },
							{ 1794180335678, 196.9, 31.04 }
						}
					}
				},
				{
					t0 = 1794180335678,
					t1 = 1794180675000,
					h0 = 20.4757,
					h1 = 25,
					kind = "at",
					place = "bar",
					role = "guest",
					slot = "night",
					decision = "plan",
					sv = {
						at = { 196.9, 31.04 }
					}
				},
				{
					t0 = 1794180675000,
					t1 = 1794180710678,
					h0 = 25,
					h1 = 25.4757,
					kind = "walk",
					place = "home",
					from = "bar",
					toward = "home",
					role = "guest",
					slot = "night",
					sv = {
						path = {
							{ 1794180675000, 196.9, 31.04 },
							{ 1794180679850, 196.9, 0 },
							{ 1794180698740, 76, 0 },
							{ 1794180705119, 76, 40.83 },
							{ 1794180710678, 40.42, 40.83 }
						}
					}
				},
				{
					t0 = 1794180710678,
					t1 = 1794181050000,
					h0 = 25.4757,
					h1 = 30,
					kind = "home",
					place = "home",
					sv = {
						at = { 287.03, -56.22 }
					}
				}
			}
		}
	},
	businesses = {
		{
			id = "bar",
			name = "LAST CALL",
			place = "bar",
			identity = {
				erc8004 = {
					chainId = 4663,
					registry = "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
					agentId = 19865,
					owner = "0x443869a4bECB269B6F06A9aFAcd4914b0d60CCf2",
					tokenUri = "https://www.grift.world/identity/bar.json",
					card = "https://www.grift.world/identity/bar.json",
					registerTx = "0x6606d4884e514a75cf1a5c1144961ad328614292ae95e66761e2097cd6931ff6",
					explorer = "https://robinhoodchain.blockscout.com/token/0x8004A169FB4a3325136EB29fA0ceB6D2e539a432/instance/19865",
					verification = {
						status = "verified",
						steps = {
							owner = true,
							agentWallet = true,
							uri = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.099Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.099Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					}
				}
			},
			status = {
				open = true,
				price = 9.5,
				rulePrice = 12.5,
				setBy = "agent",
				why = "Tuesday footfall held near 42-49 with two staff and only 3 turned away, so keep price and staffing steady.",
				stock = 11,
				staff = 2,
				agentStatus = "applied",
				soldToday = 13,
				footfallToday = 17,
				cityFootfall = 1118,
				source = "city.state"
			},
			decisions = {
				{
					at = "2026-11-08T23:22:00.515Z",
					actor = "manager",
					kind = "REORDER",
					amount = -72,
					ref = "lager",
					line = "stock was low, reordered before the weekend.",
					fact = "STOCK LOW · house lager 11 units · REORDER 24 @ $3",
					decision = true
				},
				{
					at = "2026-11-08T23:00:00.515Z",
					actor = "manager",
					kind = "STAFFING",
					amount = -54,
					line = "3 on tonight — $54 in wages",
					fact = "FOOTFALL 49 YESTERDAY · STAFF 3 · WAGES $54",
					decision = true
				},
				{
					at = "2026-11-08T22:54:00.515Z",
					actor = "manager",
					kind = "REORDER",
					amount = -72,
					ref = "lager",
					line = "shelf was running thin — called it in.",
					fact = "STOCK LOW · house lager 10 units · REORDER 24 @ $3",
					decision = true
				}
			},
			activity = {
				{
					at = "2026-11-08T23:22:00.515Z",
					actor = "manager",
					kind = "REORDER",
					amount = -72,
					ref = "lager",
					line = "stock was low, reordered before the weekend.",
					fact = "STOCK LOW · house lager 11 units · REORDER 24 @ $3",
					decision = true
				},
				{
					at = "2026-11-08T23:22:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T23:22:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T23:22:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T23:22:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T23:22:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T23:20:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T23:18:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T23:15:07.300Z",
					actor = "house",
					kind = "SUPPLY_B2B_DECLINE",
					amount = 0,
					ref = "1993533:b2b:bar-produce",
					line = "THE NIGHT MARKET has no produce to spare this round",
					fact = "NO TRADE WITH THE NIGHT MARKET · NOTHING TO SPARE",
					decision = false
				},
				{
					at = "2026-11-08T23:15:07.300Z",
					actor = "house",
					kind = "SUPPLY_VENUE_DECLINE",
					amount = 0,
					ref = "1993533:bar:stockcap",
					line = "2 kegs on hand is at the 2-keg cap (2 nights' use) — no order this round",
					fact = "NO PURCHASE THIS ROUND · STOCK AT CAP · NO MODEL ASKED",
					decision = false
				},
				{
					at = "2026-11-08T23:04:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T23:05:15.471Z",
					actor = "agent",
					kind = "AGENT_DECISION",
					amount = 0,
					ref = "shift:996766",
					line = "Tuesday footfall held near 42-49 with two staff and only 3 turned away, so keep price and staffing steady.",
					fact = "AGENT SET PRICE $9.50 · STAFF 2 · RULES WOULD CHARGE $12.50 AT 3 · COST 102µ$",
					decision = false
				},
				{
					at = "2026-11-08T23:05:15.471Z",
					actor = "agent",
					kind = "STAFFING",
					amount = 18,
					ref = "shift:996766",
					line = "agent cut tonight to 2 from 3 — $18 back in the till",
					fact = "STAFF 3 → 2 · WAGE ADJUSTMENT +$18",
					decision = false
				},
				{
					at = "2026-11-08T23:05:15.471Z",
					actor = "agent",
					kind = "AGENT_COST",
					amount = -0.000102,
					ref = "shift:996766",
					line = "asking the agent cost 102 micro-dollars",
					fact = "TOKENS 713 in / 62 out · COST 102µ$ · MEASURED",
					decision = false
				},
				{
					at = "2026-11-08T23:02:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T23:02:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T23:00:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T23:00:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T23:00:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T23:00:00.515Z",
					actor = "manager",
					kind = "STAFFING",
					amount = -54,
					line = "3 on tonight — $54 in wages",
					fact = "FOOTFALL 49 YESTERDAY · STAFF 3 · WAGES $54",
					decision = true
				},
				{
					at = "2026-11-08T23:00:07.411Z",
					actor = "house",
					kind = "SUPPLY_B2B_DECLINE",
					amount = 0,
					ref = "1993532:b2b:bar-produce",
					line = "THE NIGHT MARKET has no produce to spare this round",
					fact = "NO TRADE WITH THE NIGHT MARKET · NOTHING TO SPARE",
					decision = false
				},
				{
					at = "2026-11-08T23:00:07.411Z",
					actor = "agent",
					kind = "SUPPLY_VENUE_BUY",
					amount = -30,
					ref = "1993532:bar",
					line = "One keg at $30 fits the one-unit cap and budget, and we hold only one on hand.",
					fact = "BOUGHT 1 KEG OF KEGS @ $30 FROM THE YARD · $30 · $GRIFT 1500 → YARD + 1500 BURNT",
					decision = false
				},
				{
					at = "2026-11-08T23:00:07.411Z",
					actor = "agent",
					kind = "AGENT_COST",
					amount = -0.000139,
					ref = "supply:1993532:bar",
					line = "asking the agent cost 139 micro-dollars",
					fact = "TOKENS 1171 in / 44 out · COST 139µ$ · MEASURED",
					decision = false
				},
				{
					at = "2026-11-08T22:58:00.515Z",
					actor = "supplier",
					kind = "DELIVERY",
					amount = 0,
					ref = "lager",
					line = "delivery landed: +24 HOUSE LAGER",
					decision = false
				},
				{
					at = "2026-11-08T22:58:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T22:58:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T22:58:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T22:58:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T22:56:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T22:56:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T22:56:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T22:56:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T22:56:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T22:56:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T22:54:00.515Z",
					actor = "manager",
					kind = "REORDER",
					amount = -72,
					ref = "lager",
					line = "shelf was running thin — called it in.",
					fact = "STOCK LOW · house lager 10 units · REORDER 24 @ $3",
					decision = true
				},
				{
					at = "2026-11-08T22:54:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T22:54:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T22:54:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T22:54:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				},
				{
					at = "2026-11-08T22:54:00.515Z",
					actor = "patron",
					kind = "PATRON_SALE",
					amount = 9.5,
					ref = "lager",
					line = "patron bought 1 HOUSE LAGER @ $9.50",
					decision = false
				}
			},
			activitySource = "bar.ledger",
			records = {
				{
					kind = "business",
					label = "sealed business night 996765",
					epoch = 996765,
					schema = "businesses/1",
					chainId = 4663,
					contract = "0x79bf73502DaBB6713369b9B4254f2C9b22F54A79",
					root = "0x8611a93b6190bcb021dbe042d1a5d8e91991d2678e0a92b3824f2dc6439e3215",
					tx = "0x6a626b527eec7904d3ebce646bedb4cf18c93dc0a1b49d1c7118ed318bcb8af4",
					block = "109589503",
					data = {
						business = "bar",
						salesMicros = "298000000",
						orders = 1,
						boughtRaw = "3400000000000000000000",
						burntRaw = "1700000000000000000000"
					},
					links = {
						contract = "https://robinhoodchain.blockscout.com/address/0x79bf73502DaBB6713369b9B4254f2C9b22F54A79",
						tx = "https://robinhoodchain.blockscout.com/tx/0x6a626b527eec7904d3ebce646bedb4cf18c93dc0a1b49d1c7118ed318bcb8af4"
					},
					verification = {
						status = "verified",
						steps = {
							leaf = true,
							proof = true,
							chain = true
						},
						basis = "recorded",
						checkedAt = "2026-11-09T00:06:57.098Z",
						chainReadAt = "2026-11-08T23:28:33.714Z",
						note = "recomputed from recorded answers at 2026-11-09T00:06:57.098Z; the chain itself was read during the recording (2026-11-08T23:26:11.056Z to 2026-11-08T23:28:33.714Z)"
					},
					source = "verify.business"
				}
			},
			notes = { "GRIFT records no patron by name: sales come from the city's head count. Agents shown here are there because their own plans say so." },
			sv = {
				door = { 200.8, 8.9 },
				shell = { 192.4, 10.5, 219.6, 30.2 }
			}
		}
	},
	chain = {
		id = 4663,
		name = "Robinhood Chain",
		explorer = "https://robinhoodchain.blockscout.com",
		contracts = {
			cityCommitsV1 = "0xe3E937d6d7Fe34D4eb1082f29c63B970B8E6Af94",
			cityCommitsV2 = "0x79bf73502DaBB6713369b9B4254f2C9b22F54A79",
			erc8004Registry = "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432"
		},
		posting = {
			live = true,
			lastNight = 996765,
			lastTx = "0xd1a452c4b0b9c9281149f9fa3bc9cf88803bbdd0f4e532b31a23277df0be22b6",
			source = "verify.status"
		}
	},
	honesty = { "decided: a schedule segment marked \"plan\" goes where GRIFT's planner chose for that part of the day; its reason is the plan's own words, and decision.by says who chose. \"rota\" is the agent's working hours, not a choice.", "drawn: homes, the routes between places, departure staggers and strolls are how GRIFT's spectator view draws the day; they are not decisions. GRIFT holds no address for anyone.", "money: plan spends are caps in city dollars. LAST CALL's sales are anonymous, from the city's head count; no ledger line ties a drink to a named agent.", "time: a schedule never runs past the plan day it was read for. Past validUntil an agent is unknown: show it where it was last seen, doing nothing new.", "verified: a record is \"verified\" only when every step ran and passed (leaf recomputed from the served data, proof folded, the root read from the contract). \"unchecked\" means a step could not run, never that it passed.", "recorded: a recorded feed is real GRIFT answers from the time it names, replayed. It is not live and must be labelled as recorded wherever it is shown." }
}
]]></ProtectedString>
        </Properties>
      </Item>
    </Item>
  </Item>
</roblox>
