Skip to content

feat: audioEventSystem + avatarEmoteCommand.state - #1530

Merged
pravusjif merged 7 commits into
mainfrom
feat/playback-completion-signals-main
Aug 12, 2026
Merged

feat: audioEventSystem + avatarEmoteCommand.state#1530
pravusjif merged 7 commits into
mainfrom
feat/playback-completion-signals-main

Conversation

@pravusjif

Copy link
Copy Markdown
Member

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Test this pull request

  • The @dcl/sdk package can be tested in scenes by running

    npm install "https://sdk-team-cdn.decentraland.org/@dcl/js-sdk-toolchain/branch/feat/playback-completion-signals-main/dcl-sdk-7.25.1-31607703389.commit-6622567.tgz"
  • The @dcl/js-runtime package can be tested in scenes by running

    npm install "https://sdk-team-cdn.decentraland.org/@dcl/js-sdk-toolchain/branch/feat/playback-completion-signals-main/@dcl/js-runtime/dcl-js-runtime-7.25.1-31607703389.commit-6622567.tgz"
  • To test with npx init

    export SDK_COMMANDS="https://sdk-team-cdn.decentraland.org/@dcl/js-sdk-toolchain/branch/feat/playback-completion-signals-main/dcl-sdk-commands-7.25.1-31607703389.commit-6622567.tgz"
    npx $SDK_COMMANDS init
  • The /changerealm command to test test in-world

    /changerealm https://sdk-team-cdn.decentraland.org/ipfs/feat/playback-completion-signals-main-e2e
    
  • You can preview this build entering:
    https://playground.decentraland.org/?sdk-branch=feat/playback-completion-signals-main

@decentraland-bot decentraland-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: feat: audioEventSystem + avatarEmoteCommand.state

Summary

Clean, well-structured PR that adds a new AudioEventsSystem helper (mirroring the existing VideoEventsSystem) and integrates the new EmoteState field on AvatarEmoteCommand via a protocol bump. The audio system correctly adapts the video pattern to support both AudioSource and AudioStream components, including the edge case where only one of the two is removed. Tests are thorough and actually exceed videoEventsSystem.spec.ts coverage. All changes are purely additive — no breaking API changes.

CI: All checks passing ✅
ADR-6: PR title (feat: ...) and branch name (feat/...) comply ✅
Security: No issues found — no secrets, no user input handling, no network calls ✅
Consumer impact: Purely additive exports (AudioEventsSystem, audioEventsSystem, EmoteState). PBAvatarEmoteCommand.state is optional. No breaking changes. ✅


Findings

[P2] System function name "EventSystem" collision — latent footgun

File: packages/@dcl/ecs/src/systems/audioEvents.ts:58

The system is registered as function EventSystem(), which is the same name used by videoEvents.ts, events.ts, raycast.ts, and assetLoad.ts. While engine.addSystem() deduplicates by function reference (not name), so both systems run correctly, the engine.removeSystem(name) API uses findIndex by name — meaning a future engine.removeSystem('EventSystem') would silently remove only the first match. Renaming to function AudioEventSystem() (and ideally VideoEventSystem in its sibling) would cost nothing and eliminate the ambiguity.

This is a pre-existing pattern in the codebase, so not blocking.

[P2] registerAudioEventsEntity silently resets state tracking on re-registration

File: packages/@dcl/ecs/src/systems/audioEvents.ts:43-45

Calling registerAudioEventsEntity on an already-registered entity overwrites the map entry, resetting lastAudioState to undefined. This causes the callback to re-fire on the next tick with the current state even if it hasn't changed. This matches videoEvents.ts exactly (same behavior), but it's worth documenting — especially since it's now duplicated a second time. No test covers this scenario in either the audio or video test suite.

[P2] Code duplication between audio and video event systems — refactor opportunity

Files: audioEvents.ts and videoEvents.ts

The two files are ~95% structurally identical: map-based registration, per-tick state diffing, removal on entity-removed/component-missing, identical getState pattern. A generic createMediaEventsSystem<T>(engine, { hasComponent, eventComponent }) factory could back both, cutting ~180 duplicated lines. This PR was the natural moment for that refactor since it's the second copy of the pattern. Not blocking, but worth considering before a potential third "XEventsSystem" appears.

