Skip to content

Track Lab challenge, boss and hub levels; Modern camera wall and hit fixes; textured caustics and low-pass water spray - #93

Open
leite-matheus wants to merge 14 commits into
ThatGuyMcd:mainfrom
leite-matheus:feature/blender-track-editor
Open

leite-matheus wants to merge 14 commits into
ThatGuyMcd:mainfrom
leite-matheus:feature/blender-track-editor

Conversation

@leite-matheus

Copy link
Copy Markdown
Contributor

A follow-up to #57, merged with main at v1.0.15. Needs both owners' sign-off on the checkpoint change below (cross-play).

Track Lab: challenge, boss and hub levels from a .dkrmap

Until now a .dkrmap could only be a race track. All five other level kinds now play from one. Battle, bananas, eggs, boss and hub were each checked in game with a retail-clone header and with a track built from scratch.

  • Boss. level_load swaps an unvisited boss level for its intro scene. It finds that scene by scanning ASSET_MISC_67 with no bound, so a custom level id read past the table and loaded junk (frozen camera). skip_custom_boss_intro in custom_tracks_hooks.cpp marks the course as visited first. After the race the game returns to the hub the player came from.
  • Hub exits. A new manifest field, levelLinks ({slot, offset, track}), lets a hub EXIT lead to another installed .dkrmap. The hook sits after gzip_inflate in track_spawn_objects and patches the exit's destinationMapId. Hub → custom boss → hub was played through.
  • Checkpoints 60 → 127. This is done with instructionPatches in both recomp policies (allocate_object_pools, checkpoint_update_all). The limit is 127 because nextCheckpoint is an s8. A 120-checkpoint oval races, and the AI completes laps on it.
  • Shadow budgets. On dense custom floors the retail shadow clipping overflowed its fixed arrays (D_8011B120[32], D_8011C238[32], D_8011B330[120], which it uses as 128 entries). This crashed Paulista, an egg arena. Two hooks, dkr_shadow_clip_budget and dkr_shadow_polygon_budget, now keep it within those sizes. The fix rests on the crash dump: the crash later stopped reproducing even on the old build.
  • Addon.
    • New Mode Checklist panel.
    • The AI Node Graph panel is enabled.
    • Mode validation.
    • A "Leads to" destination for hub exits, exported as levelLinks.
    • Adding an AI branch no longer fails on the wrong class.
  • docs/CUSTOM_TRACKS.md documents the level kinds and levelLinks. Tests are in custom_tracks_tests.cpp.

Modern camera: hits and walls

  • The Modern camera's hit shake is gone: the field, its ini key and its panel slider are removed. For about 1 s after a retail hit or explosion, boost and landing shake are off too. The retail camera is untouched.
  • Wall fix. A wall behind the kart could put the eye inside the racer, filling the screen with one colour. The fix is in modern_cam:
    • collisionMinDist (1.6 m) keeps the eye at least that far from the pivot.
    • collisionLiftMax lets the boom rise over the kart instead.
    • Wall cuts slide in (collisionPullSharpness) instead of jumping 100+ units in one frame.
  • Hits. During a hit the anchor is tied to the racer, the way the retail camera is, so the kart keeps its size on screen. On a climbing bank the camera no longer drops below the racer (riseLag, slopeMax).
  • LoadCameraTuning kept the ini open, so the v1 → v2 migration from Modern camera keeps up through corners and hits; vehicle choice and a preview flight for custom tracks in Track Select #57 was never saved. It is now.
  • New tests in modern_camera_dkr_tests.cpp: WallNeverPutsTheEyeInTheRacer, WallLiftReturns, ClimbingKeepsTheCameraAbove, WallCutSlides, HitKeepsTheBoomIn.

Enhanced water: caustics and low-pass spray

  • Caustics. The two-sine caustics are replaced by a textured caustic layer: one texture sampled twice and panned in opposite directions. The texture is runtime-recomp/assets/water/WaterCaustics.png.
  • Low-pass spray. Planes and hovercraft skimming low over water or lava now throw spray curtains and mist, and streak and whiten the liquid beneath them. The strength grows with speed and falls off with height. WaterDrawParams is already at the 2048-byte stride, so each craft's clearance lives in the last two shoreline vectors (WATER_CRAFT_AIR). Water draws never fill those vectors, and lava uses only 0..15; a static_assert guards this.
  • No new patch layers. Both features ride on the existing baselined dkr-lava-glow.patch, and dkr-caustics.patch / dkr-lowpass.patch and their compose scripts are deleted. The folded patch applies with the same CMake flags and composes an RT64 tree byte-identical to the old chain.

