Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc31555625 |
@@ -19,6 +19,91 @@ page as `v0.1.0 · <sha> · <date>`, so what is deployed can always be identifie
|
||||
|
||||
---
|
||||
|
||||
## 0.8.0.16 — 2026-09-20
|
||||
|
||||
The Quickstart put where a tester can actually reach it, the last place that still told the old
|
||||
story about the Interchange, and the release that carries v0.8.0.15's corrected card text into a
|
||||
package.
|
||||
|
||||
### The Quickstart is published beside the game
|
||||
|
||||
v0.8.0.15 wrote a Quickstart for a tester who has never played, and then left it in `docs/`, where
|
||||
a tester does not look — reachable only by someone who already has the repository. Nobody being
|
||||
handed the box has it.
|
||||
|
||||
`build-web.ts` now copies `docs/StationMaster-Quickstart.md` into `dist/quickstart.md`, and the
|
||||
splash page offers it under the three doors: *New to Station Master? Read the Quickstart guide.*
|
||||
**Not a fourth door** — reading the guide is not a way to play, and giving it equal weight in that
|
||||
grid would say it is.
|
||||
|
||||
**Copied, never re-written.** The Markdown document stays the one copy. A hand-written HTML twin
|
||||
would drift from it on the first edit, which is exactly the failure #15a was raised about and
|
||||
exactly what the v0.8.0.15 pass spent its time undoing: four references a month and two minor
|
||||
versions out of date.
|
||||
|
||||
**Served as plain text, which is honest rather than good.** Tables render as rows of pipes and the
|
||||
links do not click. Rendering it into a styled page wants a small Markdown converter and is filed
|
||||
as **TODO #109**; this is the fifteen-minute version that gets the guide in front of testers for
|
||||
this round instead of leaving them without one.
|
||||
|
||||
Two things had to be true and neither is checked by `tsc`, so both are tests:
|
||||
|
||||
- **The link has to find the file.** The href on the splash page and the filename the build writes
|
||||
are two strings with nothing connecting them — rename the document and the build quietly
|
||||
publishes nothing while the page keeps offering a link that 404s. The test asserts the guide is
|
||||
in `dist`, is the guide, and is the file the splash page names. The build also warns loudly
|
||||
rather than silently skipping a missing document.
|
||||
- **A `.md` file must not arrive as a download.** The server's MIME fallback is
|
||||
`application/octet-stream`, which a browser saves instead of displaying, so the link would hand a
|
||||
tester a file rather than a page. `'.md': 'text/plain; charset=utf-8'` was added to the table in
|
||||
`http.ts`, and the test reads that table out of the source rather than asserting on a copy of it,
|
||||
which would pass while the real one was wrong.
|
||||
|
||||
### `sortsCars` says what it gates, not what the card prints
|
||||
|
||||
Asked directly after v0.8.0.15 — does everything now agree? — and the audit turned up one place that
|
||||
did not. The field's own doc comment read:
|
||||
|
||||
```ts
|
||||
/** Interchange: "Sort cars in new order". */
|
||||
sortsCars: boolean;
|
||||
```
|
||||
|
||||
which names the printed text as though it were the flag's meaning. Nothing reads it to permit a
|
||||
sort. Its two readers — `resolveExtraStart` in `apply.ts` and the enumeration in `legal.ts` — both
|
||||
ask the same question, which is whether this is the one Mainline card with a Yard Limit and
|
||||
therefore the one an Extra may be made up and started on.
|
||||
|
||||
**Comment only, and worth the bump because of where it is.** This is what a developer reads before
|
||||
using the flag, and it is the most likely source of the sentence v0.8.0.15 had to correct — the one
|
||||
telling players, on the board, that cars could be sorted at an Interchange. The name is kept for its
|
||||
link to the card face, and the comment now says outright that the name is not the meaning.
|
||||
|
||||
Everything else already agreed, and was checked rather than assumed: `mainlineDescription`, the
|
||||
generated `rules/as-built.md`, the Mainline deck reference and the Rules FAQ all say the printed
|
||||
sorting is unimplemented and that a consist is re-ordered at a Small Yard. `rules/implications.md`
|
||||
is the deliberate exception and was already correct — it transcribes what the card face prints and
|
||||
then records that the concept is "still unimplemented".
|
||||
|
||||
### The reference stamps, and what was filed rather than done
|
||||
|
||||
The five documents v0.8.0.15 stamped — the Quickstart, the Rules, Components, the Home deck and the
|
||||
Mainline deck — plus `docs/design.md` now read **v0.8.0.16**, and the Rules book's implementation
|
||||
note about `card.discard` says the same. They describe this build because the audit above re-checked
|
||||
them against it, not because the number was swept forward: the point of the stamp is that it was
|
||||
earned, and a stamp bumped without a reading is worth less than none.
|
||||
|
||||
**TODO #109** holds the part deliberately not done — rendering the guide into a styled page instead
|
||||
of serving it as plain text. `build-web.ts`'s comment names that number rather than gesturing at
|
||||
"the next step", so the file and the worklist cannot drift apart the way the references just did.
|
||||
|
||||
### Why this is a release at all
|
||||
|
||||
One player-visible change, the link to the guide, and one that only a developer reads. The
|
||||
Interchange correction a player actually sees on the board is v0.8.0.15's; this is the version the
|
||||
wrapper bundles, so that correction reaches the box rather than only the repository — and a tag
|
||||
with an uncommitted comment sitting on top of it is not a thing to package.
|
||||
|
||||
## 0.8.0.15 — 2026-09-20
|
||||
|
||||
The reference documentation brought up to the game as it actually runs, ahead of the next round of
|
||||
|
||||
@@ -582,6 +582,23 @@ What the project says about itself, and what it ships alongside the code.
|
||||
- [ ] **#88** — `card-reference.md`'s industry table may still be stale beyond Grocer's Warehouse and
|
||||
the Oil Refinery. See **Reference · #88**.
|
||||
|
||||
- [ ] **#109** — **Render the published Quickstart instead of serving it as plain text.** v0.8.0.16
|
||||
publishes `docs/StationMaster-Quickstart.md` to `dist/quickstart.md` and links it from the
|
||||
splash page, served as `text/plain` — so a tester reads the guide's tables as rows of pipes and
|
||||
its links do not click. That was the fifteen-minute version, taken deliberately to get the
|
||||
guide in front of testers for this round rather than to leave them without one.
|
||||
|
||||
**Copy the document, do not re-write it.** A hand-written HTML twin drifts from the Markdown on
|
||||
the first edit, which is the whole argument of #15a. The step is a small Markdown-to-HTML
|
||||
converter in `scripts/build-web.ts` writing `quickstart.html` beside the game, styled like the
|
||||
splash page — headings, lists, tables, links and code spans are the whole of what the guide
|
||||
uses. The `.md` MIME entry in `src/server/http.ts` and the two assertions in
|
||||
`test/web.test.ts` (`the Quickstart guide reaches the site`) move to the rendered file with it.
|
||||
|
||||
**Cost:** an afternoon, most of it in the converter's table and list handling. No dependency —
|
||||
a Markdown library would be the only runtime dependency this project has, and the guide uses a
|
||||
small enough subset that it is not worth becoming the first.
|
||||
|
||||
---
|
||||
|
||||
## Reference — measurements, rulings and rejected approaches
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Station Master — Components and Markers
|
||||
|
||||
**Describes the game as built at v0.8.0.15** (2026-09-20). Previously stamped "v0.4.5", the
|
||||
**Describes the game as built at v0.8.0.16** (2026-09-20). Previously stamped "v0.4.5", the
|
||||
prototype rules edition these references were first written against; the filename keeps that stamp
|
||||
because `src/`, `CHANGELOG.md` and `docs/rules/` all cite this file by name.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Station Master — Home Deck
|
||||
|
||||
**Describes the game as built at v0.8.0.15** (2026-09-20). Previously stamped "v0.4.5", the
|
||||
**Describes the game as built at v0.8.0.16** (2026-09-20). Previously stamped "v0.4.5", the
|
||||
prototype rules edition these references were first written against; the filename keeps that stamp
|
||||
because `src/`, `CHANGELOG.md` and `docs/rules/` all cite this file by name.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Station Master — Mainline Deck
|
||||
|
||||
**Describes the game as built at v0.8.0.15** (2026-09-20). Previously stamped "v0.4.5", the
|
||||
**Describes the game as built at v0.8.0.16** (2026-09-20). Previously stamped "v0.4.5", the
|
||||
prototype rules edition these references were first written against; the filename keeps that stamp
|
||||
because `src/`, `CHANGELOG.md` and `docs/rules/` all cite this file by name.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Station Master — Quickstart
|
||||
|
||||
**For a tester who has never played. Describes the game as built at v0.8.0.15** (2026-09-20).
|
||||
**For a tester who has never played. Describes the game as built at v0.8.0.16** (2026-09-20).
|
||||
|
||||
Read this once before you sit down. It is about twenty minutes of reading and will save you an hour
|
||||
of confusion. The deeper references are listed at the end.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Station Master — Rules
|
||||
|
||||
**Describes the game as built at v0.8.0.15** (2026-09-20). Previously stamped "v0.4.5", the
|
||||
**Describes the game as built at v0.8.0.16** (2026-09-20). Previously stamped "v0.4.5", the
|
||||
prototype rules edition this reference was first written against; the filename keeps that stamp
|
||||
because `src/`, `CHANGELOG.md` and `docs/rules/` all cite this file by name.
|
||||
|
||||
@@ -206,7 +206,7 @@ roll a tail cut into a connected Freight Facility.
|
||||
|
||||
**Freight Agent.** Make one of these operations, then the turn ends: stock one green outbound box from a matching loaded Division Yard car; clear one red inbound box to the Classification Yard; unjam one outbound, inbound, or MEN | AT | WORK load to the Classification Yard; or explicitly end without acting. Freight can be stocked only when an unclaimed empty matching car is already spotted at that industry. Passengers may wait in a green Office box without a train present.
|
||||
|
||||
Implementation note, still true at v0.8.0.15: `card.discard` is accepted during Local Operations
|
||||
Implementation note, still true at v0.8.0.16: `card.discard` is accepted during Local Operations
|
||||
without checking that the Draw option was chosen — unlike `card.play`, which does check. This is an
|
||||
implementation quirk rather than a fourth published turn option.
|
||||
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ must do.
|
||||
|
||||
## Current status
|
||||
|
||||
**v0.8.0.15.** Rules formalized, card faces specified, architecture documented, and the game
|
||||
**v0.8.0.16.** Rules formalized, card faces specified, architecture documented, and the game
|
||||
playable **solitaire and multiplayer** in a browser against an authoritative server. See
|
||||
[`../CHANGELOG.md`](../CHANGELOG.md) for what each version changed and [`../TODO.md`](../TODO.md) for
|
||||
what is open; this section is the shape of the project, not a running tally, because a
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "station-master",
|
||||
"version": "0.8.0.15",
|
||||
"version": "0.8.0.16",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Station Master — a railroad operations game",
|
||||
|
||||
@@ -188,6 +188,27 @@ if (existsSync(imageSrc)) {
|
||||
for (const f of readdirSync(imageSrc)) copyFileSync(join(imageSrc, f), join(imageOut, f));
|
||||
}
|
||||
|
||||
/**
|
||||
* The Quickstart, published beside the game so a tester can reach it from the box.
|
||||
*
|
||||
* COPIED, NOT RE-WRITTEN. `docs/StationMaster-Quickstart.md` is the one copy; a hand-written HTML
|
||||
* twin would drift from it on the first edit, which is the whole lesson of TODO #15a and of the
|
||||
* 2026-09-20 documentation pass that found four references a month out of date.
|
||||
*
|
||||
* SERVED AS PLAIN TEXT for now, which is honest rather than good: tables render as pipes and the
|
||||
* links do not click. Rendering it into a styled page needs a small Markdown converter and is
|
||||
* filed as TODO #109 — this is the fifteen-minute version that gets the guide in front of testers
|
||||
* for this round rather than leaving them without one.
|
||||
*/
|
||||
const guideSrc = join(root, 'docs/StationMaster-Quickstart.md');
|
||||
if (existsSync(guideSrc)) {
|
||||
copyFileSync(guideSrc, join(dist, 'quickstart.md'));
|
||||
} else {
|
||||
// Loud rather than silent: a missing guide is a broken link on the splash page, and the build is
|
||||
// the only place that can still notice.
|
||||
console.error('WARNING: docs/StationMaster-Quickstart.md is missing — the splash link will 404');
|
||||
}
|
||||
|
||||
// A tiny note for whoever unzips this later and wonders what it needs.
|
||||
writeFileSync(
|
||||
join(dist, 'README.txt'),
|
||||
|
||||
+14
-1
@@ -589,7 +589,20 @@ export type MainlineProfile = {
|
||||
speedStarts?: { fast: number; slow: number };
|
||||
/** Double Track: "Trains may pass". */
|
||||
trainsMayPass: boolean;
|
||||
/** Interchange: "Sort cars in new order". */
|
||||
/**
|
||||
* Interchange only. The card prints "Sort cars in new order" — **and that is not what this flag
|
||||
* does**, which is why it is worth spelling out where the field is declared.
|
||||
*
|
||||
* The printed sorting has never been implemented: nothing reads this to permit a sort, and a
|
||||
* consist is re-ordered at a Small Yard in a district (`switch.sortConsist`). What this actually
|
||||
* marks is the one Mainline card with a Yard Limit, and therefore the one an Extra may be made up
|
||||
* and started on (`apply.ts` § resolveExtraStart, `legal.ts`).
|
||||
*
|
||||
* Named for the printed text, and kept that way deliberately — renaming it would lose the link to
|
||||
* the card face — but the name has already misled once: `mainlineDescription` grew a sentence
|
||||
* telling players cars could be sorted here, which reached the board and the generated card
|
||||
* reference before it was caught on 2026-09-20.
|
||||
*/
|
||||
sortsCars: boolean;
|
||||
/** Named entry points printed on the card; some are unlocked by modifier cards. */
|
||||
entryPoints: readonly string[];
|
||||
|
||||
@@ -85,6 +85,14 @@ const MIME: Record<string, string> = {
|
||||
'.json': 'application/json; charset=utf-8',
|
||||
'.png': 'image/png',
|
||||
'.svg': 'image/svg+xml',
|
||||
/**
|
||||
* The Quickstart guide, published by `build-web.ts` as `quickstart.md`.
|
||||
*
|
||||
* text/plain ON PURPOSE. The fallback below is `application/octet-stream`, which makes a browser
|
||||
* DOWNLOAD the file instead of showing it — so without this line the splash page's "read the
|
||||
* guide" link hands a tester a file to save rather than a page to read.
|
||||
*/
|
||||
'.md': 'text/plain; charset=utf-8',
|
||||
};
|
||||
|
||||
const HEARTBEAT_MS = 20_000;
|
||||
|
||||
@@ -27,6 +27,11 @@ h1{font-size:32px;margin:0 0 2px;letter-spacing:.02em}
|
||||
a.door:hover{border-color:#4d6fa8;background:#1f2733;transform:translateY(-1px)}
|
||||
.door h2{font-size:17px;margin:0 0 5px;color:#9fb6d8}
|
||||
.door p{margin:0;color:var(--dim);font-size:13px;line-height:1.5}
|
||||
/* Not a fourth door: reading the guide is not a way to play, and giving it equal weight in the
|
||||
grid would say it is. A line under the doors, where somebody who does not know what to click
|
||||
will already be looking. */
|
||||
.newhere{margin:16px 2px 0;color:var(--dim);font-size:13px;line-height:1.55}
|
||||
.newhere a{color:#9fb6d8}
|
||||
.door .go{display:inline-block;margin-top:11px;font-size:12px;color:#5aa9e6}
|
||||
.door.disabled .go{color:var(--dim)}
|
||||
a.door.disabled{pointer-events:none}
|
||||
@@ -96,6 +101,11 @@ footer{margin-top:26px;color:var(--dim);font-size:11px;display:flex;gap:18px;fle
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p class="newhere">New to Station Master?
|
||||
<a href="./quickstart.md">Read the Quickstart guide</a> — what the game is, how you win,
|
||||
how a Stage runs, what is on the screen, and a first twenty minutes. About twenty minutes to
|
||||
read, and it will save you an hour of guessing.</p>
|
||||
|
||||
<div class="rule"></div>
|
||||
|
||||
<footer>
|
||||
|
||||
@@ -5509,3 +5509,43 @@ describe('a Division Yard with no coaches is a reportable condition', () => {
|
||||
assert.equal(waiting.loaded + waiting.empty, 2, 'the waiting coaches were miscounted');
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The Quickstart is published beside the game, so a tester on the box can reach it.
|
||||
*
|
||||
* WHY THIS IS A TEST. The link on the splash page is a plain href to a file the BUILD copies out of
|
||||
* `docs/`. Nothing else connects the two: rename the document, or move it, and the build quietly
|
||||
* publishes nothing while the splash page keeps offering a link that 404s. Neither `tsc` nor any
|
||||
* other test would notice — the whole failure lives between a file name and a string.
|
||||
*/
|
||||
describe('the Quickstart guide reaches the site', () => {
|
||||
it('is published into dist and linked from the splash page', () => {
|
||||
const guide = join(dist, 'quickstart.md');
|
||||
assert.ok(existsSync(guide), 'the build did not publish quickstart.md');
|
||||
|
||||
const text = readFileSync(guide, 'utf8');
|
||||
assert.match(text, /^# Station Master — Quickstart/, 'quickstart.md is not the guide');
|
||||
assert.match(
|
||||
text,
|
||||
/Describes the game as built at v/,
|
||||
'the guide does not say which build it describes',
|
||||
);
|
||||
|
||||
const splash = readFileSync(join(dist, 'index.html'), 'utf8');
|
||||
assert.match(splash, /href="\.\/quickstart\.md"/, 'the splash page does not link the guide');
|
||||
});
|
||||
|
||||
it('is served as text rather than handed over as a download', () => {
|
||||
/**
|
||||
* The server's MIME fallback is `application/octet-stream`, which a browser downloads instead of
|
||||
* displaying — so the link would hand a tester a file to save rather than a page to read. The
|
||||
* table is read straight out of the source: asserting on a copy of it would pass while the real
|
||||
* one was wrong.
|
||||
*/
|
||||
const http = readFileSync(join(root, 'src/server/http.ts'), 'utf8');
|
||||
const table = http.slice(http.indexOf('const MIME'), http.indexOf('const HEARTBEAT_MS'));
|
||||
assert.match(table, /'\.md':\s*'text\/plain/, 'a .md file would be served as a download');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user