Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build-platforms.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ jobs:

- name: Verify font checksums
run: python3 tests/font_manifest_test.py
- name: Check Collection artwork
run: python3 tests/collection_art_test.py

- name: Upload Linux package
uses: actions/upload-artifact@v4
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## v1.3.6 - 2026-08-27

- TPHD-style Collection screen with parchment descriptions.
- Updated Collection banner, equipped-item frames, and decorative details.
- Link starts facing forward while keeping Dusklight's model rotation.
- Corrected and aligned controller icons in Collection descriptions.

## v1.3.5 - 2026-08-27

- Top-centered oil/oxygen meters.
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ target_link_libraries(twilight_hd_hud PRIVATE dusklight_game_headers)
option(TWILIGHT_HUD_BUILD_TESTS "Build native HUD regression tests" OFF)
if (TWILIGHT_HUD_BUILD_TESTS)
enable_testing()
foreach(test_name IN ITEMS controller_prompts diamond_layout font_atlas hud_scaling hud_settings input_gate ui_refinements)
foreach(test_name IN ITEMS controller_prompts diamond_layout font_atlas hud_scaling hud_settings input_gate ui_refinements collection_layout)
add_executable(${test_name}_test tests/${test_name}_test.cpp)
target_compile_features(${test_name}_test PRIVATE cxx_std_20)
target_include_directories(${test_name}_test PRIVATE src "${DUSK_DIR}/sdk/include")
Expand Down
10 changes: 7 additions & 3 deletions GameBanana/README-Apple.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Twilight HD HUD v0.7.5
Twilight HD HUD v1.3.6

Requires official Dusklight v1.4.1-129 or newer.
Requires official Dusklight v1.4.1-141 or newer.

Updating: remove the previous Twilight HD HUD .dusk file before adding this
version. Keep only one version in the mods folder. Saves and settings are
preserved. Fully restart Dusklight after updating.

Supported platforms:
- iPhone and iPad ARM64
Expand All @@ -20,7 +24,7 @@ Apple TV installation:

Controller compatibility:
- Follow Dusklight Bindings respects the active controller profile and honors its Call Midna action without also triggering the control's normal game action.
- TPHD Fixed Bindings ignores the separate Dusklight Call Midna binding and keeps Midna on D-Pad Up.
- TPHD Fixed Bindings ignores the separate Dusklight Call Midna binding and keeps Midna on L/LB/L1.
- In both modes, the control mapped to logical R operates the third item and related menu actions.

This package contains no game files. A compatible, legally obtained disc image is required by Dusklight.
Expand Down
12 changes: 8 additions & 4 deletions GameBanana/README-Desktop.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Twilight HD HUD v0.7.5
Twilight HD HUD v1.3.6

Requires official Dusklight v1.4.1-129 or newer.
Requires official Dusklight v1.4.1-141 or newer.

Updating: remove the previous Twilight HD HUD .dusk file before adding this
version. Keep only one version in the mods folder. Saves and settings are
preserved. Fully restart Dusklight after updating.

Supported platforms:
- Windows x64
Expand All @@ -15,10 +19,10 @@ Installation:

Controller compatibility:
- Follow Dusklight Bindings respects the active controller profile and honors its Call Midna action without also triggering the control's normal game action.
- TPHD Fixed Bindings ignores the separate Dusklight Call Midna binding and keeps Midna on D-Pad Up.
- TPHD Fixed Bindings ignores the separate Dusklight Call Midna binding and keeps Midna on L/LB/L1.
- In both modes, the control mapped to logical R operates the third item and related menu actions.

On macOS, the active data folder is normally under ~/Library/Application Support/Dusklight.
On macOS, the active data folder is normally under ~/Library/Application Support/TwilitRealm/Dusklight.
On Android, use the mods folder inside the Dusklight data folder selected by the app.

This package contains no game files. A compatible, legally obtained disc image is required by Dusklight.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Dusklight **v1.4.1-141 or newer** is required. The mod is built and tested again
individual controls. At 100%, saved Controller Diamond, D-Pad, and Hearts
percentages apply independently, without multiplication. Changes apply live.
- *Twilight Princess HD*-style panels for shared three-choice dialogue screens
- TPHD-style Collection screen with parchment descriptions, decorated equipment
frames, and a forward-facing Link model that retains Dusklight's rotation controls
- Native modules for Windows x64, Linux x64, macOS Apple Silicon, Android ARM64, iOS ARM64, and tvOS ARM64

