You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .claude/agents/dcl-sdk-feature-implementation/dcl-explorer-specialist.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -46,7 +46,7 @@ After installing a new protocol package, always re-run `npm run build-protocol`
46
46
47
47
## Protocol Generation
48
48
49
-
> **Prerequisite:**`build-protocol` runs a Python `protoc` plugin (`protoc-gen-bitwise`, for the quantized/bit-packed Pulse network state) in addition to the Node toolchain. **Python 3** must be on `PATH` with the **`protobuf`** package installed (`python3 -m pip install protobuf`), otherwise generation fails with `ModuleNotFoundError: No module named 'google'`.
49
+
> **Prerequisite:**Node toolchain only. `build-protocol` runs the `protoc-gen-bitwise` plugin (for the quantized/bit-packed Pulse network state), a dependency-free Node script bundled in `@dcl/protocol` — no Python or extra packages required.
-**Plugins read from containers. Plugins never construct, initialize, or mutate a container.** If you find yourself writing `container.SomeField = new Thing()` inside `InitializeAsync`, the graph is inverted — stop and restructure.
70
70
- Containers are constructed from a single place (the composition root or a parent container). The constructor takes pre-built dependencies; the container exposes them.
71
71
- If a plugin needs a dependency that doesn't exist yet, **create a scoped container** for the feature and construct it from the composition root. You can have as many small scoped containers as you want — they are cheap, and they keep the dependency graph honest.
72
-
- Do not reach for `ObjectProxy` to paper over a missing dependency. `ObjectProxy` is an anti-pattern documented in CLAUDE.md; it exists only to unbreak legacy circular deps. For new code, the right answer is a scoped container.
72
+
-**Never introduce a new `ObjectProxy`.** The codebase was swept of it; the only legitimate remaining instances model true runtime lifecycles (`MainPlayerAvatarBaseProxy`, `ExposedCameraData.CameraEntityProxy`). For everything else use a decoupling recipe below.
73
+
74
+
### Decoupling without ObjectProxy
75
+
76
+
Match the situation to the recipe (full rationale in `docs/architecture-overview.md` § "Deferred dependencies — decoupling without ObjectProxy"):
77
+
78
+
| Situation | Fix | Existing example |
79
+
|---|---|---|
80
+
| Service trapped in a late, UI-owning container | Split services into their own container created before any consumer |`FriendsServicesContainer` (services) vs `FriendsContainer` (UI) |
81
+
| Dependency exists only when a feature flag is on | Pass `T?` (null = disabled) and null-check where `.Configured` used to be, or use a null-object |`IFriendsService?`; `NullUserBlockingCache`, `NullRoomHub`|
82
+
| Scene-world plugin needs comms/multiplayer services | Construct the plugin in `DynamicWorldContainer.WorldPlugins`, not in `StaticContainer` with an empty slot |`AvatarAttachPlugin`, `SceneMaskedEmotePlugin`, `RealmInfoPlugin`|
83
+
| Dependency is per-scene data | Add it to `ECSWorldInstanceSharedDependencies`, threaded from `SceneFactory`|`IRoomHub` for the media streaming room |
84
+
| Object created in a plugin's async `InitializeAsync` but consumed by earlier objects | Create it eagerly in a container; the plugin only *attaches* the UI-bound parts |`NavmapCommandBus` + `NavmapCommandFactory.AttachUiControllers`|
- Instantiates world plugins (`IDCLWorldPlugin`) with their dependencies
97
+
- Instantiates most world plugins (`IDCLWorldPlugin`) as `ECSWorldPlugins`
86
98
- Provides `StaticSettings` (all plugin settings)
87
99
88
100
### DynamicWorldContainer
89
101
90
102
Created after StaticContainer. Holds global plugins and runtime state.
91
103
- Instantiates global plugins (`IDCLGlobalPlugin`)
104
+
- Instantiates the world plugins whose dependencies (comms, multiplayer) only exist here, exposed as `WorldPlugins`; the bootstrap concatenates them with `StaticContainer.ECSWorldPlugins` for initialization and scene-world creation
92
105
- Creates `RealmController`, `GlobalWorldFactory`
93
106
- Manages scene lifecycle
107
+
- Never writes into `StaticContainer` — if a value created here is needed by something in `StaticContainer`, that something is constructed in the wrong container
Copy file name to clipboardExpand all lines: .claude/skills/sdk-component-implementation/SKILL.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -30,7 +30,7 @@ In `js-sdk-toolchain`, generate serialization code and optional helper functions
30
30
31
31
In `unity-explorer`:
32
32
1. Run protocol update: `npm install @dcl/protocol@experimental && npm run build-protocol`
33
-
-Requires **Python 3** on `PATH` with the **`protobuf`** package (`python3 -m pip install protobuf`) — `build-protocol` runs a Python `protoc` plugin (`protoc-gen-bitwise`); without it the build fails with `ModuleNotFoundError: No module named 'google'`.
33
+
-Node/npm only — `build-protocol` runs the `protoc-gen-bitwise` plugin, a dependency-free Node script bundled in `@dcl/protocol` (no Python or extra packages required).
34
34
2. Add partial class to `IDirtyMarker.cs`
35
35
3. Register in `ComponentsContainer.cs` using `SDKComponentBuilder<T>`
36
36
4. Create feature folder under `Explorer/Assets/DCL/SDKComponents/<Feature>/`
Copy file name to clipboardExpand all lines: CLAUDE.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -121,7 +121,7 @@ Reviewers have repeatedly identified AI-generated code by these smells. Check yo
121
121
***Defensive null-checks against non-null declarations.** If the declared type is `T` (not `T?`), don't null-check it. Trust the annotations. Every redundant check is a lie to the reader about what can happen.
122
122
***Debug/mock code in production hot paths.** Runtime bools like `DebugRandomizeX` execute on every call in retail builds. Guard debug branches with `#if UNITY_EDITOR` or move them to an editor-only companion system — never rely on a runtime flag alone.
123
123
***Plugins initializing or mutating containers.** Containers are constructed top-down from the composition root. Plugins **read** from containers. A plugin that writes into a container is a signal the dependency graph is inverted — create a scoped container instead.
124
-
***`ObjectProxy` is an anti-pattern**, not a solution. It exists to paper over circular dependencies. If you reach for it, the right fix is almost always restructuring the dependency flow.
124
+
***`ObjectProxy` is an anti-pattern** — never introduce a new instance. The codebase has been swept of it; the only legitimate remaining uses model true runtime lifecycles (`StaticContainer.MainPlayerAvatarBaseProxy` — avatar set/released as the player loads, and `ExposedCameraData.CameraEntityProxy` — entity created during world build). Every other use was a wiring-order mistake and was eliminated by restructuring. To decouple without it, pick the matching recipe from `docs/architecture-overview.md` § "Deferred dependencies — decoupling without ObjectProxy": create the service before its consumers (hoist it out of a UI container into its own container), model an optional feature as a nullable dependency or null-object, let the container that owns a late-created service also construct the plugins that need it (`DynamicWorldContainer.WorldPlugins`), or pass per-scene data through `ECSWorldInstanceSharedDependencies`.
125
125
***Retry/resolve loops without a termination condition.** A loop that re-adds the same unresolved item to the queue will spin forever when the server returns stable but empty results. Always have a "give up" predicate.
126
126
***Wiring pooled/virtualized list items per rebind.** For item pools, wire callbacks once when the item is created, not every time `SetItemData` runs. Prefer an `Action` field (single subscriber, direct assignment) over C# `event` (`+=`/`-=` churn) when there is exactly one subscriber.
127
127
***Reimplementing primitives that already exist.** Before writing manual atlas UV math, check `TMP_Sprite Asset`. Before hand-batching profile lookups, check the batched `GetProfilesAsync(IReadOnlyList<string>, ct)` overload. Before adding a bespoke event pathway, check `ViewEventBus` / `ChatEvents`.
0 commit comments