Compare commits

..

11 Commits

Author SHA1 Message Date
maj00r
744720d922 Merge pull request #44 from MaSzyna-EU07/fix/dotted-model-names
fix: a dot in a model name is part of the name, not an extension
2026-11-12 21:33:51 +01:00
maj00r
b5ba67430f fix: a dot in a model name is part of the name, not an extension
The 9-107 and 9-110 coal wagons (9-107.13.fiz and the like) found no physics, so no load could be set on them.
2026-11-12 20:57:36 +01:00
maj00r
c261d4b0ab Merge pull request #43 from MaSzyna-EU07/fix/version-no-suffix
fix: the version is the number alone, with no public beta after it
2026-11-12 19:41:31 +01:00
maj00r
3aa6a256a5 fix: the version is the number alone, with no public beta after it 2026-11-12 19:38:11 +01:00
maj00r
95c510b3ac Merge pull request #41 from MaSzyna-EU07/fix/version-in-title
The version in the window's title
2026-11-12 17:44:17 +01:00
maj00r
5af567be7f Merge pull request #40 from MaSzyna-EU07/feature/blackbox
Black box player: the simulator's recording of a run on a map, with the cab, the timetable and a summary
2026-11-12 17:44:02 +01:00
maj00r
b201b6f7ab Merge pull request #42 from MaSzyna-EU07/fix/consist-strip-selection
fix(scenarios): frame the vehicle started in, no background line between vehicles
2026-11-12 17:43:46 +01:00
maj00r
3a19fd5da2 Merge pull request #38 from MaSzyna-EU07/feat/sonarcloud-analysis
ci: analyse with SonarQube Cloud
2026-11-11 20:16:17 +01:00
maj00r
36df14abee fix(scenarios): the vehicle started in is framed, and no line of the background shows between two vehicles at a scale such as 125 % 2026-11-11 19:01:57 +01:00
maj00r
da5a1efea1 fix: the version in the window's title, where it has room at any width 2026-11-11 17:53:01 +01:00
jerrrrycho
e5330d91a6 ci: analyse with SonarQube Cloud
Runs SonarScanner for .NET around a full build and the tests, with
OpenCover coverage from coverlet, on pushes to main and on pull requests
from branches of this repository. Fork and Dependabot pull requests are
skipped: the C# analysis runs inside the build, so the token and the
built code cannot be kept on separate machines as in maszyna.

The scanner is pinned in .config/dotnet-tools.json for Dependabot.
2026-11-07 06:27:16 +01:00
7 changed files with 171 additions and 5 deletions

13
.config/dotnet-tools.json Normal file
View File

@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-sonarscanner": {
"version": "12.1.0",
"commands": [
"dotnet-sonarscanner"
],
"rollForward": true
}
}
}

114
.github/workflows/sonar.yml vendored Normal file
View File

@@ -0,0 +1,114 @@
# .github/workflows/sonar.yml
# Has SONAR_TOKEN, so it only ever builds code that is already trusted: pushes to
# main and pull requests opened from a branch of this repository.
#
# Unlike the C++ analysis of maszyna, this one cannot be split into an untrusted
# build and a trusted scan. The C# analyzers run inside the compiler, and the
# scanner's begin step, which needs the token, has to run before the build on the
# same machine. A fork's build could leave a process behind to read the token from
# the end step, so fork pull requests are not analysed here. GitHub withholds the
# secret from them (and from Dependabot) anyway.
#
# The scanner is pinned in .config/dotnet-tools.json, where Dependabot updates it.
# SonarScanner for .NET takes its settings on the command line only; it refuses
# to run next to a sonar-project.properties, so there is none.
name: SonarQube Cloud
on:
# Main branch only: analysing other branches is a paid feature, pull requests
# are not.
push:
branches:
- main
pull_request:
branches:
- main
- staging
types: [ opened, reopened, synchronize ]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: sonar-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_NOLOGO: true
SOLUTION_PATH: StarterNG.sln
TEST_PROJECT_PATH: StarterNG.Tests/StarterNG.Tests.csproj
jobs:
sonar:
name: Analyze
if: >
github.event_name != 'pull_request' ||
(github.event.pull_request.head.repo.full_name == github.repository &&
github.actor != 'dependabot[bot]')
runs-on: ubuntu-22.04
timeout-minutes: 30
steps:
# Full history, so new code and blame are told apart correctly.
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Setup .NET 9
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
dotnet-version: 9.0.x
- name: Restore tools
run: dotnet tool restore
# sonar.qualitygate.wait=true blocks the end step until the server has
# processed the report, so the job's result is the quality gate's.
- name: Begin analysis
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: >
dotnet tool run dotnet-sonarscanner begin
/k:"MaSzyna-EU07_StarterNG"
/o:"maszyna-eu07"
/n:"StarterNG"
/d:sonar.host.url="https://sonarcloud.io"
/d:sonar.token="$SONAR_TOKEN"
/d:sonar.scm.revision="${{ github.event.pull_request.head.sha || github.sha }}"
/d:sonar.cs.opencover.reportsPaths="TestResults/**/coverage.opencover.xml"
/d:sonar.cs.vstest.reportsPaths="TestResults/*.trx"
/d:sonar.qualitygate.wait=true
# A full, non-incremental build: the analyzers only see what is compiled.
- name: Build
run: >
dotnet build "${{ env.SOLUTION_PATH }}"
-c Release
--no-incremental
# Coverlet is already referenced by the test project; OpenCover is the
# format the C# analyzer reads.
- name: Test with coverage
run: >
dotnet test "${{ env.TEST_PROJECT_PATH }}"
-c Release
--no-build
--results-directory TestResults
--logger trx
--collect "XPlat Code Coverage"
--
DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
- name: End analysis
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: >
dotnet tool run dotnet-sonarscanner end
/d:sonar.token="$SONAR_TOKEN"

