Skip to content

feat: let a player answer for itself and hand back its files (v2.3.0) - #17

Merged
niqibiao merged 2 commits into
mainfrom
feat/player-remote-debugging
Aug 23, 2026
Merged

niqibiao merged 2 commits into
mainfrom
feat/player-remote-debugging

Conversation

@niqibiao

Copy link
Copy Markdown
Owner

What

A player already served /command, /batch and /health, but almost every
action was marked editor-only and there was no way to get a file off it. This
opens up what a player can genuinely answer, and adds a byte channel back.

  • runtime/info reports the responding process's device facts and its
    well-known paths (consoleLogPath, persistentDataPath, temporaryCachePath,
    dataPath). Control partition grows 5 → 6; the registry totals 62.
  • /download serves any absolute path the target can read, capped at 32MB
    per response with Range for anything larger. The editor serves it too — a
    teammate's editor is just as remote as their player.
  • editorOnly dropped from actions that already work at runtime:
    scene/hierarchy, gameobject/*, transform/set, screenshot/game_view,
    profiler/start|stop|status. Undo calls compile out; profiler/save keeps
    the flag because ProfilerDriver is editor-only.
  • component/get|modify at runtime reflects over public and
    [SerializeField] private instance fields — what those components actually
    serialize. Built-in components are refused there rather than reported in a
    second, incompatible shape, because the editor reports serialized names
    (m_LocalPosition) while reflection reports C# members.
  • component/modify that wrote nothing now fails instead of returning ok
    with an empty list. A wrong field name or an unparseable value used to be
    reported as success while the value never changed.

Verified against a live IL2CPP development player

  • runtime/info distinguishes the two processes (isEditor=false,
    WindowsPlayer, install-directory dataPath) where the editor reports its own.
  • Reflection reports exactly the 9 serializable fields of a probe component —
    public, [SerializeField] private, and inherited public — and excludes
    [NonSerialized], non-public, and static. Managed stripping did not interfere.
  • component/modify applied 9 of 12 requested fields (int, float, string, bool,
    Vector3, Color, enum, private serialized, inherited), refusing the three that
    are not serializable; values read back correct.
  • Built-in components refused for both read and write; the editor still answers
    them through SerializedObject.
  • A profiler capture recorded in the player (11.9MB) and a game-view screenshot
    were both retrieved byte-identical over /download, as was a 40MB file across
    successive ranges.

🤖 Generated with Claude Code

niqibiao and others added 2 commits August 19, 2026 22:31
A remote player could barely be reached. Of 61 commands only five ran
outside the editor, and every command that produces an artifact wrote it
to local disk and returned a path -- which names a file on someone else's
machine when the service is on a phone or a colleague's editor. There was
no outbound byte channel at all.

Two changes fix that.

A download route, GET with Range support, returns any file from the
machine running the service. Both the editor and a player serve it: a
colleague's editor is just as unreachable as their player. The path is
unrestricted deliberately -- a session can already read any file by
executing C#, so restricting the route would only push callers back to
that without making anything safer. One response is capped at 32MB so a
mistaken call cannot pull gigabytes into memory; larger files come back
through Range.

Then, everything that already worked at runtime stops claiming it needs
the editor. Several handlers touched no editor API whatsoever and were
gated only because their whole file was; others needed nothing more than
their undo bookkeeping compiled out. That takes the player-capable set
from 5 commands to 23, including the live scene hierarchy, GameObject and
transform mutation, Game View capture, and profiler recording -- which,
with the download route, finally makes "record on device, open the
capture at home" possible.

Two bounded exceptions. Undo does not exist in a player, so mutations
made there cannot be reversed. And component/get|modify covers only
components the project declares: the editor reports serialized names via
SerializedObject, while a player must reflect over fields, and those two
agree only for project types. Built-in components keep their state in
native-backed properties whose getters can have side effects -- reading
Renderer.material instantiates a copy -- so they are refused rather than
reported in a second, incompatible shape.

New runtime/info reports what the responding process is and where it
keeps files, which is how a caller learns the absolute path to hand the
download route. On Android and iOS consoleLogPath is empty because those
platforms write no log file; a project's own logging decides what there
is to retrieve.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Asking to modify fields and writing none came back ok with an empty
modifiedFields list, so a caller whose field name was wrong -- or whose
value did not parse as that field's type -- was told the write had
landed while the value never changed. Return an error naming the fields
that were requested instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 52ed89da15

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +327 to +331
return f.ToString("R");
case double d:
return d.ToString("R");
case Vector2 v2:
return $"{v2.x},{v2.y}";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Format reflected numeric values with invariant culture

On players whose current culture uses a comma decimal separator, these formatters produce values such as 1,5, while TrySetFieldValue parses scalar and vector values using InvariantCulture and also treats commas as vector delimiters. Consequently, feeding component/get output back into component/modify either fails for scalar floats or silently changes a vector such as (1.5, 2.5) to (1, 5). Format every numeric component using invariant culture so the runtime representation round-trips.

Useful? React with 👍 / 👎.

Comment on lines +309 to +312
var optedIn = Attribute.IsDefined(field, typeof(SerializeField));
if (field.IsPublic || optedIn)
{
fields.Add(field);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude readonly fields from runtime component reflection

For a custom MonoBehaviour with a public readonly field, this predicate includes that field even though Unity does not serialize readonly fields. Runtime component/get therefore exposes a property that the editor implementation does not, and component/modify may either mutate an invariant through reflection or reject a field the preceding get advertised. Filter IsInitOnly fields, along with the other Unity serialization exclusions, before returning them.

Useful? React with 👍 / 👎.

@niqibiao
niqibiao merged commit d025d67 into main Aug 23, 2026
1 check passed
@niqibiao
niqibiao deleted the feat/player-remote-debugging branch August 23, 2026 12:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant