Skip to main content

PlayerMock

In-memory stand-in for a Roblox Player used by tests. A real Player cannot be Instance.new'd and no client joins a headless Open Cloud test place, so this builds a Folder that carries the marker and identity attributes production code reads -- letting a "player" flow through guards and providers without a real join.

It is the shared replacement for the ad-hoc Instance.new("Folder") :: Player stand-ins that had accreted across the test suites. Guards keep their hard Player assert and add an explicit OR clause so support for the mock is greppable rather than a silent weakening:

assert(player:IsA("Player") or PlayerMock.isMock(player), "Bad player")

Native Player members a Folder cannot expose are read through PlayerMock.read, which is mock-only and errors on anything else (including a real Player). Consumers branch explicitly -- if PlayerMock.isMock(player) then PlayerMock.read(player, "UserId") else player.UserId -- so the real-Player path stays plain member access that luau-lsp can type-check. Each property is backed by a same-named attribute (Instance-valued members like Character by an ObjectValue child), so a test can mock a value and observe changes via [PlayerMock.getPropertyChangedSignal]:

local player = PlayerMock.new({ UserId = 12345, AccountAge = 30 })
player.Parent = game:GetService("Players") -- where real players live; see PlayerMockService.CreatePlayer

assert(PlayerMock.isMock(player))
assert(PlayerMock.read(player, "UserId") == 12345)
assert(PlayerMock.read(player, "MembershipType") == Enum.MembershipType.None)

PlayerMock.write(player, "AccountAge", 31) -- fires GetAttributeChangedSignal("AccountAge")

Native Player events follow the same shape through PlayerMock.getSignal -- mock-only, with the name validated against the engine's reflected Player events so a typo errors instead of returning a signal that can never fire -- and PlayerMock.fireSignal as the test-side trigger:

local chatted = if PlayerMock.isMock(player) then PlayerMock.getSignal(player, "Chatted") else player.Chatted
maid:GiveTask(chatted:Connect(onChatted))

PlayerMock.fireSignal(player, "Chatted", "hello") -- test-side

Results of ID-keyed engine calls (group rank, gamepass/asset ownership, ...) that production code fetches from a Roblox web API by (player, id) follow the same shape through PlayerMock.writeLookup / PlayerMock.readLookup, with the domain named after the canonical Service.Method being intercepted. The injected result lives on the mock -- one attribute per (domain, key) -- so it is centralized per player: every consumer whose answer derives from the same engine call resolves the same value, instead of each call site stubbing its own copy that can drift.

PlayerMock.writeLookup(player, "GroupService.GetRolesInGroupAsync", 372, {
	IsMember = true,
	Roles = { { Name = "Admin", Rank = 230 } },
})
-- GroupUtils.promiseRankInGroup(player, 372) now resolves 230 everywhere it is asked,
-- and promiseRoleInGroup(player, 372) resolves the matching "Admin". Values are the raw
-- engine result shape (see GroupTestUtils.assignGroupInfo for a friendlier writer).

Properties

TAG

This item is read only and cannot be modified. Read Only
PlayerMock.TAG: string

The CollectionService tag every mock carries from construction: the place-wide discovery channel PlayerMockService and PlayerMockServiceClient observe. Replication is the default -- a real Player exists for every peer, so a mock is discoverable from any ServiceBag in either realm the moment it is parented into the DataModel (tag resolution is DataModel-scoped), and a destroyed (or kicked) mock drops out automatically.

Functions

new

PlayerMock.new(
overrides{[string]any}?--

Per-property seed values, keyed by native property name.

) → Player

Constructs a mock player. The returned value is typed as Player for drop-in use, but is really a marked Folder; it is unparented -- the caller parents and/or maid:Adds it as needed. Once parented into the DataModel it is replicated: discoverable place-wide through PlayerMockService / PlayerMockServiceClient in any ServiceBag, either realm (see PlayerMock.TAG).

Every stand-in property (see PlayerMock.read) is seeded as an attribute from overrides (keyed by native property name, e.g. UserId), or the pre-authored default, so reads resolve without a real Player.

isMock

PlayerMock.isMock(valueany) → boolean

Returns whether the given value is a PlayerMock. Intended for use alongside a real-Player check in a guard, e.g. player:IsA("Player") or PlayerMock.isMock(player).

findFirstAncestorMock

PlayerMock.findFirstAncestorMock(instanceInstance) → Player?

Returns the nearest ancestor of instance that is a PlayerMock, or nil. The mock counterpart of FindFirstAncestorWhichIsA("Player") -- a mock's backing Folder is invisible to an IsA walk, so code resolving the owning player from a descendant adds the explicit OR clause:

local player = instance:FindFirstAncestorWhichIsA("Player") or PlayerMock.findFirstAncestorMock(instance)

Like the engine call, the walk starts at the parent -- instance itself is never returned.

getMockByUserId

PlayerMock.getMockByUserId(userIdnumber) → Player?

Returns the mock currently in the DataModel whose UserId stand-in matches, or nil. The mock counterpart of Players:GetPlayerByUserId -- the resolver for code paths keyed by userId alone (e.g. MarketplaceUtils.promiseUserOwnsGamePass), where no player value is in hand to isMock-branch on:

local mockPlayer = PlayerMock.getMockByUserId(userId)
if mockPlayer ~= nil then
	result = PlayerMock.readLookup(mockPlayer, "MarketplaceService.UserOwnsGamePassAsync", gamePassId)
else
	result = MarketplaceService:UserOwnsGamePassAsync(userId, gamePassId)
end

Like the engine call, only mocks in the game resolve -- discovery runs over PlayerMock.TAG, and tag resolution is DataModel-scoped, so an unparented (or destroyed) mock reads back as nil. Real UserIds are unique; seed mocks the same way, since the first match wins.

getMockByUsername

PlayerMock.getMockByUsername(usernamestring) → Player?

Returns the mock currently in the DataModel whose username stand-in matches, or nil. The mock counterpart of Players:GetUserIdFromNameAsync -- the resolver for code paths keyed by username alone (e.g. PlayersServicePromises.promiseUserIdFromName). A mock's username is its "UserService.GetUserInfosByUserIdsAsync" lookup's Username, which defaults to the mock's Name -- the same member that holds a real Player's username.

Like PlayerMock.getMockByUserId, only mocks in the game resolve, and the first match wins.

getMockFromCharacter

PlayerMock.getMockFromCharacter(characterInstance) → Player?

Returns the mock currently in the DataModel whose Character stand-in is the given model, or nil. The mock counterpart of Players:GetPlayerFromCharacter -- the resolver for code paths that start from a character (or a part of one) with no player value in hand to isMock-branch on (e.g. CharacterUtils.getPlayerFromCharacter):

