Skip to content
Open
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
83 changes: 63 additions & 20 deletions .github/workflows/replay-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,23 +159,24 @@ jobs:
uses: actions/cache@v5
with:
path: /tmp/cached-assets
key: game-assets-cache-v1

# - name: Checkout game assets repo
# if: steps.cache-assets.outputs.cache-hit != 'true'
# uses: actions/checkout@v7
# with:
# repository: fbraz3/generalsx-test-files
# path: ci-assets-repo
# lfs: true
key: game-assets-cache-v2

- name: Clone game assets repo (GitLab with GitHub fallback)
if: steps.cache-assets.outputs.cache-hit != 'true'
run: |
# GeneralsX @build fbraz3 24/06/2026 Support checking out current branch if present in assets/replays repo.
TARGET_BRANCH="${{ github.head_ref || github.ref_name }}"
clone_success=true
git clone https://gitlab.com/fbraz3/generalsx-test-files.git ci-assets-repo || clone_success=false
if [ "$clone_success" = true ]; then
cd ci-assets-repo
if [ "$TARGET_BRANCH" != "main" ] && [ -n "$TARGET_BRANCH" ]; then
if git ls-remote --heads origin "$TARGET_BRANCH" | grep -q "refs/heads/$TARGET_BRANCH"; then
echo "Branch $TARGET_BRANCH exists on GitLab. Checking out..."
git fetch origin "$TARGET_BRANCH"
git checkout "$TARGET_BRANCH"
fi
fi
git lfs install
if git lfs pull; then
echo "Successfully cloned and pulled LFS from GitLab."
Expand All @@ -192,6 +193,13 @@ jobs:
if [ "$clone_success" = false ]; then
git clone https://github.com/fbraz3/generalsx-test-files.git ci-assets-repo
cd ci-assets-repo
if [ "$TARGET_BRANCH" != "main" ] && [ -n "$TARGET_BRANCH" ]; then
if git ls-remote --heads origin "$TARGET_BRANCH" | grep -q "refs/heads/$TARGET_BRANCH"; then
echo "Branch $TARGET_BRANCH exists on GitHub. Checking out..."
git fetch origin "$TARGET_BRANCH"
git checkout "$TARGET_BRANCH"
fi
fi
git lfs install
git lfs pull
fi
Expand Down Expand Up @@ -223,11 +231,46 @@ jobs:
# 5. Check out replay repo and install maps + platform-specific replays
# Repo structure: GeneralsXZH/Maps/<MapFolder>/ GeneralsXZH/Replays/<platform>_*.rep
# -----------------------------------------------------------------------
- name: Checkout replay files repo
uses: actions/checkout@v6
- name: Cache Replay Files
id: cache-replays
uses: actions/cache@v5
with:
repository: fbraz3/GeneralsXReplays
path: ci-replays-repo
key: replay-files-cache-v1

- name: Clone replay files repo (GitLab with GitHub fallback)
if: steps.cache-replays.outputs.cache-hit != 'true'
run: |
# GeneralsX @build fbraz3 24/06/2026 Clone replays from GitLab with GitHub fallback and support checking out current branch.
TARGET_BRANCH="${{ github.head_ref || github.ref_name }}"
clone_success=true
git clone https://gitlab.com/fbraz3/generalsx-replays.git ci-replays-repo || clone_success=false
if [ "$clone_success" = true ]; then
cd ci-replays-repo
if [ "$TARGET_BRANCH" != "main" ] && [ -n "$TARGET_BRANCH" ]; then
if git ls-remote --heads origin "$TARGET_BRANCH" | grep -q "refs/heads/$TARGET_BRANCH"; then
echo "Branch $TARGET_BRANCH exists on GitLab. Checking out..."
git fetch origin "$TARGET_BRANCH"
git checkout "$TARGET_BRANCH"
fi
fi
echo "Successfully cloned replays from GitLab."
else
echo "GitLab clone failed. Cleaning up and falling back to GitHub..."
rm -rf ci-replays-repo
fi
if [ "$clone_success" = false ]; then
git clone https://github.com/fbraz3/GeneralsXReplays.git ci-replays-repo
cd ci-replays-repo
if [ "$TARGET_BRANCH" != "main" ] && [ -n "$TARGET_BRANCH" ]; then
if git ls-remote --heads origin "$TARGET_BRANCH" | grep -q "refs/heads/$TARGET_BRANCH"; then
echo "Branch $TARGET_BRANCH exists on GitHub. Checking out..."
git fetch origin "$TARGET_BRANCH"
git checkout "$TARGET_BRANCH"
fi
fi
echo "Successfully cloned replays from GitHub."
fi

