scenario-unity-architecture

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Unity architecture (gameplay programmer / architect)

Unity 架构(gameplay programmer / architect)

Expert architecture in Unity is judged by what survives the edges: the second Play session, a destroyed object mid-await, a relaunch of the built player, a class rename, a merge of two branches, a prefab dropped into an empty scene. Data lives in assets, state lives in plain C# and files, systems talk through events they do not own, and every claim is pinned by a test the agent can run without a mouse. Target: Unity 6000.3.21f1, Input System 1.20.0, Test Framework 1.6.0, macOS. If a sibling skill named here is missing from your available skills, ask the user to install it (
npx skills add scenario-labs/skills --skill <name>
); unattended, proceed from tool schemas and flag the gap.
REQUIRED BACKGROUND: scenario-unity-expert (channels, review loop, 6.3 traps). Toolkit:
ut_env
,
ut_run
,
ut_live
plus this skill's
scripts/ut_arch.py
,
scripts/AgentKit/Architecture/
jobs and
scripts/templates/
(a tested Game.Core / Game.Runtime / Game.Editor / tests layout).
Status (2026-09-24): every procedure ran in Unity 6000.3.21f1 on this Mac:
tests/code/unity-architecture/run_all.sh --live
(results in
archive/tests/unity-architecture/live_results.jsonl
).
Unity 中的专家级架构能力,体现在它能否经受住各种边界情况:第二次 Play session、await 途中对象被销毁、重新启动 built player、类重命名、两个分支合并,以及把 prefab 放入空 scene。数据存放在 assets 中,state 存放在普通 C# 与文件中,系统通过并非由自身拥有的 events 进行通信,并且每项结论都由 agent 无需使用鼠标即可运行的 test 加以验证。目标环境:Unity 6000.3.21f1、Input System 1.20.0、Test Framework 1.6.0、macOS。如果这里提到的某个 sibling skill 未包含在你的可用 skills 中,请让用户安装它(
npx skills add scenario-labs/skills --skill <name>
);在无人值守模式下,则依据 tool schemas 继续执行并标明差距。
必需背景: scenario-unity-expert(channels、review loop、6.3 traps)。工具包:
ut_env
、
ut_run
、
ut_live
,以及本 skill 的
scripts/ut_arch.py
、
scripts/AgentKit/Architecture/
jobs 和
scripts/templates/
(一套经过测试的 Game.Core / Game.Runtime / Game.Editor / tests 布局)。
状态(2026-09-24): 每项流程均已在这台 Mac 上的 Unity 6000.3.21f1 中运行:
tests/code/unity-architecture/run_all.sh --live
(结果位于
archive/tests/unity-architecture/live_results.jsonl
)。

Stance (the expert delta)