View File

@@ -65,6 +65,19 @@ public class PhysicsAndAssetsTests
Assert.Equal(125, physics.VMax);
}
[Fact]
public void A_dot_in_the_model_name_is_part_of_the_name()
{
// As in the game: cd/9-107_v1 holds 9-107.13.fiz, 9-107.6.fiz and more.
var installation = new TestInstallation(new InMemoryFileSystem()
.WithFile(Fiz("cd", "9-107_v1", "9-107.13"), "Load: LoadAccepted=coal MaxLoad=58")
.WithFile(Fiz("cd", "9-107_v1", "9-107.6"), "Load: LoadAccepted=coal MaxLoad=55"));
Assert.Equal(58, installation.Physics.For("cd/9-107_v1/", "9-107.13")!.MaxLoad);
Assert.Equal(55, installation.Physics.For("cd/9-107_v1/", "9-107.6")!.MaxLoad);
Assert.Equal(58, installation.Physics.For("cd/9-107_v1/", "9-107.13.e3d")!.MaxLoad);
}
[Fact]
public void A_dumb_variant_stands_in_when_the_model_has_no_fiz_of_its_own()
{

View File

@@ -13,6 +13,6 @@ public static class AppInfo
#if DEBUG
text += " (DEBUG)";
#endif
return text + " public beta";
return text;
}
}

View File

@@ -51,7 +51,7 @@ public sealed class FizPhysicsRepository : IPhysicsRepository
if (string.IsNullOrEmpty(model))
return null;
string name = Path.GetFileNameWithoutExtension(model);
string name = Path.GetFileName(model);
if (name.Length == 0)
return null;
@@ -60,7 +60,10 @@ public sealed class FizPhysicsRepository : IPhysicsRepository
if (_cache.TryGetValue(key, out var cached))
return cached;
var physics = Read(folder, name);
// The name as written first: in 9-107.13 the dot is part of the name, not an extension.
string bare = Path.GetFileNameWithoutExtension(name);
var physics = Read(folder, name)
?? (bare.Length > 0 && bare != name ? Read(folder, bare) : null);
_cache[key] = physics;
return physics;
}

View File

@@ -46,6 +46,9 @@ public partial class MainWindow : Window
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
InitializeComponent();
// The top bar lets the version go first when it runs short of room, which at the usual widths it does;
// the title has room for it at any width.
Title = $"{Title} {AppInfo.Version}";
ExtendClientAreaToDecorationsHint = true;
ExtendClientAreaTitleBarHeightHint = TitleBarHeight;

View File

@@ -1009,12 +1009,14 @@ public partial class Scenarios : UserControl
int thumbH = AppServices.Current.Settings.LargeThumbnails ? 64 : 32;
var bmp = LoadMini(miniName, thumbH);
// Fill, not Uniform: at a scale such as 125 % layout rounds the slot up to whole pixels,
// and a mini keeping its own width inside it left a line of the background between two vehicles.
Control visual = bmp is not null
? MiniTextures.Sharp(new Image
{
Source = bmp,
Height = thumbH,
Stretch = Avalonia.Media.Stretch.Uniform,
Stretch = Avalonia.Media.Stretch.Fill,
Margin = new Thickness(0)
})
: MiniTextures.MissingVisual(train.SkinFile, thumbH);
@@ -1022,6 +1024,22 @@ public partial class Scenarios : UserControl
bool selected = !string.IsNullOrEmpty(train.Name) &&
string.Equals(train.Name, AppServices.Current.State.StartingVehicleName,
StringComparison.OrdinalIgnoreCase);
// The vehicle started in gets a frame over its picture as well as the lines above and below:
// two thin lines alone were hard to see. Drawn over the picture, it moves no vehicle aside.
if (selected)
visual = new Panel
{
Children =
{
visual,
new Border
{
BorderThickness = new Thickness(3),
BorderBrush = SelectedVehicleBrush,
IsHitTestVisible = false
}
}
};
var slot = new Border
{
@@ -1210,7 +1228,9 @@ public partial class Scenarios : UserControl
var source = consistStack.Children[i];
// The ghost mirrors what the slot shows, stand-in wagons included, so
// dragging a vehicle with no thumbnail still carries something visible.
Control copy = source is Border { Child: Image image }
// The picked vehicle's picture sits under its frame.
var shown = source is Border { Child: Panel { Children: [Image framed, ..] } } ? framed : (source as Border)?.Child;
Control copy = shown is Image image
? new Image { Source = image.Source, Height = image.Height, Stretch = image.Stretch }
: MiniTextures.MissingVisual(null, (int)source.Bounds.Height);
panel.Children.Add(copy);