local player = Players:GetPlayerFromCharacter(model) or PlayerMock.getMockFromCharacter(model)

Like the engine call, only the exact character model matches -- a descendant part resolves nil, so callers walking up from a descendant keep their own ancestor walk -- and only mocks in the game resolve (discovery runs over PlayerMock.TAG, and tag resolution is DataModel-scoped).

read

PlayerMock.read(
playerPlayer,--

must be a PlayerMock

propertyNamestring
) → any

Reads a stand-in native Player property off a mock: the seeded backing attribute, or the pre-authored typed default when unset. Errors on anything that is not a PlayerMock -- including a real Player -- so call sites must branch explicitly:

local userId = if PlayerMock.isMock(player) then PlayerMock.read(player, "UserId") else player.UserId

Keeping the real-Player read as plain member access preserves luau-lsp's native property typing on the hot path instead of funneling every read through this any-returning helper.

write

PlayerMock.write(
playerPlayer,--

must be a PlayerMock

propertyNamestring,
valueany
) → ()

Mocks a native Player property on a mock by writing its backing attribute, which also fires the instance's GetAttributeChangedSignal(propertyName) so observers see the change.

Writing Character = nil carries the engine's despawn semantics (the model is destroyed) -- see PlayerMock.removeCharacter.

getPropertyChangedSignal

PlayerMock.getPropertyChangedSignal(
playerPlayer,--

must be a PlayerMock

propertyNamestring
) → RBXScriptSignal

Returns the signal that fires when the given stand-in property changes on a mock: the backing attribute's changed signal, or the backing ObjectValue's Value-changed signal for Instance-valued members like Character. Mock-only, like PlayerMock.read -- the real-Player path stays player:GetPropertyChangedSignal(propertyName).

readLookup

PlayerMock.readLookup(
playerPlayer,--

must be a PlayerMock

domainstring,--

a known lookup domain, e.g. "GroupService.GetRolesInGroupAsync"

keynumber | EnumItem | string--

what the call is keyed by (groupId, gamePassId, CoreGuiType, subscriptionId, ...)

) → any

Reads the injected result for an ID-keyed engine call off a mock: the value a test injected through PlayerMock.writeLookup, or the domain's pre-authored default when unset. The domain is the canonical Service.Method the production code path bottoms out in, validated against the pre-authored set so a typo errors instead of silently reading a default.

Like PlayerMock.read, this is mock-only and errors on anything else (including a real Player), so consumers branch explicitly and the real-Player path keeps calling the real service:

if PlayerMock.isMock(player) then
	-- same raw result shape the engine call returns, so parsing below runs unchanged
	return PlayerMock.readLookup(player, "GroupService.GetRolesInGroupAsync", groupId)
end
return GroupService:GetRolesInGroupAsync(player.UserId, groupId)

Effect-recording domains (e.g. StarterGui.SetCoreGuiEnabled) run the same machinery in the other direction: production wrote the value through PlayerMock.writeLookup, and the test reads it here to assert the engine effect.

writeLookup

PlayerMock.writeLookup(
playerPlayer,--

must be a PlayerMock

domainstring,--

a known lookup domain, e.g. "MarketplaceService.UserOwnsGamePassAsync"

keynumber | EnumItem | string,--

what the call is keyed by (groupId, gamePassId, CoreGuiType, subscriptionId, ...)

valueany--

must match the domain's value shape; nil clears back to the default

) → ()

Injects the result a mock answers for an ID-keyed engine call, or clears it back to the domain default with nil. Because the value is stored on the mock -- one attribute per (domain, key) -- every consumer whose answer derives from the same engine call resolves the same value, and the write fires the backing attribute's GetAttributeChangedSignal so observers see the change.

PlayerMock.writeLookup(player, "GroupService.GetRolesInGroupAsync", 372, {
	IsMember = true,
	Roles = { { Name = "Admin", Rank = 230 } },
})
PlayerMock.writeLookup(player, "MarketplaceService.UserOwnsGamePassAsync", 12345, true)

For effect-recording domains (e.g. StarterGui.SetCoreGuiEnabled) the writer is production code instead: it performs the engine effect on the mock, and the test observes it through PlayerMock.readLookup or the backing attribute's changed signal.

getLookupChangedSignal

PlayerMock.getLookupChangedSignal(
playerPlayer,--

must be a PlayerMock

domainstring,--

a known lookup domain, e.g. "Player.IsFriendsWithAsync"

keynumber | EnumItem | string--

what the call is keyed by

) → RBXScriptSignal

Returns the signal that fires when the injected result for an ID-keyed engine call changes on a mock: the backing attribute's changed signal. This lets a production observer treat a mid-test PlayerMock.writeLookup as the engine's own change event -- e.g. RxFriendUtils re-reading friendship when the "Player.IsFriendsWithAsync" domain changes, standing in for the CoreGui friended/unfriended events a mock can never receive. Mock-only, like PlayerMock.readLookup.

loadCharacterAsync

PlayerMock.loadCharacterAsync(
playerPlayer,--

must be a PlayerMock

characterModel?--

the new character; nil builds a default R15 rig

) → Model

Emulates Player:LoadCharacterAsync() on a mock. The caller supplies the character model -- e.g. Players:CreateHumanoidModelFromUserId/FromDescription (both work in cloud test runs) or a hand-built rig -- or omits it to get a default R15 built from an empty HumanoidDescription (may yield while the engine builds it).