- name: Set up user data directories (Linux)
if: inputs.platform == 'linux' || inputs.platform == 'linux-flatpak'
Expand All @@ -244,12 +287,12 @@ jobs:
ls -la "$MAPS_DIR/"
fi

# Install platform-specific replays
REPLAY_COUNT=$(ls "$GITHUB_WORKSPACE/ci-replays-repo/GeneralsXZH/Replays"/linux_*.rep 2>/dev/null | wc -l)
# Install all replays
REPLAY_COUNT=$(ls "$GITHUB_WORKSPACE/ci-replays-repo/GeneralsXZH/Replays"/*.rep 2>/dev/null | wc -l)
if [ "$REPLAY_COUNT" -eq 0 ]; then
echo "WARNING: No linux_*.rep files found in replay repo"
echo "WARNING: No .rep files found in replay repo"
else
cp "$GITHUB_WORKSPACE/ci-replays-repo/GeneralsXZH/Replays"/linux_*.rep "$REPLAYS_DIR/"
cp "$GITHUB_WORKSPACE/ci-replays-repo/GeneralsXZH/Replays"/*.rep "$REPLAYS_DIR/"
echo "Replays installed ($REPLAY_COUNT files):"
ls -la "$REPLAYS_DIR/"
fi
Expand Down Expand Up @@ -279,12 +322,12 @@ jobs:
ls -la "$MAPS_DIR/"
fi

# Install platform-specific replays
REPLAY_COUNT=$(ls "$GITHUB_WORKSPACE/ci-replays-repo/GeneralsXZH/Replays"/macos_*.rep 2>/dev/null | wc -l)
# Install all replays
REPLAY_COUNT=$(ls "$GITHUB_WORKSPACE/ci-replays-repo/GeneralsXZH/Replays"/*.rep 2>/dev/null | wc -l)
if [ "$REPLAY_COUNT" -eq 0 ]; then
echo "WARNING: No macos_*.rep files found in replay repo"
echo "WARNING: No .rep files found in replay repo"
else
cp "$GITHUB_WORKSPACE/ci-replays-repo/GeneralsXZH/Replays"/macos_*.rep "$REPLAYS_DIR/"
cp "$GITHUB_WORKSPACE/ci-replays-repo/GeneralsXZH/Replays"/*.rep "$REPLAYS_DIR/"
echo "Replays installed ($REPLAY_COUNT files):"
ls -la "$REPLAYS_DIR/"
fi
Expand Down
18 changes: 9 additions & 9 deletions Core/GameEngine/Include/Common/GameDefines.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,29 +80,29 @@
#endif

#ifndef PRESERVE_RETAIL_SCRIPTED_CAMERA
#define PRESERVE_RETAIL_SCRIPTED_CAMERA (1) // Retain scripted camera behavior present in retail Generals 1.08 and Zero Hour 1.04
#define PRESERVE_RETAIL_SCRIPTED_CAMERA (0) // Retain scripted camera behavior present in retail Generals 1.08 and Zero Hour 1.04
#endif

#ifndef RETAIL_COMPATIBLE_CRC
#define RETAIL_COMPATIBLE_CRC (1) // Game is expected to be CRC compatible with retail Generals 1.08, Zero Hour 1.04
#define RETAIL_COMPATIBLE_CRC (0) // Game is expected to be CRC compatible with retail Generals 1.08, Zero Hour 1.04
#endif

#ifndef RETAIL_COMPATIBLE_XFER_SAVE
#define RETAIL_COMPATIBLE_XFER_SAVE (1) // Game is expected to be Xfer Save compatible with retail Generals 1.08, Zero Hour 1.04
#define RETAIL_COMPATIBLE_XFER_SAVE (0) // Game is expected to be Xfer Save compatible with retail Generals 1.08, Zero Hour 1.04
#endif

// This is here to easily toggle between the retail compatible with fixed pathfinding fallback and pure fixed pathfinding mode
#ifndef RETAIL_COMPATIBLE_PATHFINDING
#define RETAIL_COMPATIBLE_PATHFINDING (1)
#define RETAIL_COMPATIBLE_PATHFINDING (0)
#endif

// This is here to easily toggle between the retail compatible pathfinding memory allocation and the new static allocated data mode
#ifndef RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION
#define RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION (1)
#define RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION (0)
#endif

#ifndef RETAIL_COMPATIBLE_CIRCLE_FILL_ALGORITHM
#define RETAIL_COMPATIBLE_CIRCLE_FILL_ALGORITHM (1) // Use the original circle fill algorithm, which is more efficient but less accurate
#define RETAIL_COMPATIBLE_CIRCLE_FILL_ALGORITHM (0) // Use the original circle fill algorithm, which is more efficient but less accurate
#endif

// Disable non retail fixes in the networking, such as putting more data per UDP packet
Expand All @@ -115,7 +115,7 @@
// but put them behind this macro.

#ifndef RETAIL_COMPATIBLE_AIGROUP
#define RETAIL_COMPATIBLE_AIGROUP (1) // AIGroup logic is expected to be CRC compatible with retail Generals 1.08, Zero Hour 1.04
#define RETAIL_COMPATIBLE_AIGROUP (0) // AIGroup logic is expected to be CRC compatible with retail Generals 1.08, Zero Hour 1.04
#endif

#ifndef ENABLE_GAMETEXT_SUBSTITUTES
Expand Down Expand Up @@ -174,5 +174,5 @@

#define MIN_DISPLAY_BIT_DEPTH 16
#define DEFAULT_DISPLAY_BIT_DEPTH 32
#define DEFAULT_DISPLAY_WIDTH 800 // The standard resolution this game was designed for
#define DEFAULT_DISPLAY_HEIGHT 600 // The standard resolution this game was designed for
#define DEFAULT_DISPLAY_WIDTH 1024 // The standard resolution this game was designed for
#define DEFAULT_DISPLAY_HEIGHT 768 // The standard resolution this game was designed for
5 changes: 3 additions & 2 deletions Core/GameEngine/Source/Common/INI/INI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine
#define DEFINE_DEATH_NAMES

#include "WWMath/wwmath.h"
#include "Common/INI.h"
#include "Common/INIException.h"

Expand Down Expand Up @@ -1866,15 +1867,15 @@ void INI::parseDurationReal( INI *ini, void * /*instance*/, void *store, const v
void INI::parseDurationUnsignedInt( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ )
{
UnsignedInt val = scanUnsignedInt(ini->getNextToken());
*(UnsignedInt *)store = (UnsignedInt)ceilf(ConvertDurationFromMsecsToFrames((Real)val));
*(UnsignedInt *)store = (UnsignedInt)WWMath::Ceil(ConvertDurationFromMsecsToFrames((Real)val));
}

// ------------------------------------------------------------------------------------------------
// parse a duration in msec and convert to duration in integral number of frames, (unsignedshort) rounding UP
void INI::parseDurationUnsignedShort( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ )
{
UnsignedInt val = scanUnsignedInt(ini->getNextToken());
*(UnsignedShort *)store = (UnsignedShort)ceilf(ConvertDurationFromMsecsToFrames((Real)val));
*(UnsignedShort *)store = (UnsignedShort)WWMath::Ceil(ConvertDurationFromMsecsToFrames((Real)val));
}

//-------------------------------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ GameMessageDisposition PlaceEventTranslator::translateGameMessage(const GameMess
Int x, y;
x = mouse.x - start.x;
y = mouse.y - start.y;
if( sqrt( (x * x) + (y * y) ) >= PLACEMENT_DRAG_THRESHOLD_DIST )
if( WWMath::SqrtOrigin( (x * x) + (y * y) ) >= PLACEMENT_DRAG_THRESHOLD_DIST )
{

TheInGameUI->setPlacementEnd(&mouse);
Expand Down
17 changes: 9 additions & 8 deletions Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
// Author: Michael S. Booth, October 2001
#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine

#include "WWMath/wwmath.h"
#include "GameLogic/AIPathfind.h"

#include "Common/PerfTimer.h"
Expand Down Expand Up @@ -921,7 +922,7 @@ void Path::computePointOnPath(
// compute distance of point from this path segment
Real toDistSqr = sqr(toPos.x) + sqr(toPos.y);
Real offsetDistSq = toDistSqr - sqr(alongPathDist);
Real offsetDist = (offsetDistSq <= 0.0) ? 0.0 : sqrt(offsetDistSq);
Real offsetDist = (offsetDistSq <= 0.0) ? 0.0 : WWMath::SqrtOrigin(offsetDistSq);

// If we are basically on the path, return the next path node as the movement goal.
// However, the farther off the path we get, the movement goal becomes closer to our
Expand Down Expand Up @@ -2070,7 +2071,7 @@ UnsignedInt PathfindCell::costToGoal( PathfindCell *goal )
Int dy = m_info->m_pos.y - goal->getYIndex();
#define NO_REAL_DIST
#ifdef REAL_DIST
Int cost = COST_ORTHOGONAL*sqrt(dx*dx + dy*dy);
Int cost = COST_ORTHOGONAL*WWMath::SqrtOrigin(dx*dx + dy*dy);
#else
if (dx<0) dx = -dx;
if (dy<0) dy = -dy;
Expand All @@ -2096,7 +2097,7 @@ UnsignedInt PathfindCell::costToHierGoal( PathfindCell *goal )
}
Int dx = m_info->m_pos.x - goal->getXIndex();
Int dy = m_info->m_pos.y - goal->getYIndex();
Int cost = REAL_TO_INT_FLOOR(COST_ORTHOGONAL*sqrt(dx*dx + dy*dy) + 0.5f);
Int cost = REAL_TO_INT_FLOOR(COST_ORTHOGONAL*WWMath::SqrtOrigin(dx*dx + dy*dy) + 0.5f);
return cost;
}

Expand Down Expand Up @@ -6444,7 +6445,7 @@ Int Pathfinder::examineNeighboringCells(PathfindCell *parentCell, PathfindCell *
} else {
dx = newCellCoord.x - goalCell->getXIndex();
dy = newCellCoord.y - goalCell->getYIndex();
costRemaining = COST_ORTHOGONAL*sqrt(dx*dx + dy*dy);
costRemaining = COST_ORTHOGONAL*WWMath::SqrtOrigin(dx*dx + dy*dy);
costRemaining -= attackDistance/2;
if (costRemaining<0)
costRemaining=0;
Expand Down Expand Up @@ -6768,7 +6769,7 @@ Path *Pathfinder::internalFindPath( Object *obj, const LocomotorSet& locomotorSe
dx = from->x - to->x;
dy = from->y - to->y;

Int count = sqrt(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2);
Int count = WWMath::SqrtOrigin(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2);
if (count<2) count = 2;
Int i;
color.green = 0;
Expand Down Expand Up @@ -7459,7 +7460,7 @@ Path *Pathfinder::findGroundPath( const Coord3D *from,
dx = from->x - to->x;
dy = from->y - to->y;

Int count = sqrt(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2);
Int count = WWMath::SqrtOrigin(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2);
if (count<2) count = 2;
Int i;
color.green = 0;
Expand Down Expand Up @@ -8163,7 +8164,7 @@ Path *Pathfinder::internal_findHierarchicalPath( Bool isHuman, const LocomotorSu
dx = from->x - to->x;
dy = from->y - to->y;

Int count = sqrt(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2);
Int count = WWMath::SqrtOrigin(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2);
if (count<2) count = 2;
Int i;
color.green = 0;
Expand Down Expand Up @@ -11216,7 +11217,7 @@ Path *Pathfinder::findSafePath( const Object *obj, const LocomotorSet& locomotor
farthestDistanceSqr = distSqr;
if (cellCount > MAX_CELLS) {
#ifdef INTENSE_DEBUG
DEBUG_LOG(("Took intermediate path, dist %f, goal dist %f", sqrt(farthestDistanceSqr), repulsorRadius));
DEBUG_LOG(("Took intermediate path, dist %f, goal dist %f", WWMath::SqrtOrigin(farthestDistanceSqr), repulsorRadius));
#endif
ok = true; // Already a big search, just take this one.
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@

#include "GameLogic/GameLogic.h"
#include "GameLogic/TerrainLogic.h"
// GeneralsX @bugfix fbraz3 24/06/2026 Save and restore FPU precision mode when calling audio manager entrypoints.
#include "GameLogic/FPUControl.h"

#include "Common/file.h"
#include "VideoDevice/FFmpeg/FFmpegFile.h"
Expand Down Expand Up @@ -108,6 +110,7 @@ MiniAudioManager::~MiniAudioManager()
#if defined(_DEBUG) || defined(_INTERNAL)
AudioHandle MiniAudioManager::addAudioEvent(const AudioEventRTS *eventToAdd)
{
ScopedFPUGuard fpuGuard;
if (TheGlobalData->m_preloadReport) {
if (!eventToAdd->getEventName().isEmpty()) {
m_allEventsLoaded.insert(eventToAdd->getEventName());
Expand Down Expand Up @@ -176,6 +179,7 @@ void MiniAudioManager::audioDebugDisplay(DebugDisplayInterface *dd, void *, FILE
//-------------------------------------------------------------------------------------------------
void MiniAudioManager::init()
{
ScopedFPUGuard fpuGuard;
AudioManager::init();
#ifdef INTENSE_DEBUG
return;
Expand Down Expand Up @@ -204,6 +208,7 @@ void MiniAudioManager::reset()
//-------------------------------------------------------------------------------------------------
void MiniAudioManager::update()
{
ScopedFPUGuard fpuGuard;
AudioManager::update();
setDeviceListenerPosition();
processRequestList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@

#include "GameLogic/GameLogic.h"
#include "GameLogic/TerrainLogic.h"
// GeneralsX @bugfix fbraz3 24/06/2026 Save and restore FPU precision mode when calling audio manager entrypoints.
#include "GameLogic/FPUControl.h"

#include "Common/file.h"

Expand Down Expand Up @@ -126,6 +128,7 @@ OpenALAudioManager::~OpenALAudioManager()
#if defined(_DEBUG) || defined(_INTERNAL)
AudioHandle OpenALAudioManager::addAudioEvent(const AudioEventRTS* eventToAdd)
{
ScopedFPUGuard fpuGuard;
if (TheGlobalData->m_preloadReport) {
if (!eventToAdd->getEventName().isEmpty()) {
m_allEventsLoaded.insert(eventToAdd->getEventName());
Expand Down Expand Up @@ -490,6 +493,7 @@ ALenum OpenALAudioManager::getALFormat(uint8_t channels, uint8_t bitsPerSample)
//-------------------------------------------------------------------------------------------------
void OpenALAudioManager::init()
{
ScopedFPUGuard fpuGuard;
AudioManager::init();
#ifdef INTENSE_DEBUG
DEBUG_LOG(("Sound has temporarily been disabled in debug builds only. jkmcd\n"));
Expand Down Expand Up @@ -528,6 +532,7 @@ void OpenALAudioManager::reset()
//-------------------------------------------------------------------------------------------------
void OpenALAudioManager::update()
{
ScopedFPUGuard fpuGuard;
AudioManager::update();
setDeviceListenerPosition();
processRequestList();
Expand Down
6 changes: 1 addition & 5 deletions Core/Libraries/Include/Lib/BaseType.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,13 +223,9 @@ __forceinline float fast_float_ceil(float f)
#define INT_TO_REAL(x) ((Real)(x))

// once we've ceiled/floored, trunc and round are identical, and currently, round is faster... (srj)
#if RTS_GENERALS /*&& RETAIL_COMPATIBLE_CRC*/
#define REAL_TO_INT_CEIL(x) (fast_float2long_round(ceilf(x)))
#define REAL_TO_INT_FLOOR(x) (fast_float2long_round(floorf(x)))
#else
// GeneralsX @feature fbraz 03/05/2026 Use deterministic fast_float functions universally to ensure cross-platform replay determinism.
#define REAL_TO_INT_CEIL(x) (fast_float2long_round(fast_float_ceil(x)))
#define REAL_TO_INT_FLOOR(x) (fast_float2long_round(fast_float_floor(x)))
#endif

#define FAST_REAL_TRUNC(x) fast_float_trunc(x)
#define FAST_REAL_CEIL(x) fast_float_ceil(x)
Expand Down
Loading
Loading