build: the exe is LlamaCasty.exe, and the publish script proves the artifact
Renames AssemblyName to LlamaCasty before the first Steam depot — Steamworks is explicit that renaming a live exe is painful, so this had to land first, not later. Four assembly-qualified pack URIs carried the old name, not three: MainWindow (window icon), OverlayHost (logo), RenameRecordingDialog and KeepBroadcastDialog. Pack URIs resolve by assembly name at runtime, so a stale one compiles, passes every test and then silently renders nothing the first time that view opens. AssemblyIdentityTests guards both halves — the assembly name, and no source XAML still naming the legacy assembly. Packaging/Package.appxmanifest and package-msix.sh carry the exe name too. scripts/publish.sh replaces the by-hand publish: single-file win-x64, ReadyToRun, ffmpeg beside the exe, and it refuses to finish if ffmpeg, LICENSE, PRIVACY.md or THIRD-PARTY-NOTICES.txt are missing, or if a test assembly leaked in. That last guard exists because the first publish was wrong in a way no test could see: PublishSingleFile + IncludeNativeLibrariesForSelfExtract bundled the libav*.dll family into the exe — a 9-file output with no ffmpeg folder and a 182 MB binary, so the locator would have found nothing at runtime. ExcludeFromSingleFile="true" on the content keeps the folder a folder. Diffing against a control publish without the single-file flags found it; guessing at item globs wasted two attempts first (recorded in MyMistakes.md). Measured: 127 MB exe + 129 MB ffmpeg = 257 MB depot. Tests: AssemblyIdentity, StorePackaging, BuildStamp, BundledFonts all pass; app builds with 0 warnings.
This commit is contained in:
@@ -156,7 +156,7 @@
|
||||
|
||||
<!-- Hub view -->
|
||||
<StackPanel Visibility="{Binding IsHubVisible, Converter={StaticResource BoolToVis}}">
|
||||
<Image Source="/ytLive;component/Assets/llamacasty-logo.jpg" Height="125" Stretch="Uniform"
|
||||
<Image Source="/LlamaCasty;component/Assets/llamacasty-logo.jpg" Height="125" Stretch="Uniform"
|
||||
HorizontalAlignment="Center" Margin="0,0,0,12"/>
|
||||
<TextBlock Text="{Binding AppVersionLabel}" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="12"
|
||||
HorizontalAlignment="Center" Margin="0,4,0,0"/>
|
||||
|
||||
+7
-3
@@ -28,9 +28,13 @@ dotnet publish ytLive.csproj -c Release -r win-x64 --self-contained true \
|
||||
read DLC ownership. Its absence must degrade to the free profile, never to a crash.
|
||||
- **WebView2 is the one machine dependency.** Valve's Common Redistributables configure the
|
||||
Evergreen bootstrapper as a prerequisite, so the depot declares it instead of us shipping it.
|
||||
- **`AssemblyName` is still `ytLive`** → the exe is `ytLive.exe` today. The rename to
|
||||
`LlamaCasty.exe` is queued (three pack URIs break — see `product.md`), and **must land before the
|
||||
first depot upload**, because Steam's docs are explicit that renaming a live exe is painful.
|
||||
- **`AssemblyName` is `LlamaCasty`** (renamed 2026-11-01, before the first depot because
|
||||
Steamworks warns about renaming a live exe) → the artifact is `LlamaCasty.exe` plus its
|
||||
`ffmpeg\` folder. `scripts/publish.sh` stages both and refuses to finish if either is missing.
|
||||
- **⛔ `ExcludeFromSingleFile="true"` on the ffmpeg content.** Without it, `PublishSingleFile` +
|
||||
`IncludeNativeLibrariesForSelfExtract` swallow the libav\*.dll family **into the exe** — a
|
||||
182 MB binary with no `ffmpeg\` folder beside it, so the locator finds nothing and every stream
|
||||
fails at runtime. The publish output is ~257 MB: 127 MB exe + 129 MB ffmpeg.
|
||||
- **Portable mode** (`portable.txt` → `.\data\`) survives as a feature for a copied install; it is
|
||||
not a delivery vector.
|
||||
|
||||
|
||||
+23
-18
@@ -1,35 +1,40 @@
|
||||
# HANDOFF — 2026-10-31 (Steam is the only channel; the site says so)
|
||||
# HANDOFF — 2026-11-01 (the exe is LlamaCasty.exe; the depot folder builds and verifies)
|
||||
|
||||
## State
|
||||
|
||||
- Branch `main`, level with `origin` through `57ef9e7`; this unit is local.
|
||||
- **Distribution is Steam-exclusive** (creator ruling 2026-10-31): a free app on Steam with the paid
|
||||
edition as a **Steam DLC**; the **$10/month** plan is sold off-Steam by Polar and *stated, never
|
||||
sold*, inside a Steam build. Winget, the direct zip, the Store and the certificate that served
|
||||
them are retired. `commercial.md` → Channels owns policy, `Distribution.md` the depot mechanics,
|
||||
`decisions.md` the cause of death.
|
||||
- **Entitlement is `steamDlcOwned || polarKeyValid`**, both resolving to one `OutputProfile`.
|
||||
`steam_api64.dll` ships beside the exe; the app must start without Steam and fall back to free.
|
||||
- **Site updated and deployed** (`a9d07ce`): "Coming soon to Steam" ×6, zero Microsoft references,
|
||||
privacy page says Steam bills the paid edition. The CTA gains a wishlist button only when the
|
||||
store page is public.
|
||||
- `www.llamacasty.com` resolves and 301s to the apex. Recipe in `MyMistakes.md`.
|
||||
- Branch `main`, level with `origin` through `8f79b20`; this unit (the artifact rename) is local.
|
||||
- **The shipped exe is `LlamaCasty.exe`.** `AssemblyName` renamed; four pack URIs fixed (the docs
|
||||
said three — `KeepBroadcastDialog.xaml:5` was the fourth); manifest, MSIX script and
|
||||
`StorePackagingTests` follow. Guarded by `ytLive.Tests/AssemblyIdentityTests.cs`.
|
||||
- **`scripts/publish.sh`** stages `artifacts/steam-stage` and refuses to finish if `ffmpeg\`, the
|
||||
licence, privacy or notices file is missing, or test assemblies leaked in. Output 257 MB.
|
||||
- **Distribution is Steam-exclusive** (creator ruling 2026-10-31): free app + paid edition as a
|
||||
Steam **DLC**; the **$10/month** plan sold off-Steam by Polar and *stated, never sold*, in-app.
|
||||
`commercial.md` → Channels is policy; `Distribution.md` is depot mechanics.
|
||||
- **Entitlement is `steamDlcOwned || polarKeyValid`**; `steam_api64.dll` ships beside the exe and
|
||||
the app must start without Steam, falling back to free.
|
||||
- **Site is live with Steam** (`a9d07ce`). `www.llamacasty.com` 301s to the apex.
|
||||
|
||||
## Next action
|
||||
|
||||
1. **Steamworks onboarding** (creator is in it now): NDA → identity → company details → tax
|
||||
interview → payout bank → **$100 Steam Direct** → create the base app, note the App ID.
|
||||
2. Then the surge slice: artifact shape (rename + publish + `steam_api64.dll`) → OutputProfile gate
|
||||
+ TASK 53 geometry tests → entitlement seam → licence UI + a Steam-less launch test.
|
||||
1. **OutputProfile gate** — ≤921,600 px / ≤30 fps for the stream **and** the recorder, greyed tier
|
||||
entries, plus the TASK 53 geometry tests that pin every tier.
|
||||
2. Then the **entitlement seam** (`IEntitlement`, Steam DLC path behind a fake, Polar path) and the
|
||||
licence UI.
|
||||
3. Steamworks: App ID waiting on identity verification; then depot, store page (public 2 weeks
|
||||
before release) and the DLC app.
|
||||
|
||||
## Landmines
|
||||
|
||||
- **⛔ `ExcludeFromSingleFile="true"` on the ffmpeg content** — without it the publisher bundles
|
||||
libav\*.dll into the exe and the app ships unable to find ffmpeg. Recipe in `MyMistakes.md`.
|
||||
- **Nothing may gate a feature.** The only paid delta is the output profile; `ShowBrandFlash` stays
|
||||
`false` and entitlement must never enable it.
|
||||
- **No checkout inside a Steam build** — the Wallet rule. The monthly is named, never sold, in-app.
|
||||
- **The org access token never ships.** `organization_id` is public; the token is not.
|
||||
- **The site publishes the model, never the numbers**, and never takes payment or links a checkout.
|
||||
- **No self-updater and no version banner**: Steam updates the app.
|
||||
- Pre-existing: the **full test suite hangs** (WASAPI teardown). Per-class runs are the way.
|
||||
- Pre-existing: the **full test suite hangs** (WASAPI teardown) — per-class runs only; and
|
||||
`ytLive.Tests` carries 3 xUnit analyzer warnings the app-only gate does not see.
|
||||
- Not every hex is a theme colour — `BorderColorPalette`/chat defaults are USER DATA; on-stream
|
||||
graphics come from `OutputPalette`. A red settings control is still a bug.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Keep this broadcast?" Height="240" Width="460"
|
||||
Icon="/ytLive;component/Assets/llamacasty-icon.png"
|
||||
Icon="/LlamaCasty;component/Assets/llamacasty-icon.png"
|
||||
WindowStartupLocation="CenterOwner" ResizeMode="NoResize"
|
||||
ShowInTaskbar="False" Background="{StaticResource Brush.Background}">
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
mc:Ignorable="d"
|
||||
Title="{Binding WindowTitle}" Height="1040" Width="1920"
|
||||
MinWidth="1366" MinHeight="768"
|
||||
Icon="/ytLive;component/Assets/llamacasty-icon.png"
|
||||
Icon="/LlamaCasty;component/Assets/llamacasty-icon.png"
|
||||
Background="{StaticResource Brush.Background}"
|
||||
FontFamily="{StaticResource FontFamily.Sans}"
|
||||
WindowStartupLocation="Manual"
|
||||
|
||||
@@ -166,6 +166,35 @@ in Studio → Settings → Channel → **Feature eligibility**:
|
||||
*"Advanced YouTube features aren't available for this account"*. If ID verification is ever
|
||||
wanted on a personal channel, do it **before** converting to a brand account.
|
||||
|
||||
### THE SINGLE-FILE PUBLISHER EATS ffmpeg (2026-11-01)
|
||||
|
||||
**Problem:** `dotnet publish` with `PublishSingleFile=true` +
|
||||
`IncludeNativeLibrariesForSelfExtract=true` produced a **9-file** output with **no `ffmpeg\`
|
||||
folder** and a **182 MB exe** — the libav\*.dll family had been bundled *inside* the binary. The
|
||||
build was green, the tests passed, and the app would have shipped unable to find ffmpeg at
|
||||
runtime (the locator looks for `ffmpeg\ffmpeg.exe` beside the exe).
|
||||
|
||||
**Diagnosis that worked:** diff the publish output against a **control publish with the
|
||||
single-file flags removed** — ffmpeg was there. Then add flags back one at a time;
|
||||
`IncludeNativeLibrariesForSelfExtract` was the one that broke it.
|
||||
|
||||
**Fix:** mark the content `ExcludeFromSingleFile="true"`.
|
||||
|
||||
```xml
|
||||
<None Include="ffmpeg\**" CopyToOutputDirectory="PreserveNewest"
|
||||
CopyToPublishDirectory="Always" ExcludeFromSingleFile="true" />
|
||||
```
|
||||
|
||||
After: 127 MB exe, `ffmpeg\` present, depot ≈ 257 MB total.
|
||||
|
||||
- ⚠️ **The `<None Remove="ffmpeg\**" />` before it is not the fix** — it silences a duplicate-item
|
||||
warning from the SDK's default glob, and I tried it first and wrongly believed it had worked.
|
||||
Two failed remedies in a row is the research trigger: read the publisher's own metadata
|
||||
(`ExcludeFromSingleFile`) instead of guessing at item globs.
|
||||
- ⚠️ **A clean build and green tests cannot see this.** Only the artifact can, which is why
|
||||
`scripts/publish.sh` fails loudly when `ffmpeg\`, `LICENSE`, `PRIVACY.md` or
|
||||
`THIRD-PARTY-NOTICES.txt` are missing, and when test assemblies leak in.
|
||||
|
||||
### STEAM IS THE DISTRIBUTOR — AND WHAT IT DOES NOT ALLOW (2026-10-31)
|
||||
|
||||
**Why recorded:** the distribution answer changed three times in one day (winget → zip+GitHub →
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<Applications>
|
||||
<Application
|
||||
Id="LlamaCasty"
|
||||
Executable="ytLive.exe"
|
||||
Executable="LlamaCasty.exe"
|
||||
EntryPoint="Windows.FullTrustApplication"
|
||||
uap10:TrustLevel="mediumIL"
|
||||
uap10:RuntimeBehavior="packagedClassicApp">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Rename Recording" Height="304" Width="460"
|
||||
Icon="/ytLive;component/Assets/llamacasty-icon.png"
|
||||
Icon="/LlamaCasty;component/Assets/llamacasty-icon.png"
|
||||
WindowStartupLocation="CenterOwner" ResizeMode="NoResize"
|
||||
ShowInTaskbar="False" Background="{StaticResource Brush.Background}">
|
||||
|
||||
|
||||
@@ -7,22 +7,18 @@
|
||||
|
||||
## Next up
|
||||
|
||||
**The surge week (4 days, in this order).** Not every row below — this slice only, because it is
|
||||
what the first public release is waiting on:
|
||||
**The surge week (3 days, in this order).** Not every row below — this slice only, because it is
|
||||
what the first public release is waiting on. Day 1 (artifact shape) is **done**: the exe is
|
||||
`LlamaCasty.exe` and `scripts/publish.sh` stages the depot folder.
|
||||
|
||||
1. **Artifact shape** — `LlamaCasty.exe` rename (three pack URIs), `dotnet publish` profile with
|
||||
`steam_api64.dll` beside the exe. Ends with a build that starts standalone and falls back to the
|
||||
free profile.
|
||||
2. **OutputProfile gate** — ≤921,600 px / ≤30 fps for the stream **and** the recorder, greyed tier
|
||||
1. **OutputProfile gate** — ≤921,600 px / ≤30 fps for the stream **and** the recorder, greyed tier
|
||||
entries, plus the TASK 53 geometry tests that pin every tier.
|
||||
3. **Entitlement seam** — `IEntitlement` with the Steam DLC path and a fake for tests, Polar's
|
||||
customer-portal path for the monthly, `--activate` parsing, system messages.
|
||||
4. **Real-machine day** — licence UI skeleton, a Steam-less launch proving the free profile, a test
|
||||
broadcast.
|
||||
2. Then the **entitlement seam** (`IEntitlement`, Steam DLC path behind a fake, Polar path) and the
|
||||
licence UI.
|
||||
|
||||
Detail: [`commercial.md`](docs/ai/commercial.md) → Output profile / Entitlement;
|
||||
[`Distribution.md`](Distribution.md). Day 5, if the four go well: the visibility picker (a hard
|
||||
prerequisite for any *public* build).
|
||||
[`Distribution.md`](Distribution.md). After that: the visibility picker (a hard prerequisite for
|
||||
any *public* build).
|
||||
|
||||
**TASK 3·17 — the Text layer** stays the first product row after the surge. The `TextOverlay` source
|
||||
type exists as a placeholder and nothing else does: `AddSource` creates it 0×0, all four compositor
|
||||
@@ -80,9 +76,9 @@ Dog test.
|
||||
`UpdateBroadcast` in the same unit (it is the only code that can change a broadcast's privacy).
|
||||
- **Kill the dev multi-instance affordance** — `Helpers/InstanceProfile.cs` is already `#if DEBUG`;
|
||||
re-prove it on a Release build.
|
||||
- **Release engineering** — `scripts/publish.sh` (self-contained win-x64, no test artifacts),
|
||||
Debug-only `InternalsVisibleTo`, the `LlamaCasty.exe` assembly rename (three pack URIs, incl.
|
||||
`MainWindow.xaml:14`), full license texts beside `THIRD-PARTY-NOTICES.txt` (TASK 4 req 9).
|
||||
- **Release engineering** — `scripts/publish.sh` **done** (self-contained win-x64, `ffmpeg\`
|
||||
guarded, no test artifacts), `LlamaCasty.exe` assembly rename **done**, full license texts beside
|
||||
`THIRD-PARTY-NOTICES.txt` (TASK 4 req 9). Outstanding: Debug-only `InternalsVisibleTo`.
|
||||
- **Gold pass** (TASK 36): the screens/layers audit, native-Windows verification, and the
|
||||
branding-flash decision.
|
||||
|
||||
|
||||
+13
-9
@@ -28,18 +28,22 @@ the internal repo/assembly/namespace name only. Exact current split:
|
||||
| surface | name | note |
|
||||
|---|---|---|
|
||||
| product / site / marketing | **LlamaCasty** | llamacasty.com, llamachile.tube |
|
||||
| repo dir, csproj, `RootNamespace` | `ytLive` | 230 files: `namespace ytLive.*` |
|
||||
| `AssemblyName` → shipped exe | `ytLive` | `ytLive.exe` |
|
||||
| repo dir, csproj file, `RootNamespace` | `ytLive` | 230+ files: `namespace ytLive.*` |
|
||||
| `AssemblyName` → shipped exe | **`LlamaCasty`** | `LlamaCasty.exe` — renamed 2026-11-01 |
|
||||
| app data dir | `%APPDATA%\ytLlive` | **capital L**, drifts from `ytLive` |
|
||||
| `Distribution.md` tells customers | `LlamaCasty.exe` | ⚠ **mismatch vs the real `ytLive.exe`** |
|
||||
|
||||
**Consequences, so nobody re-hunts them:**
|
||||
- Renaming `<AssemblyName>` to `LlamaCasty` is the cheap win (the customer-visible exe
|
||||
name matches what `Distribution.md` already promises; namespaces can stay `ytLive.*`).
|
||||
It is **4 lines**, but it hard-breaks three pack URIs that spell the assembly name:
|
||||
`MainWindow.xaml:14` (the **window icon** — taskbar/Alt-Tab), `OverlayHost.xaml:96`,
|
||||
`RenameRecordingDialog.xaml:5`. Miss those and you ship a broken icon. Queued in TASKS.md
|
||||
under the shipping task, not done.
|
||||
- The assembly rename landed 2026-11-01 (before the first Steam depot, because Steamworks warns
|
||||
that renaming a live exe is painful). Namespaces stay `ytLive.*` — they are not user-visible
|
||||
and renaming them would churn every file for nothing.
|
||||
- An assembly-qualified pack URI spells the assembly name, so the rename **hard-breaks silently**
|
||||
at runtime, not at compile time. Four carried it, not three:
|
||||
`MainWindow.xaml:14` (window icon — taskbar/Alt-Tab), `OverlayHost.xaml:96`,
|
||||
`RenameRecordingDialog.xaml:5`, and `KeepBroadcastDialog.xaml:5`.
|
||||
`ytLive.Tests/AssemblyIdentityTests.cs` now guards both halves: the assembly name is
|
||||
`LlamaCasty` and no source XAML still names the legacy assembly.
|
||||
- `Packaging/Package.appxmanifest` (`Executable`) and `scripts/package-msix.sh` carry the exe
|
||||
name too; the MSIX is unused but must not lie about the artifact.
|
||||
- Renaming the `%APPDATA%\ytLlive` folder is **breaking** — it would orphan every existing
|
||||
user's layout DB, `Assets` and settings. Never do it casually; needs a migration.
|
||||
- No code compares its own assembly name (no `GetExecutingAssembly().GetName().Name` checks),
|
||||
|
||||
@@ -39,7 +39,7 @@ mkdir -p "$STAGE_WSL"
|
||||
--nologo -v minimal \
|
||||
-o "$(wslpath -w "$STAGE_WSL")"
|
||||
|
||||
[ -f "$STAGE_WSL/ytLive.exe" ] || { echo "publish produced no ytLive.exe" >&2; exit 1; }
|
||||
[ -f "$STAGE_WSL/LlamaCasty.exe" ] || { echo "publish produced no LlamaCasty.exe" >&2; exit 1; }
|
||||
[ -d "$STAGE_WSL/ffmpeg" ] || {
|
||||
echo "ffmpeg/ is missing from the package — it must ship bundled (Store policy 10.2.2)." >&2
|
||||
exit 1
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bash
|
||||
# publish.sh — build the artifact that goes into the Steam depot.
|
||||
# Usage: ./scripts/publish.sh [output-dir] (default: artifacts/steam-stage)
|
||||
#
|
||||
# Why a script: the flags below are load-bearing and easy to lose. PublishTrimmed stays OFF
|
||||
# (WPF resolves XAML by reflection), the single-file bundle must extract natives or ffmpeg-
|
||||
# adjacent loading breaks, and ReadyToRun is what keeps startup under a second on a cold
|
||||
# machine. Getting one of them wrong does not fail the build — it fails on a user's PC.
|
||||
#
|
||||
# The output is a folder, not a zip: steamcmd uploads a directory into a depot, and Steam
|
||||
# delivers it. There is no installer, no admin prompt and no self-updater (Steam updates).
|
||||
#
|
||||
# Requires WINDOWS dotnet — see docs/ai/environment.md -> Run.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DOTNET="/mnt/c/Program Files/dotnet/dotnet.exe"
|
||||
[ -x "$DOTNET" ] || { echo "❌ Windows dotnet not found at $DOTNET"; exit 1; }
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
OUT_REL="${1:-artifacts/steam-stage}"
|
||||
OUT="$ROOT/$OUT_REL"
|
||||
|
||||
echo "══ publish ══"
|
||||
rm -rf "$OUT"
|
||||
mkdir -p "$OUT"
|
||||
|
||||
"$DOTNET" publish "$(wslpath -w "$ROOT/ytLive.csproj")" \
|
||||
-c Release -r win-x64 --self-contained true \
|
||||
-p:PublishSingleFile=true \
|
||||
-p:IncludeNativeLibrariesForSelfExtract=true \
|
||||
-p:EnableCompressionInSingleFile=true \
|
||||
-p:PublishReadyToRun=true \
|
||||
-p:DebugType=none \
|
||||
--nologo -v minimal \
|
||||
-o "$(wslpath -w "$OUT")"
|
||||
|
||||
echo "══ verify the staged artifact ══"
|
||||
fail() { echo "❌ $1" >&2; exit 1; }
|
||||
|
||||
[ -f "$OUT/LlamaCasty.exe" ] || fail "publish produced no LlamaCasty.exe (AssemblyName reverted?)"
|
||||
[ -d "$OUT/ffmpeg" ] || fail "ffmpeg/ is missing — it must ship beside the exe (docs/ai/output.md)"
|
||||
[ -f "$OUT/LICENSE" ] || fail "LICENSE is missing from the artifact"
|
||||
[ -f "$OUT/PRIVACY.md" ] || fail "PRIVACY.md is missing from the artifact"
|
||||
[ -f "$OUT/THIRD-PARTY-NOTICES.txt" ] || fail "THIRD-PARTY-NOTICES.txt is missing from the artifact"
|
||||
|
||||
# Test artifacts must never reach a depot: a shipped xunit host is a support ticket.
|
||||
if compgen -G "$OUT/*.Tests.dll" > /dev/null || compgen -G "$OUT/xunit*" > /dev/null; then
|
||||
fail "test assemblies leaked into the publish output"
|
||||
fi
|
||||
|
||||
# steam_api64.dll arrives with the Steamworks SDK; until then its absence is expected and the
|
||||
# app must fall back to the free profile, so this is a notice, not a failure.
|
||||
if [ ! -f "$OUT/steam_api64.dll" ]; then
|
||||
echo "⚠ steam_api64.dll not staged — the build will run as the free profile only."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
du -sh "$OUT" | awk '{print " artifact size: " $1}'
|
||||
echo "✓ staged: $OUT_REL"
|
||||
echo ""
|
||||
echo "Next: steamcmd +run_app_build scripts/app_build_<appid>.vdf"
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace ytLive.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// The shipped binary is <c>LlamaCasty.exe</c> (renamed 2026-11-01, before the first Steam
|
||||
/// depot). Two things have to agree for that to be true, and neither fails the build when it
|
||||
/// doesn't:
|
||||
///
|
||||
/// <list type="number">
|
||||
/// <item>The assembly name comes from <c>ytLive.csproj</c> and silently reverting it produces
|
||||
/// <c>ytLive.exe</c> again — which is why the rename lands before a depot upload rather than
|
||||
/// after (Steamworks: renaming a live exe is painful).</item>
|
||||
/// <item>Assembly-qualified WPF pack URIs (<c>/Assembly;component/path</c>) resolve by that
|
||||
/// same name. A stale <c>/ytLive;component/…</c> compiles, runs, and then fails <b>silently</b>
|
||||
/// the moment that particular view opens — a blank window icon, a missing logo in the overlay
|
||||
/// host. Nobody notices until a dialog is opened at the wrong time, so the rename is exactly the
|
||||
/// kind of change that needs a source-level guard.</item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para>The C# namespaces stay <c>ytLive</c>: they are not user-visible, and the file churn
|
||||
/// would buy nothing. Only the assembly name and the URIs that quote it move.</para>
|
||||
/// </summary>
|
||||
public class AssemblyIdentityTests
|
||||
{
|
||||
private const string ShipName = "LlamaCasty";
|
||||
private const string LegacyName = "ytLive";
|
||||
|
||||
[Fact]
|
||||
public void The_assembly_is_LlamaCasty_and_no_pack_uri_still_names_the_legacy_assembly()
|
||||
{
|
||||
Assert.Equal(ShipName, typeof(MainWindow).Assembly.GetName().Name);
|
||||
|
||||
var root = RepoRoot();
|
||||
var stale = Directory.EnumerateFiles(root, "*.xaml", SearchOption.AllDirectories)
|
||||
.Where(path => !IsBuildOutput(path))
|
||||
.Where(path => File.ReadAllText(path).Contains("/" + LegacyName + ";component/", StringComparison.Ordinal))
|
||||
.Select(path => Path.GetRelativePath(root, path))
|
||||
.ToArray();
|
||||
|
||||
Assert.True(stale.Length == 0,
|
||||
"These XAML files still point at the legacy assembly name in a pack URI, which " +
|
||||
"fails silently at runtime: " + string.Join(", ", stale));
|
||||
}
|
||||
|
||||
/// <summary>Walks up from the test output directory to the repo root (the folder holding
|
||||
/// <c>ytLive.csproj</c>).</summary>
|
||||
private static string RepoRoot()
|
||||
{
|
||||
var dir = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (dir != null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(dir.FullName, "ytLive.csproj"))) return dir.FullName;
|
||||
dir = dir.Parent;
|
||||
}
|
||||
throw new InvalidOperationException("Could not locate the repo root from " + AppContext.BaseDirectory);
|
||||
}
|
||||
|
||||
/// <summary>Publish and compile output carry copies of the XAML (and old generated XAML);
|
||||
/// only the source tree is the thing under test.</summary>
|
||||
private static bool IsBuildOutput(string path)
|
||||
{
|
||||
var parts = path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||
return parts.Any(part => part.Equals("bin", StringComparison.OrdinalIgnoreCase)
|
||||
|| part.Equals("obj", StringComparison.OrdinalIgnoreCase)
|
||||
|| part.Equals("artifacts", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
@@ -97,7 +97,7 @@ public class StorePackagingTests
|
||||
var application = package.Element(Foundation + "Applications")!
|
||||
.Element(Foundation + "Application")!;
|
||||
Assert.Equal("Windows.FullTrustApplication", (string?)application.Attribute("EntryPoint"));
|
||||
Assert.Equal("ytLive.exe", (string?)application.Attribute("Executable"));
|
||||
Assert.Equal("LlamaCasty.exe", (string?)application.Attribute("Executable"));
|
||||
Assert.Equal("mediumIL", (string?)application.Attribute(Uap10 + "TrustLevel"));
|
||||
Assert.Equal("packagedClassicApp", (string?)application.Attribute(Uap10 + "RuntimeBehavior"));
|
||||
|
||||
|
||||
+17
-2
@@ -8,7 +8,10 @@
|
||||
<UseWPF>true</UseWPF>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
<ApplicationIcon>Assets\llamacasty-icon.ico</ApplicationIcon>
|
||||
<AssemblyName>ytLive</AssemblyName>
|
||||
<!-- The shipped exe is LlamaCasty.exe (renamed 2026-11-01, before the first Steam depot so
|
||||
the store never sees the legacy name). RootNamespace stays `ytLive` — the C# namespaces
|
||||
are not user-visible and renaming them would churn every file for no benefit. -->
|
||||
<AssemblyName>LlamaCasty</AssemblyName>
|
||||
<RootNamespace>ytLive</RootNamespace>
|
||||
<!-- 64-bit Windows only (creator ruling 2026-10-09). The bundled ffmpeg is the win64 build
|
||||
and ONNX Runtime ships no win-x86 native, so a 32-bit process could never stream; a
|
||||
@@ -116,7 +119,19 @@
|
||||
from a pinned URL, verifying SHA-256 first. CopyToPublishDirectory=Always is the
|
||||
one that matters for the MSIX package. -->
|
||||
<ItemGroup>
|
||||
<None Include="ffmpeg\**" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="Always" />
|
||||
<!-- `Remove` first: the SDK's default None glob already matched ffmpeg\**, so an Include
|
||||
without removing it produced DUPLICATE items — one carrying the copy metadata and one
|
||||
with none. Harmless until PublishSingleFile + IncludeNativeLibrariesForSelfExtract came
|
||||
along, where the bundler collapses the duplicate and the folder silently stops being
|
||||
published (found 2026-11-01: a 9-file publish with no ffmpeg in it). -->
|
||||
<!-- ⛔ ExcludeFromSingleFile is load-bearing. With PublishSingleFile +
|
||||
IncludeNativeLibrariesForSelfExtract the bundler treats the libav*.dll family as native
|
||||
libs and swallows them INTO the exe — a 182 MB exe with no ffmpeg\ folder beside it, so
|
||||
the locator finds nothing and every stream fails at runtime. The metadata keeps the
|
||||
folder a folder. (Found 2026-11-01 by diffing the publish output against a control
|
||||
publish without the single-file flags.) -->
|
||||
<None Remove="ffmpeg\**" />
|
||||
<None Include="ffmpeg\**" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="Always" ExcludeFromSingleFile="true" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="GuardBundledFfmpeg" BeforeTargets="CoreCompile">
|
||||
|
||||
Reference in New Issue
Block a user