Planned improvements are tracked in [ROADMAP.md](ROADMAP.md).
Expand Down
8 changes: 4 additions & 4 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Updates:

- Top-centered oil/oxygen meters.
- Howl button matching the selected layout/style.
- Updated item-help icons, including ZL/LT/L2 for bomb-arrow combinations.
- Assignment icons hidden while item descriptions are open.
- TPHD-style Collection screen with parchment descriptions.
- Updated Collection banner, equipped-item frames, and decorative details.
- Link starts facing forward while keeping Dusklight's model rotation.
- Corrected and aligned controller icons in Collection descriptions.
24 changes: 24 additions & 0 deletions assets/source/collection-banner.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions assets/source/collection-equipment-frame.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions assets/source/collection-parchment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Collection parchment

Original texture created for the Collection screen. The source PNG retains
transparent worn edges. The packaged BTI contains pixel data only; labels,
separator and ornament are separate native UI elements.

Build with `python3 tools/encode_collection_parchment.py` (Pillow required).

## Creation prompt

Use case: stylized-concept. Asset type: transparent game-menu parchment panel
texture, not a mockup. Create a single flat rectangular sheet of pale cream
parchment viewed perfectly straight-on, wide 2.4:1 shape on genuinely transparent
background, filling almost the entire canvas with a narrow transparent margin.
Very restrained light fibers and cloudy warm ivory texture, slightly darker
faded beige edges with subtle irregular worn fibers, no curled corners. The
central 90 percent must be nearly uniform pale ivory to support small dark
text in a game UI. Keep all edges close to a rectangle, softly feathered with
tiny rough imperfections, no large tears. No text, no symbols, no border, no
gold decorations, no objects, no hands, no scenery, no perspective, no dark
shadow, no watermark. Output a wide landscape image with actual alpha
transparency outside the paper.
Binary file added assets/source/collection-parchment.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion mod.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "org.twilight.hd_hud",
"name": "Twilight HD HUD",
"version": "1.3.5",
"version": "1.3.6",
"author": "Hyrule Hysteria",
"description": "A focused Twilight Princess HD-style HUD and control-layout package for Dusklight."
}
Binary file added res/menu/collection-banner.bti
Binary file not shown.
Binary file added res/menu/collection-equipment-frame.bti
Binary file not shown.
Binary file added res/menu/collection-parchment.bti
Binary file not shown.
155 changes: 155 additions & 0 deletions src/collection_layout.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#pragma once

#include <array>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <string>
#include <string_view>