[P2] Return object methods are unnecessary wrappers

File: packages/@dcl/ecs/src/systems/audioEvents.ts:84-95

Every method in the returned object is a one-line delegation to the identically-named closure function (e.g., removeAudioEventsEntity(entity) { removeAudioEventsEntity(entity) }). Could return { removeAudioEventsEntity, registerAudioEventsEntity, hasAudioEventsEntity, getAudioState } directly. Same pattern exists in videoEvents.ts — pre-existing, not blocking.

[P2] Test assertions check call counts but not call arguments

File: test/ecs/events/audioEventsSystem.spec.ts

Callback tests use toHaveBeenCalled() / toHaveBeenCalledTimes(n) without verifying the payload via toHaveBeenCalledWith(...). A callback invoked with a wrong or stale state value would still pass. Strengthening at least the core "run callback on audio status change" test with expect(fn).toHaveBeenCalledWith(expect.objectContaining({ state: MediaState.MS_PLAYING })) would catch payload regressions.

[P2] Missing test edge cases

File: test/ecs/events/audioEventsSystem.spec.ts

A few untested but safe-by-inspection paths:

  • getAudioState on an entity with AudioSource but no AudioEvent data yet → should return undefined
  • Registering a callback before any AudioEvent value exists (realistic renderer flow)
  • Multiple rapid addValue calls within a single tick → only the final state fires the callback (intermediate states are silently dropped — worth documenting this behavior)
  • Re-registering the same entity with a different callback (resets lastAudioState, item above)
  • State cycling: LOADING → PLAYING → LOADING to confirm dedupe is keyed on state value, not object identity

Verdict: ✅ Approved

No P0 or P1 issues. All findings are P2 suggestions — mostly inherited from the existing VideoEventsSystem pattern that this PR mirrors faithfully. The implementation is correct, the adaptation for dual audio components (AudioSource + AudioStream) is well-handled, and test coverage is solid. The EmoteState / AvatarEmoteCommand changes are clean schema additions with proper serialization round-trip tests.


Reviewed by Jarvis 🤖 · Requested by Gabriel Díaz (<@U03MGHMAJL8>) via Slack

Comment thread packages/@dcl/ecs/src/systems/audioEvents.ts Outdated
Comment thread packages/@dcl/ecs/src/systems/audioEvents.ts
Comment thread packages/@dcl/ecs/src/systems/audioEvents.ts
Comment thread test/ecs/events/audioEventsSystem.spec.ts Outdated
… into feat/playback-completion-signals-main
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 11, 2026

Copy link
Copy Markdown

Deploying js-sdk-toolchain with  Cloudflare Pages  Cloudflare Pages

Latest commit: 9969494
Status: ✅  Deploy successful!
Preview URL: https://fef0d208.js-sdk-toolchain.pages.dev
Branch Preview URL: https://feat-playback-completion-sig-1ztg.js-sdk-toolchain.pages.dev

View logs

@cyaiox cyaiox left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🚀

@pravusjif
pravusjif removed the request for review from nicoecheza August 12, 2026 12:14
@pravusjif
pravusjif merged commit f858f90 into main Aug 12, 2026
8 checks passed
@pravusjif
pravusjif deleted the feat/playback-completion-signals-main branch August 12, 2026 14:45
popuz added a commit that referenced this pull request Aug 12, 2026
Resolves the conflicts main brought in with #1530 (audioEventSystem,
avatarEmoteCommand.state) and #1532 (jest 30, node >=24.16, dependency
slimming). Every conflict sat in a generated artifact, so main's side was
taken and the artifacts were regenerated instead of merged by hand.

@dcl/protocol moves off the PR tarball onto the published
1.0.0-31617402096.commit-86c4613, now that decentraland/protocol#455 is
merged. The development-dependency gate therefore no longer applies, and the
AvatarEmoteMask entry the experimental-based tarball dragged into
playground-assets.api.md is gone.

Lockfiles are regenerated with the Node version CI pins (24.16.0), which
drops the "libc" fields an older npm had written.

Co-Authored-By: Claude <noreply@anthropic.com>
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.

3 participants