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
27 changes: 26 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# A version tag (v0.1.0): test, build the installer with that version, and publish it as a GitHub Release.
# 0.x versions and versions with a suffix (v1.0.0-rc1) are marked as pre-releases.
# The release text is release-notes/<tag>.md (release-notes/v1.2.0.md), followed by GitHub's generated list of changes.
# A release needs its notes file; a pre-release without one gets the generated list only.
name: Release

on:
Expand All @@ -15,6 +17,25 @@ jobs:
steps:
- uses: actions/checkout@v4

# Checked first, so a missing notes file fails the run in seconds rather than after the build.
- name: Find the release notes
id: notes
shell: pwsh
run: |
$tag = '${{ github.ref_name }}'
$version = $tag.TrimStart('v')
$prerelease = $version.StartsWith('0.') -or $version.Contains('-')
$file = "release-notes/$tag.md"
if (Test-Path $file) {
if (-not (Get-Content $file -Raw).Trim()) { throw "$file is empty: write the release notes, then tag again" }
"file=$file" >> $env:GITHUB_OUTPUT
} elseif ($prerelease) {
Write-Host "::notice::No $file; the pre-release gets GitHub's generated notes only"
} else {
throw "No release notes for $tag. Add $file (see release-notes/README.md), then tag again."
}
"prerelease=$($prerelease.ToString().ToLowerInvariant())" >> $env:GITHUB_OUTPUT

- uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
Expand Down Expand Up @@ -53,8 +74,12 @@ jobs:
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
NOTES_FILE: ${{ steps.notes.outputs.file }}
PRERELEASE: ${{ steps.notes.outputs.prerelease }}
run: |
$version = '${{ github.ref_name }}'.TrimStart('v')
# With --generate-notes, the notes file comes first and GitHub's list of changes is added after it.
$flags = @('--title', "OrthoStreamXP $version", '--generate-notes')
if ($version.StartsWith('0.') -or $version.Contains('-')) { $flags += '--prerelease' }
if ($env:NOTES_FILE) { $flags += @('--notes-file', $env:NOTES_FILE) }
if ($env:PRERELEASE -eq 'true') { $flags += '--prerelease' }
gh release create '${{ github.ref_name }}' (Get-ChildItem artifacts\releases -File).FullName @flags
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dotnet test --solution OrthoStreamXP.slnx --filter-method "*Name*" # single te
- `TreatWarningsAsErrors` and `AnalysisMode=Recommended` are on (`Directory.Build.props`): warnings fail the build.
- `MountSmokeTests` performs a real WinFsp mount with synthetic imagery; it is skipped locally if WinFsp is missing, but always runs when the `CI` env var is set. CI (`.github/workflows/ci.yml`) runs on Windows and installs WinFsp.
- Tests use `FakeTileServer` / `TempTree` helpers instead of the network.
- Releases: pushing a `v*` tag runs `.github/workflows/release.yml`, which needs `release-notes/<tag>.md` (pre-releases may skip it) and puts it above GitHub's generated notes. See `release-notes/README.md`.

## Branching

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Traditional photo scenery often requires downloading and building large areas be
- **Settings that adapt to your setup** — test your connection before the first flight to get a recommended preset, and after a sufficiently long flight, get recommendations based on how the scenery performed on your PC and internet connection. You decide whether to apply them.
- **Even colours** — optionally soften the patchwork look where neighbouring photos were taken on different days, so a grey, hazy patch stands out less.
- **Seasonal colours** — optionally give summer imagery a more autumnal or winter appearance, based on the simulated date and location.
- **Clean photos up close** — X-Plane's procedural grass and dirt speckles are kept off the aerial photos by default, or faded to the strength you like, without changing your scenery packages.
- **Exposure and a live preview** — tone down photos that look too bright next to X-Plane's own scenery, and see any colour change on a photo from your cache before you fly.
- **See what happened during your flight** — review download performance, scenery loading, and your flight route.

Expand Down
Binary file modified docs/OrthoStreamXP-UserGuide.pdf
Binary file not shown.
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ OrthoStreamXP adds real-world aerial photos to your X-Plane 12 scenery while you
1. [Installation](installation.md): what you need and how to install it.
2. [First setup](first-setup.md): the six-step setup that runs the first time you open OrthoStreamXP.
3. [Flying](flying.md): the start order, the Home page, and the report you get after each flight.
4. [Settings](settings.md): quality, folders, downloads, colours, seasons and exposure.
4. [Settings](settings.md): quality and ground detail, folders, downloads, colours, seasons and exposure.
5. [Cache](cache.md): how much disk space the scenery uses and how to free it.
6. [Packages](packages.md): downloading the ground mesh of a whole region, the quickest way to get scenery packages.
7. [Troubleshooting](troubleshooting.md): common problems, the log, updates, uninstalling and reporting a bug.
Expand Down
Binary file modified docs/images/settings-quality.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 13 additions & 1 deletion docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ At the bottom of every tab:

Choose how sharp the ground is. Each preset comes with settings tested for it and keeps its own textures in the cache, so you can switch at any time without losing anything.

| Preset | Textures | Imagery zoom | Ground detail | Best for |
| Preset | Textures | Imagery zoom | Resolution | Best for |
|---|---|---|---|---|
| **Balanced** | 1024 px | 15, 16 near airports | ~6.8 m per pixel | High flying, a slower connection or little graphics memory |
| **Sharp** | 2048 px | 16, 17 near airports | ~3.4 m per pixel | Everyday flying, approaches included |
Expand All @@ -31,6 +31,18 @@ Each card shows the imagery zoom, the metres per pixel, roughly how long a new a
- The **Recommended** badge marks the preset that suits your connection and graphics card, with the reason below it.
- If a preset's photo textures plus X-Plane's own needs (about 5 GB) would be more than your graphics card has, its card shows a warning. X-Plane may then stutter or lower its texture detail; a smaller preset is safer.

### Ground detail

Up close, X-Plane can sprinkle its own procedural grass, shrubs and dirt over the ground. This helps X-Plane's blurry default textures, but on sharp aerial photos it looks like noise: dark speckles and pale stony patches that the real imagery does not have. The photo scenery packages ask for it in every photo tile, so you see it with any scenery packages.

The **Ground detail** slider sets how strong this effect is:

- **Off** (0 %, the default): the photos are shown as they are.
- **10–90 %**: a lighter version of X-Plane's effect, for some extra texture when you are low.
- **100 %**: X-Plane's full effect, as the packages define it.

Your scenery packages are not changed: OrthoStreamXP adjusts the terrain files as X-Plane reads them. The in-between strengths are made from X-Plane's own decal files, so they need the **X-Plane folder** to be set. Like every setting, a change applies from the next start.

## Folders