Fixes

  • Auto boot with a custom character crashed with Unadmitted custom presentation resource. This happens because auto boot skips character select, where the presentation is initialised. prepare_presentation now initialises it on demand. It allocates nothing in the guest, so the heap order is unchanged.
  • Track Lab vertex paint. Paint on the baked layer now reaches the export; before, it was display-only and lost. A moved or shared .blend finds its base model beside itself.
  • Plain runtime configure. A configure without the release overlay links again:
    • DKRCrashReports is now linked into DKRPortGame.
    • dkr_analogue_driving gets a retail pass-through: an MSVC /alternatename in runtime_code_mods.cpp, used only when the overlay does not define it.
  • Pinned bytes. Twelve first-party files touched above are bases of the v1.0.7 source capsule. They join DRIFTED_BASES in check_pinned_bytes.py, so they are checked against their pinned Git blob, which still matches.

Pattern checks

  • python scripts/check.py --suite guard,tooling passes. The pattern guard reports 0 new findings: the low-pass layer is folded, and no new patches/<name>/ or compose scripts are added.
  • No new guest hooks outside the existing hook files, and no hard-coded addresses: the new ones live in revision_addresses.hpp and the recomp policies.
  • netplay/legacy_identity.hpp does not exist yet, so nothing was spliced anywhere else. See Risk.

Testing

  • MSVC Release build of DKRPortGame (build/dkr-runtime-rt64) after the v1.0.15 merge: Egg Arena and Boss Circuit boot and race.
  • In game before the merge:
    • All five level kinds and the 120-checkpoint oval.
    • Hub → custom boss → hub.
    • Paulista, a full egg match.
    • Auto boot with a custom AI character, a 45 s race with no fatal error.
    • Low-pass spray on a Whale Bay clone with a plane and a hovercraft.
  • Not played yet:
    • The camera wall/hit changes, which were checked with an offline replay of DKR's wall bounce and the unit tests.
    • Low-pass spray since it moved into WATER_CRAFT_AIR.
    • Vertex paint in a race; it was checked in headless Blender only.
  • Track Lab suites (tools/blender/run_tests.py) pass.

Risk

  • Cross-play. The checkpoint instructionPatches change the guest's object-pool layout. Online already refuses mismatched builds, but this is a simulation-affecting change, and AGENTS.md asks for both owners' approval. If you'd rather not take it now, dropping the two checkpoint entries from the recomp policies brings back 60 checkpoints. Custom tracks with more than 60 checkpoints would then need re-exporting.
  • /alternatename pass-through for dkr_analogue_driving is MSVC-only and only matters for configures without the online-performance overlay. Say if you'd prefer it handled differently.
  • Rollback:
    • Water: revert the water commits; the pre-fold chain is in history.
    • Camera: only the Modern camera changes; the Retail camera is unaffected.

…ed vertex colours

A .blend shared or moved keeps naming an absolute model path that is no longer there; the recorded file is now looked for beside the .blend first. Vertex Paint edits on the baked layer reach the export, and meshes without a baked layer use their active colour layer.
Keeps Track Lab, the modern camera rework, the auto boot character fix and the water caustics/low-pass layers. The projection probe takes main's parallel draw buffer, which fixes the same D3D stride limit. The low-pass and caustics patches still have to be re-authored on main's new lava/waterfall chain (WaterDrawParams is now a full 2048 bytes).
…hain