namespace twilight_hd_hud {

// Keep native item IDs: equipment actions and submenus still use the original
// grid coordinates. Only presentation and directional navigation change.
struct CollectionCell {
int column;
int row;
float x;
float y;
float width;
};

inline constexpr std::array<CollectionCell, 19> kCollectionCells = {{
{4, 2, 338, 80, 46}, {3, 2, 396, 80, 46}, {5, 2, 454, 80, 46},
{3, 1, 260, 145, 46}, {4, 1, 320, 145, 46},
{3, 0, 478, 145, 46}, {4, 0, 538, 145, 46},
{5, 0, 158, 205, 112}, {6, 0, 655, 200, 80},
{0, 3, 75, 310, 44}, {1, 3, 130, 310, 44},
{0, 4, 185, 310, 44}, {1, 4, 240, 310, 44},
{2, 3, 75, 370, 44}, {2, 4, 130, 370, 44},
{3, 3, 185, 370, 44}, {3, 4, 240, 370, 44},
{0, 5, 158, 416, 156}, {1, 5, 638, 416, 156},
}};

inline int collection_cell_index(int column, int row) {
for (std::size_t i = 0; i < kCollectionCells.size(); ++i) {
if (kCollectionCells[i].column == column && kCollectionCells[i].row == row)
return static_cast<int>(i);
}
return -1;
}

enum class CollectionDirection { Left, Right, Up, Down };

inline int collection_neighbor(int current, CollectionDirection direction,
const std::array<bool, kCollectionCells.size()>& available) {
if (current < 0 || current >= static_cast<int>(kCollectionCells.size())) return current;
const auto& origin = kCollectionCells[current];
int best = current;
float bestScore = std::numeric_limits<float>::max();
const bool horizontal = direction == CollectionDirection::Left ||
direction == CollectionDirection::Right;
const float sign = direction == CollectionDirection::Left ||
direction == CollectionDirection::Up ? -1.0f : 1.0f;
for (std::size_t i = 0; i < kCollectionCells.size(); ++i) {
if (!available[i] || static_cast<int>(i) == current) continue;
const float dx = kCollectionCells[i].x - origin.x;
const float dy = kCollectionCells[i].y - origin.y;
const float forward = (horizontal ? dx : dy) * sign;
const float sideways = std::fabs(horizontal ? dy : dx);
if (forward < 1.0f) continue;
// Prefer the same visual row/column without trapping the cursor when
// an item is missing or crossing between equipment and collectibles.
const float score = forward + 3.0f * sideways +
(sideways >= 1.0f ? 10000.0f : 0.0f) +
(sideways > forward ? 10000.0f : 0.0f);
if (score < bestScore) {
bestScore = score;
best = static_cast<int>(i);
}
}
return best;
}

struct CollectionViewport {
float left;
float top;
float scale;
};

inline float collection_cursor_half_extent(float renderedSize, float scale) {
// The bracket artwork already extends outward from its pane origin.
return std::fmax(1.0f, renderedSize * 0.5f - 2.0f * scale);
}

inline constexpr std::int16_t collection_model_angle(std::int16_t nativeAngle, bool wolf) {
const int nativeRest = wolf ? -0x510c : -0x5b1c;
int angle = static_cast<int>(nativeAngle) - nativeRest - 32768;
if (angle < -32768) angle += 65536;
if (angle > 32767) angle -= 65536;
return static_cast<std::int16_t>(angle);
}

inline constexpr float collection_inline_icon_scale(unsigned type) {
// The replacement target/trigger cap occupies half its square texture.
// Other controller icons need only a small increase beside the body font.
return type == 3 ? 2.0f : type <= 8 ? 1.25f : 1.0f;
}

inline constexpr float kCollectionIconSpacing = 1.0f;
inline constexpr float kCollectionPromptCenterY = 48.0f;

inline bool collection_icon_slots_match(std::string_view drawn, std::string_view current,
std::size_t slotCount, std::size_t iconCount) {
return !drawn.empty() && drawn == current && slotCount > 0 &&
slotCount <= 35 && slotCount == iconCount;
}

inline float collection_inline_icon_x(float prefixWidth, bool followsSpace) {
// Authored spaces plus the texture's transparent rim made the leading gap
// too wide. Use part of that space without touching unspaced text.
return std::fmax(0.0f, prefixWidth + (followsSpace ? -2.0f : 0.5f));
}

struct CollectionTextPrefix {
std::string line;
std::string format;
};

inline CollectionTextPrefix collection_text_prefix(std::string_view text) {
CollectionTextPrefix result;
for (std::size_t i = 0; i < text.size();) {
if (text[i] == '\n') {
result.line = result.format;
++i;
} else if (text[i] == '\x1b' && i + 3 < text.size() && text[i + 3] == '[') {
const auto end = text.find(']', i + 4);
if (end == std::string_view::npos) break;
const auto code = text.substr(i + 1, 2);
const auto escape = text.substr(i, end + 1 - i);
result.line.append(escape);
if (code == "FX" || code == "FY" || code == "SH" || code == "SV")
result.format.append(escape);
i = end + 1;
} else {
result.line += text[i++];
}
}
return result;
}

// The native projection helper assumes a viewport whose top is -100. The
// Collection screen uses the full-height viewport at zero instead. Adjust
// coordinates at the helper boundary, for both Link and the mirror/crystal.
inline constexpr float collection_projection_y(float screenY) {
return screenY - 100.0f;
}

inline CollectionViewport collection_viewport(float left, float top, float right, float bottom) {
const float scale = std::fmin((right - left) / 796.0f, (bottom - top) / 448.0f);
return {(left + right - 796.0f * scale) * 0.5f,
(top + bottom - 448.0f * scale) * 0.5f, scale};
}

} // namespace twilight_hd_hud
Loading
Loading