![Settings: Folders](images/settings-folders.png)
Expand Down
1 change: 1 addition & 0 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ When something goes wrong, Home shows a notice that says what happened, usually
| **Blurry ground for a while** | A new area takes a few minutes the first time. If it happens often, try a lower preset or apply the tuning suggestions after your flight (see [Flying](flying.md#tuning-suggestions)). |
| **Patchy colours between photos** | Turn on **Even out the colours between photos** in [Settings › Colours](settings.md#colours). |
| **Photos look too bright next to X-Plane's own scenery** | Lower **Exposure** in [Settings › Colours](settings.md#exposure). |
| **Noisy, speckled ground up close** | X-Plane's grass and dirt detail is drawn over the photos. Set **Ground detail** to **Off** (or lower it) in [Settings › Quality](settings.md#ground-detail). |

## The Log page

Expand Down
37 changes: 37 additions & 0 deletions release-notes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Release notes

One Markdown file per release, named after its tag: `v1.2.0.md` for the tag `v1.2.0`.

When the tag is pushed, the [release workflow](../.github/workflows/release.yml) puts the file's text at the top of
the GitHub Release. GitHub's generated list of changes (merged pull requests, new contributors) follows it.

## Releasing

1. Write `release-notes/vX.Y.Z.md` and commit it with the release.
2. Push the tag `vX.Y.Z`.

A release (`v1.2.0`) needs its notes file. If the file is missing or empty, the workflow stops at its first step,
before anything is built or published: add the file, then delete and push the tag again. A pre-release (`v0.x`, or a
suffix such as `v1.2.0-rc1`) may have no notes file; it then gets the generated list only.

## Writing the notes

The notes are for people who fly with OrthoStreamXP, not for developers: say what changed for them, in plain words.
GitHub already lists the pull requests below, so there is no need to repeat them.

```markdown
## What's new

- **Feature name.** What it does and where to find it.

## Fixes

- What no longer goes wrong.

## Good to know

- Anything to do after updating, or a change of behaviour people should expect.
```

Leave out the sections that have nothing in them. The page title ("OrthoStreamXP 1.2.0") is set by the workflow,
so the file needs no title of its own.
7 changes: 7 additions & 0 deletions release-notes/v1.2.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## What's new

- **Ground detail.** X-Plane draws its own procedural grass, shrubs and dirt over the ground up close. On sharp aerial photos this looks like noise: dark speckles and pale stony patches that the real imagery does not have. It is now off by default, so the photos look as they do on the map. If you like some extra texture when flying low, raise **Ground detail** in **Settings › Quality**: anything between 10 and 90 % is a lighter version of the effect, and 100 % is X-Plane's full effect. Your scenery packages are not changed.

## Good to know

- After updating, the ground up close looks cleaner than before. To get the old look back, set **Ground detail** to 100 %.
5 changes: 5 additions & 0 deletions src/OrthoStreamXP/Configuration/MountSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ internal sealed record MountSettings
public double ProvisionalWait { get; init; }
public string? TextureStore { get; init; }
public bool LazyTerrainListing { get; init; }
/// <summary>How strong X-Plane's detail decals are on the photo terrain, 0 (left out) to 100 percent (as the packages define).</summary>
public int GroundDetail { get; init; }
/// <summary>A texture-quality value from an older settings file: no longer used, noted in the log.</summary>
public string? IgnoredTextureQuality { get; init; }
/// <summary>Photo textures are served level by level as X-Plane reads them (off: built whole first).</summary>
Expand Down Expand Up @@ -137,6 +139,8 @@ public static MountSettings FromValues(SettingValues values, bool console = true
throw new SettingsException($"exposure must be between {OrthoStreamXP.Textures.Exposure.Min} and {OrthoStreamXP.Textures.Exposure.Full}");
string readLogValue = values.String("read-log") ?? "off";
bool readLog = ParseOnOff(readLogValue) ?? throw new SettingsException($"read-log must be on or off, not '{readLogValue}'");
int groundDetail = values.Int("ground-detail", 0);
if (groundDetail is < 0 or > Scenery.TerrainDecals.Full) throw new SettingsException("ground-detail must be between 0 and 100");

if (downloadWorkers < 1) throw new SettingsException("download-workers must be at least 1");
if (downloadTimeout is < 1 or > 120) throw new SettingsException("download-timeout must be between 1 and 120 seconds");
Expand Down Expand Up @@ -188,6 +192,7 @@ public static MountSettings FromValues(SettingValues values, bool console = true
ProvisionalWait = provisionalWait,
TextureStore = textureStore,
LazyTerrainListing = values.Flag("lazy-terrain-listing"),
GroundDetail = groundDetail,
IgnoredTextureQuality = values.String("texture-quality"),
MipStreaming = mipStreaming,
NearWait = nearWait,
Expand Down
1 change: 1 addition & 0 deletions src/OrthoStreamXP/Configuration/SettingsOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ internal static class SettingsOptions
.Add("filesystem-threads", $"Threads answering X-Plane's file requests, 2-512. A request waiting for a texture build keeps its thread, so this must comfortably exceed the textures X-Plane waits for at once (default: {MountSettings.DefaultFileSystemThreads})")
.Add("texture-quality", "No longer used: the texture size follows level-change (accepted so older settings files still load)")
.Add("background-textures", "Build missing DDS files in a bounded background queue when a local DDS fallback exists", arity: 0)
.Add("ground-detail", "How strong X-Plane's detail decals are over the photo scenery, 0-100 percent. The terrain definitions name a decal library (DECAL_LIB) that sprinkles procedural grass, shrubs and dirt over the ground up close; it adds texture to X-Plane's own blurry land-class textures, but over sharp aerial photos it looks like noise. 0 leaves the decals out, 100 keeps them as the packages define, and anything between fades them (read from the X-Plane folder, so it needs xplane). Applied to the photo tiles' terrain definitions as X-Plane reads them; the packages' files are not changed. Takes effect at the next start (default: 0)")
.Add("lazy-terrain-listing", "Show an empty terrain directory listing; DSF-referenced .ter files remain directly accessible", arity: 0)
.Add("prefetch-loaded-scenery", "on or off: build the textures of each DSF X-Plane loads as soon as it opens the DSF's terrain definitions, before X-Plane asks for them one by one (default: on)")
.Add("track-aircraft", "Follow the aircraft through X-Plane's UDP DATA output (row 20) and build the loaded scenery's textures ahead of it", arity: 0)
Expand Down
1 change: 1 addition & 0 deletions src/OrthoStreamXP/Gui/Settings/SettingNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ internal static class SettingNames
private static readonly Dictionary<string, string> Names = new(StringComparer.Ordinal)
{
["level-change"] = "Quality",
["ground-detail"] = "Ground detail",
["xplane"] = "X-Plane folder",
["base-packages"] = "Scenery packages folder",
["source"] = "Scenery packages folder",
Expand Down
19 changes: 19 additions & 0 deletions src/OrthoStreamXP/Gui/ViewModels/SettingsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,24 @@ public double Exposure

public string ExposureText => $"{Exposure:0} %";

/// <summary>How strong X-Plane's grass and dirt decals are over the photos, in percent, for the slider.</summary>
public double GroundDetail
{
get => _document.GetDouble("ground-detail") ?? 0;
set
{
Save("ground-detail", Math.Clamp((int)Math.Round(value), 0, Scenery.TerrainDecals.Full).ToString(CultureInfo.InvariantCulture));
Raise();
Raise(nameof(GroundDetailText));
}
}

public string GroundDetailText => GroundDetail switch
{
<= 0 => "Off",
_ => $"{GroundDetail:0} %",
};

public string XPlaneFolder
{
get => _xplane;
Expand Down Expand Up @@ -464,6 +482,7 @@ public void Reload()
{
nameof(Provider), nameof(DownloadWorkers), nameof(DownloadWorkersText), nameof(FilePath), nameof(CacheEstimate),
nameof(MatchColoursOn), nameof(MatchColoursOnText), nameof(MatchStrength), nameof(MatchStrengthText), nameof(SeasonsOn), nameof(SeasonsOnText), nameof(Season), nameof(SeasonStrength), nameof(SeasonStrengthText), nameof(Exposure), nameof(ExposureText),
nameof(GroundDetail), nameof(GroundDetailText),
nameof(TunedRows), nameof(TuningText), nameof(CanReset), nameof(ResetQuestion),
})
Raise(name);
Expand Down
23 changes: 23 additions & 0 deletions src/OrthoStreamXP/Gui/Views/SettingsView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,29 @@
<TextBlock Text="{Binding CustomLevelNote}" Foreground="{DynamicResource SystemFillColorCautionBrush}" TextWrapping="Wrap" Margin="2,0,0,8"
Visibility="{Binding CustomLevelNote, Converter={StaticResource Visible}}" />

<!-- Ground detail: X-Plane's decals over the photos -->
<TextBlock Text="Ground detail" Style="{StaticResource SectionTitle}" FontSize="16" Margin="2,28,0,2" />
<TextBlock Style="{StaticResource Secondary}" Margin="2,0,0,10" TextWrapping="Wrap"
Text="X-Plane's own grass and dirt texture, drawn over the photos up close." />
<Border Style="{StaticResource RowCard}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel VerticalAlignment="Center" Margin="0,0,24,0">
<TextBlock Text="Ground detail" />
<TextBlock Style="{StaticResource Caption}" Margin="0,2,0,0" TextWrapping="Wrap"
Text="X-Plane can sprinkle procedural grass, shrubs and dirt over the ground when you are low. That livens up its own blurry textures, but on sharp aerial photos it looks like noise, so it is off by default. Raise it if you like some extra texture up close: 100 % is X-Plane's full effect. Your scenery packages are not changed." />
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
<Slider Width="200" Minimum="0" Maximum="100" TickFrequency="10" IsSnapToTickEnabled="True" VerticalAlignment="Center"
Value="{Binding GroundDetail, Delay=400}" AutomationProperties.Name="Ground detail" />
<TextBlock Text="{Binding GroundDetailText}" Width="80" Margin="12,0,0,0" VerticalAlignment="Center" />
</StackPanel>
</Grid>
</Border>

</StackPanel>
</TabItem>
<TabItem Header="Folders">
Expand Down
10 changes: 9 additions & 1 deletion src/OrthoStreamXP/Mounting/MountSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,21 @@ private void StartAll()
if (s.TextureStore is not null) Log.Info($"Lazy local DDS store: {s.TextureStore}");
if (s.LazyTerrainListing)
Log.Info("Terrain directory listing is lazy; DSF-referenced definitions remain available by direct path");
// X-Plane's own folder holds the decal libraries; with a plain mountpoint there is none.
var decals = new TerrainDecals(Path.GetDirectoryName(s.CustomScenery), s.GroundDetail);
Log.Info(s.GroundDetail switch
{
0 => "Ground detail: 0 %; X-Plane's grass and dirt decals are left out of the photo terrain (the packages are not changed)",
TerrainDecals.Full => "Ground detail: 100 %; X-Plane draws its grass and dirt decals over the photos as the packages define",
_ => $"Ground detail: {s.GroundDetail} %; X-Plane's grass and dirt decals are faded over the photos (the packages are not changed)",
});

foreach (PackagePlan package in s.Packages)
{
var roots = new SceneryRoots(package.Source, package.Cache, s.TextureStore, s.Textures);
TerrainDefinitions.RemoveCachedCopies(package.Cache);
roots.RenameToCacheNames();
_mounts.Add(new PackageMount(package, roots, builder, textureOptions, s.TrackAircraft, s.LoadAhead, s.LazyTerrainListing,
_mounts.Add(new PackageMount(package, roots, builder, textureOptions, s.TrackAircraft, s.LoadAhead, s.LazyTerrainListing, decals,
streaming ? new TextureStreamer(roots, builder!, _view, model, streamingOptions) : null, _view, _readLog, _seasons, _usage, _colours, _loads, exposure));
Log.Info($"Package {package.Name}: read-only from {package.Source}, cache overlay {package.Cache}, mounted at {package.Mountpoint}");
}
Expand Down
Loading
Loading