converters: Testkorpus und Harness ziehen nach torben/chemenu-testfiles, Workflow Converters mit Pfad-Trigger, Release-Gate (#232); 10.1.0-beta.2
CI / verify (push) Successful in 7m58s
CI / pwsh (push) Successful in 2m52s
Converters / converters (push) Failing after 1m0s
Release / converters (push) Successful in 27s
Release / release (push) Successful in 37s
Release / wiki-mirror (push) Skipped

Files changed:
- .gitattributes
- .gitea/workflows/converters.yml
- .gitea/workflows/release.yml
- .gitignore
- CHANGES.md
- VERSION
- docs/dev/development.md
- instructions/dev/dev-setup.md
- instructions/dev/publish-and-ci.md
- instructions/dev/testing-conventions.md
- testfiles/README.md
- testfiles/mail/github-outlook.msg
- testfiles/mail/github-roundcube.eml
- testfiles/mail/gog-outlook.msg
- testfiles/mail/gog-roundcube.eml
- testfiles/mail/gog-roundcube.eml.expect
- testfiles/mail/tldr-outlook.msg
- testfiles/mail/tldr-roundcube.eml
- testfiles/office/excel-artificial.xlsx
- testfiles/office/excel-business.xlsx
- testfiles/office/excel-business.xlsx.expect
- testfiles/office/excel-science.xlsx
- testfiles/office/pptx-baseline.pptx
- testfiles/office/pptx-master.pptx
- testfiles/office/pptx-objects.pptx
- testfiles/office/word-complex.docx
- testfiles/office/word-simple.docx
- testfiles/pdf/einstein-dissertation.pdf
- testfiles/pdf/ifg.pdf
- testfiles/pdf/nist-ai-rmf.pdf
- testfiles/pdf/nist-ssdf-genai.pdf
- testfiles/zip/archive.zip
- tools/chemenu/tests/test_raw_derive_testfiles.py

Co-Authored-By: Claude Code <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SnAJ7Z3CpVD3PRbN73QtU2
This commit is contained in:
torbenandClaude Code committed 2026-10-14 15:05:12 +02:00
1 parent 3bf179ae8c
commit 71170bc1e8
33 files changed
+231 -2517

No files matched your search

-4
View File
@@ -11,7 +11,3 @@
# and normalizing a CRLF source on `git add` would change it. # and normalizing a CRLF source on `git add` would change it.
/raw/** -text /raw/** -text
/incoming/** -text /incoming/** -text
# Real documents for the `raw derive` tests are compared byte for byte (an .eml with CRLF
# included), so nothing under testfiles/ is ever normalized.
/testfiles/** -text
+87
View File
@@ -0,0 +1,87 @@
# The converters against real documents.
#
# `ci.yml` proves the filters on documents built to be easy (`test_raw_derive.py`,
# `test_raw_derive_binary.py`). This workflow derives the real ones: the test corpus and its
# harness live in the private repository `torben/chemenu-testfiles`, cloned into `testfiles/`
# (gitignored in this repository) and run with this checkout's `tools/.venv`. The corpus is large
# (some 76 MB, one book needs 48 s per derivation) and holds material that may not sit in a public
# repository, which is why none of it runs in the standard CI.
#
# When it runs:
# - on a push to `main` that touches the converter surface (the `paths:` list below). Gitea
# evaluates `paths:` the way GitHub does - observed for `paths-ignore` in `ci.yml` and in use for
# `paths:` in `release.yml`; do not re-derive it;
# - by hand (`workflow_dispatch`), which is the way to run it after a change in the private
# repository alone: a push there starts nothing here, and a commit that reaches the converters
# only indirectly is not in the list;
# - before a release without suffix: `release.yml` carries the same steps as its job `converters`
# and the release job waits for it. A candidate (`-beta.N`) does not run it.
#
# It always tests `main` of the private repository. A converter change that alters an expected
# output (`.expect`) is therefore two commits, one per repository, the private one first.
#
# Runner shape: identical to ci.yml, and for the same reason - see its header for `nodejs`.
#
# Access to the private repository: the per-job token (`${{ gitea.token }}`), as `release.yml`
# uses it for this repository. If this instance does not let it read a second, private repository
# of the same owner, the clone step fails with an authentication error; the way out is a read token
# in 1Password, loaded with `1password/load-secrets-action` the way `ci.yml` loads its secret
# reference (the service account sees only the CI-CD vault).
name: Converters
on:
push:
branches: [main]
paths:
- 'tools/chemenu/filters/**'
- 'tools/chemenu/commands/raw/derive.py'
- 'tools/chemenu/commands/raw/accept.py'
- 'tools/chemenu/tests/test_raw_derive*.py'
- 'tools/chemenu/tests/binary_formats.py'
- 'tools/requirements.txt'
- '.gitea/workflows/converters.yml'
workflow_dispatch:
jobs:
converters:
runs-on: linux-docker
timeout-minutes: 30
container:
image: debian:trixie-slim
env:
WIKITOOL_SESSION_ID: converters-${{ github.run_id }}
WIKI_TRACE_DIR: /tmp/wikitool-trace
steps:
- name: System dependencies
run: |
set -eu
apt-get update -qq
apt-get install -y --no-install-recommends \
python3 python3-venv git nodejs ripgrep ca-certificates
rm -rf /var/lib/apt/lists/*
- uses: actions/checkout@v7
- name: The private test corpus
uses: actions/checkout@v7
with:
repository: torben/chemenu-testfiles
token: ${{ gitea.token }}
path: testfiles
- name: Tool environment
run: |
set -eu
git config --global --add safe.directory "$GITHUB_WORKSPACE"
tools/preflight.sh
tools/.venv/bin/python -m pip install --quiet pytest
- name: Real documents
# `-c pytest.ini` applies the suite's own configuration to a harness that lives outside
# `tools/`; the harness conftest pulls in the suite's conftest (testfiles/README.md).
run: |
set -eu
cd tools
.venv/bin/python -m pytest -c pytest.ini ../testfiles/harness -q --durations=5
+65
View File
@@ -24,6 +24,13 @@
# tag). It is a separate job so that a failing mirror leaves the release and its # tag). It is a separate job so that a failing mirror leaves the release and its
# tag standing: the release is the product, the wiki a view of it. It does not # tag standing: the release is the product, the wiki a view of it. It does not
# run for a `-beta.N` candidate, for which `release` sets `skip`. # run for a `-beta.N` candidate, for which `release` sets `skip`.
#
# `converters` is the first job: the converter tests against the real documents of the private
# repository `torben/chemenu-testfiles` (converters.yml has the reasoning and is the reference for
# the steps; the two are kept in step by hand, because this instance's support for calling one
# workflow from another is not something this repository relies on). `release` waits for it with
# `needs:`, so a red harness stops the run before a release or a tag exists. For a candidate it
# decides `skip` the way `release` does and ends after its first steps.
name: Release name: Release
@@ -39,7 +46,65 @@ on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
converters:
runs-on: linux-docker
timeout-minutes: 30
container:
image: debian:trixie-slim
env:
WIKITOOL_SESSION_ID: release-converters-${{ github.run_id }}
WIKI_TRACE_DIR: /tmp/wikitool-trace
steps:
- name: System dependencies
run: |
set -eu
apt-get update -qq
apt-get install -y --no-install-recommends \
python3 python3-venv git nodejs ripgrep ca-certificates
rm -rf /var/lib/apt/lists/*
- uses: actions/checkout@v7
- name: A candidate needs no converter run
id: version
run: |
set -eu
case "$(tr -d '[:space:]' < VERSION)" in
*-beta.*)
echo "VERSION is a running candidate - the converter tests run for a release only."
echo "skip=true" >> "$GITHUB_OUTPUT"
;;
*)
echo "skip=false" >> "$GITHUB_OUTPUT"
;;
esac
- name: The private test corpus
if: steps.version.outputs.skip != 'true'
uses: actions/checkout@v7
with:
repository: torben/chemenu-testfiles
token: ${{ gitea.token }}
path: testfiles
- name: Tool environment
if: steps.version.outputs.skip != 'true'
run: |
set -eu
git config --global --add safe.directory "$GITHUB_WORKSPACE"
tools/preflight.sh
tools/.venv/bin/python -m pip install --quiet pytest
- name: Real documents
if: steps.version.outputs.skip != 'true'
run: |
set -eu
cd tools
.venv/bin/python -m pytest -c pytest.ini ../testfiles/harness -q --durations=5
release: release:
needs: converters
runs-on: linux-docker runs-on: linux-docker
container: container:
image: debian:trixie-slim image: debian:trixie-slim
+6
View File
@@ -192,6 +192,12 @@ npm-debug.log*
# anchored back open by the content backstop below, same as `incoming/`. # anchored back open by the content backstop below, same as `incoming/`.
/quarantine/ /quarantine/
# The private test corpus and converter harness (Gitea #232): a clone of
# torben/chemenu-testfiles, made by hand (instructions/dev/dev-setup.md) and run by
# .gitea/workflows/converters.yml. It is its own repository, so this one never tracks it; a clone
# without access simply has no such directory, and nothing in the stack's suite needs it.
/testfiles/
# Content backstop - keep this block last. Nothing under raw/, kb/, work/ or sites/ may # Content backstop - keep this block last. Nothing under raw/, kb/, work/ or sites/ may
# be excluded by a pattern above; see the header note for why directory patterns # be excluded by a pattern above; see the header note for why directory patterns
# still have to be anchored rather than relying on these negations. # still have to be anchored rather than relying on these negations.
+6 -1
View File
@@ -14,14 +14,19 @@ place only: `instructions/dev/version-parts.md` § The candidate model.
--- ---
## 10.1.0-beta.1 - 2026-10-13 - raw derive PDF: Gliederung mit Seite und Zeile im Kopf, Hinterlegungen, Durchstreichungen und abweichende Textfarben als Auszeichnung im Text, Bildseiten als CHECK ## 10.1.0-beta.2 - 2026-10-14 - Konverter-Testkorpus und Harness ziehen in das private Repository chemenu-testfiles; neuer Workflow Converters, Freigabe-Gate im Release
**Author:** Torben Nehmer **Author:** Torben Nehmer
<!-- wikitool:bumps --> <!-- wikitool:bumps -->
- raw derive PDF: Gliederung mit Seite und Zeile im Kopf, Hinterlegungen, Durchstreichungen und abweichende Textfarben als Auszeichnung im Text, Bildseiten als CHECK - raw derive PDF: Gliederung mit Seite und Zeile im Kopf, Hinterlegungen, Durchstreichungen und abweichende Textfarben als Auszeichnung im Text, Bildseiten als CHECK
- Konverter-Testkorpus und Harness ziehen in das private Repository chemenu-testfiles; neuer Workflow Converters, Freigabe-Gate im Release
<!-- /wikitool:bumps --> <!-- /wikitool:bumps -->
### Konverter-Tests gegen echte Dokumente im privaten Repository
Der Testkorpus mit echten Dokumenten (PDF, Mail, Office, ZIP) und sein Test liegen nicht mehr in diesem Repository, sondern in dem privaten `torben/chemenu-testfiles`, das ein Entwickler als ignoriertes `testfiles/` neben den Stack klont (kein Submodul; ohne Zugriff fehlt nur dieser Lauf). Der neue Workflow `Converters` führt den Harness bei einem Push auf `main` aus, der die Konverter berührt, und auf Zuruf; `release.yml` führt ihn vor jedem Release ohne Suffix aus und bricht bei Rot vor dem Tag ab. Die gewöhnliche CI enthält den Test nicht mehr, die Abdeckungsgrenze hält. Eine Instanz merkt davon nichts.
### Große PDFs: Gliederung und Auszeichnungen ### Große PDFs: Gliederung und Auszeichnungen
Bei einem PDF schreibt `raw derive` die Gliederung als Feld `outline` in den Kopf, je Eintrag mit Ebene, Seite und der Zeile der Seitenmarke in `derived.md`; so findet ein Leser einen Abschnitt, ohne das ganze Dokument zu lesen. Im Text stehen drei Auszeichnungen für das, was die Seite nur durch ihre Form sagt: `<mark data-farbe="gelb">…</mark>` für eine Hinterlegung, `~~…~~` für eine Durchstreichung und `*…*` für eine vom Fließtext abweichende Textfarbe ohne Link. Sie geben das Aussehen wieder, nicht die Bedeutung; ohne sie ist der Text derselbe wie zuvor. Das Feld `markings` im Kopf ist die Legende mit Anzahl und Seiten, kein Tag überschreitet eine Zeile oder Seitenmarke, und Bildseiten melden einen CHECK. Das Layout liest `pdfminer.six`, das neu in `requirements.txt` steht; ein PDF ohne Gliederung und Auszeichnungen wird wie bisher abgeleitet, vorhandene `derived.md` bleiben unberührt. Bei einem PDF schreibt `raw derive` die Gliederung als Feld `outline` in den Kopf, je Eintrag mit Ebene, Seite und der Zeile der Seitenmarke in `derived.md`; so findet ein Leser einen Abschnitt, ohne das ganze Dokument zu lesen. Im Text stehen drei Auszeichnungen für das, was die Seite nur durch ihre Form sagt: `<mark data-farbe="gelb">…</mark>` für eine Hinterlegung, `~~…~~` für eine Durchstreichung und `*…*` für eine vom Fließtext abweichende Textfarbe ohne Link. Sie geben das Aussehen wieder, nicht die Bedeutung; ohne sie ist der Text derselbe wie zuvor. Das Feld `markings` im Kopf ist die Legende mit Anzahl und Seiten, kein Tag überschreitet eine Zeile oder Seitenmarke, und Bildseiten melden einen CHECK. Das Layout liest `pdfminer.six`, das neu in `requirements.txt` steht; ein PDF ohne Gliederung und Auszeichnungen wird wie bisher abgeleitet, vorhandene `derived.md` bleiben unberührt.
+1 -1
View File
@@ -1 +1 @@
10.1.0-beta.1 10.1.0-beta.2
+26 -2
View File
@@ -40,8 +40,20 @@ Then have the agent run `instructions/bootstrap.md`: the preflight, then
and how `dist export` runs as a test tool - is and how `dist export` runs as a test tool - is
[instructions/dev/dev-setup.md](../../instructions/dev/dev-setup.md). [instructions/dev/dev-setup.md](../../instructions/dev/dev-setup.md).
Real documents for `raw derive` live in [testfiles/](../../testfiles/README.md) and are Real documents for `raw derive` do not live in this repository. The corpus and its test harness
committed, so CI derives them too. The repository is public: that README says what may go in. are the private repository `torben/chemenu-testfiles`, cloned by hand into `testfiles/` (gitignored
here, deliberately not a submodule, so a clone without access works unchanged):
```bash
git clone ssh://git@gitea.nehmer.net:10101/torben/chemenu-testfiles.git testfiles
cd tools && .venv/bin/python -m pytest -c pytest.ini ../testfiles/harness
```
It is private because the corpus is large (some 76 MB; one book takes 48 s per derivation) and
holds material that may not sit in a public repository, such as publishers' books and vendors'
documents. Without the clone, `pytest` runs unchanged and the real-document tests are simply not
there; the README of the private repository says what is in it, what each file's origin is and
whether it could be public. The tests run in their own workflow, see [The CI half](#the-ci-half).
What a clone has and an instance does not: What a clone has and an instance does not:
@@ -181,6 +193,18 @@ the download. `.gitea/workflows/nightly.yml` is the drift check against time rat
a commit. `.gitea/workflows/release.yml` is step 6 above, and `.gitea/workflows/wiki-mirror.yml` a commit. `.gitea/workflows/release.yml` is step 6 above, and `.gitea/workflows/wiki-mirror.yml`
refills the Gitea wiki by hand (see below). refills the Gitea wiki by hand (see below).
`.gitea/workflows/converters.yml` runs the converters against the real documents of the private
repository (the clone above, run with the checkout's `tools/.venv`). It starts by itself on a push
to `main` that touches the converter surface - `tools/chemenu/filters/`, `raw/derive.py`,
`raw/accept.py`, `tests/test_raw_derive*.py`, `tests/binary_formats.py`, `tools/requirements.txt`
and the workflow itself - and by hand (`workflow_dispatch`), which is how to run it after a change
in the private repository alone: a push there starts nothing here. It always tests `main` of the
private repository, so a converter change that alters an expected output is two commits, the
private one first. Before a release without suffix, `release.yml` runs the same steps as a job its
release job waits for; a red harness ends the run before a release or a tag exists, and a
`-beta.N` candidate skips it. The job reads the private repository with the per-job token; if
that instance ever stops allowing this, the workflow's header names the 1Password fallback.
Three more workflows carry the live tests of the tracker adapters (Super Productivity, CalDAV): Three more workflows carry the live tests of the tracker adapters (Super Productivity, CalDAV):
`ci.yml` runs the CalDAV half in a step of its own against Radicale as a process, `ci.yml` runs the CalDAV half in a step of its own against Radicale as a process,
`tracker-live.yml` runs nightly and covers both providers, and `sp-live-image.yml` builds the `tracker-live.yml` runs nightly and covers both providers, and `sp-live-image.yml` builds the
+18
View File
@@ -40,6 +40,7 @@ development material under `instructions/dev/` and `commonplace/`, and no
| `USER.md`/`SOUL.md` describe a demo operator and persona | Written by the operator during setup | | `USER.md`/`SOUL.md` describe a demo operator and persona | Written by the operator during setup |
| `tools/wikitool dist upgrade` refuses: there is no stamp to compare against. The checkout follows `main` with `tools/wikitool sync` | Updated with `dist upgrade --latest` | | `tools/wikitool dist upgrade` refuses: there is no stamp to compare against. The checkout follows `main` with `tools/wikitool sync` | Updated with `dist upgrade --latest` |
| `instructions/dev/`, `docs/dev/`, `commonplace/` and `.gitea/` are present | Never shipped | | `instructions/dev/`, `docs/dev/`, `commonplace/` and `.gitea/` are present | Never shipped |
| `testfiles/` may hold the private test corpus (step 6) | Never present |
5. **Use `dist export` as a build and test tool.** It writes exactly the tree a release ships, so 5. **Use `dist export` as a build and test tool.** It writes exactly the tree a release ships, so
it is how a change to the shipped surface is looked at before it is released: it is how a change to the shipped surface is looked at before it is released:
@@ -56,6 +57,23 @@ development material under `instructions/dev/` and `commonplace/`, and no
`.gitea/workflows/ci.yml` is that replay and the reference for it. Keep scratch trees outside `.gitea/workflows/ci.yml` is that replay and the reference for it. Keep scratch trees outside
this checkout. this checkout.
6. **Clone the private test corpus, if you have access.** The real documents the converters are
tested against, and the harness that runs them, are a repository of their own and not part of
this one:
```bash
git clone ssh://git@gitea.nehmer.net:10101/torben/chemenu-testfiles.git testfiles
cd tools && .venv/bin/python -m pytest -c pytest.ini ../testfiles/harness
```
The clone sits in `testfiles/`, which `.gitignore` excludes; it is deliberately not a
submodule. Without access nothing breaks: `pytest` in `tools/` needs none of it, and a
session that changes a converter leaves the real-document run to the `converters` workflow
([publish-and-ci.md](publish-and-ci.md) step 3). What is missing without the clone is the
local run of that harness, nothing else. The corpus holds files that may not be public, so
nothing from it is copied into this repository, an issue or a bug report without its README
saying the file could be public.
## Decision points ## Decision points
- **Asked to set up an instance from this checkout** (`dist export` into the user's folder, or a - **Asked to set up an instance from this checkout** (`dist export` into the user's folder, or a
+13 -1
View File
@@ -7,7 +7,8 @@ description: How a stack change is published and how its CI run is waited for -
The local checks cover what they cover on this machine; CI covers the same checks plus a full The local checks cover what they cover on this machine; CI covers the same checks plus a full
`setup-instance.md` replay against a fresh `dist export` (`.gitea/workflows/ci.yml`). A green run `setup-instance.md` replay against a fresh `dist export` (`.gitea/workflows/ci.yml`). A green run
on the published commit is therefore the end of the checked stretch of a work package, not a on the published commit - and, for a converter change, a green `Converters` run (step 3) - is
therefore the end of the checked stretch of a work package, not a
formality after it - which is why waiting for it belongs to the phase that wrote the code formality after it - which is why waiting for it belongs to the phase that wrote the code
(`stack-build`), and a red run sends the work back there rather than into the closing phase. (`stack-build`), and a red run sends the work back there rather than into the closing phase.
@@ -54,6 +55,17 @@ formality after it - which is why waiting for it belongs to the phase that wrote
- Keep the polling out of the main context where the harness allows it - a background - Keep the polling out of the main context where the harness allows it - a background
command or a fork - so a long wait does not fill the session with run listings. command or a fork - so a long wait does not fill the session with run listings.
**Converter changes have a second run.** When the published commit touches the converter
surface - `tools/chemenu/filters/`, `tools/chemenu/commands/raw/derive.py` or `accept.py`,
`tools/chemenu/tests/test_raw_derive*.py`, `tools/chemenu/tests/binary_formats.py`,
`tools/requirements.txt` or `.gitea/workflows/converters.yml` - the `Converters` workflow
(`converters.yml`) starts by itself beside CI and derives the real documents of the private
test corpus. It takes longer than CI (allow **10 minutes**); wait for it the same way, with
the same give-up time, and count a red run like a red CI run (step 4). Start it by hand
(`actions_run_write`, `dispatch_workflow` on `main`) when only the private repository changed
or when the commit reaches the converters only indirectly. A release without suffix runs the
same steps as the first job of `release.yml`, which the release waits for.
4. **A red run is not waited past.** Read the failing job's log, fix the cause, and go back to 4. **A red run is not waited past.** Read the failing job's log, fix the cause, and go back to
step 1: this is still build work, whichever skill published. The phase that published ends step 1: this is still build work, whichever skill published. The phase that published ends
only on a green run, and the issue stays open until there is one. only on a green run, and the issue stays open until there is one.
+9 -1
View File
@@ -187,7 +187,15 @@ Whenever you add or change a test under `tools/chemenu/tests/`.
## Scope ## Scope
Applies to `tools/chemenu/tests/` only. It says nothing about what to test - the test/review Applies to `tools/chemenu/tests/` only. Tests against **real documents** are not among them:
they live in the private repository `chemenu-testfiles` (`harness/`, with the corpus beside it),
cloned into the gitignored `testfiles/` ([dev-setup.md](dev-setup.md) step 6) and run with this
checkout's `tools/.venv` by the `converters` workflow. A new file for the corpus is a case for
that harness, not a fixture here; the synthetic filter tests (`test_raw_derive.py`,
`test_raw_derive_binary.py`) stay and are what the suite relies on. The harness borrows this
suite's `conftest.py`, so a change to a fixture there reaches it too.
This file says nothing about what to test - the test/review
expectations for a stack change are the local checks in [publish-and-ci.md](publish-and-ci.md) expectations for a stack change are the local checks in [publish-and-ci.md](publish-and-ci.md)
(`docs verify`, `instructions verify`, pytest), run from `stack-build`. CI runs the suite once, (`docs verify`, `instructions verify`, pytest), run from `stack-build`. CI runs the suite once,
unhardened, because the fixture makes a second hardened run redundant; see the note on the Tests unhardened, because the fixture makes a second hardened run redundant; see the note on the Tests
-66
View File
@@ -1,66 +0,0 @@
# testfiles
Real documents (PDF, mail, Office, ZIP) that CI feeds to `raw derive`, beyond the synthetic
fixtures in `tools/chemenu/tests/binary_formats.py`. `tools/chemenu/tests/test_raw_derive_testfiles.py`
picks up every file under this directory, so adding a file is adding a test case - no code change.
The directory exists only in a development clone: `dist export` does not ship it.
## What goes in
**Everything here is committed to a public repository.** A file belongs here only when it can be
public:
- a document you wrote for the purpose, or one whose licence allows redistribution;
- no real people, customers, employers, addresses or internal URLs - in the text *and* in the
metadata (PDF author and producer, Office author and company, mail headers, ZIP entry names);
- nothing that was received in confidence.
The author names of a published work (a NIST publication, a statute) are part of the work, not a
real person in the sense of the second item. Links that address an account - unsubscribe,
settings, tracking - count as personal data even when no name is in them: they let anyone holding
the file reach the recipient's account at the sender. Replace such text with filler of the same
length, so a mail keeps its structure.
Removing a file later does not remove it from the history.
## Where the files come from
| Files | Origin and basis for publishing them |
|-------|--------------------------------------|
| `pdf/nist-ai-rmf.pdf`, `pdf/nist-ssdf-genai.pdf` | NIST AI 600-1 and NIST SP 800-218A: works of the US federal government, public domain. The author in the PDF metadata is the author of the published work. |
| `pdf/ifg.pdf` | Informationsfreiheitsgesetz as published on gesetze-im-internet.de: an official work (§ 5 UrhG), free of copyright. |
| `pdf/einstein-dissertation.pdf` | Einstein's dissertation (1905), a public-domain work. The file is a scan from a web download; the digitizer's terms of use were not checked. Kept deliberately by the maintainer for the scanned pages (OCR coverage). |
| `mail/*` (GitHub, GOG and TLDR newsletters, each as `.eml` and `.msg`) | Mails received by the maintainer; promotional and informational content of the senders, released by the maintainer as test fixtures. Recipient address, names, host names and recipient-bound tokens are replaced by filler of the same length. |
| `office/*`, `zip/archive.zip` | Written by the maintainer for this purpose, with fictitious data; release confirmed by the maintainer. Author fields in the Office metadata are empty. The archive holds copies of six files of this directory (among them two mails) and is rebuilt from the sanitized copies. |
## Layout
| Folder | For |
|--------|-----|
| `pdf/` | PDFs, including scanned ones without a text layer |
| `mail/` | `.eml` and `.msg`, with and without attachments, with attached mails |
| `office/` | `.docx`, `.pptx`, `.xlsx` (tracked changes, comments, hidden sheets) |
| `zip/` | archives, including mail-in-ZIP cases |
The folders are a convention; the test reads every file below this directory. Keep file names
short and free of spaces - a name that is too long for the `raw/` path budget fails the test for
the right reason, but it is a nuisance to find.
## What the test checks
For each file, in a throwaway instance: `raw derive` succeeds; the original comes back byte for
byte (a ZIP is unpacked and gets a `.md` beside its documents instead); a second run over the same
bytes gives the same text apart from `derived:`; and `raw accept` takes the folder. The text
compared is every `.md` of the result plus the sheet files of a workbook (`<stem>.sheets/*.tsv`,
cells and formula lists).
A file may carry a sidecar `<file name>.expect` (`offer.pdf.expect` for `offer.pdf`): one line per
piece of text that must appear in the derived text - in any `.md` or sheet TSV of the result -
empty lines and lines starting with `#` ignored. A formula is named by its bare text, such as
`SUM(Sales!L6:L29)`, not by a tab-separated line. Without one the file only has to derive, be stable and be accepted. A document that is
meant to be refused (encrypted, damaged) does not belong here; the synthetic tests cover refusals.
## Trying a file by hand
`raw derive` moves what it is given, so work on a copy in a throwaway instance, never on
`incoming/` of this clone.
Binary file not shown.
-279
View File
@@ -1,279 +0,0 @@
Return-Path: <noreply@github.com>
Delivered-To: tester@mail.local
Received: from localhost (localhost [127.0.0.1])
by relay1.mail.local (Postfix) with ESMTP id 1FBDF3C79
for <tester@mail.local>; Fri, 9 Oct 2026 11:33:17 +0200 (CEST)
Authentication-Results: relay1.mail.local (amavis); dkim=pass (2048-bit key)
header.d=github.com
Received: from relay1.mail.local ([127.0.0.1])
by localhost (relay1.mail.local [127.0.0.1]) (amavis, port 10026) with LMTP
id qUuEj-q26P2w for <tester@mail.local>;
Fri, 9 Oct 2026 11:33:15 +0200 (CEST)
Received: from gw1.mail.local (gw1.mail.local [192.0.2.12])
by relay1.mail.local (Postfix) with ESMTPS id EDEF21322
for <tester@mail.local>; Fri, 9 Oct 2026 11:33:15 +0200 (CEST)
Received: from gw1.mail.local (localhost [127.0.0.1])
by gw1.mail.local (Proxmox) with ESMTP id D3AC64175D
for <tester@mail.local>; Fri, 09 Oct 2026 09:33:15 +0000 (UTC)
Received-SPF: pass (github.com: 192.30.252.200 is authorized to use 'noreply@github.com' in 'mfrom' identity (mechanism 'ip4:192.30.252.0/22' matched)) receiver=gw1.mail.local; identity=mailfrom; envelope-from="noreply@github.com"; helo=out-17.smtp.github.com; client-ip=192.30.252.200
Received: from out-17.smtp.github.com (out-17.smtp.github.com [192.30.252.200])
(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256)
(No client certificate requested)
by gw1.mail.local (Proxmox) with ESMTPS
for <tester@mail.local>; Fri, 09 Oct 2026 09:33:12 +0000 (UTC)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=github.com;
s=pf2026; t=1791538385;
bh=PUBUAPrF+j4smoQFdA2YzmimF06eina+b1lMnXVkNSk=;
h=Date:From:Reply-To:To:Cc:Subject:List-ID:List-Archive:List-Post:
From;
b=Glbp4FV+ONO7KVD75/XQI32S6nNkgMDQFHE88AhWhiFTBqjvc7Nw0brNmPpI7QiBH
1XUObBokb3bzlcyCfF6GjccclB4rMdaJQN5sPHlhI5XgNdf1y/u1E+3TMBbegldNVi
fcyAbLCfs5YBofGXvZZRIka2l4OPLhmKTIHhhfnCFBmwfWJQ8DrxdXprVpsWTlss2A
e2D10Cntxq5yeMZM6/ogKBUBlkRcWM3tyqmBxoNYZxYwlQhCrGb851oM8mg5VWGOuW
SzD44XgjIoNFbYIGD1FalbbNNaPM9DD02b4sEiKf7KTxIgfIqCiUdCFmE/rmJbp8V7
Z4W0FfTcgnejg==
Received: from github.com (hubbernetes-node-b6fc28b.va3-iad.github.net [10.48.68.62])
by smtp.github.com (Postfix) with ESMTPA id 6DC44501B32
for <tester@mail.local>; Fri, 9 Oct 2026 02:33:05 -0700 (PDT)
Date: Fri, 09 Oct 2026 02:33:05 -0700
From: Boliang Zhang <notifications@github.com>
Reply-To: microsoft/PowerToys <noreply@github.com>
To: microsoft/PowerToys <PowerToys@noreply.github.com>
Cc: Subscribed <subscribed@noreply.github.com>
Message-ID: <microsoft/PowerToys/releases/407742994@github.com>
Subject: [microsoft/PowerToys] Pre-release v0.102.2814.0 - Preview
v0.102.2814.0
Mime-Version: 1.0
Content-Type: multipart/alternative;
boundary="--==_mimepart_6ac8b4d169f87_11712283000d9";
charset=UTF-8
Content-Transfer-Encoding: 7bit
Precedence: list
X-GitHub-Sender: LegendaryBlair
X-GitHub-Recipient: testermuster
X-GitHub-Reason: subscribed
List-ID: microsoft/PowerToys <PowerToys.microsoft.github.com>
List-Archive: https://github.com/microsoft/PowerToys
List-Post: <mailto:noreply@github.com>
X-GitHub-Notify-Platform: newsies
X-Auto-Response-Suppress: All
destinations: tester@mail.local
X-GitHub-Recipient-Address: tester@mail.local
----==_mimepart_6ac8b4d169f87_11712283000d9
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: 7bit
<!-- BEGIN POWERTOYS PREVIEW AGENT -->
# PowerToys Preview v0.102.2814.0
This is a preview-channel build for the **PowerToys 0.102 release train**, produced from the `stable` branch. It follows [v0.102.2803.0](https://github.com/microsoft/PowerToys/releases/tag/v0.102.2803.0); the notes below describe changes since that preview, not the complete 0.102 versus 0.101 changelog. This is not the final stable release.
## Installer Hashes
| Description | Filename | sha256 hash |
| --- | --- | --- |
| Per user - x64 | [PowerToysUserSetup-0.102.2814.0-x64.exe][ptUserX64] | 0E0BC3040FAFA85373410BB9221F869DCD6B355B1D95E1B56A11E94FC757D170 |
| Per user - ARM64 | [PowerToysUserSetup-0.102.2814.0-arm64.exe][ptUserArm64] | ECA42FE6646ACD8BE81E8BD3C03B46D6AD8CA8F8D04A080280A01209CD4E07A8 |
| Machine wide - x64 | [PowerToysSetup-0.102.2814.0-x64.exe][ptMachineX64] | 1AB750116360130C7A5B9B7DF1BD6795FD4BB3BF9538DADD64892F40D9DCDAD4 |
| Machine wide - ARM64 | [PowerToysSetup-0.102.2814.0-arm64.exe][ptMachineArm64] | DD37E0895EE43F306883F08B9ADB5367AFF5087C2A90E67C84D24FF345B72C2E |
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.102.2814.0/PowerToysUserSetup-0.102.2814.0-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.102.2814.0/PowerToysUserSetup-0.102.2814.0-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.102.2814.0/PowerToysSetup-0.102.2814.0-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.102.2814.0/PowerToysSetup-0.102.2814.0-arm64.exe
## Highlights
- **Mouse Button Lock**: Added opt-in left, right, and middle button locking so dragging and selection can continue without holding a button down.
- **Light Switch**: Added a CLI for status, theme switching, and schedule control, with JSON output for automation.
- **Settings**: Added a CLI to inspect module states and save enable/disable changes while PowerToys is closed.
- **Command Palette**: Fixed dragging packaged apps onto the dock and displaying their loaded icons.
- **Quick Accent**: Restored the horizontal scrollbar for overflowing character lists.
- **Shortcut Guide**: Improved startup and indexing, including fixes for rapid-reopen crashes and first-activation flicker.
- **Settings**: Replaced animated welcome-page GIFs with muted, looping H.264 videos and a fallback image.
- **ZoomIt**: Fixed DSC configuration to use actual registry-backed settings and notify the running utility of changes.
## Command Palette
- Fixed dragging packaged apps onto the dock so pinned entries launch correctly and their icons appear when loaded in [#51018](https://github.com/microsoft/PowerToys/pull/51018) by [@jiripolasek](https://github.com/jiripolasek)
## Light Switch
- Added a CLI to query status, set or toggle configured theme targets, and enable or disable schedules on the running Light Switch service, with JSON output for automation in [#50459](https://github.com/microsoft/PowerToys/pull/50459)
## Mouse Button Lock
- Added opt-in Mouse Button Lock for left, right, and middle buttons so dragging and selection can continue after a long press without continuously holding a button in [#49279](https://github.com/microsoft/PowerToys/pull/49279) by [@MuyuanMS](https://github.com/MuyuanMS) and [@owenpkent](https://github.com/owenpkent)
## Quick Accent
- Restored the horizontal scrollbar for overflowing character lists so Quick Accent shows the current position without clipping the scrollbar in [#50924](https://github.com/microsoft/PowerToys/pull/50924) by [@daverayment](https://github.com/daverayment)
## Settings
- Added a Settings CLI to inspect module states in text or JSON and save enable/disable changes while PowerToys is closed in [#50507](https://github.com/microsoft/PowerToys/pull/50507) by [@Copilot](https://github.com/Copilot) and [@MuyuanMS](https://github.com/MuyuanMS)
- Replaced animated welcome-page GIFs with muted, looping H.264 videos, with a PowerToys logo fallback when playback is unavailable in [#51057](https://github.com/microsoft/PowerToys/pull/51057) by [@Copilot](https://github.com/Copilot)
## Shortcut Guide
- Improved startup and shortcut indexing, preventing rapid-reopen crashes and initial overlay flicker while picking up PowerToys shortcut changes on the next launch in [#50609](https://github.com/microsoft/PowerToys/pull/50609) by [@daverayment](https://github.com/daverayment)
## ZoomIt
- Fixed the ZoomIt DSC resource to read and update actual registry-backed settings, detect configuration drift, and notify a running ZoomIt instance to reload changes in [#50551](https://github.com/microsoft/PowerToys/pull/50551) by [@Gijsreyn](https://github.com/Gijsreyn)
## Development
- Removed unused central package entries and documented retained dependency pins without changing the versions resolved by existing projects in [#50880](https://github.com/microsoft/PowerToys/pull/50880) by [@Copilot](https://github.com/Copilot)
- Moved local build logs into artifacts\logs so context-menu packaging no longer fails when trying to include open MSBuild log files in [#51056](https://github.com/microsoft/PowerToys/pull/51056) by [@Copilot](https://github.com/Copilot)
- Removed obsolete analyzer suppressions for retired template code to keep shared build diagnostics easier to maintain in [#51061](https://github.com/microsoft/PowerToys/pull/51061) by [@Copilot](https://github.com/Copilot)
- Promoted IL2081 trimming diagnostics to build errors in the shared AOT configuration while retaining explicit project-level exceptions in [#51073](https://github.com/microsoft/PowerToys/pull/51073) by [@Copilot](https://github.com/Copilot)
- Aligned main's checked-in release train with stable's existing 0.102 setting; the previous preview already used this release train in [#51111](https://github.com/microsoft/PowerToys/pull/51111) by [@Copilot](https://github.com/Copilot) and [@LegendaryBlair](https://github.com/LegendaryBlair)
- Enabled nullable reference checking across File Explorer preview and thumbnail projects and their unit tests, with annotations and guards that preserve existing preview behavior in [#51099](https://github.com/microsoft/PowerToys/pull/51099) by [@Copilot](https://github.com/Copilot)
- Updated PowerToys Run architecture documentation to point contributors to the current plugin-management and query-processing code in [#50519](https://github.com/microsoft/PowerToys/pull/50519) by [@ElenaGe216](https://github.com/ElenaGe216) and [@Gavin-Yau](https://github.com/Gavin-Yau)
- Enabled warnings-as-errors for the shared UI-test framework after resolving its analyzer warnings and documenting narrowly scoped exceptions in [#51085](https://github.com/microsoft/PowerToys/pull/51085) by [@Copilot](https://github.com/Copilot)
- Corrected developer documentation links so readers can reach the referenced Settings, PowerToys Run, and Group Policy files in [#51108](https://github.com/microsoft/PowerToys/pull/51108) by [@xThreeh](https://github.com/xThreeh)
- Updated Image Resizer observable models for WinRT and AOT compatibility and made the corresponding MVVM generator diagnostic fail the build while preserving settings serialization in [#51067](https://github.com/microsoft/PowerToys/pull/51067) by [@Copilot](https://github.com/Copilot)
- Corrected native build configuration warnings, removed an unused COM reference, and separated intermediate output folders without changing shipped filenames or output locations in [#51090](https://github.com/microsoft/PowerToys/pull/51090) by [@Copilot](https://github.com/Copilot)
## Changes needing final review
Two direct integration commits merged the reviewed main changes into stable without separate PRs: [2646ff0204b8](https://github.com/microsoft/PowerToys/commit/2646ff0204b82fb0ddcaf9810f1ef2841ec5009c) and [1fca166ef506](https://github.com/microsoft/PowerToys/commit/1fca166ef506f0f7391615879c13fa3769bdaafa). Both preserve the existing stable-only PowerDisplay diagnostics omission; no new branch-transition removals were identified.
<!-- END POWERTOYS PREVIEW AGENT -->
--
View it on GitHub:
https://github.com/microsoft/PowerToys/releases/tag/v0.102.2814.0
You are receiving this because you are subscribed to this thread.
Message ID: <microsoft/PowerToys/releases/407742994@github.com>
----==_mimepart_6ac8b4d169f87_11712283000d9
Content-Type: text/html;
charset=UTF-8
Content-Transfer-Encoding: 7bit
<p></p>
<h1><a href="https://github.com/microsoft/PowerToys/releases/tag/v0.102.2814.0">Preview v0.102.2814.0</a></h1><p style="font-size:small;-webkit-text-size-adjust:none;">Repository: <a href="https://github.com/microsoft/PowerToys">microsoft/PowerToys</a> &middot; Tag: <a href="https://github.com/microsoft/PowerToys/tree/v0.102.2814.0">v0.102.2814.0</a> &middot; Commit: <a href="https://github.com/microsoft/PowerToys/commit/1fca166ef506f0f7391615879c13fa3769bdaafa">1fca166</a> &middot; Released by: <a href="https://github.com/LegendaryBlair">LegendaryBlair</a></p>
<h1>PowerToys Preview v0.102.2814.0</h1>
<p>This is a preview-channel build for the <strong>PowerToys 0.102 release train</strong>, produced from the <code>stable</code> branch. It follows <a href="https://github.com/microsoft/PowerToys/releases/tag/v0.102.2803.0">v0.102.2803.0</a>; the notes below describe changes since that preview, not the complete 0.102 versus 0.101 changelog. This is not the final stable release.</p>
<h2>Installer Hashes</h2>
<table>
<thead>
<tr>
<th>Description</th>
<th>Filename</th>
<th>sha256 hash</th>
</tr>
</thead>
<tbody>
<tr>
<td>Per user - x64</td>
<td><a href="https://github.com/microsoft/PowerToys/releases/download/v0.102.2814.0/PowerToysUserSetup-0.102.2814.0-x64.exe">PowerToysUserSetup-0.102.2814.0-x64.exe</a></td>
<td>0E0BC3040FAFA85373410BB9221F869DCD6B355B1D95E1B56A11E94FC757D170</td>
</tr>
<tr>
<td>Per user - ARM64</td>
<td><a href="https://github.com/microsoft/PowerToys/releases/download/v0.102.2814.0/PowerToysUserSetup-0.102.2814.0-arm64.exe">PowerToysUserSetup-0.102.2814.0-arm64.exe</a></td>
<td>ECA42FE6646ACD8BE81E8BD3C03B46D6AD8CA8F8D04A080280A01209CD4E07A8</td>
</tr>
<tr>
<td>Machine wide - x64</td>
<td><a href="https://github.com/microsoft/PowerToys/releases/download/v0.102.2814.0/PowerToysSetup-0.102.2814.0-x64.exe">PowerToysSetup-0.102.2814.0-x64.exe</a></td>
<td>1AB750116360130C7A5B9B7DF1BD6795FD4BB3BF9538DADD64892F40D9DCDAD4</td>
</tr>
<tr>
<td>Machine wide - ARM64</td>
<td><a href="https://github.com/microsoft/PowerToys/releases/download/v0.102.2814.0/PowerToysSetup-0.102.2814.0-arm64.exe">PowerToysSetup-0.102.2814.0-arm64.exe</a></td>
<td>DD37E0895EE43F306883F08B9ADB5367AFF5087C2A90E67C84D24FF345B72C2E</td>
</tr>
</tbody>
</table>
<h2>Highlights</h2>
<ul>
<li><strong>Mouse Button Lock</strong>: Added opt-in left, right, and middle button locking so dragging and selection can continue without holding a button down.</li>
<li><strong>Light Switch</strong>: Added a CLI for status, theme switching, and schedule control, with JSON output for automation.</li>
<li><strong>Settings</strong>: Added a CLI to inspect module states and save enable/disable changes while PowerToys is closed.</li>
<li><strong>Command Palette</strong>: Fixed dragging packaged apps onto the dock and displaying their loaded icons.</li>
<li><strong>Quick Accent</strong>: Restored the horizontal scrollbar for overflowing character lists.</li>
<li><strong>Shortcut Guide</strong>: Improved startup and indexing, including fixes for rapid-reopen crashes and first-activation flicker.</li>
<li><strong>Settings</strong>: Replaced animated welcome-page GIFs with muted, looping H.264 videos and a fallback image.</li>
<li><strong>ZoomIt</strong>: Fixed DSC configuration to use actual registry-backed settings and notify the running utility of changes.</li>
</ul>
<h2>Command Palette</h2>
<ul>
<li>Fixed dragging packaged apps onto the dock so pinned entries launch correctly and their icons appear when loaded in <a href="https://github.com/microsoft/PowerToys/pull/51018" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/PowerToys/pull/51018/hovercard">#51018</a> by <a href="https://github.com/jiripolasek">@jiripolasek</a></li>
</ul>
<h2>Light Switch</h2>
<ul>
<li>Added a CLI to query status, set or toggle configured theme targets, and enable or disable schedules on the running Light Switch service, with JSON output for automation in <a href="https://github.com/microsoft/PowerToys/pull/50459" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/PowerToys/pull/50459/hovercard">#50459</a></li>
</ul>
<h2>Mouse Button Lock</h2>
<ul>
<li>Added opt-in Mouse Button Lock for left, right, and middle buttons so dragging and selection can continue after a long press without continuously holding a button in <a href="https://github.com/microsoft/PowerToys/pull/49279" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/PowerToys/pull/49279/hovercard">#49279</a> by <a href="https://github.com/MuyuanMS">@MuyuanMS</a> and <a href="https://github.com/owenpkent">@owenpkent</a></li>
</ul>
<h2>Quick Accent</h2>
<ul>
<li>Restored the horizontal scrollbar for overflowing character lists so Quick Accent shows the current position without clipping the scrollbar in <a href="https://github.com/microsoft/PowerToys/pull/50924" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/PowerToys/pull/50924/hovercard">#50924</a> by <a href="https://github.com/daverayment">@daverayment</a></li>
</ul>
<h2>Settings</h2>
<ul>
<li>Added a Settings CLI to inspect module states in text or JSON and save enable/disable changes while PowerToys is closed in <a href="https://github.com/microsoft/PowerToys/pull/50507" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/PowerToys/pull/50507/hovercard">#50507</a> by <a href="https://github.com/Copilot">@Copilot</a> and <a href="https://github.com/MuyuanMS">@MuyuanMS</a></li>
<li>Replaced animated welcome-page GIFs with muted, looping H.264 videos, with a PowerToys logo fallback when playback is unavailable in <a href="https://github.com/microsoft/PowerToys/pull/51057" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/PowerToys/pull/51057/hovercard">#51057</a> by <a href="https://github.com/Copilot">@Copilot</a></li>
</ul>
<h2>Shortcut Guide</h2>
<ul>
<li>Improved startup and shortcut indexing, preventing rapid-reopen crashes and initial overlay flicker while picking up PowerToys shortcut changes on the next launch in <a href="https://github.com/microsoft/PowerToys/pull/50609" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/PowerToys/pull/50609/hovercard">#50609</a> by <a href="https://github.com/daverayment">@daverayment</a></li>
</ul>
<h2>ZoomIt</h2>
<ul>
<li>Fixed the ZoomIt DSC resource to read and update actual registry-backed settings, detect configuration drift, and notify a running ZoomIt instance to reload changes in <a href="https://github.com/microsoft/PowerToys/pull/50551" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/PowerToys/pull/50551/hovercard">#50551</a> by <a href="https://github.com/Gijsreyn">@Gijsreyn</a></li>
</ul>
<h2>Development</h2>
<ul>
<li>Removed unused central package entries and documented retained dependency pins without changing the versions resolved by existing projects in <a href="https://github.com/microsoft/PowerToys/pull/50880" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/PowerToys/pull/50880/hovercard">#50880</a> by <a href="https://github.com/Copilot">@Copilot</a></li>
<li>Moved local build logs into artifacts\logs so context-menu packaging no longer fails when trying to include open MSBuild log files in <a href="https://github.com/microsoft/PowerToys/pull/51056" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/PowerToys/pull/51056/hovercard">#51056</a> by <a href="https://github.com/Copilot">@Copilot</a></li>
<li>Removed obsolete analyzer suppressions for retired template code to keep shared build diagnostics easier to maintain in <a href="https://github.com/microsoft/PowerToys/pull/51061" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/PowerToys/pull/51061/hovercard">#51061</a> by <a href="https://github.com/Copilot">@Copilot</a></li>
<li>Promoted IL2081 trimming diagnostics to build errors in the shared AOT configuration while retaining explicit project-level exceptions in <a href="https://github.com/microsoft/PowerToys/pull/51073" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/PowerToys/pull/51073/hovercard">#51073</a> by <a href="https://github.com/Copilot">@Copilot</a></li>
<li>Aligned main's checked-in release train with stable's existing 0.102 setting; the previous preview already used this release train in <a href="https://github.com/microsoft/PowerToys/pull/51111" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/PowerToys/pull/51111/hovercard">#51111</a> by <a href="https://github.com/Copilot">@Copilot</a> and <a href="https://github.com/LegendaryBlair">@LegendaryBlair</a></li>
<li>Enabled nullable reference checking across File Explorer preview and thumbnail projects and their unit tests, with annotations and guards that preserve existing preview behavior in <a href="https://github.com/microsoft/PowerToys/pull/51099" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/PowerToys/pull/51099/hovercard">#51099</a> by <a href="https://github.com/Copilot">@Copilot</a></li>
<li>Updated PowerToys Run architecture documentation to point contributors to the current plugin-management and query-processing code in <a href="https://github.com/microsoft/PowerToys/pull/50519" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/PowerToys/pull/50519/hovercard">#50519</a> by <a href="https://github.com/ElenaGe216">@ElenaGe216</a> and <a href="https://github.com/Gavin-Yau">@Gavin-Yau</a></li>
<li>Enabled warnings-as-errors for the shared UI-test framework after resolving its analyzer warnings and documenting narrowly scoped exceptions in <a href="https://github.com/microsoft/PowerToys/pull/51085" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/PowerToys/pull/51085/hovercard">#51085</a> by <a href="https://github.com/Copilot">@Copilot</a></li>
<li>Corrected developer documentation links so readers can reach the referenced Settings, PowerToys Run, and Group Policy files in <a href="https://github.com/microsoft/PowerToys/pull/51108" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/PowerToys/pull/51108/hovercard">#51108</a> by <a href="https://github.com/xThreeh">@xThreeh</a></li>
<li>Updated Image Resizer observable models for WinRT and AOT compatibility and made the corresponding MVVM generator diagnostic fail the build while preserving settings serialization in <a href="https://github.com/microsoft/PowerToys/pull/51067" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/PowerToys/pull/51067/hovercard">#51067</a> by <a href="https://github.com/Copilot">@Copilot</a></li>
<li>Corrected native build configuration warnings, removed an unused COM reference, and separated intermediate output folders without changing shipped filenames or output locations in <a href="https://github.com/microsoft/PowerToys/pull/51090" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/PowerToys/pull/51090/hovercard">#51090</a> by <a href="https://github.com/Copilot">@Copilot</a></li>
</ul>
<h2>Changes needing final review</h2>
<p>Two direct integration commits merged the reviewed main changes into stable without separate PRs: <a href="https://github.com/microsoft/PowerToys/commit/2646ff0204b82fb0ddcaf9810f1ef2841ec5009c">2646ff0204b8</a> and <a href="https://github.com/microsoft/PowerToys/commit/1fca166ef506f0f7391615879c13fa3769bdaafa">1fca166ef506</a>. Both preserve the existing stable-only PowerDisplay diagnostics omission; no new branch-transition removals were identified.</p>
&mdash;<p>This release has 9 assets:</p><ul><li>GroupPolicyObjectFiles-0.102.2814.0.zip</li><li>PowerToysSetup-0.102.2814.0-arm64.exe</li><li>PowerToysSetup-0.102.2814.0-x64.exe</li><li>PowerToysUserSetup-0.102.2814.0-arm64.exe</li><li>PowerToysUserSetup-0.102.2814.0-x64.exe</li><li>symbols-arm64.zip</li><li>symbols-x64.zip</li><li>Source code (zip)</li><li>Source code (tar.gz)</li></ul><p>Visit the <a href="https://github.com/microsoft/PowerToys/releases/tag/v0.102.2814.0">release page</a> to download them.</p>
<p style="font-size:small;-webkit-text-size-adjust:none;color:#666;">&mdash;<br />You are receiving this because you are watching this repository.<br /><a href="https://github.com/microsoft/PowerToys/releases/tag/v0.102.2814.0?email_source=notifications&amp;email_token=XXXXXX00XX0X0XXX0XXXXXX0XXXXXX0XXXXX0X0XXX0X0X0XXX0X00XXXXXXXXXXXXXXXXXXXXXXXXXXXX0XXXXXXXXXX00XX0XXX0XXXXXXX0XXXXXXXXXXXXXXXXXXX0XXXXXXX0XXX0XXXX">View it on GitHub</a> or <a href="https://github.com/microsoft/PowerToys/unsubscribe_via_email/XXXXXX0X0X0XXXX00XX00X00XXXXXXXXXXXX0XX0XX0X">unsubscribe</a> from all notifications for this repository.<img src="https://github.com/notifications/beacon/XXXXXX0X0X0XXXXXXXXXXXX0XXXXXXXXXXXX0XXXXXXXXXX0XXXXX00XXXXX00X0XX0XXXXXXXXXXXXXXXX0XX0XXXXXX0XXX0XXXXXXXXXXXXXXXXXXX00XXXXXXXXXXXXXXXXXXX.gif" height="1" width="1" alt="" /></p>
<script type="application/ld+json">[
{
"@context": "http://schema.org",
"@type": "EmailMessage",
"potentialAction": {
"@type": "ViewAction",
"target": "https://github.com/microsoft/PowerToys/releases/tag/v0.102.2814.0?email_source=notifications\u0026email_token=AAVORIZ25RX3JURVCYQUFS35TCWFDA5CNFSL6Z3JMQ5C6L3HNF2C22DVMIXVEZLMMVQXGZJPGQYDONZUGI4TSNFGOJSWC43PN2VHG5LCONRXE2LCMVSKKZLWMVXHJK3HNVQWS3C7MNWGSY3L",
"url": "https://github.com/microsoft/PowerToys/releases/tag/v0.102.2814.0?email_source=notifications\u0026email_token=AAVORIZ25RX3JURVCYQUFS35TCWFDA5CNFSL6Z3JMQ5C6L3HNF2C22DVMIXVEZLMMVQXGZJPGQYDONZUGI4TSNFGOJSWC43PN2VHG5LCONRXE2LCMVSKKZLWMVXHJK3HNVQWS3C7MNWGSY3L",
"name": "View Release"
},
"description": "View this Release on GitHub",
"publisher": {
"@type": "Organization",
"name": "GitHub",
"url": "https://github.com"
}
}
]</script>
----==_mimepart_6ac8b4d169f87_11712283000d9--
Binary file not shown.
-637
View File
@@ -1,637 +0,0 @@
Return-Path: <sm.5404404998.3f5ada29ba85a3ae55-newsletter=email3.gog.com@emsgrid.com>
Delivered-To: tester@mail.local
Received: from localhost (localhost [127.0.0.1])
by relay1.mail.local (Postfix) with ESMTP id E7A713D1B
for <tester@mail.local>; Fri, 9 Oct 2026 12:10:57 +0200 (CEST)
Authentication-Results: relay1.mail.local (amavis); dkim=pass (2048-bit key)
header.d=smgrid.eu header.b="GW2zRKwt"; dkim=pass (2048-bit key)
header.d=email3.gog.com header.b="eFv0TUK7"
Received: from relay1.mail.local ([127.0.0.1])
by localhost (relay1.mail.local [127.0.0.1]) (amavis, port 10026) with LMTP
id WujfPgrqimYD for <tester@mail.local>;
Fri, 9 Oct 2026 12:10:57 +0200 (CEST)
Received: from gw1.mail.local (gw1.mail.local [192.0.2.12])
by relay1.mail.local (Postfix) with ESMTPS id 719511322
for <tester@mail.local>; Fri, 9 Oct 2026 12:10:57 +0200 (CEST)
Received: from gw1.mail.local (localhost [127.0.0.1])
by gw1.mail.local (Proxmox) with ESMTP id 5876C41760
for <tester@mail.local>; Fri, 09 Oct 2026 10:10:57 +0000 (UTC)
Received-SPF: pass (emsgrid.com: Sender is authorized to use 'sm.#.3f5ada29ba85a3ae55-newsletter=email3.gog.com@emsgrid.com' in 'mfrom' identity (mechanism 'include:_spf.jupiter.salesmanago.pl' matched)) receiver=gw1.mail.local; identity=mailfrom; envelope-from="sm.#.3f5ada29ba85a3ae55-newsletter=email3.gog.com@emsgrid.com"; helo=amalthea.emgrid.pl; client-ip=89.25.191.77
Received: from amalthea.emgrid.pl (amalthea.emgrid.pl [89.25.191.77])
(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256)
(No client certificate requested)
by gw1.mail.local (Proxmox) with ESMTPS
for <tester@mail.local>; Fri, 09 Oct 2026 10:10:56 +0000 (UTC)
Received: from s12 (_local_)
by smgrid.pl (mta04) with ESMTP id 4j1N0n4lGxzJtb5T
for <tester@mail.local>; Fri, 09 Oct 2026 10:10:49 +0000 (UTC)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=smgrid.eu;
s=sm-trackid; t=1791540649; x=1791585649;
bh=gpu7ZaQ1XIQKWRN0rsfuWvDWyeFeTBF+wOJbmRwqrPE=;
h=Date:From:To:Subject:List-Unsubscribe:List-Unsubscribe-Post:Date:
From:To:Subject:List-Id:List-Unsubscribe:List-Unsubscribe-Post:CC;
b=GW2zRKwtIA4UQOhiuIUky5HB0uL1Psi4DZ2ftjurUdK8bOACUxDwYj8819gVPKbHi
SzVltCunE1hRyUfXil3qqFVuzO8SaKigpIfXECPuxzFSJJAQ+RGQeMwPVRNB43MCqd
oA7C094/1Q0sX5a+KAYAEtoaRXJ7HUK1plKyQrl2gAYlIdhC5/Hqb338nLePYf79iZ
DXw1XciZns9ZEjQXQhhTYSFNUxu1Gp2C9frzs1vDe6a8pyW61WfsMOw0TVuhLFGOpx
kovUlNK7QCg283mQ+ZpNCLpAM1vb8SadJs0SwbC7vzIOGPVmaqyusBefaSDqbIK9R7
EFY0488QZRhDg==
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=email3.gog.com;
s=salesmanago; t=1791540649; x=1791585649;
bh=gpu7ZaQ1XIQKWRN0rsfuWvDWyeFeTBF+wOJbmRwqrPE=;
h=Date:From:To:Subject:List-Unsubscribe:List-Unsubscribe-Post:Date:
From:To:Subject:List-Id:List-Unsubscribe:List-Unsubscribe-Post:CC;
b=eFv0TUK7rrzF5OMLnsbYs0/Llxja3xiP57DsvZfbbh0e+fDY14eLLGaf6021fm+cP
wc5w//FRTjPV5Q4912AWJjNlLK1WdXibM6aLKQc6o13HipWX+DP5MsWPsmoxcDtxQc
ro5P+sPVEvkn06+xKj+NF03DCqPrH1ae5QZ0cWm3McLyb4L9YVsJZe+/LzoJXJicFY
GrXyk7GdIcGSr9t37r8f6N9vZf7qt/60Y7Wtth/EU0yDiUfWn3eI/aYGE96Ac/CURC
V8CHnicop8uUHBGj9GMuDo+FDk9FGy87+dSx4+0O1ms+Xs+mvT2b9+zr6Csfr96Vwd
l/nxTSL6BvW7w==
Date: Fri, 9 Oct 2026 12:10:49 +0200 (CEST)
From: "GOG.COM" <newsletter@email3.gog.com>
To: testermuster <tester@mail.local>
Message-Id: <5404404998.40649541@emsgrid.com5404404998>
Subject: =?UTF-8?Q?Der_Albtraum_beginnt:_SILENT_HIL?=
=?UTF-8?Q?L:_Townfall_ist_jetzt_erh=C3=A4ltlich!?=
X-Abuse: Please report abuse here: abuse@manago.ai
X-Campaign: 000xx0x0-0x0x-0000-00xx-xx0xxxx00x0x
Feedback-ID: 000xx0x0-0x0x-0000-00xx-xx0xxxx00x0x:3f5ada29ba85a3ae:ManagoAI
List-Unsubscribe: <mailto:unsubscribe+XXxXxXxxx-Xx0XXXx-0xXXXXxX0Xx0xXxX000-xxXxxx00@emsgrid.com?subject=XXxXxXxxx-Xx0XXXx-0xXXXXxX0Xx0xXxX000-xxXxxx00>,
<https://gog.salesmanago.com/optOut.htm?track=XxxXXX00XXxxXxXXXXxxXXXXXXxxXXXXXXXXXxXxXXxXXXX0XXxXXx0XXX0XXXX0XXXxXXXxXXxXXXXXXXxXXxXXXXXxXXXxXXXXXXXXXXXxXXXXXXxxXx0xXXxxXX00XXXxXxXxXX0xXxX0XXXxXXXXXX0xXXXXXXXxXXX0XXxXXXXxXXxxXxXXXXxXXXXXXXXxxXXxXXXxxXXXXXXXXXXxXXXXXxX0XXxxXXXxXXXxXXXXXX0xXXXXXXXXXxXXXXXXXXXxXXxXXXX0XX0xXXXXXXXxxXXXXXxxXxXXXX0XXxXxXXxxXx00XXXxXxXXXXxxXxX0XXXXXXXxXXXXXXXXXXxxx0X0XXxXXx0XXX0xXXXxXXxxXXXXXXxXXXXxXXxXXXXXXXXXXXX0XXxXXxXXXXxXXXX0XXXxXx0xXXxxXx00XXXXXXXxXX0XXXXx>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
Precedence: bulk
X-Entity-Ref-ID: none
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary="----=_Part_4135371_1142483364.1791540649541"
------=_Part_4135371_1142483364.1791540649541
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 7bit
Plain text version not available
------=_Part_4135371_1142483364.1791540649541
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
<!DOCTYPE html><html xmlns=3D"http://www.w3.org/1999/xhtml" xmlns:v=3D"urn:=
schemas-microsoft-com:vml" xmlns:o=3D"urn:schemas-microsoft-com:office:offi=
ce" lang=3Dde><head></head><head><title>Der Albtraum beginnt: SILENT HILL: =
Townfall ist jetzt erh=C3=A4ltlich!</title><meta http-equiv=3DContent-Type =
content=3D"text/html; charset=3Dutf-8"/><meta content=3D"Benhauer.pl" name=
=3Dauthor /><meta content=3D"width=3Ddevice-width, initial-scale=3D1.0" nam=
e=3Dviewport><meta name=3Drobots content=3Dnoindex/><meta content=3D"ie=3De=
dge" http-equiv=3DX-UA-Compatible><meta name=3Dcolor-scheme content=3Donly>=
<link href=3D"https://fonts.googleapis.com/css2?family=3DOpen+Sans:ital,wgh=
t@0,300..800;1,300..800&family=3DLato:ital,wght@0,100;0,300;0,400;0,700;0,9=
00;1,100;1,300;1,400;1,700;1,900&display=3Dswap" rel=3Dstylesheet><!--[if g=
te mso 9]><xml><o:OfficeDocumentSettings><o:AllowPNG/><o:PixelsPerInch>96</=
o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]--><!--[if mso]><=
style>.dnd-hide-mobile{display:block !important}</style><![endif]--><style>=
h1{font-size:30px;font-weight:500}h2{font-size:26px;font-weight:500}h3{font=
-size:20px;font-weight:500}h4{font-size:18px;font-weight:500}h5{font-size:1=
6px;font-weight:500}h6{font-size:12px;font-weight:500}p{font-size:inherit}p=
,h1,h2,h3,h4,h5,h6{text-align:left;color:inherit;letter-spacing:normal;font=
-family:Arial,Helvetica,sans-serif;margin:0}span{line-height:inherit;font-f=
amily:Arial,Helvetica,sans-serif}strong,u,em{line-height:inherit}a{text-dec=
oration:none;color:#000}a span,a u,a strong,a i,a p,a img,a div{pointer-eve=
nts:none}img+div{display:none}table{box-sizing:border-box}table,td{border-c=
ollapse:collapse}mark{color:inherit}ul,ol{margin-top:0;margin-bottom:0}wbr{=
display:none}ul>li,ol>li{margin-top:0;margin-bottom:0;margin-left:0}ul>li p=
,ul>li h1,ul>li h2,ul>li h3,ul>li h4,ul>li h5,ul>li h6,ol>li p,ol>li h1,ol>=
li h2,ol>li h3,ol>li h4,ol>li h5,ol>li h6{display:inline}div[class*=3D" gwp=
"][class*=3D"dnd-height "]{height:auto}.mail-detail-content a[href]{color:#=
000;text-decoration:none}@media screen and (min-width:700px){#proton-root .=
dnd-height{height:100% !important}.dnd-height{height:100%;min-height:inheri=
t}.dnd-width-100-percent{width:100%}.dnd-display-table-cell{display:table-c=
ell}.dnd-display-table{display:table}.dnd-display-table-row{display:table-r=
ow}.dnd-max-width-600-px{max-width:600px}.dnd-max-width-420-px{max-width:42=
0px}}</style><style>@media screen and (min-width:600px){.dnd-hide-mobile{di=
splay:table-cell !important}.dnd-hide-desktop{display:none !important}.dnd-=
hide-desktop{display:table-cell}}</style><style>.moz-text-htm .dnd-hide-mob=
ile,.moz-text-html .dnd-hide-mobile{display:table-cell !important}.moz-text=
-htm .dnd-hide-desktop,.moz-text-html .dnd-hide-desktop{display:none !impor=
tant}</style><style>.moz-text-htm .dnd-width-100-percent,.moz-text-html .dn=
d-width-100-percent{width:100%}.moz-text-htm .dnd-display-table-cell,.moz-t=
ext-html .dnd-display-table-cell{display:table-cell}.moz-text-htm .dnd-disp=
lay-table,.moz-text-html .dnd-display-table{display:table}.moz-text-htm .dn=
d-display-table-row,.moz-text-html .dnd-display-table-row{display:table-row=
}.moz-text-htm .dnd-max-width-600-px,.moz-text-html .dnd-max-width-600-px{m=
ax-width:600px}.moz-text-htm .dnd-max-width-420-px,.moz-text-html .dnd-max-=
width-420-px{max-width:420px}</style></head><body style=3D"margin:0;padding=
:0;-ms-text-size-adjust:100%;-webkit-font-smoothing:antialiased;-webkit-tex=
t-size-adjust:100%;word-spacing:normal;mso-line-height-rule:exactly;backgro=
und-color:#000000;"><div style=3D"background-image: url('https://gog.salesm=
anago.com/api/3f5ada29ba85a3ae/000xx0x0-0x0x-0000-00xx-xx0xxxx00x0x/0x0000x=
x-x00x-0x0x-00x0-x0000xxxx000/t.gif');"></div><!--[if gte mso 9]><v:rect fi=
ll=3Df stroke=3Df style=3D"width:1pt;height:1pt;margin:0;padding:0;mso-hide=
:all;"><v:fill src=3D"https://gog.salesmanago.com/api/3f5ada29ba85a3ae/000x=
x0x0-0x0x-0000-00xx-xx0xxxx00x0x/0x0000xx-x00x-0x0x-00x0-x0000xxxx000/t.gif=
"/></v:rect><![endif]--><center dir=3Dltr lang=3Dde class=3Dwrapper style=
=3D"width:100%;table-layout:fixed;-webkit-text-size-adjust:100%;-ms-text-si=
ze-adjust:100%;"><table role=3Dpresentation align=3Dcenter border=3D0 cellp=
adding=3D0 cellspacing=3D0 style=3D"width:100%;" width=3D"100%"><tbody><tr>=
<td><span style=3D"visibility:hidden;opacity:0;color:#000000;height:0;width=
:0;display:none !important;font-size:0px;"> Betritt&nbsp;St.&nbsp;Amelia,&n=
bsp;stelle&nbsp;dich&nbsp;dem,&nbsp;was&nbsp;geblieben&nbsp;ist,&nbsp;und&n=
bsp;finde&nbsp;heraus,&nbsp;warum&nbsp;alle&nbsp;verschwunden&nbsp;sind. &n=
bsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp=
;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp; &=
nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbs=
p;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&=
zwnj;&nbsp; &nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nb=
sp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;=
&zwnj;&nbsp;&zwnj;&nbsp; &nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&n=
bsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp=
;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp; &nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&=
nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbs=
p;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp; &nbsp;&zwnj;&nbsp;&zwnj;=
&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nb=
sp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp; &nbsp;&zwnj=
;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&n=
bsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp=
; &nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&=
nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbs=
p;&zwnj;&nbsp; &nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;=
&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nb=
sp;&zwnj;&nbsp;&zwnj;&nbsp; &nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj=
;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&n=
bsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp; &nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwn=
j;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&=
nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp; &nbsp;&zwnj;&nbsp;&zw=
nj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;=
&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp; &nbsp;&z=
wnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj=
;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&n=
bsp; &nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwn=
j;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&=
nbsp;&zwnj;&nbsp; &nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zw=
nj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;=
&nbsp;&zwnj;&nbsp;&zwnj;&nbsp; &nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&z=
wnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj=
;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp; </span></td></tr></tbody></tabl=
e><table role=3Dpresentation align=3Dcenter border=3D0 cellpadding=3D0 cell=
spacing=3D0 width=3D"100%" style=3D"width:100%;background-color:#000000;"><=
tbody><tr><td valign=3Dtop><!--[if (gte mso 9)|(IE)]><table role=3Dpresenta=
tion border=3D0 cellpadding=3D0 cellspacing=3D0 width=3D600 align=3Dcenter>=
<tr><td><![endif]--><table align=3Dcenter border=3D0 cellpadding=3D0 cellsp=
acing=3D0 width=3D"100%" role=3Dpresentation style=3D"width:100%;margin:0px=
auto;border-collapse:collapse;max-width:600px;"><tr><td align=3Dcenter col=
span=3D3 style=3D"padding:0px;margin:0px;height:0px;" height=3D0></td></tr>=
<tr><td width=3D0 style=3D"width:0px;"></td><td align=3Dcenter><table borde=
r=3D0 cellpadding=3D0 cellspacing=3D0 width=3D"100%" role=3Dpresentation st=
yle=3D"width:100%;"><tr><td><!--[if (gte mso 9)|(IE)]><table border=3D0 rol=
e=3Dpresentation cellpadding=3D0 cellspacing=3D0 width=3D"100%" style=3D"ba=
ckground-color:transparent;"><tr><td><![endif]--><!--[if !mso]--><div style=
=3D"background-color:transparent;border-radius:0px;border-color:#000000;bor=
der-style:none;border-width:0px;"><!--[endif]--><table role=3Dpresentation =
border=3D0 cellpadding=3D0 cellspacing=3D0 width=3D"100%"><tr><td style=3D"=
padding:0px;"><table border=3D0 cellpadding=3D0 cellspacing=3D0 width=3D"10=
0%" role=3Dpresentation><tr><td align=3Dcenter style=3D"padding:0px;"><tabl=
e role=3Dpresentation border=3D0 cellpadding=3D0 cellspacing=3D0 height=3D"=
100%" width=3D"100%" style=3D"width:100%;"><tr><td align=3Dcenter><div styl=
e=3D"padding:0;margin:0;font-size:0;width:100%;box-sizing:border-box;" clas=
s=3Ddnd-display-table><div class=3Ddnd-display-table-row><!--[if (gte mso 9=
)|(IE)]><table border=3D0 role=3Dpresentation cellpadding=3D0 cellspacing=
=3D0 width=3D"100%" align=3Dcenter><tr><![endif]--><!--[if (gte mso 9)|(IE)=
]><td valign=3Dtop width=3D"100%" style=3D"background-color:transparent;"><=
![endif]--><!--[if !mso]--><div style=3D"box-sizing:border-box;vertical-ali=
gn:top;background-color:transparent;border-color:#000000;border-style:none;=
border-width:0px;border-radius:0px;" class=3D"dnd-width-100-percent dnd-dis=
play-table-cell dnd-width-100-percent dnd-display-table-cell dnd-width-100-=
percent dnd-display-table-cell"><!--[endif]--><table role=3Dpresentation bo=
rder=3D0 cellpadding=3D0 cellspacing=3D0 width=3D"100%" height=3D"100%" cla=
ss=3Dbhr-dnd-height style=3D"width:100%;"><tr><td class=3Dbhr-dnd-height st=
yle=3D"font-size:14px;padding:0px;vertical-align:top;"><table role=3Dpresen=
tation border=3D0 cellpadding=3D0 cellspacing=3D0 width=3D"100%" style=3D"w=
idth:100%;"><tr><td align=3Dcenter style=3D"padding:20px 0px;"><a target=3D=
_blank style=3D"display:block;width:100%;" id=3Dsmlid_148 href=3D"https://g=
og.salesmanago.com/smrd.htm?url=3Dhttps%3A%2F%2Fwww.gog.com%2F%3Futm_source=
%3Dsalesmanago%26utm_medium%3Demail%26utm_campaign%3D20261009_nl_SILENT_HIL=
L_TOWNFALL_exclusive_DE&amp;track=3DXxxXXX00XXxxXxXXXXxxXXXXXXxxXXXXXXXXXxX=
xXXxXXXX0XXxXXx0XXX0XXXX0XXXxXXXxXXxXXXXXXXxXXxXXXXXxXXXxXXXXXXXXXXXxXXXXXX=
xxXx0xXXxxXX00XXXxXxXxXXxxxXXXXXxxxXXXXXXxXXX0XXXxxXXXXXxxXxXxXXxxXX0XXXXxx=
0XXXXxxXXXXXXXxXXXXXXXXXXXxXXxxXXXXXXxXXX00XXxxXxX0XXxxXXXxXXx"><!--[if !ms=
o]--><img style=3D"width:100%;display:block;border:0;" src=3D"https://sm-cd=
n.eu/se0lr6cd9oehyaev.png" alt=3D"GOG.com logo =E2=80=94 white square icon =
with stylised rounded 'gog' letters, representing the DRM-free digital game=
store" class=3Ddnd-max-width-600-px><!--[endif]--><!--[if mso]><img src=3D=
"https://sm-cdn.eu/se0lr6cd9oehyaev.png" style=3D"display:block;margin:0;" =
width=3D600 border=3D0 alt=3D"GOG.com logo =E2=80=94 white square icon with=
stylised rounded 'gog' letters, representing the DRM-free digital game sto=
re"/><![endif]--></a></td></tr></table><table role=3Dpresentation border=3D=
0 cellpadding=3D0 cellspacing=3D0 width=3D"100%" style=3D"width:100%;"><tr>=
<td align=3Dcenter style=3D"padding:20px 0px 0px;"><a target=3D_blank style=
=3D"display:block;width:100%;max-width:520px;" id=3Dsmlid_389 href=3D"https=
://gog.salesmanago.com/smrd.htm?url=3Dhttps%3A%2F%2Fwww.gog.com%2Fen%2Fgame=
%2Fsilent_hill_townfall%3Futm_source%3Dsalesmanago%26utm_medium%3Demail%26u=
tm_campaign%3D20261009_nl_SILENT_HILL_TOWNFALL_exclusive_DE&amp;track=3DXxx=
XXX00XXxxXxXXXXxxXXXXXXxxXXXXXXXXXxXxXXxXXXX0XXxXXx0XXX0XXXX0XXXxXXXxXXxXXX=
XXXXxXXxXXXXXxXXXxXXXXXXXXXXXxXXXXXXxxXx0xXXxxXX00XXXxXxXxXXxxxXXXXXxxxXXXX=
XXxXXX0XXXxxXXXXXxxXxXxXXxxXX0XXXXxx0XXXXxxXXXXXXXxXXXXXXXXXXXxXXxxXXXXXXxX=
XX00XXxxXxX0XXxxXXXXXXx"><!--[if !mso]--><img style=3D"width:100%;display:b=
lock;border:0px;max-width:520px;" src=3D"https://sm-cdn.eu/zizj1m5nxvc5lz84=
.png" alt=3D"Silent Hill: Townfall logo with pale 'Silent Hill' text and di=
stressed red handwritten 'townfall' on white background"><!--[endif]--><!--=
[if mso]><img src=3D"https://sm-cdn.eu/zizj1m5nxvc5lz84.png" style=3D"displ=
ay:block;margin:0;" width=3D520 border=3D0 alt=3D"Silent Hill: Townfall log=
o with pale 'Silent Hill' text and distressed red handwritten 'townfall' on=
white background"/><![endif]--></a></td></tr></table><table role=3Dpresent=
ation border=3D0 cellpadding=3D0 cellspacing=3D0 width=3D"100%" style=3D"wi=
dth:100%;"><tr><td style=3D"font-size:14px;color:#000000;font-family:Arial,=
sans-serif;padding:10px;"><div style=3D"margin:0;padding:0;"><div><p dir=
=3Dauto style=3D"font-size:14px;line-height:1.4;text-align:center;margin:0p=
x;word-break:break-word;"><span style=3D"color:#b5d3d5;font-family:&quot;Op=
en Sans&quot;, sans-serif;font-size:16px;letter-spacing:normal;">ist jetzt =
erh=C3=A4ltlich. Und es ruft nach dir.</span></p></div></div></td></tr></ta=
ble><table role=3Dpresentation border=3D0 cellpadding=3D0 cellspacing=3D0 w=
idth=3D"100%" class=3Dwidget-button style=3D"width:100%;"><tr><td align=3Dc=
enter style=3D"padding:10px 0px 20px;"><!--[if !mso]--><div style=3D"displa=
y:inline-block;"><a target=3D_blank style=3D"display:inline-block;max-width=
:100%;box-sizing:border-box;text-size-adjust:none;word-break:break-word;tex=
t-decoration:none;font-size:14px;color:#000000;background-color:#f6244a;bor=
der-radius:5px;border-color:#000000;border-style:none;border-width:0px;padd=
ing:14px 30px;" href=3D"https://gog.salesmanago.com/smrd.htm?url=3Dhttps%3A=
%2F%2Fwww.gog.com%2Fen%2Fgame%2Fsilent_hill_townfall%3Futm_source%3Dsalesma=
nago%26utm_medium%3Demail%26utm_campaign%3D20261009_nl_SILENT_HILL_TOWNFALL=
_exclusive_DE&amp;track=3DXxxXXX00XXxxXxXXXXxxXXXXXXxxXXXXXXXXXxXxXXxXXXX0X=
XxXXx0XXX0XXXX0XXXxXXXxXXxXXXXXXXxXXxXXXXXxXXXxXXXXXXXXXXXxXXXXXXxxXx0xXXxx=
XX00XXXxXxXxXXxxxXXXXXxxxXXXXXXxXXX0XXXxxXXXXXxxXxXxXXxxXX0XXXXxx0XXXXxxXXX=
XXXXxXXXXXXXXXXXxXXxxXXXXXXxXXX00XXxxXxX0XXxxXXXxXXx" id=3Dsmlid_390><p dir=
=3Dauto style=3D"font-size:inherit;text-align:center;margin:0px;word-break:=
break-word;color:#ffffff;font-family:Arial, Helvetica, sans-serif;"><span s=
tyle=3D"color:#ffffff;font-family:Lato, Arial, Helvetica, sans-serif;font-s=
ize:16px;"><strong>JETZT KAUFEN</strong></span></p></a></div><!--[endif]-->=
<!--[if mso]><o:anchorlock/><v:roundrect xmlns:v=3D"urn:schemas-microsoft-c=
om:vml" xmlns:w=3D"urn:schemas-microsoft-com:office:word" href=3D"https://g=
og.salesmanago.com/smrd.htm?url=3Dhttps%3A%2F%2Fwww.gog.com%2Fen%2Fgame%2Fs=
ilent_hill_townfall%3Futm_source%3Dsalesmanago%26utm_medium%3Demail%26utm_c=
ampaign%3D20261009_nl_SILENT_HILL_TOWNFALL_exclusive_DE&track=3DXxxXXX00XXx=
xXxXXXXxxXXXXXXxxXXXXXXXXXxXxXXxXXXX0XXxXXx0XXX0XXXX0XXXxXXXxXXxXXXXXXXxXXx=
XXXXXxXXXxXXXXXXXXXXXxXXXXXXxxXx0xXXxxXX00XXXxXxXxXXxxxXXXXXxxxXXXXXXxXXX0X=
XXxxXXXXXxxXxXxXXxxXX0XXXXxx0XXXXxxXXXXXXXxXXXXXXXXXXXxXXxxXXXXXXxXXX00XXxx=
XxX0XXxxXXXXXX0XXx" style=3D"v-text-anchor:middle;height:43pt;width:129pt;"=
stroke=3Df arcsize=3D"10%" fillcolor=3D"#f6244a"><w:anchorlock/><v:textbox=
inset=3D"0,0,0,0"><table role=3Dpresentation cellpadding=3D0 cellspacing=
=3D0 border=3D0 width=3D"100%" height=3D"100%"><tr><td style=3D"font-size:1=
4px;word-break:break-word;" align=3Dcenter valign=3Dmiddle><p dir=3Dauto st=
yle=3D"font-size:inherit;text-align:center;margin:0px;word-break:break-word=
;color:#ffffff;font-family:Arial, Helvetica, sans-serif;"><span style=3D"co=
lor:#ffffff;font-family:Lato, Arial, Helvetica, sans-serif;font-size:16px;"=
><strong>JETZT KAUFEN</strong></span></p></td></tr></table><p style=3D"marg=
in:0;mso-hide:all"><o:p xmlns:o=3D"urn:schemas-microsoft-com:office:office"=
>=E2=80=8B =E2=80=8B</o:p></p></v:textbox></v:roundrect><![endif]--></td></=
tr></table></td></tr></table><!--[if !mso]--></div><!--[endif]--><!--[if (g=
te mso 9)|(IE)]></td></tr></table><![endif]--></div></div></td></tr></table=
></td></tr><tr><td align=3Dcenter style=3D"padding:0px;"><table role=3Dpres=
entation border=3D0 cellpadding=3D0 cellspacing=3D0 height=3D"100%" width=
=3D"100%" style=3D"width:100%;"><tr><td align=3Dcenter><div style=3D"paddin=
g:0;margin:0;font-size:0;width:100%;box-sizing:border-box;" class=3Ddnd-dis=
play-table><div class=3Ddnd-display-table-row><!--[if (gte mso 9)|(IE)]><ta=
ble border=3D0 role=3Dpresentation cellpadding=3D0 cellspacing=3D0 width=3D=
"100%" align=3Dcenter><tr><![endif]--><!--[if (gte mso 9)|(IE)]><td valign=
=3Dtop width=3D"100%" style=3D"background-color:#101422;"><![endif]--><!--[=
if !mso]--><div style=3D"box-sizing:border-box;vertical-align:top;backgroun=
d-color:#101422;border-color:#9a5319;border-style:none;border-width:0px;bor=
der-radius:0px;" class=3D"dnd-width-100-percent dnd-display-table-cell dnd-=
width-100-percent dnd-display-table-cell dnd-width-100-percent dnd-display-=
table-cell"><!--[endif]--><table role=3Dpresentation border=3D0 cellpadding=
=3D0 cellspacing=3D0 width=3D"100%" height=3D"100%" class=3Dbhr-dnd-height =
style=3D"width:100%;"><tr><td class=3Dbhr-dnd-height style=3D"font-size:14p=
x;padding:0px;vertical-align:top;"><table role=3Dpresentation border=3D0 ce=
llpadding=3D0 cellspacing=3D0 width=3D"100%" style=3D"width:100%;"><tr><td =
style=3D"font-size:14px;color:#000000;font-family:Arial, sans-serif;padding=
:20px 10px 10px;"><div style=3D"margin:0;padding:0;"><div><p dir=3Dauto sty=
le=3D"font-size:24px;line-height:1.4;text-align:center;margin:0px;word-brea=
k:break-word;"><span style=3D"color:#b5d3d5;font-family:&quot;Open Sans&quo=
t;, sans-serif;font-size:24px;letter-spacing:normal;"><strong>WILLKOMMEN au=
f ST.&nbsp; AMELIA, 1996</strong></span></p></div></div></td></tr></table><=
table role=3Dpresentation border=3D0 cellpadding=3D0 cellspacing=3D0 width=
=3D"100%" style=3D"width:100%;"><tr><td align=3Dcenter style=3D"padding:0px=
;"><a target=3D_blank style=3D"display:block;width:100%;max-width:566px;" i=
d=3Dsmlid_380 href=3D"https://gog.salesmanago.com/smrd.htm?url=3Dhttps%3A%2=
F%2Fwww.gog.com%2Fen%2Fgame%2Fsilent_hill_townfall%3Futm_source%3Dsalesmana=
go%26utm_medium%3Demail%26utm_campaign%3D20261009_nl_SILENT_HILL_TOWNFALL_e=
xclusive_DE&amp;track=3DXxxXXX00XXxxXxXXXXxxXXXXXXxxXXXXXXXXXxXxXXxXXXX0XXx=
XXx0XXX0XXXX0XXXxXXXxXXxXXXXXXXxXXxXXXXXxXXXxXXXXXXXXXXXxXXXXXXxxXx0xXXxxXX=
00XXXxXxXxXXxxxXXXXXxxxXXXXXXxXXX0XXXxxXXXXXxxXxXxXXxxXX0XXXXxx0XXXXxxXXXXX=
XXxXXXXXXXXXXXxXXxxXXXXXXxXXX00XXxxXxX0XXxxXXX0XXx"><!--[if !mso]--><img st=
yle=3D"width:100%;display:block;border:0px;max-width:566px;" src=3D"https:/=
/sm-cdn.eu/6mfdjwo6r4n201xm.gif" alt=3D"Tiled collage of a person in a red =
puffer vest and beanie, seen from behind, looking at a dimly lit hotel fa=
=C3=A7ade at night"><!--[endif]--><!--[if mso]><img src=3D"https://sm-cdn.e=
u/6mfdjwo6r4n201xm.gif" style=3D"display:block;margin:0;" width=3D566 borde=
r=3D0 alt=3D"Tiled collage of a person in a red puffer vest and beanie, see=
n from behind, looking at a dimly lit hotel fa=C3=A7ade at night"/><![endif=
]--></a></td></tr></table><table role=3Dpresentation border=3D0 cellpadding=
=3D0 cellspacing=3D0 width=3D"100%" style=3D"width:100%;"><tr><td style=3D"=
font-size:14px;color:#000000;font-family:Arial, sans-serif;padding:10px 40p=
x 0px;"><div style=3D"margin:0;padding:0;"><div><p dir=3Dauto style=3D"font=
-size:16px;text-align:center;margin:0px;word-break:break-word;"><span style=
=3D"color:#f2f2f2;font-family:&quot;Open Sans&quot;, sans-serif;font-size:1=
6px;letter-spacing:normal;">=E2=80=8BEs ist 1996, du kommst in der abgelege=
nen, vom Nebel verschlungenen Inselstadt vor der K=C3=BCste Schottlands an.=
Erkunde die leeren Stra=C3=9Fen, verlassenen H=C3=A4user und verfallenden =
Wahrzeichen, w=C3=A4hrend Simon Ordell an einen Ort zur=C3=BCckkehrt, der u=
nheimlich vertraut wirkt.</span></p></div></div></td></tr></table><table ro=
le=3Dpresentation border=3D0 cellpadding=3D0 cellspacing=3D0 width=3D"100%"=
style=3D"width:100%;"><tr><td align=3Dcenter style=3D"padding:10px 0px;"><=
a target=3D_blank style=3D"display:block;width:100%;max-width:566px;" id=3D=
smlid_309 href=3D"https://gog.salesmanago.com/smrd.htm?url=3Dhttps%3A%2F%2F=
www.gog.com%2Fen%2Fgame%2Fsilent_hill_townfall%3Futm_source%3Dsalesmanago%2=
6utm_medium%3Demail%26utm_campaign%3D20261009_nl_SILENT_HILL_TOWNFALL_exclu=
sive_DE&amp;track=3DXxxXXX00XXxxXxXXXXxxXXXXXXxxXXXXXXXXXxXxXXxXXXX0XXxXXx0=
XXX0XXXX0XXXxXXXxXXxXXXXXXXxXXxXXXXXxXXXxXXXXXXXXXXXxXXXXXXxxXx0xXXxxXX00XX=
XxXxXxXXxxxXXXXXxxxXXXXXXxXXX0XXXxxXXXXXxxXxXxXXxxXX0XXXXxx0XXXXxxXXXXXXXxX=
XXXXXXXXXXxXXxxXXXXXXxXXX00XXxxXxX0XXxxXXXXXXx"><!--[if !mso]--><img style=
=3D"width:100%;display:block;border:0px;max-width:566px;" src=3D"https://sm=
-cdn.eu/p4ocxtthair6i8ex.gif" alt=3D"Collage of dark horror film stills sho=
wing a bloodied humanoid creature lunging in a dim room with red-lit window=
s"><!--[endif]--><!--[if mso]><img src=3D"https://sm-cdn.eu/p4ocxtthair6i8e=
x.gif" style=3D"display:block;margin:0;" width=3D566 border=3D0 alt=3D"Coll=
age of dark horror film stills showing a bloodied humanoid creature lunging=
in a dim room with red-lit windows"/><![endif]--></a></td></tr></table><ta=
ble role=3Dpresentation border=3D0 cellpadding=3D0 cellspacing=3D0 width=3D=
"100%" style=3D"width:100%;"><tr><td style=3D"font-size:14px;color:#000000;=
font-family:Arial, sans-serif;padding:4px 40px 16px;"><div style=3D"margin:=
0;padding:0;"><div><p dir=3Dauto style=3D"font-size:16px;text-align:center;=
margin:0px;word-break:break-word;"><span style=3D"color:#f2f2f2;font-family=
:&quot;Open Sans&quot;, sans-serif;font-size:16px;letter-spacing:normal;">D=
ie Stadt mag verlassen wirken, aber du bist alles andere als allein. Mit be=
grenzten Waffen und Werkzeugen bedeutet =C3=9Cberleben zu wissen, wann du d=
ich versteckst, wann du rennst und wann du k=C3=A4mpfst, wenn Schrecken aus=
der Stille auftauchen.</span></p></div></div></td></tr></table><table role=
=3Dpresentation border=3D0 cellpadding=3D0 cellspacing=3D0 width=3D"100%" s=
tyle=3D"width:100%;"><tr><td class=3Ddnd-hide-mobile align=3Dcenter style=
=3D"display:none;padding:0px 0px 10px;"><a target=3D_blank style=3D"display=
:block;width:100%;max-width:566px;" id=3Dsmlid_381 href=3D"https://gog.sale=
smanago.com/smrd.htm?url=3Dhttps%3A%2F%2Fwww.gog.com%2Fen%2Fgame%2Fsilent_h=
ill_townfall%3Futm_source%3Dsalesmanago%26utm_medium%3Demail%26utm_campaign=
%3D20261009_nl_SILENT_HILL_TOWNFALL_exclusive_DE&amp;track=3DXxxXXX00XXxxXx=
XXXXxxXXXXXXxxXXXXXXXXXxXxXXxXXXX0XXxXXx0XXX0XXXX0XXXxXXXxXXxXXXXXXXxXXxXXX=
XXxXXXxXXXXXXXXXXXxXXXXXXxxXx0xXXxxXX00XXXxXxXxXXxxxXXXXXxxxXXXXXXxXXX0XXXx=
xXXXXXxxXxXxXXxxXX0XXXXxx0XXXXxxXXXXXXXxXXXXXXXXXXXxXXxxXXXXXXxXXX00XXxxXxX=
0XXxxXXXXXXx"><!--[if !mso]--><img style=3D"width:100%;display:block;border=
:0px;max-width:566px;" src=3D"https://sm-cdn.eu/6pfhlu8a02h59fut.gif" alt=
=3D"Repeated image grid of a lone person in a red jacket approaching a fogg=
y town square monument with protest placards"><!--[endif]--><!--[if mso]><i=
mg src=3D"https://sm-cdn.eu/6pfhlu8a02h59fut.gif" style=3D"display:block;ma=
rgin:0;" width=3D566 border=3D0 alt=3D"Repeated image grid of a lone person=
in a red jacket approaching a foggy town square monument with protest plac=
ards"/><![endif]--></a></td></tr></table><table role=3Dpresentation border=
=3D0 cellpadding=3D0 cellspacing=3D0 width=3D"100%" style=3D"width:100%;"><=
tr><td style=3D"font-size:14px;color:#000000;font-family:Arial, sans-serif;=
padding:4px 40px 16px;"><div style=3D"margin:0;padding:0;"><div><p dir=3Dau=
to style=3D"font-size:16px;text-align:center;margin:0px;word-break:break-wo=
rd;color:#f2f2f2;letter-spacing:normal;font-family:&quot;Open Sans&quot;, s=
ans-serif;"><span style=3D"color:#f2f2f2;font-family:&quot;Open Sans&quot;,=
sans-serif;font-size:16px;letter-spacing:normal;">Das gr=C3=B6=C3=9Fte R=
=C3=A4tsel der Stadt ist, was mit den Menschen passiert ist, die einst dort=
lebten. Folge den Spuren, die sie hinterlassen haben, l=C3=B6se storygetri=
ebene R=C3=A4tsel und finde heraus, warum du zur=C3=BCckgerufen wurdest.</s=
pan></p></div></div></td></tr></table><table role=3Dpresentation border=3D0=
cellpadding=3D0 cellspacing=3D0 width=3D"100%" class=3Dwidget-button style=
=3D"width:100%;"><tr><td align=3Dcenter style=3D"padding:0px 0px 20px;"><!-=
-[if !mso]--><div style=3D"display:inline-block;"><a target=3D_blank style=
=3D"display:inline-block;max-width:100%;box-sizing:border-box;text-size-adj=
ust:none;word-break:break-word;text-decoration:none;font-size:14px;color:#0=
00000;background-color:#f6244a;border-radius:5px;border-color:#000000;borde=
r-style:none;border-width:0px;padding:14px 30px;" href=3D"https://gog.sales=
manago.com/smrd.htm?url=3Dhttps%3A%2F%2Fwww.gog.com%2Fen%2Fgame%2Fsilent_hi=
ll_townfall%3Futm_source%3Dsalesmanago%26utm_medium%3Demail%26utm_campaign%=
3D20261009_nl_SILENT_HILL_TOWNFALL_exclusive_DE&amp;track=3DXxxXXX00XXxxXxX=
XXXxxXXXXXXxxXXXXXXXXXxXxXXxXXXX0XXxXXx0XXX0XXXX0XXXxXXXxXXxXXXXXXXxXXxXXXX=
XxXXXxXXXXXXXXXXXxXXXXXXxxXx0xXXxxXX00XXXxXxXxXXxxxXXXXXxxxXXXXXXxXXX0XXXxx=
XXXXXxxXxXxXXxxXX0XXXXxx0XXXXxxXXXXXXXxXXXXXXXXXXXxXXxxXXXXXXxXXX00XXxxXxX0=
XXxxXXXxXXx" id=3Dsmlid_391><p dir=3Dauto style=3D"font-size:inherit;text-a=
lign:center;margin:0px;word-break:break-word;color:#ffffff;font-family:Aria=
l, Helvetica, sans-serif;"><span style=3D"color:#ffffff;font-family:Lato, A=
rial, Helvetica, sans-serif;font-size:16px;"><strong>JETZT KAUFEN</strong><=
/span></p></a></div><!--[endif]--><!--[if mso]><o:anchorlock/><v:roundrect =
xmlns:v=3D"urn:schemas-microsoft-com:vml" xmlns:w=3D"urn:schemas-microsoft-=
com:office:word" href=3D"https://gog.salesmanago.com/smrd.htm?url=3Dhttps%3=
A%2F%2Fwww.gog.com%2Fen%2Fgame%2Fsilent_hill_townfall%3Futm_source%3Dsalesm=
anago%26utm_medium%3Demail%26utm_campaign%3D20261009_nl_SILENT_HILL_TOWNFAL=
L_exclusive_DE&track=3DXxxXXX00XXxxXxXXXXxxXXXXXXxxXXXXXXXXXxXxXXxXXXX0XXxX=
Xx0XXX0XXXX0XXXxXXXxXXxXXXXXXXxXXxXXXXXxXXXxXXXXXXXXXXXxXXXXXXxxXx0xXXxxXX0=
0XXXxXxXxXXxxxXXXXXxxxXXXXXXxXXX0XXXxxXXXXXxxXxXxXXxxXX0XXXXxx0XXXXxxXXXXXX=
XxXXXXXXXXXXXxXXxxXXXXXXxXXX00XXxxXxX0XXxxXXXXXXXXXx" style=3D"v-text-ancho=
r:middle;height:43pt;width:129pt;" stroke=3Df arcsize=3D"10%" fillcolor=3D"=
#f6244a"><w:anchorlock/><v:textbox inset=3D"0,0,0,0"><table role=3Dpresenta=
tion cellpadding=3D0 cellspacing=3D0 border=3D0 width=3D"100%" height=3D"10=
0%"><tr><td style=3D"font-size:14px;word-break:break-word;" align=3Dcenter =
valign=3Dmiddle><p dir=3Dauto style=3D"font-size:inherit;text-align:center;=
margin:0px;word-break:break-word;color:#ffffff;font-family:Arial, Helvetica=
, sans-serif;"><span style=3D"color:#ffffff;font-family:Lato, Arial, Helvet=
ica, sans-serif;font-size:16px;"><strong>JETZT KAUFEN</strong></span></p></=
td></tr></table><p style=3D"margin:0;mso-hide:all"><o:p xmlns:o=3D"urn:sche=
mas-microsoft-com:office:office">=E2=80=8B =E2=80=8B</o:p></p></v:textbox><=
/v:roundrect><![endif]--></td></tr></table><table role=3Dpresentation width=
=3D"100%" border=3D0 cellspacing=3D0 cellpadding=3D0 style=3D"width:100%;">=
<tr><td valign=3Dtop align=3Dcenter style=3D"padding:0px 0px 10px;"><!--[if=
(gte mso 9)|(IE)]><table role=3Dpresentation border=3D0 cellpadding=3D0 ce=
llspacing=3D0 width=3D"50%"><tr><td><![endif]--><table role=3Dpresentation =
border=3D0 cellspacing=3D0 cellpadding=3D0 style=3D"width:100%;max-width:50=
%;" width=3D50 align=3Dcenter><tr><td style=3D"height:1px;border-bottom:2px=
solid #f6244a;"></td></tr></table><!--[if (gte mso 9)|(IE)]></td></tr></ta=
ble><![endif]--></td></tr></table><table role=3Dpresentation border=3D0 cel=
lpadding=3D0 cellspacing=3D0 width=3D"100%" style=3D"width:100%;"><tr><td s=
tyle=3D"font-size:14px;color:#000000;font-family:Arial, sans-serif;padding:=
4px 40px 0px;"><div style=3D"margin:0;padding:0;"><div><p dir=3Dauto style=
=3D"font-size:16px;text-align:center;margin:0px;word-break:break-word;font-=
family:Arial, Helvetica, sans-serif;"><span style=3D"color:#f2f2f2;font-fam=
ily:Arial, Helvetica, sans-serif;font-size:16px;letter-spacing:normal;">=E2=
=80=8BHole dir die&nbsp;</span><span style=3D"color:#f6244a;font-family:Ari=
al, Helvetica, sans-serif;font-size:16px;letter-spacing:normal;"><strong>De=
luxe Edition</strong></span><span style=3D"color:#ffffff;font-family:Arial,=
Helvetica, sans-serif;font-size:16px;">=E2=80=8B mit dem digitalen Artbook=
, dem Soundtrack und einem alternativen Outfit f=C3=BCr Simon und tauche no=
ch tiefer in die Welt des Spiels ein.</span></p></div></div></td></tr></tab=
le><table role=3Dpresentation border=3D0 cellpadding=3D0 cellspacing=3D0 wi=
dth=3D"100%" class=3Dwidget-button style=3D"width:100%;"><tr><td align=3Dce=
nter style=3D"padding:20px 0px 10px;"><!--[if !mso]--><div style=3D"display=
:inline-block;"><a target=3D_blank style=3D"display:inline-block;max-width:=
100%;box-sizing:border-box;text-size-adjust:none;word-break:break-word;text=
-decoration:none;font-size:14px;color:#000000;background-color:#b69a53;bord=
er-radius:5px;border-color:#000000;border-style:none;border-width:0px;paddi=
ng:14px 30px;" href=3D"https://gog.salesmanago.com/smrd.htm?url=3Dhttps%3A%=
2F%2Fwww.gog.com%2Fen%2Fgame%2Fsilent_hill_townfall_deluxe_edition%3Futm_so=
urce%3Dsalesmanago%26utm_medium%3Demail%26utm_campaign%3D20261009_nl_SILENT=
_HILL_TOWNFALL_exclusive_DE&amp;track=3DXxxXXX00XXxxXxXXXXxxXXXXXXxxXXXXXXX=
XXxXxXXxXXXX0XXxXXx0XXX0XXXX0XXXxXXXxXXxXXXXXXXxXXxXXXXXxXXXxXXXXXXXXXXXxXX=
XXXXxxXx0xXXxxXX00XXXxXxXxXXxxxXXXXXxxxXXXXXXxXXX0XXXxxXXXXXxxXxXxXXxxXX0XX=
XXxx0XXXXxxXXXXXXXxXXXXXXXXXXXxXXxxXXXXXXxXXX00XXxxXxX0XXxxXXX0XXx" id=3Dsm=
lid_392><p dir=3Dauto style=3D"font-size:inherit;text-align:center;margin:0=
px;word-break:break-word;color:#000000;font-family:Arial, Helvetica, sans-s=
erif;"><span style=3D"color:#000000;font-family:Lato, Arial, Helvetica, san=
s-serif;font-size:16px;"><strong>TAUCHE TIEFER EIN</strong></span></p></a><=
/div><!--[endif]--><!--[if mso]><o:anchorlock/><v:roundrect xmlns:v=3D"urn:=
schemas-microsoft-com:vml" xmlns:w=3D"urn:schemas-microsoft-com:office:word=
" href=3D"https://gog.salesmanago.com/smrd.htm?url=3Dhttps%3A%2F%2Fwww.gog.=
com%2Fen%2Fgame%2Fsilent_hill_townfall_deluxe_edition%3Futm_source%3Dsalesm=
anago%26utm_medium%3Demail%26utm_campaign%3D20261009_nl_SILENT_HILL_TOWNFAL=
L_exclusive_DE&track=3DXxxXXX00XXxxXxXXXXxxXXXXXXxxXXXXXXXXXxXxXXxXXXX0XXxX=
Xx0XXX0XXXX0XXXxXXXxXXxXXXXXXXxXXxXXXXXxXXXxXXXXXXXXXXXxXXXXXXxxXx0xXXxxXX0=
0XXXxXxXxXXxxxXXXXXxxxXXXXXXxXXX0XXXxxXXXXXxxXxXxXXxxXX0XXXXxx0XXXXxxXXXXXX=
XxXXXXXXXXXXXxXXxxXXXXXXxXXX00XXxxXxX0XXxxXXXXXXxXXx" style=3D"v-text-ancho=
r:middle;height:43pt;width:156pt;" stroke=3Df arcsize=3D"10%" fillcolor=3D"=
#b69a53"><w:anchorlock/><v:textbox inset=3D"0,0,0,0"><table role=3Dpresenta=
tion cellpadding=3D0 cellspacing=3D0 border=3D0 width=3D"100%" height=3D"10=
0%"><tr><td style=3D"font-size:14px;word-break:break-word;" align=3Dcenter =
valign=3Dmiddle><p dir=3Dauto style=3D"font-size:inherit;text-align:center;=
margin:0px;word-break:break-word;color:#000000;font-family:Arial, Helvetica=
, sans-serif;"><span style=3D"color:#000000;font-family:Lato, Arial, Helvet=
ica, sans-serif;font-size:16px;"><strong>TAUCHE TIEFER EIN</strong></span><=
/p></td></tr></table><p style=3D"margin:0;mso-hide:all"><o:p xmlns:o=3D"urn=
:schemas-microsoft-com:office:office">=E2=80=8B =E2=80=8B</o:p></p></v:text=
box></v:roundrect><![endif]--></td></tr></table><table role=3Dpresentation =
border=3D0 cellpadding=3D0 cellspacing=3D0 width=3D"100%" style=3D"width:10=
0%;"><tr><td align=3Dcenter style=3D"padding:10px 0px 24px;"><a target=3D_b=
lank style=3D"display:block;width:100%;max-width:566px;" id=3Dsmlid_393 hre=
f=3D"https://gog.salesmanago.com/smrd.htm?url=3Dhttps%3A%2F%2Fwww.gog.com%2=
Fen%2Fgame%2Fsilent_hill_townfall_deluxe_edition%3Futm_source%3Dsalesmanago=
%26utm_medium%3Demail%26utm_campaign%3D20261009_nl_SILENT_HILL_TOWNFALL_exc=
lusive_DE&amp;track=3DXxxXXX00XXxxXxXXXXxxXXXXXXxxXXXXXXXXXxXxXXxXXXX0XXxXX=
x0XXX0XXXX0XXXxXXXxXXxXXXXXXXxXXxXXXXXxXXXxXXXXXXXXXXXxXXXXXXxxXx0xXXxxXX00=
XXXxXxXxXXxxxXXXXXxxxXXXXXXxXXX0XXXxxXXXXXxxXxXxXXxxXX0XXXXxx0XXXXxxXXXXXXX=
xXXXXXXXXXXXxXXxxXXXXXXxXXX00XXxxXxX0XXxxXXXXXXx"><!--[if !mso]--><img styl=
e=3D"width:100%;display:block;border:0px;max-width:566px;" src=3D"https://s=
m-cdn.eu/201rzlcno5wr6skh.png" alt=3D"Silent Hill: Townfall Deluxe Edition =
digital promo showing game box, artbook, soundtrack and alternate outfit fo=
r Simon"><!--[endif]--><!--[if mso]><img src=3D"https://sm-cdn.eu/201rzlcno=
5wr6skh.png" style=3D"display:block;margin:0;" width=3D566 border=3D0 alt=
=3D"Silent Hill: Townfall Deluxe Edition digital promo showing game box, ar=
tbook, soundtrack and alternate outfit for Simon"/><![endif]--></a></td></t=
r></table></td></tr></table><!--[if !mso]--></div><!--[endif]--><!--[if (gt=
e mso 9)|(IE)]></td></tr></table><![endif]--></div></div></td></tr></table>=
</td></tr><tr><td align=3Dcenter style=3D"padding:20px 0px 0px;"><table rol=
e=3Dpresentation border=3D0 cellpadding=3D0 cellspacing=3D0 height=3D"100%"=
width=3D"100%" style=3D"width:100%;"><tr><td align=3Dcenter><div style=3D"=
padding:0;margin:0;font-size:0;width:100%;box-sizing:border-box;" class=3Dd=
nd-display-table><div class=3Ddnd-display-table-row><!--[if (gte mso 9)|(IE=
)]><table border=3D0 role=3Dpresentation cellpadding=3D0 cellspacing=3D0 wi=
dth=3D"100%" align=3Dcenter><tr><![endif]--><!--[if (gte mso 9)|(IE)]><td v=
align=3Dtop width=3D"100%" style=3D"background-color:#111218;"><![endif]-->=
<!--[if !mso]--><div style=3D"box-sizing:border-box;vertical-align:top;back=
ground-color:#111218;border-color:#000000;border-style:none;border-width:0p=
x;border-radius:0px;" class=3D"dnd-width-100-percent dnd-display-table-cell=
dnd-width-100-percent dnd-display-table-cell dnd-width-100-percent dnd-dis=
play-table-cell"><!--[endif]--><table role=3Dpresentation border=3D0 cellpa=
dding=3D0 cellspacing=3D0 width=3D"100%" height=3D"100%" class=3Dbhr-dnd-he=
ight style=3D"width:100%;"><tr><td class=3Dbhr-dnd-height style=3D"font-siz=
e:14px;padding:0px;vertical-align:top;"><table role=3Dpresentation border=
=3D0 cellpadding=3D0 cellspacing=3D0 width=3D"100%" style=3D"width:100%;"><=
tr><td align=3Dcenter style=3D"padding:0px;"><a target=3D_blank style=3D"di=
splay:block;width:100%;" id=3Dsmlid_394 href=3D"https://gog.salesmanago.com=
/smrd.htm?url=3Dhttps%3A%2F%2Fwww.gog.com%3Futm_source%3Dsalesmanago%26utm_=
medium%3Demail%26utm_campaign%3D20261009_nl_SILENT_HILL_TOWNFALL_exclusive_=
DE&amp;track=3DXxxXXX00XXxxXxXXXXxxXXXXXXxxXXXXXXXXXxXxXXxXXXX0XXxXXx0XXX0X=
XXX0XXXxXXXxXXxXXXXXXXxXXxXXXXXxXXXxXXXXXXXXXXXxXXXXXXxxXx0xXXxxXX00XXXxXxX=
xXXxxxXXXXXxxxXXXXXXxXXX0XXXxxXXXXXxxXxXxXXxxXX0XXXXxx0XXXXxxXXXXXXXxXXXXXX=
XXXXXxXXxxXXXXXXxXXX00XXxxXxX0XXxxXXXxXXx"><!--[if !mso]--><img style=3D"wi=
dth:100%;display:block;border:0;" src=3D"https://storage.googleapis.com/s-i=
mg/tuo7k81ot2k2vcfp.png" alt=3D"Was es bedeutet, auf GOG zu kaufen / Spiele=
deine Spiele offline / Bis zu 30 Tage / R=C3=BCckerstattung Die Erhaltung =
von Videospielen" class=3Ddnd-max-width-420-px><!--[endif]--><!--[if mso]><=
img src=3D"https://storage.googleapis.com/s-img/tuo7k81ot2k2vcfp.png" style=
=3D"display:block;margin:0;" width=3D600 border=3D0 alt=3D"Was es bedeutet,=
auf GOG zu kaufen / Spiele deine Spiele offline / Bis zu 30 Tage / R=C3=BC=
ckerstattung Die Erhaltung von Videospielen"/><![endif]--></a></td></tr></t=
able><table role=3Dpresentation border=3D0 cellpadding=3D0 cellspacing=3D0 =
width=3D"100%" style=3D"width:100%;"><tr><td style=3D"font-size:14px;color:=
#000000;font-family:Arial, sans-serif;padding:0px 30px 20px;"><div style=3D=
"margin:0;padding:0;"><div><p dir=3Dauto style=3D"font-size:14px;line-heigh=
t:1.15;text-align:center;margin:0px;word-break:break-word;color:#333333;let=
ter-spacing:normal;font-family:Arial, Helvetica, sans-serif, Helvetica, san=
s-serif;"><a target=3D_blank rel=3D"noopener noreferrer nofollow" href=3D"h=
ttps://gog.salesmanago.com/email/messageView.htm?conversation=3D000xx0x0-0x=
0x-0000-00xx-xx0xxxx00x0x&amp;co=3D0x0000xx-x00x-0x0x-00x0-x0000xxxx000&amp=
;smlid=3D0" data-id=3Dsmlid_396 style=3D"text-decoration:none;color:#333333=
;"><span style=3D"color:#aec6ff;font-family:&quot;Open Sans&quot;, sans-ser=
if;font-size:10px;background-color:transparent;letter-spacing:normal;"><u>P=
robleme bei der Darstellung dieser Email? Klick bitte hier.</u></span></a><=
/p><p dir=3Dauto style=3D"font-size:inherit;text-align:left;margin:0px;word=
-break:break-word;"><a target=3D_blank rel=3D"noopener noreferrer nofollow"=
href=3D"https://gog.salesmanago.com/email/messageView.htm?conversation=3D0=
00xx0x0-0x0x-0000-00xx-xx0xxxx00x0x&amp;co=3D0x0000xx-x00x-0x0x-00x0-x0000x=
xxx000&amp;smlid=3D0" data-id=3Dsmlid_395 style=3D"text-decoration:none;col=
or:#333333;"><span style=3D"color:#94a3b8;font-family:&quot;Open Sans&quot;=
, sans-serif;font-size:10px;background-color:transparent;letter-spacing:nor=
mal;"><u>=E2=80=8B</u></span></a></p><p dir=3Dauto style=3D"font-size:10px;=
text-align:center;margin:0px;word-break:break-word;color:#94a3b8;letter-spa=
cing:normal;font-family:&quot;Open Sans&quot;, sans-serif, Helvetica, sans-=
serif;"><span style=3D"color:#94a3b8;font-family:&quot;Open Sans&quot;, san=
s-serif;font-size:10px;letter-spacing:normal;">Du erh=C3=A4ltst diese Email=
, weil sich deine Email-Adresse auf unserer Empf=C3=A4ngerliste befindet.&n=
bsp;Wenn du keine Emails mehr von uns erhalten m=C3=B6chtest, kannst du dic=
h </span><a target=3D_blank rel=3D"noopener noreferrer nofollow" href=3D"ht=
tps://gog.salesmanago.com/smrd.htm?url=3Dhttps%3A%2F%2Fwww.gog.com%2Funsubs=
cribe%2F0XXxXXXXx0xX0X0xxXXX0Xx0%2Fpromo%3Futm_source%3Dsalesmanago%26utm_m=
edium%3Demail%26utm_campaign%3D20261009_nl_SILENT_HILL_TOWNFALL_exclusive_D=
E&amp;track=3DXxxXXX00XXxxXxXXXXxxXXXXXXxxXXXXXXXXXxXxXXxXXXX0XXxXXx0XXX0XX=
XX0XXXxXXXxXXxXXXXXXXxXXxXXXXXxXXXxXXXXXXXXXXXxXXXXXXxxXx0xXXxxXX00XXXxXxXx=
XXxxxXXXXXxxxXXXXXXxXXX0XXXxxXXXXXxxXxXxXXxxXX0XXXXxx0XXXXxxXXXXXXXxXXXXXXX=
XXXXxXXxxXXXXXXxXXX00XXxxXxX0XXxxXXXXXXXXXx" data-id=3Dsmlid_397 style=3D"t=
ext-decoration:none;color:#333333;"><span style=3D"color:#aec6ff;font-famil=
y:&quot;Open Sans&quot;, sans-serif;font-size:10px;letter-spacing:normal;">=
<u>hier abmelden</u></span></a><span style=3D"color:#aec6ff;font-family:&qu=
ot;Open Sans&quot;, sans-serif;font-size:10px;letter-spacing:normal;">.&nbs=
p;</span></p><p dir=3Dauto style=3D"font-size:10px;text-align:center;margin=
:0px;word-break:break-word;color:#94a3b8;letter-spacing:normal;font-family:=
&quot;Open Sans&quot;, sans-serif, Helvetica, sans-serif;"><span style=3D"c=
olor:#94a3b8;font-family:&quot;Open Sans&quot;, sans-serif;font-size:10px;l=
etter-spacing:normal;">=E2=80=8B</span></p><p dir=3Dauto style=3D"font-size=
:10px;text-align:center;margin:0px;word-break:break-word;color:#94a3b8;lett=
er-spacing:normal;font-family:&quot;Open Sans&quot;, sans-serif, Helvetica,=
sans-serif;"><span style=3D"color:#94a3b8;font-family:&quot;Open Sans&quot=
;, sans-serif;font-size:10px;letter-spacing:normal;">Diese Email dient ledi=
glich der Information und unterbreitet nicht das Angebot. Preise und Angebo=
te k=C3=B6nnen sich =C3=A4ndern.&nbsp;Weitere Informationen zu den einzelne=
n Angeboten finden sich auf </span><a target=3D_blank rel=3D"noopener noref=
errer nofollow" href=3D"https://gog.salesmanago.com/smrd.htm?url=3Dhttps%3A=
%2F%2Fwww.gog.com%3Futm_source%3Dsalesmanago%26utm_medium%3Demail%26utm_cam=
paign%3D20261009_nl_SILENT_HILL_TOWNFALL_exclusive_DE&amp;track=3DXxxXXX00X=
XxxXxXXXXxxXXXXXXxxXXXXXXXXXxXxXXxXXXX0XXxXXx0XXX0XXXX0XXXxXXXxXXxXXXXXXXxX=
XxXXXXXxXXXxXXXXXXXXXXXxXXXXXXxxXx0xXXxxXX00XXXxXxXxXXxxxXXXXXxxxXXXXXXxXXX=
0XXXxxXXXXXxxXxXxXXxxXX0XXXXxx0XXXXxxXXXXXXXxXXXXXXXXXXXxXXxxXXXXXXxXXX00XX=
xxXxX0XXxxXXXXXXxXXx" data-id=3Dsmlid_399 style=3D"text-decoration:none;col=
or:#333333;"><span style=3D"color:#94a3b8;font-family:&quot;Open Sans&quot;=
, sans-serif;font-size:10px;letter-spacing:normal;">GOG.COM.</span></a><spa=
n style=3D"color:#94a3b8;font-family:&quot;Open Sans&quot;, sans-serif;font=
-size:10px;letter-spacing:normal;">&nbsp;</span></p><p dir=3Dauto style=3D"=
font-size:10px;text-align:center;margin:0px;word-break:break-word;color:#94=
a3b8;letter-spacing:normal;font-family:&quot;Open Sans&quot;, sans-serif, H=
elvetica, sans-serif;"><span style=3D"color:#94a3b8;font-family:&quot;Open =
Sans&quot;, sans-serif;font-size:10px;letter-spacing:normal;">&nbsp;</span>=
</p><p dir=3Dauto style=3D"font-size:10px;text-align:center;margin:0px;word=
-break:break-word;color:#94a3b8;letter-spacing:normal;font-family:&quot;Ope=
n Sans&quot;, sans-serif, Helvetica, sans-serif;"><a target=3D_blank rel=3D=
"noopener noreferrer nofollow" href=3D"https://gog.salesmanago.com/smrd.htm=
?url=3Dhttps%3A%2F%2Fwww.gog.com%3Futm_source%3Dsalesmanago%26utm_medium%3D=
email%26utm_campaign%3D20261009_nl_SILENT_HILL_TOWNFALL_exclusive_DE&amp;tr=
ack=3DXxxXXX00XXxxXxXXXXxxXXXXXXxxXXXXXXXXXxXxXXxXXXX0XXxXXx0XXX0XXXX0XXXxX=
XXxXXxXXXXXXXxXXxXXXXXxXXXxXXXXXXXXXXXxXXXXXXxxXx0xXXxxXX00XXXxXxXxXXxxxXXX=
XXxxxXXXXXXxXXX0XXXxxXXXXXxxXxXxXXxxXX0XXXXxx0XXXXxxXXXXXXXxXXXXXXXXXXXxXXx=
xXXXXXXxXXX00XXxxXxX0XXxxXXXXXXXXXx" data-id=3Dsmlid_398 style=3D"text-deco=
ration:none;color:#333333;"><span style=3D"color:#94a3b8;font-family:&quot;=
Open Sans&quot;, sans-serif;font-size:10px;letter-spacing:normal;">GOG.COM =
=C2=A9 GOG sp. z o.o., ul. Jagiello=C5=84ska 74, 03-301 Warschau, Polen.</s=
pan></a><span style=3D"color:#94a3b8;font-family:&quot;Open Sans&quot;, san=
s-serif;font-size:10px;letter-spacing:normal;">&nbsp;Alle anderen Marken si=
nd Eigentum ihrer jeweiligen Inhaber.</span></p></div></div></td></tr></tab=
le></td></tr></table><!--[if !mso]--></div><!--[endif]--><!--[if (gte mso 9=
)|(IE)]></td></tr></table><![endif]--></div></div></td></tr></table></td></=
tr></table></td></tr></table><!--[if !mso]--></div><!--[endif]--><!--[if (g=
te mso 9)|(IE)]></td></tr></table><![endif]--></td></tr></table></td><td wi=
dth=3D0 style=3D"width:0px;"></td></tr><tr><td align=3Dcenter colspan=3D3 s=
tyle=3D"padding:0px;margin:0px;height:0px;" height=3D0></td></tr></table><!=
--[if (gte mso 9)|(IE)]></td></tr></table><![endif]--></td></tr></tbody></t=
able></center><a href=3D"https://gog.manago.ai/bot.htm?track=3DXxxXXX00XXxx=
XxXXXXxxXXXXXXxxXXXXXXXXXxXxXXxXXXX0XXxXXx0XXX0XXXX0XXXxXXXxXXxXXXXXXXxXXxX=
XXXXxXXXxXXXXXXXXXXXxXXXXXXxxXx0xXXxxXX00XXXxXxXxXXxxxXXXXXxxxXXXXXXxXXX0XX=
XxxXXXXXxxXxXxXXxxXX0XXXXxx0XXXXxxXXXXXXXxXXXXXXXXXXXxXXxxXXXXXXxXXX00XXx" =
style=3D"display:block;position:absolute;width:1px;height:1px;"></a></body>=
</html>
------=_Part_4135371_1142483364.1791540649541--
-3
View File
@@ -1,3 +0,0 @@
# the text is in the HTML part only; the plain-text part is a one-line placeholder
Silent Hill: Townfall
JETZT KAUFEN
Binary file not shown.
File diff suppressed because it is too large. Load diff
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
# the bare formula text: it is in a .formulas.tsv only, not in derived.md and not in a cell TSV
SUM(Sales!L6:L29)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,120 +0,0 @@
"""`wikitool raw derive` against the real documents in `testfiles/`.
The synthetic fixtures in `binary_formats.py` and `test_raw_derive.py` prove the filters on
documents built to be easy; the files in `testfiles/` are whatever the world produced. Every file
there is derived, compared with itself on a second run and accepted into `raw/`, and an optional
`<file>.expect` next to it names the text that must come out. A distributed instance has no
`testfiles/`, so the whole module is then skipped.
"""
from __future__ import annotations
import importlib.util
import shutil
from pathlib import Path
import pytest
from chemenu import filters
from chemenu.commands.raw.accept import raw_accept_command
from chemenu.tests.test_raw_derive import ( # noqa: F401 (tree is a fixture)
_derive,
_header_and_text,
tree,
)
TESTFILES = Path(__file__).resolve().parents[3] / "testfiles"
SIDECAR = ".expect"
pytestmark = pytest.mark.skipif(
importlib.util.find_spec("pypdf") is None or importlib.util.find_spec("olefile") is None,
reason="pypdf and olefile are needed for real PDFs, MSGs and archives holding them",
)
def _documents() -> list[Path]:
if not TESTFILES.is_dir():
return []
return sorted(
path
for path in TESTFILES.rglob("*")
if path.is_file()
and not path.name.startswith(".")
and path.name != "README.md"
and path.suffix != SIDECAR
)
def _id(path: Path) -> str:
return path.relative_to(TESTFILES).as_posix()
def _texts(folder: Path) -> dict[str, str]:
texts = {}
for md in sorted(folder.rglob("*.md")):
lines = md.read_text(encoding="utf-8").splitlines()
texts[md.relative_to(folder).as_posix()] = "\n".join(
line for line in lines if not line.startswith("derived:")
)
for tsv in sorted(folder.rglob("*.sheets/*.tsv")):
texts[tsv.relative_to(folder).as_posix()] = tsv.read_text(encoding="utf-8")
return texts
def _expected(document: Path) -> list[str]:
sidecar = document.with_name(document.name + SIDECAR)
if not sidecar.is_file():
return []
lines = sidecar.read_text(encoding="utf-8").splitlines()
return [line for line in lines if line.strip() and not line.startswith("#")]
def _put_and_derive(tree: Path, document: Path) -> Path:
(tree / "incoming" / document.name).write_bytes(document.read_bytes())
_derive(f"incoming/{document.name}")
return tree / "incoming" / document.stem
@pytest.mark.parametrize("document", _documents(), ids=_id)
def test_a_real_document_derives_stably_and_is_accepted(tree, document):
assert filters.filter_for(document.name) is not None, (
f"no import filter takes {document.name}; a file in testfiles/ must be one `raw derive` reads"
)
folder = _put_and_derive(tree, document)
assert not (tree / "incoming" / document.name).exists()
if document.suffix.lower() == ".zip":
assert any(folder.rglob("*.md")), "an unpacked archive gets a .md beside its documents"
else:
original = folder / f"original{document.suffix.lower()}"
assert original.read_bytes() == document.read_bytes()
header, _text = _header_and_text(folder / "derived.md")
assert header["derived_by"] == "wikitool raw derive"
assert header["original_name"] == document.name
first = _texts(folder)
for needle in _expected(document):
assert any(needle in text for text in first.values()), f"{needle!r} not in the derived text"
shutil.rmtree(folder)
assert _texts(_put_and_derive(tree, document)) == first, "the same bytes gave a different text"
raw_accept_command(
files=[Path(f"incoming/{document.stem}")], fidelity="verbatim", authority="reporting",
page=None, replaces=None, dry_run=False,
)
assert not (tree / "incoming" / document.stem).exists()
def test_texts_covers_the_sheet_tsvs_beside_the_markdown(tmp_path):
(tmp_path / "x.sheets").mkdir()
(tmp_path / "derived.md").write_text("derived: now\nbody\n", encoding="utf-8")
cells = tmp_path / "x.sheets" / "01-a.tsv"
cells.write_text("a\tb\n", encoding="utf-8")
before = _texts(tmp_path)
assert before["derived.md"] == "body"
assert before["x.sheets/01-a.tsv"] == "a\tb\n"
cells.write_text("a\tc\n", encoding="utf-8")
assert _texts(tmp_path) != before