The observable sequence encodes the engine's avatar loading event ordering (https://devforum.roblox.com/t/avatar-loading-event-ordering-improvements/269607), and PlayerMock.spec asserts each step -- correct both together if that understanding is ever corrected:

  1. CharacterRemoving(old) fires while Character still points at the old model and it is still parented -- the event's "just before removal" contract (not part of the announcement)
  2. Character nils and the old character is destroyed (see RxCharacterUtils.observeLastCharacterBrio's assumption); nil-before-destroy lets observers tear down while the instance is still alive
  3. the new rig is fully built before any signal fires -- the announcement's "appearance initialized / rig built and scaled" steps; the caller's rig (or the built default) stands in
  4. Character is set to the new model (the property Changed fires)
  5. the new character is parented to the Workspace
  6. CharacterAdded(new) fires -- after both the assignment and the Workspace parenting, per the announcement above (the pre-2019 "not in Workspace yet" gotcha is gone)
  7. CharacterAppearanceLoaded(new) fires -- after CharacterAdded, with the rig finalized
  8. only then does the call return, mirroring "LoadCharacter returns" ending the announced order

Per the same announcement, CharacterAdded fires only during avatar loading -- which is why a plain PlayerMock.write(player, "Character", model) deliberately does not fire it.

Each call also replaces the mock's Backpack stand-in with a fresh empty one before any spawn signal fires, like the engine does on respawn (minus the StarterPack copy) -- see PlayerMock.getBackpack. The first call additionally inserts the StarterGear stand-in, which later spawns keep -- see PlayerMock.getStarterGear.

loadMinimalCharacterAsync

PlayerMock.loadMinimalCharacterAsync(
playerPlayer--

must be a PlayerMock

) → Model

PlayerMock.loadCharacterAsync with a minimal hand-built rig -- an anchored HumanoidRootPart (the PrimaryPart) and a Humanoid -- instead of a full R15 built from a HumanoidDescription. That is the smallest shape character-driven code paths (equip flows, humanoid observers) accept, and building it never yields, so specs that only need a character spawn instantly:

local character = PlayerMock.loadMinimalCharacterAsync(playerMock)

removeCharacter

PlayerMock.removeCharacter(
playerPlayer--

must be a PlayerMock

) → ()

Emulates the character being removed with no replacement, i.e. player.Character = nil: CharacterRemoving fires while Character still points at the model, Character is set to nil, and the model is destroyed. No-op when no character is loaded.

Runs automatically when the mock itself is destroyed, mirroring the engine cleaning up the character when its player leaves or is kicked.

getBackpack

PlayerMock.getBackpack(
playerPlayer--

must be a PlayerMock

) → Backpack?

Returns the mock's current Backpack stand-in, or nil before the first spawn -- the engine only inserts a player's Backpack when their character spawns. The stand-in is a genuine Backpack instance parented to the mock (so it is the child named "Backpack", like a real Player's), which means production code that observes the backpack's children -- e.g. RxInstanceUtils.observeLastNamedChildBrio(player, "Backpack", "Backpack") -- works unchanged.

PlayerMock.loadCharacterAsync replaces it with a fresh empty one on every spawn, like the engine does on respawn; the test parents Tools into it directly:

local character = PlayerMock.loadCharacterAsync(player, rig)
local backpack = assert(PlayerMock.getBackpack(player))
tool.Parent = backpack

getStarterGear

PlayerMock.getStarterGear(
playerPlayer--

must be a PlayerMock

) → StarterGear?

Returns the mock's current StarterGear stand-in, or nil before the first spawn -- the engine only inserts a player's StarterGear alongside their first character spawn. Like the Backpack stand-in it is a genuine StarterGear instance parented to the mock, so class-based lookups (player:FindFirstChildOfClass("StarterGear")) work unchanged; unlike the Backpack, PlayerMock.loadCharacterAsync never replaces it -- the engine keeps a player's StarterGear across respawns. Consumers that dot-index player.StarterGear resolve it through the usual explicit branch:

local starterGear = if PlayerMock.isMock(player)
	then PlayerMock.getStarterGear(player)
	else player.StarterGear

getPlayerGui

PlayerMock.getPlayerGui(
playerPlayer--

must be a PlayerMock

) → PlayerGui

Returns the mock's PlayerGui stand-in, parented at construction -- mirroring the engine inserting a player's PlayerGui at join (unlike the Backpack, which only appears at first spawn). PlayerGui cannot be Instance.new'd, so like the mock itself the stand-in is really a Folder (named "PlayerGui") typed as the native class; consumers resolve it through the usual explicit branch instead of FindFirstChildOfClass:

local playerGui = if PlayerMock.isMock(player)
	then PlayerMock.getPlayerGui(player)
	else player:FindFirstChildOfClass("PlayerGui")

PlayerGuiUtils branches this way internally, so consumers of it work against a mock without changes. Tests parent ScreenGui/Frame surfaces into the stand-in directly.

getPlayerScripts

PlayerMock.getPlayerScripts(
playerPlayer--

must be a PlayerMock

) → PlayerScripts

Returns the mock's PlayerScripts stand-in, parented at construction -- mirroring the engine inserting a player's PlayerScripts at join, like the PlayerGui stand-in. PlayerScripts cannot be Instance.new'd, so the stand-in is really a Folder (named "PlayerScripts") typed as the native class. A Folder can never satisfy an IsA("PlayerScripts") class filter, so consumers observing the child by class resolve it through the usual explicit branch:

local playerScriptsClassName = if PlayerMock.isMock(localPlayer) then "Folder" else "PlayerScripts"
RxInstanceUtils.observeLastNamedChildBrio(localPlayer, playerScriptsClassName, "PlayerScripts")

Tests parent script stand-ins (e.g. an RbxCharacterSounds LocalScript) into it directly.

kick

PlayerMock.kick(
playerPlayer,--

must be a PlayerMock

messagestring?--

recorded for PlayerMock.getKickMessage; nil records ""

) → ()

Emulates Player:Kick(message) on a mock. Kick is the special case among the stand-ins: a method whose observable effect is the engine removing the player from the game, not a property or event a test seeds -- so the mock really performs the removal sequence instead of merely recording the call:

  1. the kick message is recorded (see PlayerMock.getKickMessage -- no client exists to show it to)
  2. the character is removed (CharacterRemoving fires while Character still points at it, Character nils, the model is destroyed), mirroring the engine cleaning up a kicked player's character
  3. the mock leaves the DataModel (Parent = nil, not a destroy -- a held reference stays readable), so the native AncestryChanged signal genuinely fires -- the same way a kicked player's instance leaves Players

Players.PlayerRemoving is a Players-service event only the engine fires, so consumers of that event cannot observe a mock kick; observe AncestryChanged instead.

Production code branches explicitly, like every other mock seam:

if PlayerMock.isMock(player) then
	PlayerMock.kick(player, reason)
else
	player:Kick(reason)
end

getKickMessage

PlayerMock.getKickMessage(
playerPlayer--

must be a PlayerMock

) → string?

Returns the message a mock was kicked with (via PlayerMock.kick), or nil when it was never kicked. A kick with no message reads back as "". Stays readable after the kick destroys the mock, as long as the caller holds a reference.

getSignal

PlayerMock.getSignal(
playerPlayer,--

must be a PlayerMock

eventNamestring
) → RBXScriptSignal

Reads a stand-in native Player event off a mock: the genuine native signal when the backing Folder inherits it from Instance (AncestryChanged, Destroying, ...), otherwise a BindableEvent-backed signal a test fires through PlayerMock.fireSignal. The name is validated against the engine's reflected Player events, so a typo errors instead of returning a signal that can never fire.

Like PlayerMock.read, this is mock-only and errors on anything else (including a real Player), so call sites branch explicitly and the real-Player path stays plain member access:

local chatted = if PlayerMock.isMock(player) then PlayerMock.getSignal(player, "Chatted") else player.Chatted

fireSignal

PlayerMock.fireSignal(
playerPlayer,--

must be a PlayerMock

eventNamestring,
...any--

Event arguments delivered to connected handlers.

) → ()

Fires the backing signal for a Player-class event on a mock, so code connected through PlayerMock.getSignal observes the event as if the engine had fired it.

Only Player-class events can be fired; events the backing Folder inherits from Instance resolve to genuine native signals, which only the engine fires.

bindInput

PlayerMock.bindInput(
playerPlayer,--

must be a PlayerMock

domainstring,--

a known input domain, e.g. "ContextActionService.BindAction"

actionNamestring,
...any--

the engine call's remaining args, e.g. functionToBind, createTouchButton, ...inputTypes

) → ()

Emulates a context-restricted ContextActionService call on a mock. The domain is the canonical Service.Method -- validated against the pre-authored set so a typo errors, like PlayerMock.readLookup -- and the args after it mirror the engine call's own args, so a production mock branch is the identical call aimed at the mock local player (there is no input to receive):

local localPlayer = Players.LocalPlayer or PlayerMock.getMockedLocalPlayer()
if localPlayer ~= nil and PlayerMock.isMock(localPlayer) then
	PlayerMock.bindInput(localPlayer, "ContextActionService.BindAction", "Drag", onDragAction, false, Enum.UserInputType.MouseButton2)
else
	ContextActionService:BindAction("Drag", onDragAction, false, Enum.UserInputType.MouseButton2)
end

Unbinding goes through the same entry point -- the domain names the operation:

PlayerMock.bindInput(localPlayer, "ContextActionService.UnbindAction", "Drag")

A test dispatches a bound action through PlayerMock.fireInput as if the engine had routed input to it. What the emulation deliberately does not model: touch buttons, priority routing, input-type routing, and the engine's bind stack -- all bind domains share one action registry per mock (like the engine's), and rebinding a name simply replaces the callback.

isInputBound

PlayerMock.isInputBound(
playerPlayer,--

must be a PlayerMock

actionNamestring
) → boolean

Returns whether the given action is currently bound on a mock (via PlayerMock.bindInput). Keyed by the action name alone -- both bind domains share one action registry, like the engine.

fireInput

PlayerMock.fireInput(
playerPlayer,--

must be a PlayerMock

actionNamestring,
userInputStateEnum.UserInputState,
inputObjectany?--

a real InputObject or a bindable-safe stand-in table

) → Enum.ContextActionResult?

Dispatches a bound action on a mock, invoking the bound callback with (actionName, userInputState, inputObject) -- the argument order the engine uses -- and returning its result. Test-side counterpart of PlayerMock.bindInput, like PlayerMock.fireSignal is for PlayerMock.getSignal.

Errors when the action is not bound: with no engine sink logic modeled, firing an unbound action can only be a test mistake (typo'd name, or firing after the production unbind).

The invocation goes through the backing BindableFunction, so a stand-in inputObject must be bindable-safe -- a real InputObject reference, or a plain metatable-free table of the fields the callback reads (e.g. UserInputType, Position, Delta).

setMockedLocalPlayer

PlayerMock.setMockedLocalPlayer(
playerPlayer?--

must be a PlayerMock in the DataModel, or nil to clear

) → ()

Designates a mock as the local player for the client realm (or clears it with nil). Read through PlayerMock.getMockedLocalPlayer by client code falling back from Players.LocalPlayer.

Call this directly before booting bags to pre-designate -- matching production, where Players.LocalPlayer exists before any service runs -- and a booting PlayerMockServiceClient adopts the designation as its local player and owns its cleanup. After boot, designate through PlayerMockServiceClient.SetLocalPlayer instead, which records the designation per simulated client.

The mock must be parented into the DataModel first: the designation is carried as a CollectionService tag, and GetTagged only resolves parented instances -- an unparented designation would silently read back as nil.

getMockedLocalPlayer

PlayerMock.getMockedLocalPlayer() → Player?

Returns the mock designated as the local player (via PlayerMockServiceClient), or nil. This is only ever the mock -- there is deliberately no helper that resolves the real Players.LocalPlayer, so call sites fall back explicitly and the real read stays visible to luau-lsp:

local localPlayer = Players.LocalPlayer or PlayerMock.getMockedLocalPlayer()
Show raw api
{
    "functions": [
        {
            "name": "new",
            "desc": "Constructs a mock player. The returned value is typed as `Player` for drop-in use, but is really\na marked `Folder`; it is unparented -- the caller parents and/or `maid:Add`s it as needed. Once\nparented into the DataModel it is replicated: discoverable place-wide through [PlayerMockService] /\n[PlayerMockServiceClient] in any ServiceBag, either realm (see [PlayerMock.TAG]).\n\nEvery stand-in property (see [PlayerMock.read]) is seeded as an attribute from `overrides` (keyed by\nnative property name, e.g. `UserId`), or the pre-authored default, so reads resolve without a real\nPlayer.",
            "params": [
                {
                    "name": "overrides",
                    "desc": "Per-property seed values, keyed by native property name.",
                    "lua_type": "{ [string]: any }?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Player"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 175,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "isMock",
            "desc": "Returns whether the given value is a [PlayerMock]. Intended for use alongside a real-Player check\nin a guard, e.g. `player:IsA(\"Player\") or PlayerMock.isMock(player)`.",
            "params": [
                {
                    "name": "value",
                    "desc": "",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 233,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "findFirstAncestorMock",
            "desc": "Returns the nearest ancestor of `instance` that is a [PlayerMock], or nil. The mock counterpart\nof `FindFirstAncestorWhichIsA(\"Player\")` -- a mock's backing Folder is invisible to an `IsA`\nwalk, so code resolving the owning player from a descendant adds the explicit OR clause:\n\n```lua\nlocal player = instance:FindFirstAncestorWhichIsA(\"Player\") or PlayerMock.findFirstAncestorMock(instance)\n```\n\nLike the engine call, the walk starts at the parent -- `instance` itself is never returned.",
            "params": [
                {
                    "name": "instance",
                    "desc": "",
                    "lua_type": "Instance"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Player?"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 253,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "getMockByUserId",
            "desc": "Returns the mock currently in the DataModel whose `UserId` stand-in matches, or nil. The mock\ncounterpart of `Players:GetPlayerByUserId` -- the resolver for code paths keyed by userId alone\n(e.g. [MarketplaceUtils.promiseUserOwnsGamePass]), where no player value is in hand to\n`isMock`-branch on:\n\n```lua\nlocal mockPlayer = PlayerMock.getMockByUserId(userId)\nif mockPlayer ~= nil then\n\tresult = PlayerMock.readLookup(mockPlayer, \"MarketplaceService.UserOwnsGamePassAsync\", gamePassId)\nelse\n\tresult = MarketplaceService:UserOwnsGamePassAsync(userId, gamePassId)\nend\n```\n\nLike the engine call, only mocks in the game resolve -- discovery runs over [PlayerMock.TAG],\nand tag resolution is DataModel-scoped, so an unparented (or destroyed) mock reads back as nil.\nReal UserIds are unique; seed mocks the same way, since the first match wins.",
            "params": [
                {
                    "name": "userId",
                    "desc": "",
                    "lua_type": "number"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Player?"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 289,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "getMockByUsername",
            "desc": "Returns the mock currently in the DataModel whose username stand-in matches, or nil. The mock\ncounterpart of `Players:GetUserIdFromNameAsync` -- the resolver for code paths keyed by username\nalone (e.g. [PlayersServicePromises.promiseUserIdFromName]). A mock's username is its\n\"UserService.GetUserInfosByUserIdsAsync\" lookup's `Username`, which defaults to the mock's\n`Name` -- the same member that holds a real Player's username.\n\nLike [PlayerMock.getMockByUserId], only mocks in the game resolve, and the first match wins.",
            "params": [
                {
                    "name": "username",
                    "desc": "",
                    "lua_type": "string"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Player?"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 313,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "getMockFromCharacter",
            "desc": "Returns the mock currently in the DataModel whose `Character` stand-in is the given model, or\nnil. The mock counterpart of `Players:GetPlayerFromCharacter` -- the resolver for code paths\nthat start from a character (or a part of one) with no player value in hand to `isMock`-branch\non (e.g. [CharacterUtils.getPlayerFromCharacter]):\n\n```lua\nlocal player = Players:GetPlayerFromCharacter(model) or PlayerMock.getMockFromCharacter(model)\n```\n\nLike the engine call, only the exact character model matches -- a descendant part resolves nil,\nso callers walking up from a descendant keep their own ancestor walk -- and only mocks in the\ngame resolve (discovery runs over [PlayerMock.TAG], and tag resolution is DataModel-scoped).",
            "params": [
                {
                    "name": "character",
                    "desc": "",
                    "lua_type": "Instance"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Player?"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 346,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "read",
            "desc": "Reads a stand-in native `Player` property off a mock: the seeded backing attribute, or the\npre-authored typed default when unset. Errors on anything that is not a [PlayerMock] -- including a\nreal `Player` -- so call sites must branch explicitly:\n\n```lua\nlocal userId = if PlayerMock.isMock(player) then PlayerMock.read(player, \"UserId\") else player.UserId\n```\n\nKeeping the real-Player read as plain member access preserves luau-lsp's native property typing on\nthe hot path instead of funneling every read through this `any`-returning helper.",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                },
                {
                    "name": "propertyName",
                    "desc": "",
                    "lua_type": "string"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "any"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 374,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "write",
            "desc": "Mocks a native `Player` property on a mock by writing its backing attribute, which also fires the\ninstance's `GetAttributeChangedSignal(propertyName)` so observers see the change.\n\nWriting `Character = nil` carries the engine's despawn semantics (the model is destroyed) --\nsee [PlayerMock.removeCharacter].",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                },
                {
                    "name": "propertyName",
                    "desc": "",
                    "lua_type": "string"
                },
                {
                    "name": "value",
                    "desc": "",
                    "lua_type": "any"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 405,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "getPropertyChangedSignal",
            "desc": "Returns the signal that fires when the given stand-in property changes on a mock: the backing\nattribute's changed signal, or the backing ObjectValue's Value-changed signal for Instance-valued\nmembers like `Character`. Mock-only, like [PlayerMock.read] -- the real-Player path stays\n`player:GetPropertyChangedSignal(propertyName)`.",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                },
                {
                    "name": "propertyName",
                    "desc": "",
                    "lua_type": "string"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "RBXScriptSignal"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 450,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "readLookup",
            "desc": "Reads the injected result for an ID-keyed engine call off a mock: the value a test injected\nthrough [PlayerMock.writeLookup], or the domain's pre-authored default when unset. The domain is\nthe canonical `Service.Method` the production code path bottoms out in, validated against the\npre-authored set so a typo errors instead of silently reading a default.\n\nLike [PlayerMock.read], this is mock-only and errors on anything else (including a real\n`Player`), so consumers branch explicitly and the real-Player path keeps calling the real\nservice:\n\n```lua\nif PlayerMock.isMock(player) then\n\t-- same raw result shape the engine call returns, so parsing below runs unchanged\n\treturn PlayerMock.readLookup(player, \"GroupService.GetRolesInGroupAsync\", groupId)\nend\nreturn GroupService:GetRolesInGroupAsync(player.UserId, groupId)\n```\n\nEffect-recording domains (e.g. `StarterGui.SetCoreGuiEnabled`) run the same machinery in the\nother direction: production wrote the value through [PlayerMock.writeLookup], and the test reads\nit here to assert the engine effect.",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                },
                {
                    "name": "domain",
                    "desc": "a known lookup domain, e.g. \"GroupService.GetRolesInGroupAsync\"",
                    "lua_type": "string"
                },
                {
                    "name": "key",
                    "desc": "what the call is keyed by (groupId, gamePassId, CoreGuiType, subscriptionId, ...)",
                    "lua_type": "number | EnumItem | string"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "any"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 673,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "writeLookup",
            "desc": "Injects the result a mock answers for an ID-keyed engine call, or clears it back to the\ndomain default with nil. Because the value is stored on the mock -- one attribute per\n(domain, key) -- every consumer whose answer derives from the same engine call resolves the\nsame value, and the write fires the backing attribute's `GetAttributeChangedSignal` so\nobservers see the change.\n\n```lua\nPlayerMock.writeLookup(player, \"GroupService.GetRolesInGroupAsync\", 372, {\n\tIsMember = true,\n\tRoles = { { Name = \"Admin\", Rank = 230 } },\n})\nPlayerMock.writeLookup(player, \"MarketplaceService.UserOwnsGamePassAsync\", 12345, true)\n```\n\nFor effect-recording domains (e.g. `StarterGui.SetCoreGuiEnabled`) the writer is production\ncode instead: it performs the engine effect on the mock, and the test observes it through\n[PlayerMock.readLookup] or the backing attribute's changed signal.",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                },
                {
                    "name": "domain",
                    "desc": "a known lookup domain, e.g. \"MarketplaceService.UserOwnsGamePassAsync\"",
                    "lua_type": "string"
                },
                {
                    "name": "key",
                    "desc": "what the call is keyed by (groupId, gamePassId, CoreGuiType, subscriptionId, ...)",
                    "lua_type": "number | EnumItem | string"
                },
                {
                    "name": "value",
                    "desc": "must match the domain's value shape; nil clears back to the default",
                    "lua_type": "any"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 716,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "getLookupChangedSignal",
            "desc": "Returns the signal that fires when the injected result for an ID-keyed engine call changes on a\nmock: the backing attribute's changed signal. This lets a production observer treat a mid-test\n[PlayerMock.writeLookup] as the engine's own change event -- e.g. [RxFriendUtils] re-reading\nfriendship when the \"Player.IsFriendsWithAsync\" domain changes, standing in for the CoreGui\nfriended/unfriended events a mock can never receive. Mock-only, like [PlayerMock.readLookup].",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                },
                {
                    "name": "domain",
                    "desc": "a known lookup domain, e.g. \"Player.IsFriendsWithAsync\"",
                    "lua_type": "string"
                },
                {
                    "name": "key",
                    "desc": "what the call is keyed by",
                    "lua_type": "number | EnumItem | string"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "RBXScriptSignal"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 747,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "loadCharacterAsync",
            "desc": "Emulates `Player:LoadCharacterAsync()` on a mock. The caller supplies the character model -- e.g.\n`Players:CreateHumanoidModelFromUserId`/`FromDescription` (both work in cloud test runs) or a\nhand-built rig -- or omits it to get a default R15 built from an empty `HumanoidDescription`\n(may yield while the engine builds it).\n\nThe observable sequence encodes the engine's avatar loading event ordering\n(https://devforum.roblox.com/t/avatar-loading-event-ordering-improvements/269607), and\nPlayerMock.spec asserts each step -- correct both together if that understanding is ever\ncorrected:\n\n1. `CharacterRemoving(old)` fires while `Character` still points at the old model and it is\n   still parented -- the event's \"just before removal\" contract (not part of the announcement)\n2. `Character` nils and the old character is destroyed (see [RxCharacterUtils.observeLastCharacterBrio]'s\n   assumption); nil-before-destroy lets observers tear down while the instance is still alive\n3. the new rig is fully built before any signal fires -- the announcement's \"appearance\n   initialized / rig built and scaled\" steps; the caller's rig (or the built default) stands in\n4. `Character` is set to the new model (the property `Changed` fires)\n5. the new character is parented to the Workspace\n6. `CharacterAdded(new)` fires -- after both the assignment and the Workspace parenting, per\n   the announcement above (the pre-2019 \"not in Workspace yet\" gotcha is gone)\n7. `CharacterAppearanceLoaded(new)` fires -- after `CharacterAdded`, with the rig finalized\n8. only then does the call return, mirroring \"LoadCharacter returns\" ending the announced order\n\nPer the same announcement, `CharacterAdded` fires only during avatar loading -- which is why a\nplain `PlayerMock.write(player, \"Character\", model)` deliberately does not fire it.\n\nEach call also replaces the mock's `Backpack` stand-in with a fresh empty one before any spawn\nsignal fires, like the engine does on respawn (minus the StarterPack copy) -- see\n[PlayerMock.getBackpack]. The first call additionally inserts the `StarterGear` stand-in,\nwhich later spawns keep -- see [PlayerMock.getStarterGear].",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                },
                {
                    "name": "character",
                    "desc": "the new character; nil builds a default R15 rig",
                    "lua_type": "Model?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Model"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 796,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "loadMinimalCharacterAsync",
            "desc": "[PlayerMock.loadCharacterAsync] with a minimal hand-built rig -- an anchored `HumanoidRootPart`\n(the `PrimaryPart`) and a `Humanoid` -- instead of a full R15 built from a `HumanoidDescription`.\nThat is the smallest shape character-driven code paths (equip flows, humanoid observers) accept,\nand building it never yields, so specs that only need *a* character spawn instantly:\n\n```lua\nlocal character = PlayerMock.loadMinimalCharacterAsync(playerMock)\n```",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Model"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 848,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "removeCharacter",
            "desc": "Emulates the character being removed with no replacement, i.e. `player.Character = nil`:\n`CharacterRemoving` fires while `Character` still points at the model, `Character` is set to\nnil, and the model is destroyed. No-op when no character is loaded.\n\nRuns automatically when the mock itself is destroyed, mirroring the engine cleaning up the\ncharacter when its player leaves or is kicked.",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 875,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "getBackpack",
            "desc": "Returns the mock's current `Backpack` stand-in, or nil before the first spawn -- the engine only\ninserts a player's Backpack when their character spawns. The stand-in is a genuine `Backpack`\ninstance parented to the mock (so it is the child named \"Backpack\", like a real Player's), which\nmeans production code that observes the backpack's children -- e.g.\n`RxInstanceUtils.observeLastNamedChildBrio(player, \"Backpack\", \"Backpack\")` -- works unchanged.\n\n[PlayerMock.loadCharacterAsync] replaces it with a fresh empty one on every spawn, like the\nengine does on respawn; the test parents `Tool`s into it directly:\n\n```lua\nlocal character = PlayerMock.loadCharacterAsync(player, rig)\nlocal backpack = assert(PlayerMock.getBackpack(player))\ntool.Parent = backpack\n```",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Backpack?"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 902,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "getStarterGear",
            "desc": "Returns the mock's current `StarterGear` stand-in, or nil before the first spawn -- the engine\nonly inserts a player's StarterGear alongside their first character spawn. Like the Backpack\nstand-in it is a genuine `StarterGear` instance parented to the mock, so class-based lookups\n(`player:FindFirstChildOfClass(\"StarterGear\")`) work unchanged; unlike the Backpack,\n[PlayerMock.loadCharacterAsync] never replaces it -- the engine keeps a player's StarterGear\nacross respawns. Consumers that dot-index `player.StarterGear` resolve it through the usual\nexplicit branch:\n\n```lua\nlocal starterGear = if PlayerMock.isMock(player)\n\tthen PlayerMock.getStarterGear(player)\n\telse player.StarterGear\n```",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "StarterGear?"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 926,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "getPlayerGui",
            "desc": "Returns the mock's `PlayerGui` stand-in, parented at construction -- mirroring the engine\ninserting a player's PlayerGui at join (unlike the Backpack, which only appears at first\nspawn). `PlayerGui` cannot be `Instance.new`'d, so like the mock itself the stand-in is really\na `Folder` (named \"PlayerGui\") typed as the native class; consumers resolve it through the\nusual explicit branch instead of `FindFirstChildOfClass`:\n\n```lua\nlocal playerGui = if PlayerMock.isMock(player)\n\tthen PlayerMock.getPlayerGui(player)\n\telse player:FindFirstChildOfClass(\"PlayerGui\")\n```\n\n[PlayerGuiUtils] branches this way internally, so consumers of it work against a mock without\nchanges. Tests parent ScreenGui/Frame surfaces into the stand-in directly.",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "PlayerGui"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 951,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "getPlayerScripts",
            "desc": "Returns the mock's `PlayerScripts` stand-in, parented at construction -- mirroring the engine\ninserting a player's PlayerScripts at join, like the PlayerGui stand-in. `PlayerScripts` cannot\nbe `Instance.new`'d, so the stand-in is really a `Folder` (named \"PlayerScripts\") typed as the\nnative class. A Folder can never satisfy an `IsA(\"PlayerScripts\")` class filter, so consumers\nobserving the child by class resolve it through the usual explicit branch:\n\n```lua\nlocal playerScriptsClassName = if PlayerMock.isMock(localPlayer) then \"Folder\" else \"PlayerScripts\"\nRxInstanceUtils.observeLastNamedChildBrio(localPlayer, playerScriptsClassName, \"PlayerScripts\")\n```\n\nTests parent script stand-ins (e.g. an `RbxCharacterSounds` `LocalScript`) into it directly.",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "PlayerScripts"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 975,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "kick",
            "desc": "Emulates `Player:Kick(message)` on a mock. Kick is the special case among the stand-ins: a\n*method* whose observable effect is the engine removing the player from the game, not a property\nor event a test seeds -- so the mock really performs the removal sequence instead of merely\nrecording the call:\n\n1. the kick message is recorded (see [PlayerMock.getKickMessage] -- no client exists to show it to)\n2. the character is removed (`CharacterRemoving` fires while `Character` still points at it,\n   `Character` nils, the model is destroyed), mirroring the engine cleaning up a kicked player's\n   character\n3. the mock leaves the DataModel (`Parent = nil`, not a destroy -- a held reference stays\n   readable), so the native `AncestryChanged` signal genuinely fires -- the same way a kicked\n   player's instance leaves `Players`\n\n`Players.PlayerRemoving` is a `Players`-service event only the engine fires, so consumers of that\nevent cannot observe a mock kick; observe `AncestryChanged` instead.\n\nProduction code branches explicitly, like every other mock seam:\n\n```lua\nif PlayerMock.isMock(player) then\n\tPlayerMock.kick(player, reason)\nelse\n\tplayer:Kick(reason)\nend\n```",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                },
                {
                    "name": "message",
                    "desc": "recorded for [PlayerMock.getKickMessage]; nil records \"\"",
                    "lua_type": "string?"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 1016,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "getKickMessage",
            "desc": "Returns the message a mock was kicked with (via [PlayerMock.kick]), or nil when it was never\nkicked. A kick with no message reads back as `\"\"`. Stays readable after the kick destroys the\nmock, as long as the caller holds a reference.",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "string?"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 1037,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "getSignal",
            "desc": "Reads a stand-in native `Player` event off a mock: the genuine native signal when the backing\nFolder inherits it from `Instance` (`AncestryChanged`, `Destroying`, ...), otherwise a\n`BindableEvent`-backed signal a test fires through [PlayerMock.fireSignal]. The name is\nvalidated against the engine's reflected `Player` events, so a typo errors instead of\nreturning a signal that can never fire.\n\nLike [PlayerMock.read], this is mock-only and errors on anything else (including a real\n`Player`), so call sites branch explicitly and the real-Player path stays plain member access:\n\n```lua\nlocal chatted = if PlayerMock.isMock(player) then PlayerMock.getSignal(player, \"Chatted\") else player.Chatted\n```",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                },
                {
                    "name": "eventName",
                    "desc": "",
                    "lua_type": "string"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "RBXScriptSignal"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 1118,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "fireSignal",
            "desc": "Fires the backing signal for a `Player`-class event on a mock, so code connected through\n[PlayerMock.getSignal] observes the event as if the engine had fired it.\n\nOnly `Player`-class events can be fired; events the backing Folder inherits from `Instance`\nresolve to genuine native signals, which only the engine fires.",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                },
                {
                    "name": "eventName",
                    "desc": "",
                    "lua_type": "string"
                },
                {
                    "name": "...",
                    "desc": "Event arguments delivered to connected handlers.",
                    "lua_type": "any"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 1144,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "bindInput",
            "desc": "Emulates a context-restricted `ContextActionService` call on a mock. The domain is the canonical\n`Service.Method` -- validated against the pre-authored set so a typo errors, like\n[PlayerMock.readLookup] -- and the args after it mirror the engine call's own args, so a\nproduction mock branch is the identical call aimed at the mock local player (there is no input\nto receive):\n\n```lua\nlocal localPlayer = Players.LocalPlayer or PlayerMock.getMockedLocalPlayer()\nif localPlayer ~= nil and PlayerMock.isMock(localPlayer) then\n\tPlayerMock.bindInput(localPlayer, \"ContextActionService.BindAction\", \"Drag\", onDragAction, false, Enum.UserInputType.MouseButton2)\nelse\n\tContextActionService:BindAction(\"Drag\", onDragAction, false, Enum.UserInputType.MouseButton2)\nend\n```\n\nUnbinding goes through the same entry point -- the domain names the operation:\n\n```lua\nPlayerMock.bindInput(localPlayer, \"ContextActionService.UnbindAction\", \"Drag\")\n```\n\nA test dispatches a bound action through [PlayerMock.fireInput] as if the engine had routed\ninput to it. What the emulation deliberately does not model: touch buttons, priority routing,\ninput-type routing, and the engine's bind stack -- all bind domains share one action registry\nper mock (like the engine's), and rebinding a name simply replaces the callback.",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                },
                {
                    "name": "domain",
                    "desc": "a known input domain, e.g. \"ContextActionService.BindAction\"",
                    "lua_type": "string"
                },
                {
                    "name": "actionName",
                    "desc": "",
                    "lua_type": "string"
                },
                {
                    "name": "...",
                    "desc": "the engine call's remaining args, e.g. `functionToBind, createTouchButton, ...inputTypes`",
                    "lua_type": "any"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 1252,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "isInputBound",
            "desc": "Returns whether the given action is currently bound on a mock (via [PlayerMock.bindInput]).\nKeyed by the action name alone -- both bind domains share one action registry, like the engine.",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                },
                {
                    "name": "actionName",
                    "desc": "",
                    "lua_type": "string"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 1269,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "fireInput",
            "desc": "Dispatches a bound action on a mock, invoking the bound callback with\n`(actionName, userInputState, inputObject)` -- the argument order the engine uses -- and\nreturning its result. Test-side counterpart of [PlayerMock.bindInput], like\n[PlayerMock.fireSignal] is for [PlayerMock.getSignal].\n\nErrors when the action is not bound: with no engine sink logic modeled, firing an unbound\naction can only be a test mistake (typo'd name, or firing after the production unbind).\n\nThe invocation goes through the backing `BindableFunction`, so a stand-in `inputObject` must be\nbindable-safe -- a real `InputObject` reference, or a plain metatable-free table of the fields\nthe callback reads (e.g. `UserInputType`, `Position`, `Delta`).",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock",
                    "lua_type": "Player"
                },
                {
                    "name": "actionName",
                    "desc": "",
                    "lua_type": "string"
                },
                {
                    "name": "userInputState",
                    "desc": "",
                    "lua_type": "Enum.UserInputState"
                },
                {
                    "name": "inputObject",
                    "desc": "a real InputObject or a bindable-safe stand-in table",
                    "lua_type": "any?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Enum.ContextActionResult?"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 1295,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "setMockedLocalPlayer",
            "desc": "Designates a mock as the local player for the client realm (or clears it with nil). Read through\n[PlayerMock.getMockedLocalPlayer] by client code falling back from `Players.LocalPlayer`.\n\nCall this directly *before* booting bags to pre-designate -- matching production, where\n`Players.LocalPlayer` exists before any service runs -- and a booting [PlayerMockServiceClient]\nadopts the designation as its local player and owns its cleanup. After boot, designate through\n[PlayerMockServiceClient.SetLocalPlayer] instead, which records the designation per simulated\nclient.\n\nThe mock must be parented into the DataModel first: the designation is carried as a\nCollectionService tag, and `GetTagged` only resolves parented instances -- an unparented\ndesignation would silently read back as nil.",
            "params": [
                {
                    "name": "player",
                    "desc": "must be a PlayerMock in the DataModel, or nil to clear",
                    "lua_type": "Player?"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 1330,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        },
        {
            "name": "getMockedLocalPlayer",
            "desc": "Returns the mock designated as the local player (via [PlayerMockServiceClient]), or nil. This is\nonly ever the mock -- there is deliberately no helper that resolves the real `Players.LocalPlayer`,\nso call sites fall back explicitly and the real read stays visible to luau-lsp:\n\n```lua\nlocal localPlayer = Players.LocalPlayer or PlayerMock.getMockedLocalPlayer()\n```",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Player?"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 1358,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        }
    ],
    "properties": [
        {
            "name": "TAG",
            "desc": "The CollectionService tag every mock carries from construction: the place-wide discovery channel\n[PlayerMockService] and [PlayerMockServiceClient] observe. Replication is the default -- a real\n`Player` exists for every peer, so a mock is discoverable from any ServiceBag in either realm the\nmoment it is parented into the DataModel (tag resolution is DataModel-scoped), and a destroyed\n(or kicked) mock drops out automatically.",
            "lua_type": "string",
            "readonly": true,
            "source": {
                "line": 94,
                "path": "src/player-mock/src/Shared/PlayerMock.lua"
            }
        }
    ],
    "types": [],
    "name": "PlayerMock",
    "desc": "In-memory stand-in for a Roblox `Player` used by tests. A real `Player` cannot be\n`Instance.new`'d and no client joins a headless Open Cloud test place, so this builds a\n`Folder` that carries the marker and identity attributes production code reads -- letting a\n\"player\" flow through guards and providers without a real join.\n\nIt is the shared replacement for the ad-hoc `Instance.new(\"Folder\") :: Player` stand-ins that\nhad accreted across the test suites. Guards keep their hard `Player` assert and add an explicit\nOR clause so support for the mock is greppable rather than a silent weakening:\n\n```lua\nassert(player:IsA(\"Player\") or PlayerMock.isMock(player), \"Bad player\")\n```\n\nNative `Player` members a Folder cannot expose are read through [PlayerMock.read], which is\nmock-only and errors on anything else (including a real `Player`). Consumers branch explicitly --\n`if PlayerMock.isMock(player) then PlayerMock.read(player, \"UserId\") else player.UserId` -- so the\nreal-Player path stays plain member access that luau-lsp can type-check. Each property is backed by\na same-named attribute (Instance-valued members like `Character` by an ObjectValue child), so a test\ncan mock a value and observe changes via [PlayerMock.getPropertyChangedSignal]:\n\n```lua\nlocal player = PlayerMock.new({ UserId = 12345, AccountAge = 30 })\nplayer.Parent = game:GetService(\"Players\") -- where real players live; see PlayerMockService.CreatePlayer\n\nassert(PlayerMock.isMock(player))\nassert(PlayerMock.read(player, \"UserId\") == 12345)\nassert(PlayerMock.read(player, \"MembershipType\") == Enum.MembershipType.None)\n\nPlayerMock.write(player, \"AccountAge\", 31) -- fires GetAttributeChangedSignal(\"AccountAge\")\n```\n\nNative `Player` events follow the same shape through [PlayerMock.getSignal] -- mock-only, with\nthe name validated against the engine's reflected `Player` events so a typo errors instead of\nreturning a signal that can never fire -- and [PlayerMock.fireSignal] as the test-side trigger:\n\n```lua\nlocal chatted = if PlayerMock.isMock(player) then PlayerMock.getSignal(player, \"Chatted\") else player.Chatted\nmaid:GiveTask(chatted:Connect(onChatted))\n\nPlayerMock.fireSignal(player, \"Chatted\", \"hello\") -- test-side\n```\n\nResults of ID-keyed engine calls (group rank, gamepass/asset ownership, ...) that production\ncode fetches from a Roblox web API by (player, id) follow the same shape through\n[PlayerMock.writeLookup] / [PlayerMock.readLookup], with the domain named after the canonical\n`Service.Method` being intercepted. The injected result lives on the mock -- one attribute per\n(domain, key) -- so it is centralized per player: every consumer whose answer derives from the\nsame engine call resolves the same value, instead of each call site stubbing its own copy that\ncan drift.\n\n```lua\nPlayerMock.writeLookup(player, \"GroupService.GetRolesInGroupAsync\", 372, {\n\tIsMember = true,\n\tRoles = { { Name = \"Admin\", Rank = 230 } },\n})\n-- GroupUtils.promiseRankInGroup(player, 372) now resolves 230 everywhere it is asked,\n-- and promiseRoleInGroup(player, 372) resolves the matching \"Admin\". Values are the raw\n-- engine result shape (see GroupTestUtils.assignGroupInfo for a friendlier writer).\n```",
    "source": {
        "line": 65,
        "path": "src/player-mock/src/Shared/PlayerMock.lua"
    }
}