Repository navigation
Show a map's doors in replays - #471
Conversation
The decoder lists when each door moved, read from when its actor is written because the door fields have no names, plus the A Link wall's OnDie. The viewer draws doors at the moment on screen, cones cut against them on the worker, and a capture saves a row for each door changed, part way or not.
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 44 minutes. View limit details
|
| Layer / File(s) | Summary |
|---|---|
Collect and serialize door movements native/replay/src/collect.rs, native/replay/src/analysis/*, native/replay/src/document.rs, native/replay/src/analysis/mod.rs, native/replay/tests/corpus.rs, native/replay/Cargo.toml, native/replay/src/header.rs, docs/replay-format.md |
The native decoder collects recognized door writes and wall-break events, derives door movements, and adds them to replay documents. The replay format documents the doors data and the updated decoder version. Unit and corpus tests validate event analysis and output. |
Reconstruct replay door states lib/const/map_doors.dart, lib/replay/replay_document.dart, lib/replay/replay_doors.dart, lib/replay/replay_frame.dart, test/replay_doors_test.dart |
The replay client parses door movements, matches actors to configured map doors, and calculates door amounts and masks for each frame. Tests cover movement timing, round resets, map filtering, and missing door data. |
Apply door masks to cone calculations lib/replay/replay_cone_cuts.dart, lib/replay/replay_cone_worker.dart, lib/replay/replay_frame.dart, lib/replay_view.dart, test/replay_doors_test.dart, test/replay_frame_test.dart |
Cone requests carry door masks. The worker loads map-specific door walls and uses the mask when generating cones. Tests cover mask thresholds, worker behavior, and worker startup. |
Render and capture replay doors lib/replay/replay_frame.dart, lib/replay/replay_capture.dart, lib/widgets/map_door_layer.dart, lib/widgets/replay/replay_canvas.dart, test/replay_capture_test.dart, test/replay_doors_test.dart |
The replay canvas renders door amounts. Local and cloud captures append changed doors as utility rows. Tests cover captured door rows. |
Estimated code review effort: 4 (Complex) | ~45 minutes
Change: Feature
Sequence Diagram(s)
sequenceDiagram
participant ReplayFrameBuilder
participant ReplayDoors
participant ReplayCanvas
participant ReplayConeWorker
ReplayFrameBuilder->>ReplayDoors: at(timeMs, round)
ReplayDoors-->>ReplayFrameBuilder: door state amounts
ReplayFrameBuilder->>ReplayCanvas: frame with door amounts
ReplayFrameBuilder->>ReplayConeWorker: cone request with door mask
Possibly related PRs
- SunkenInTime/icarus#467: Defines the
MapDoormetadata andMapDoorBarsrendering that this change extends for replay. - SunkenInTime/icarus#461: Preserves the utility-row
mapDoorfield used by replay capture rows. - SunkenInTime/icarus#414: Establishes the decoder baseline that this change versions and extends with
doorsdata.
Merge Risk: 
Low · previous review 301b6
-
An unusually long door spin can briefly show a skewed pose. This is a bounded visual issue that can be fixed before merge or accepted for follow-up.
Pre-merge checks: 
4 · 
1
-
Failed checks: 1 warning Passed checks: 4 passedFull details: Docstring Coverage
-
Explanation
Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 9 functions across 6 files. (15 skipped: 15 unsupported.)
Resolution
Write docstrings for the functions missing them to satisfy the coverage threshold.
-
Finishing Touches
-
Generate docstrings
-
- Commit to this branch
- Create a new PR
Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
… move where the recording ends
| MapDoorKind.drop => move.endMs > timeMs | ||
| ? (timeMs - move.startMs) / (move.endMs - move.startMs) | ||
| : 1.0, |
There was a problem hiding this comment.
ReplayDoors.at uses the recorded span as a full drop, but the decoder can shorten or extend that span to the recording's end. A wall starting at 40,000 ms in a recording ending at 40,875 ms appears fully down at the end, although only half its 1.75-second drop has elapsed. An extended span makes a completed drop look too slow.
Use the wall's fixed 1.75-second pace and clamp the result, as spins already do with _spinMs.
ci: test native-replay windows
Fifth and last PR for Lotus and Summit doors. The editor has them since #467 and #468, but a replay showed every door at rest. So a Summit wall dropped in the match stayed up in the viewer, and Lotus cones ran through doors that were open. This reads what the doors did from the replay and shows it.
What a user sees
How it reads them
The decoder (
native/replay, 0.3.2 to 0.3.3, which re-decodes cached replays) adds adoorslist:{ actor, startMs, endMs }(docs/replay-format.md, "Doors").OnDieonRespawningWallPlate, once a round. Abyss has a wall of the same class, soRespawningWallPlaterows can appear there; no door on Abyss stands for it, so the viewer ignores them.In Dart,
ReplayDoorsturns the moves into door states at a moment.MapDoor.replayActorties each door inlib/const/map_doors.dartto its actor, checked against the level file's positions.ReplayConeAimcarries the doors as a bitmask, so a cone recuts when they change. The worker loads its models with door walls.Ground truth
Against the game's own named events, on every replay here that has those doors:
MulticastPlayAnimationon a switchOnDieThe first rule I wrote found 27 of 31 spins. That's how I found the late-round spin and two spins back to back. The second 13.00 Lotus replay decodes with 24 spins and 13 breaks, but I didn't dump its named events, so it isn't compared. No 13.09 Summit replay exists on this machine. The corpus test passes on both the 13.00 and 13.09 corpora and checks that every move sits inside a round and lasts as long as that door takes.
In the release viewer (a driver build with scratch seek hooks that aren't in this PR, on real replays):
CodeRabbit pointed out that a move longer than 10 s turned a door past half a turn; it's now held at half a turn, with a test. Astra reviewed this and found no blockers. It found two real bugs, both fixed here: a spinning door cut cones side-on for its whole turn, and a recording that ended mid-move dropped the move or ended it early. It also found a test that couldn't fail, now gone, and a corpus check too loose to catch a move running into the next round, now tightened.
Tests: 8 Rust unit tests built from real timings, plus
door_actor;test/replay_doors_test.dartcovers states over time, the bitmask, captured rows, and the worker cutting a real Lotus model with A Link whole and broken; a capture test through the local store. 183 Rust lib tests and 138 Dart tests across the replay and door files pass.What isn't covered
Fix the existing Summit drop-speed issue before merging.
Findings
Summary
Adds replay door moves from decoding through playback and capture.
No new findings were accepted. Review used source inspection; no runtime or visual checks were run.
SunkenInTime documents two accepted limits: captures save moving doors as changed, and playback without the cone worker uses the editor's empty door state.
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR A[Replay actor writes] --> B[Decoded door moves] B --> C[ReplayDoors at current time] C --> D[Draw door progress] C --> E[Door bitmask] E --> F[Cone worker] C --> G[Changed door rows] G --> H[Local or cloud capture]Reviews (2) · Last reviewed commit: "Hold a spin at half a turn when its move..." · Reviewed by Greptile