WaterDrawParams is a full 2048 bytes on main, so the per-craft clearance no longer gets its own field: it lives in the last two shoreline vectors (WATER_CRAFT_AIR), which water draws never fill and lava never reaches (it uses segments 0..15). Clearance is written only for water draws. Patches regenerated by the compose scripts; shader math and constants unchanged.
Regenerate the low-pass and caustics water layers on main's updated
stability/waterfall patches (context only, same changes), and keep the
camera Shake copy that matches the removed hit shake.
main's pattern guard refuses new first-party patch layers, so the caustics
changes now ride on the existing baselined dkr-lava-glow.patch instead of a
separate dkr-caustics.patch, and the texture moves out of patches/ to
runtime-recomp/assets/water. The low-pass layer is regenerated on top of it.
The composed RT64 tree is byte-identical to the previous chain.
Custom boss levels skip the retail boss intro lookup, which ran past the
intro table for a custom level id. Hub EXITs can lead to another installed
.dkrmap through manifest levelLinks, resolved after the object map is
inflated. Tracks can carry up to 127 checkpoints. Shadow clipping on dense
custom floors now respects the retail array sizes instead of overflowing
them. A plain runtime configure links again through a retail pass-through
for dkr_analogue_driving when the overlay does not define it.
The addon gets a Mode Checklist panel for battle, banana, egg, boss and hub
levels, an enabled AI Node Graph panel, mode validation and a "Leads to"
destination for hub exits that is exported as manifest levelLinks. Adding
an AI branch no longer fails on the wrong class.
game_main.cpp and rt64_renderer.cpp call the native crash context directly since v1.0.15, but only the release overlay linked DKRCrashReports into DKRPortGame, so a configure without the overlay failed with two unresolved externals.
main's pattern guard refuses new first-party patch layers and string-replace
pipelines, so the low-pass changes now ride on the baselined
dkr-lava-glow.patch (as the caustics did in 80e4611) and dkr-lowpass.patch
and compose-water-lowpass.py are removed. The folded patch applies with the
same CMake flags and composes an RT64 tree byte-identical to the previous
lava-glow + low-pass chain.
Twelve first-party files evolved by the Track Lab, modern camera and
enhanced-water work are bases of the v1.0.7 source capsule. Like the
existing drifted bases, their pins are now checked against the historical
blob restore_capsule.py reads, which still matches.

@swhitt swhitt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The shadow budget hooks and the boss intro skip are pretty nicely reasoned against the decomp.

The camera commit override is the one I'm most nervous about, mostly because the wall changes haven't been played yet.

// than metres in one frame.
if (g_pending_closest > 0.0) {
const double cleared = distance_between(dkr_cam_position(rdram, camera), g_pending_pivot);
if ((cleared < g_pending_closest * 0.95 || g_pending_sliding) &&

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This puts back modern_cam's eye after the retail clearance pass already decided it was in a wall. So while sliding (or under closest) the drawn eye sits wherever modern_cam had it, which can be behind the wall, and segment_at below then picks the segment for that spot. I think that trades "inside the racer" for "inside the wall" for the length of the slide.

Since the wall changes are still unplayed, it'd be good to try a tight wall on a retail track with this before merge.

return bits;
}
#if defined(_MSC_VER)
#pragma comment(linker, "/alternatename:dkr_analogue_driving=dkr_analogue_driving_retail")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

/alternatename is MSVC-only, so a plain configure on clang (macOS/Linux) still won't link. Maybe also mark the pass-through __attribute__((weak)) named dkr_analogue_driving under #else?

}
const std::int32_t value = read_word(rdram, at);
if ((value & kRaceVisitedAndCleared) != kRaceVisitedAndCleared) {
write_word(rdram, at, value | kRaceVisitedAndCleared);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lend_course_clear takes RACE_CLEARED back before the save, but this sets visited|cleared and never clears them. save_data.c packs those bits for every RACETYPE_BOSS level up to levelCount.

Does a custom boss level's flag end up in the EEPROM after a race?

cache.frame.previous[i]=current;
cache.objects[i]=object;
}
// Bounded calibration trace for user-run low-pass playtests.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: this trace is always on in release. Maybe gate it behind the water trace level like the camera ones?

if str(o.get(scene.PROP_ID)) == level_types.WEAPON_BALLOON]
chests = []
if bananas:
chests = [(int(o.get("playerID", 1)) - 1, o) for o in objects

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not masked here, but mode_checklist.owner() does (playerID - 1) & 3. A chest with playerID 0 shows up as racer 3 in the checklist, but here it's -1 and destination_tags skips it - the Bank nodes row stays MISSING and blocks export. Maybe just use mode_checklist.owner(CHEST, ...) here.

},
{
"function": "allocate_object_pools",
"vram": "0x8000C008",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Checked this against the decomp. gTrackCheckpoints is the only thing sized by MAX_CHECKPOINTS, and the slti at checkpoint_update_all is the only bound on the load, so I think these two patches cover it. 127 also keeps everything inside the s8 fields.

Worth calling out: allocate_object_pools runs once at boot, so the extra ~3 KB is taken for every level, retail included, and every allocation after it moves. Online already refuses mismatched builds, so that's probably fine. It might be good to add a line to the CHANGELOG saying this build can't play online with older ones.

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.

2 participants