立场(专家的差异化能力)

  1. ScriptableObjects are definitions, never saves. Play-mode writes to an SO persist in the Editor and reset on every launch of a build (Code Monkey, 5a-ztc5gcFw [00:04:17]). Observed sharper: the write lives only in Editor memory (
    IsDirty
    false, the
    .asset
    unchanged even after
    SaveAssets
    ), so a file hash never sees it; only an in-memory guard in the process that plays does (
    so_guard
    , the prefab SO test). A macOS player run twice started from the baked value while the save FILE carried over. Runtime modifiers apply to a struct copy (
    config.Stats
    , Tarodev, tE1qH8OxO2Y [00:11:18]); live registries are runtime sets, never scene searches (Hipple, raQ3iHhE_Kk [00:40:06]). Saves: versioned JSON of ids, atomic write, backup, migrations.
  2. Read Enter Play Mode before trusting any Play test. With domain reload off (an option in 6.3, the default for new 6.6 projects) a static counter read 2 and a static event had 2 subscribers on the second Play (observed). Unregister static handlers on
    ExitingPlayMode
    (the Manual's best practice; observed clean on its own) and reset values at
    SubsystemRegistration
    ;
    DoublePlay
    proves both. Scene reload off makes Editor startup unrepresentative, and a script change still reloads the domain (6.3 Manual).
  3. Every await has an owner. Awaits survive Destroy and Play exit (Unity, eXkFPoEp3BA [00:13:28]): a token-less loop ticked 13 to 14 more times in the 0.3 s after
    Destroy
    (observed). MonoBehaviours pass
    destroyCancellationToken
    (
    Lifetime.Linked(this)
    when IO must also stop at exit); plain C# services pass
    Application.exitCancellationToken
    . Never await an Awaitable twice (pooled:
    AsTask()
    ), never block on a Task. Release players skip the main-thread check: off the main thread a development player threw on
    persistentDataPath
    ,
    Time.time
    ,
    transform
    and
    new GameObject
    , the release player threw nothing (observed). Smoke-test threading in a development build.
  4. Designers wire, code decides. Serialized SO references are the injector designers can see (Hipple [00:14:39]). Channels decouple sender and receivers, iterate listeners backwards ([00:33:23]) and ship a debug plan: counts, a log toggle,
    RaiseDebug()
    from a job, an inspector button ([00:36:07]). UnityEvent is a serialized function call ([00:29:26]): rare designer-wired responses only (0 B per invoke measured on 6.3, but targets are hard references resolved by method name). One composition root runs first (
    [DefaultExecutionOrder(-1000)]
    , git-amend, PJcBJ60C970 [00:12:01]; without it a consumer found nothing, observed) and fails loudly. Each prefab runs alone in an empty scene ([00:16:29]). Statics suit a small solo project only (Weimann, hQE8lQk9ikE [00:01:28]; Tarodev [00:03:00]), with resets. Enums only for sets fixed "by some law"; categories designers extend become SO assets ([00:46:39]).
  5. Rebinding is a state machine with six traps. The action must be disabled (the package throws otherwise); Escape does not cancel a Button rebind by default; a composite head cannot be rebound; duplicates are judged on
    effectivePath
    , composite parts only against earlier parts, modifier combos per
    shortcutKeysConsumeInput
    (off by default: Shift+B also fires B); Reset All deletes the persisted overrides;
    LoadBindingOverridesFromJson
    wipes existing overrides unless passed
    false
    (samyam, csqVa2Vimao [00:12:51], [00:27:44], [00:33:22]; 1.20 manual; all tested). For the UI, start from the Rebinding UI sample: in 1.20 it already disables and disposes but lacks four fixes (
    ut_arch.rebind_audit
    ). Each
    new
    of a generated input class is a separate asset: share one.
  6. Assemblies are a contract. A Core with
    noEngineReferences
    holds rules testable in milliseconds; runtime asmdefs never reference editor ones (the Editor compiles it, the player build fails, observed). Test asmdefs reference
    Unity.InputSystem.TestFramework
    ;
    testables
    was not needed on 6000.3.21f1 (observed).
  7. Serialized data survives refactors and merges only on purpose.
    [FormerlySerializedAs]
    for fields,
    [MovedFrom]
    for renamed
    [SerializeReference]
    classes (without it the entry loaded as null, observed),
    [Serializable]
    on every
    [SerializeReference]
    type (6.3 tolerated its absence; 6.4 requires it per the version notes). UnityYAMLMerge exits 2 on any conflicted field even with
    -l
    /
    -r
    , and without them writes the BASE value (observed): check the exit code, then open the result in Unity.
  1. ScriptableObject 是定义,绝不是存档。 在 Play mode 中对 SO 的写入会保留在 Editor 中,并且每次启动 build 时都会重置(Code Monkey, 5a-ztc5gcFw [00:04:17])。更精确的观察结果是:该写入只存在于 Editor 内存中(
    IsDirty
    为 false,即使调用
    SaveAssets
    后
    .asset
    也未改变),因此 file hash 永远检测不到它;只有实际运行游戏的进程中的 in-memory guard 才能检测到(
    so_guard
    、prefab SO test)。macOS player 连续运行两次时,第二次仍从 baked value 开始,而 save FILE 会保留下来。Runtime modifiers 应作用于 struct copy(
    config.Stats
    ,Tarodev, tE1qH8OxO2Y [00:11:18]);live registries 应使用 runtime sets,绝不能通过 scene searches 获取(Hipple, raQ3iHhE_Kk [00:40:06])。Saves:包含 ids 的 versioned JSON、atomic write、backup 和 migrations。
  2. 信任任何 Play test 之前,先读取 Enter Play Mode 设置。 关闭 domain reload(6.3 中的一项选项,也是 6.6 新项目的默认设置)时,第二次 Play 中 static counter 的读数为 2,static event 有 2 个 subscribers(实测)。在
    ExitingPlayMode
    中注销 static handlers(Manual 推荐的 best practice;单独使用时实测结果干净),并在
    SubsystemRegistration
    中重置值;
    DoublePlay
    可同时证明这两点。关闭 scene reload 会使 Editor startup 不具代表性,而 script 变更仍会 reload domain(6.3 Manual)。
  3. 每个 await 都必须有 owner。 Awaits 会在 Destroy 和 Play exit 之后继续存在(Unity, eXkFPoEp3BA [00:13:28]):一个不带 token 的 loop 在
    Destroy
    后的 0.3 s 内又 tick 了 13 到 14 次(实测)。MonoBehaviours 应传入
    destroyCancellationToken
    (IO 也必须在退出时停止时使用
    Lifetime.Linked(this)
    );普通 C# services 应传入
    Application.exitCancellationToken
    。绝不要 await 同一个 Awaitable 两次(pooled:使用
    AsTask()
    ),也绝不要阻塞等待 Task。Release players 会跳过 main-thread check:在非 main thread 上,development player 在访问
    persistentDataPath
    、
    Time.time
    、
    transform
    和执行
    new GameObject
    时抛出异常,而 release player 未抛出任何异常(实测)。应在 development build 中对 threading 进行 smoke test。
  4. Designers 负责 wiring,code 负责决策。 序列化的 SO references 是 designers 可见的 injector(Hipple [00:14:39])。Channels 解耦 sender 与 receivers、反向遍历 listeners([00:33:23]),并提供 debug plan:counts、log toggle、由 job 触发的
    RaiseDebug()
    、inspector button([00:36:07])。UnityEvent 是一种 serialized function call:仅用于少见的、由 designer wiring 的 responses(6.3 中实测每次 invoke 为 0 B,但 targets 是通过 method name 解析的 hard references)。一个 composition root 应最先运行(
    [DefaultExecutionOrder(-1000)]
    、git-amend、PJcBJ60C970 [00:12:01];缺少它时,consumer 找不到任何内容,实测),并且在失败时明确报错。每个 prefab 都应在空 scene 中独立运行([00:16:29])。Statics 仅适合小型 solo project(Weimann, hQE8lQk9ikE [00:01:28];Tarodev [00:03:00]),并且必须带 resets。Enums 仅适用于由某种“规则”固定的集合;designers 可以扩展的 categories 应变为 SO assets([00:46:39])。
  5. Rebinding 是一个包含六个陷阱的 state machine。 必须先禁用 action(否则 package 会抛出异常);默认情况下,Escape 不会取消 Button rebind;composite head 无法 rebind;duplicates 根据
    effectivePath
    判断,composite parts 只与更早的 parts 比较,modifier combos 则取决于
    shortcutKeysConsumeInput
    (默认关闭:Shift+B 也会触发 B);Reset All 会删除持久化的 overrides;除非传入
    false
    ,否则
    LoadBindingOverridesFromJson
    会清空现有 overrides(samyam, csqVa2Vimao [00:12:51]、[00:27:44]、[00:33:22];1.20 manual;均已测试)。对于 UI,应从 Rebinding UI sample 入手:在 1.20 中,它已经会 disable 和 dispose,但仍缺少四项修复(
    ut_arch.rebind_audit
    )。每次
    new
    generated input class 都会创建一个独立 asset:应共享同一个实例。
  6. Assemblies 是一份 contract。 使用
    noEngineReferences
    的 Core 承载可在数毫秒内测试的 rules;runtime asmdefs 绝不能引用 editor asmdefs(Editor 可以完成编译,但 player build 会失败,实测)。Test asmdefs 应引用
    Unity.InputSystem.TestFramework
    ;在 6000.3.21f1 上不需要
    testables
    (实测)。
  7. Serialized data 只有经过有意设计,才能在 refactors 和 merges 中存活。 Fields 使用
    [FormerlySerializedAs]
    ,重命名的
    [SerializeReference]
    classes 使用
    [MovedFrom]
    (缺少后者时,entry 会加载为 null,实测),每个
    [SerializeReference]
    type 都要添加
    [Serializable]
    (6.3 可以缺少该特性,但根据 version notes,6.4 要求必须添加)。即使带有
    -l
    /
    -r
    ,UnityYAMLMerge 遇到任何 conflicted field 都会以 2 退出;不带这些参数时则会写入 BASE value(实测):应检查 exit code,然后在 Unity 中打开结果。

Establish first

首先明确

Team shape (who wires behavior: designers or programmers), project lifetime, target platforms (Web has no managed threads), Enter Play Mode setting, input devices (keyboard-mouse, gamepad, touch, local multiplayer) and modifier shortcuts, save needs (slots, cloud, migrations), VCS (Git + LFS by default). Defaults: project-wide actions, Game.Core/Runtime/Editor/Tests asmdefs, SO definitions + plain C# state, event channels between systems, runtime sets for registries, one
GameRoot
, JSON saves under
persistentDataPath
, Git with the UnityYAMLMerge driver. Run
ArchJobs.ProjectFacts
and write its answers into the plan.
团队形态(由 designers 还是 programmers wiring behavior)、project lifetime、target platforms(Web 不支持 managed threads)、Enter Play Mode 设置、input devices(keyboard-mouse、gamepad、touch、local multiplayer)和 modifier shortcuts、save needs(slots、cloud、migrations)、VCS(默认使用 Git + LFS)。默认方案:project-wide actions、Game.Core/Runtime/Editor/Tests asmdefs、SO definitions + plain C# state、系统之间的 event channels、用于 registries 的 runtime sets、唯一的
GameRoot
、存放在
persistentDataPath
下的 JSON saves,以及配置 UnityYAMLMerge driver 的 Git。运行
ArchJobs.ProjectFacts
,并将其答案写入 plan。

Workflow

Workflow

  1. Scaffold and compile. Goal: rules in pure C#. How:
    ut_arch.install(P)
    ,
    ut_arch.scaffold(P)
    (A1), then
    ArchJobs.ProjectFacts
    (
    Unity -batchmode -nographics -quit -executeMethod AgentKit.Architecture.ArchJobs.ProjectFacts
    ). GATE: zero compile errors and warnings;
    ArchJobs.AssemblyGraph
    : Game.Core
    engine_refs == 0
    ,
    violations == []
    .
  2. Data as assets. Goal: definitions designers edit. How:
    ArchJobs.CreateAssets
    with
    ut_arch.item_specs()
    (
    SerializedObject
    edits,
    managed_refs
    for
    [SerializeReference]
    lists, SetDirty only on change) (A2, A13). GATE: second run all
    unchanged
    ;
    ItemDatabaseValidator
    clean.
  3. Core systems with tests. Goal: inventory, health, events, sets. How: templates;
    ut_run.run_tests(P, "EditMode", assemblies="Game.Tests.EditMode")
    . GATE: all pass;
    ut_arch.code_lint
    no warn or error in runtime code (A10).
  4. Save and load. Goal: state that survives relaunch. How:
    SaveService
    (A3). GATE: EditMode round trip, corrupt-file fallback, v1 migration;
    CrossProcessSave
    across two editor processes; a player run twice for persistence claims (A4); a development player when saves touch threads (A12).
  5. Input. Goal: every device, rebinding that persists and resets. How: project-wide actions,
    InputContext
    ,
    RebindFlow
    ,
    BindingStore
    ,
    .inputactions
    edited as JSON;
    ArchInputAudit.Audit
    (A5, A15). GATE:
    InputTestFixture
    tests per trap; audit without
    missing_scheme
    or
    duplicate_binding
    ;
    rebind_audit
    missing nothing.
  6. Async and lifecycle. Goal: no orphan work. How:
    async Awaitable Start()
    with a token,
    AsTask
    , PlayMode suite; async tests as a
    [UnityTest]
    returning the inner Awaitable, or
    async Task
    (A6, A7, A14). GATE: no tick after Destroy; prefab isolation and prefab SO guard pass.
  7. Play-mode hardening. Goal: session 2 equals session 1, events reach responses. How:
    ArchJobs.StaticStateAudit
    , then
    ArchPlayMode.DoublePlay
    with
    so_guard
    and
    invoke
    (
    RaiseDebug
    on each channel) in the project's mode (A8, A11). GATE: no
    arch.static_without_reset
    in own assemblies; watched values equal across sessions;
    so_changed_in_memory == []
    ; every invoke
    ok
    with its response counted.
  8. Version control. Goal: merges and moves never break references. How:
    ArchJobs.SetVcsSettings
    ,
    ut_arch.write_vcs_files
    ,
    git_setup_unity_merge
    ,
    meta_audit
    ,
    yaml_merge
    , then
    ArchJobs.InspectAsset
    (A9). GATE:
    meta_audit.ok
    ;
    .meta
    not in LFS; clean merges exit 0 and deserialize; conflicts stay unmerged with a report.
  9. Hand off with evidence (scenario-unity-expert report format): Unity call per step, Verified, Assumed.
  1. Scaffold 并完成编译。 目标:将 rules 放入 pure C#。方法:执行
    ut_arch.install(P)
    、
    ut_arch.scaffold(P)
    (A1),然后运行
    ArchJobs.ProjectFacts
    (
    Unity -batchmode -nographics -quit -executeMethod AgentKit.Architecture.ArchJobs.ProjectFacts
    )。GATE:零 compile errors 和 warnings;
    ArchJobs.AssemblyGraph
    :Game.Core
    engine_refs == 0
    ,
    violations == []
    。
  2. 将数据作为 assets。 目标:提供 designers 可编辑的 definitions。方法:使用
    ArchJobs.CreateAssets
    和
    ut_arch.item_specs()
    (
    SerializedObject
    edits、用于
    [SerializeReference]
    lists 的
    managed_refs
    、仅在发生变化时调用 SetDirty)(A2, A13)。GATE:第二次运行时全部为
    unchanged
    ;
    ItemDatabaseValidator
    clean。
  3. 使用 tests 构建 core systems。 目标:inventory、health、events、sets。方法:templates;
    ut_run.run_tests(P, "EditMode", assemblies="Game.Tests.EditMode")
    。GATE:全部通过;
    ut_arch.code_lint
    在 runtime code 中无 warn 或 error(A10)。
  4. Save 与 load。 目标:state 在 relaunch 后仍然存在。方法:
    SaveService
    (A3)。GATE:EditMode round trip、corrupt-file fallback、v1 migration;通过两个 editor processes 执行
    CrossProcessSave
    ;对于 persistence claims,执行两次 player run(A4);当 saves 涉及 threads 时,执行 development player(A12)。
  5. Input。 目标:支持所有 devices、持久化且可重置的 rebinding。方法:project-wide actions、
    InputContext
    、
    RebindFlow
    、
    BindingStore
    、以 JSON 编辑
    .inputactions
    ;
    ArchInputAudit.Audit
    (A5, A15)。GATE:针对每个陷阱的
    InputTestFixture
    tests;audit 不出现
    missing_scheme
    或
    duplicate_binding
    ;
    rebind_audit
    无任何缺项。
  6. Async 与 lifecycle。 目标:不存在 orphan work。方法:使用带 token 的
    async Awaitable Start()
    、
    AsTask
    、PlayMode suite;async tests 使用返回 inner Awaitable 的
    [UnityTest]
    ,或使用
    async Task
    (A6, A7, A14)。GATE:Destroy 后不再 tick;prefab isolation 和 prefab SO guard 通过。
  7. Play-mode hardening。 目标:session 2 等同于 session 1,events 能到达 responses。方法:
    ArchJobs.StaticStateAudit
    ,然后在项目模式下运行
    ArchPlayMode.DoublePlay
    ,并带上
    so_guard
    与
    invoke
    (每个 channel 上执行
    RaiseDebug
    )(A8, A11)。GATE:自有 assemblies 中不存在
    arch.static_without_reset
    ;watched values 在各 session 间相等;
    so_changed_in_memory == []
    ;每次 invoke 均为
    ok
    ,且其 response 计数正确。
  8. Version control。 目标:merges 和 moves 永远不会破坏 references。方法:
    ArchJobs.SetVcsSettings
    、
    ut_arch.write_vcs_files
    、
    git_setup_unity_merge
    、
    meta_audit
    、
    yaml_merge
    ,然后执行
    ArchJobs.InspectAsset
    (A9)。GATE:
    meta_audit.ok
    ;
    .meta
    不在 LFS 中;clean merges 以 0 退出且可以反序列化;conflicts 保持 unmerged 状态并生成 report。
  9. 携带 evidence 交接(scenario-unity-expert report format):每一步给出 Unity call、Verified、Assumed。

Numbers

Numbers

ValueRelative toSource
11 to 13 sone batch job on this project (editor boot included)observed
about 11 s / 16 sEditMode suite (33 tests) / PlayMode suite (17 tests), final runobserved
13 to 14 tickstoken-less 0.02 s Awaitable loop in the 0.3 s after
Destroy
observed
2 / 2static counter / static event subscribers in Play session 2, domain reload offobserved
4 of 6 / 0 of 6Unity calls that threw off the main thread in a development / release macOS playerobserved
0 Bheap growth over 10,000 channel raises or persistent UnityEvent invokes after warm-upobserved
about 37,000 to 61,000 fpsbatch
-nographics
Play mode (uncapped): wait on time, not frames
observed
0.05 s; false
PerformInteractiveRebinding
OnMatchWaitForAnother
;
shortcutKeysConsumeInput
default
package source 1.20; observed
next
Update
(33 ms at 30 fps)
Task continuation started on the main thread6.3 Manual
about 500 KBbinary size above which Git LFS paysPettersen 2016
exit 0 / 2UnityYAMLMerge clean / any conflicted field (also with
-l
,
-r
)
observed
114.7 MB / 311.2 MBmacOS Mono release / development player of a probe sceneobserved
数值相对于来源
11 到 13 秒此项目上的一个 batch job(含 editor boot)实测
约 11 秒 / 16 秒最终运行中的 EditMode suite(33 tests)/ PlayMode suite(17 tests)实测
13 到 14 ticks
Destroy
后 0.3 s 内、不带 token 的 0.02 s Awaitable loop
实测
2 / 2关闭 domain reload 时,Play session 2 中的 static counter / static event subscribers实测
4 of 6 / 0 of 6development / release macOS player 中在非 main thread 上抛出异常的 Unity calls实测
0 Bwarm-up 后执行 10,000 次 channel raises 或持久 UnityEvent invokes 的 heap growth实测
约 37,000 到 61,000 fpsbatch
-nographics
Play mode(uncapped):应等待时间,而不是 frames
实测
0.05 秒;false
PerformInteractiveRebinding
的
OnMatchWaitForAnother
;
shortcutKeysConsumeInput
默认值
package source 1.20;实测
next
Update
(30 fps 时为 33 ms)
在 main thread 上启动的 Task continuation6.3 Manual
约 500 KBGit LFS 开始值得使用的 binary size 阈值Pettersen 2016
exit 0 / 2UnityYAMLMerge clean / 存在任何 conflicted field(即使带有
-l
、
-r
)
实测
114.7 MB / 311.2 MBprobe scene 的 macOS Mono release / development player实测

Quality gates

Quality gates

  • Measurable: zero compile errors and warnings;
    AssemblyGraph.violations == []
    ; EditMode and PlayMode
    failed == 0
    in the full suite, not only single tests;
    code_lint
    no warn or error in runtime code;
    so_guard
    and the prefab SO test clean (
    SnapshotAssets
    changed_on_disk == []
    only proves editor tools saved nothing);
    StaticStateAudit
    clean;
    DoublePlay
    sessions equal;
    ArchInputAudit
    warn 0;
    rebind_audit
    complete;
    meta_audit.ok
    ; merge exit codes checked; player relaunch report (A4) and development-player report (A12) when persistence or threads are claimed.
  • Visual (few): after a scene merge, a camera capture before and after (scenario-unity-expert
    AgentCapture
    ); a rebinding screen shows the new binding text (scenario-unity-ui).
  • 可衡量: 零 compile errors 和 warnings;
    AssemblyGraph.violations == []
    ;完整 suite 中的 EditMode 和 PlayMode 均为
    failed == 0
    ,而不只是单个 tests;
    code_lint
    在 runtime code 中无 warn 或 error;
    so_guard
    和 prefab SO test clean(
    SnapshotAssets
    的
    changed_on_disk == []
    只能证明 editor tools 没有保存任何内容);
    StaticStateAudit
    clean;
    DoublePlay
    sessions 相等;
    ArchInputAudit
    warn 0;
    rebind_audit
    完整;
    meta_audit.ok
    ;检查 merge exit codes;当声称涉及 persistence 或 threads 时,提供 player relaunch report(A4)和 development-player report(A12)。
  • 视觉检查(少量): scene merge 前后各进行一次 camera capture(scenario-unity-expert
    AgentCapture
    );rebinding screen 显示新的 binding text(scenario-unity-ui)。

Common mistakes

Common mistakes

MistakeWhat it looks likeFix
save stored in an SOworks in the Editor, lost after relaunch of the build
SaveService
file; SO read-only (A3, A4)
SO guard by file hashpasses while Play writes an SO
so_guard
/ prefab SO test in the playing process (A11)
session state in an unreferenced SOvalue back to baked after a scene load, in the buildkeep it referenced or hold it in C#
statics with domain reload offsecond Play starts at 2, handler runs twiceExitingPlayMode unregister + SubsystemRegistration reset
await without tokenlogic runs after the object died or Play ended
destroyCancellationToken
,
Lifetime.App
for services
awaiting an Awaitable twiceexception or hang
AsTask()
once
threads checked in a release build onlyfine now, undefined laterdevelopment player smoke test (A12)
listeners iterated forwarda self-removing response skips the nextiterate backwards
composition root without execution ordera consumer's Awake finds no service
[DefaultExecutionOrder(-1000)]
rebinding an enabled action
InvalidOperationException
RebindFlow
disables, re-enables on complete and cancel
Button rebind without
WithCancelingThrough
Escape becomes the binding
.WithCancelingThrough("<Keyboard>/escape")
Reset All keeps the file; loads not layeredold rebinds return; earlier overrides vanish
BindingStore.ResetAll
;
Load(..., removeExisting: false)
InputSystem.actions
in tests or with PlayerInput
null after fixture tests; pairing breaks
EditorBuildSettings.TryGetConfigObject(...)
;
playerInput.actions
a generated input class per consumera rebind shows on one screen onlyone shared instance
runtime asmdef references editor asmdefEditor fine, player build fails
AssemblyGraph
explicit-reference check
class renamed behind
[SerializeReference]
entries load as null
[MovedFrom]
in the same change
trusting a merge outputconflicted field silently back to basecheck exit 2, resolve,
InspectAsset
错误表现修复
存档存储在 SO 中在 Editor 中有效,但 build 重新启动后丢失
SaveService
file;SO read-only(A3, A4)
使用 file hash 保护 SO检查通过,但 Play 实际写入了 SO在正在运行的进程中使用
so_guard
/ prefab SO test(A11)
session state 位于未引用的 SO 中scene load 后或 build 中,值恢复为 baked value保持其被引用,或保存在 C# 中
关闭 domain reload 时使用 statics第二次 Play 从 2 开始,handler 运行两次ExitingPlayMode unregister + SubsystemRegistration reset
await 不带 tokenlogic 在对象销毁或 Play 结束后仍运行
destroyCancellationToken
,services 使用
Lifetime.App
两次 await 同一个 Awaitableexception 或 hang只调用一次
AsTask()
仅在 release build 中检查 threads当前正常,以后结果未定义development player smoke test(A12)
正向遍历 listenersself-removing response 会跳过下一个反向遍历
composition root 缺少 execution orderconsumer 的 Awake 找不到 service
[DefaultExecutionOrder(-1000)]
rebind 一个已启用的 action
InvalidOperationException
RebindFlow
在完成和取消时分别 disable、re-enable
Button rebind 未使用
WithCancelingThrough
Escape 成为 binding
.WithCancelingThrough("<Keyboard>/escape")
Reset All 保留文件;loads 未分层旧 rebinds 恢复;较早的 overrides 消失
BindingStore.ResetAll
;
Load(..., removeExisting: false)
在 tests 或使用 PlayerInput 时访问
InputSystem.actions
fixture tests 后为 null;pairing 损坏
EditorBuildSettings.TryGetConfigObject(...)
;
playerInput.actions
每个 consumer 创建一个 generated input classrebind 只显示在一个 screen 上共享同一个实例
runtime asmdef 引用 editor asmdefEditor 正常,player build 失败
AssemblyGraph
explicit-reference check
重命名
[SerializeReference]
后面的 class
entries 加载为 null在同一次变更中添加
[MovedFrom]
信任 merge 输出conflicted field 静默恢复为 base检查 exit 2、解决冲突、执行
InspectAsset

Handoffs

Handoffs

  • Receives from scenario-unity-expert: brief, channel, project facts. From scenario-unity-gameplay: which systems need health, damage, state machines (the FSM pattern lives there).
  • Delivers to scenario-unity-ui:
    RebindFlow
    ,
    BindingStore
    ,
    InputContext
    , channels (with
    RaiseDebug
    ) for HUD and settings screens, with tests. To scenario-unity-gameplay:
    IDamageable
    ,
    Health
    ,
    Services
    ,
    GameRoot
    , runtime sets. To scenario-unity-pipeline-automation: asmdef layout,
    run_tests(..., assemblies=...)
    , development builds,
    .gitignore
    /
    .gitattributes
    , merge driver. To scenario-unity-performance: GC per frame (scenario-unity-expert
    AgentProfile
    ), per-object Awaitable loops. To scenario-unity-web: no managed threads (SaveAsync stays synchronous, no
    Task.Delay
    ). To scenario-unity-mobile: touch schemes,
    persistentDataPath
    . Packet: files written, tests with totals, audits with counts, Verified and Assumed.
  • 接收自 scenario-unity-expert:brief、channel、project facts。来自 scenario-unity-gameplay:哪些 systems 需要 health、damage、state machines(FSM pattern 位于其中)。
  • 交付给 scenario-unity-ui:用于 HUD 和 settings screens 的
    RebindFlow
    、
    BindingStore
    、
    InputContext
    、channels(包含
    RaiseDebug
    ),并附带 tests。交付给 scenario-unity-gameplay:
    IDamageable
    、
    Health
    、
    Services
    、
    GameRoot
    、runtime sets。交付给 scenario-unity-pipeline-automation:asmdef layout、
    run_tests(..., assemblies=...)
    、development builds、
    .gitignore
    /
    .gitattributes
    、merge driver。交付给 scenario-unity-performance:每帧 GC(scenario-unity-expert
    AgentProfile
    )、per-object Awaitable loops。交付给 scenario-unity-web:no managed threads(SaveAsync 保持 synchronous,不使用
    Task.Delay
    )。交付给 scenario-unity-mobile:touch schemes、
    persistentDataPath
    。Packet:写入的文件、包含总数的 tests、包含计数的 audits、Verified 和 Assumed。

Unity 6.3 notes

Unity 6.3 notes

  • Enter Play Mode is a dropdown ("When entering Play Mode"); 6.6 new projects default to "Reload Scene only"; 6.5 adds code lifecycle attributes and
    AutoStaticsCleanup
    .
  • FindObjectsByType<T>(FindObjectsSortMode.None)
    (the sort mode is obsolete from 6.4);
    [SerializeField]
    only on fields;
    GetInstanceID
    becomes a compile error in 6.5 (
    EntityId
    );
    [SerializeReference]
    types must be
    [Serializable]
    from 6.4 (version notes, unverified there).
  • Templates run the Input System only (legacy
    Input.*
    throws); Input System 1.20 bundled;
    OnMouseDown
    works with it only from 6.4.
  • Object.Destroy
    : verified live on 6000.3.21f1 (
    LifecycleTests
    , 2026-09-24) that
    OnDisable
    reaches every descendant (four levels); the 6.4 upgrade guide and the version notes describe pre-6.4 editors as "only direct children", so keep the idempotent
    OnDestroy
    unregistration for older editors.
  • Force Text and Visible Meta Files are the 6.3 defaults (observed); 6.6 turns YAML word wrap off and removes "Reduce Version Control Noise".
  • Enter Play Mode 是一个 dropdown(“When entering Play Mode”);6.6 新项目默认使用“Reload Scene only”;6.5 新增 code lifecycle attributes 和
    AutoStaticsCleanup
    。
  • FindObjectsByType<T>(FindObjectsSortMode.None)
    (sort mode 从 6.4 起废弃);
    [SerializeField]
    只能用于 fields;
    GetInstanceID
    在 6.5 中会成为 compile error(
    EntityId
    );从 6.4 起,
    [SerializeReference]
    types 必须添加
    [Serializable]
    (version notes,未在 6.4 上验证)。
  • Templates 仅运行 Input System(legacy
    Input.*
    会抛出异常);内置 Input System 1.20;
    OnMouseDown
    从 6.4 起才能与其协同工作。
  • Object.Destroy
    :已在 6000.3.21f1 上实时验证(
    LifecycleTests
    ,2026-09-24),
    OnDisable
    可到达每一层 descendant(共四层);6.4 upgrade guide 和 version notes 将 6.4 之前的 editors 描述为“only direct children”,因此应继续在较旧的 editors 中保留幂等的
    OnDestroy
    unregistration。
  • Force Text 和 Visible Meta Files 是 6.3 的默认设置(实测);6.6 会关闭 YAML word wrap,并移除“Reduce Version Control Noise”。

References

References

  • references/procedures.md
    : A1 to A15 with full code, live test and recorded result.
  • references/expert-notes.md
    : principles by expert with timestamps, decision tables, every observation.
  • references/critique.md
    : the self-review rubric.
  • references/gui-paths.md
    : menus and windows for the same tasks.
  • references/sources.md
    : sources, credentials, URLs, best timestamps, revision history.
  • scripts/ut_arch.py
    ,
    scripts/AgentKit/Architecture/*.cs
    ,
    scripts/templates/
    : the tested code.
  • references/procedures.md
    :A1 到 A15,包含完整代码、live test 和记录结果。
  • references/expert-notes.md
    :专家原则、时间戳、决策表和所有观察结果。
  • references/critique.md
    :self-review rubric。
  • references/gui-paths.md
    :完成相同任务所使用的 menus 和 windows。
  • references/sources.md
    :sources、credentials、URLs、最佳时间戳和 revision history。
  • scripts/ut_arch.py
    、
    scripts/AgentKit/Architecture/*.cs
    、
    scripts/templates/
    :经过测试的代码。