diff --git a/.bundle/config b/.bundle/config new file mode 100644 index 000000000..236922881 --- /dev/null +++ b/.bundle/config @@ -0,0 +1,2 @@ +--- +BUNDLE_PATH: "vendor/bundle" diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml new file mode 100644 index 000000000..20865d57a --- /dev/null +++ b/.github/workflows/visual-regression.yml @@ -0,0 +1,78 @@ +name: Visual Regression + +on: + pull_request: + branches: [ main, master ] + paths: + - 'assets/**' + - '_includes/**' + - '_layouts/**' + - 'categories/**/*.md' + - 'pages/**/*.md' + - 'index.md' + - '_config.yml' + - '_config.dev.yml' + - 'backstop.config.js' + - 'package.json' + - 'package-lock.json' + - '.github/workflows/visual-regression.yml' + +permissions: + contents: read + +jobs: + backstop: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: npm + + - name: Install Node dependencies + run: npm ci + + - name: Install Playwright Chromium + run: npx playwright install --with-deps chromium + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2.2' + bundler-cache: true + + - name: Start Jekyll preview + env: + LANG: C.UTF-8 + LC_ALL: C.UTF-8 + run: | + bundle exec jekyll serve --config _config.yml,_config.dev.yml --host 127.0.0.1 --port 4000 > jekyll.log 2>&1 & + for attempt in {1..60}; do + if curl --fail --silent --show-error http://127.0.0.1:4000/ > /dev/null; then + exit 0 + fi + sleep 2 + done + cat jekyll.log + exit 1 + + - name: Run BackstopJS + env: + BACKSTOP_BASE_URL: http://127.0.0.1:4000 + run: npm run backstop:test + + - name: Upload Backstop report + if: always() + uses: actions/upload-artifact@v4 + with: + name: backstop-report + path: | + test/backstop/report + test/backstop/test + test/backstop/ci + jekyll.log diff --git a/.gitignore b/.gitignore index 36fbca2f2..33ce67faf 100755 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,9 @@ templates /public/generated/placeholders/*.svg _site.backup/ */__pycache__/* +build/ + +# BackstopJS visual regression reports and generated test screenshots. +/test/backstop/ci +/test/backstop/test +/test/backstop/report diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..6621bbf0b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "markdown.copyFiles.destination": { + "/**/*.md": "/public/images/${documentBaseName}/${fileName}" + } +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 657d733b5..34b9b4c56 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,6 +38,14 @@ This can include: * **Tutorials** - on reverse engineering or writing emulators * **Research material related to the game industry** - e.g. content of game industry conferences, programming/software books, game industry magazines, or even just game magazines from the past that contain interviews with game developers +### Let categories incubate smaller topics +
🌱
+When adding information on a topic that is useful but not yet large enough for its own page, add it to the most relevant existing category or hub page first. +This keeps related research discoverable while avoiding lots of thin pages. + +Once a section grows large enough to stand alone, split it into a dedicated page and leave a short summary plus an internal link from the original category page. +The category page should remain useful as a high-level guide, while the dedicated page can hold the deeper history, technical details, references, and examples. + ### Types of content we don't want
@@ -52,6 +60,9 @@ Please try to link out to other sites that have high-quality information on a pa However, please provide at least a brief description of the page you are linking to and its content before the reference so readers understand the relevance. The page here should still form a cohesive narrative even without the reader following the external links. If an external page is critical, tell the user to read it before continuing. +When the prose is talking about a specific external site, article, tool page, archive, or other resource, link the name or descriptive phrase directly in the body text. +Footnotes are enough when the source is only being used to support a claim, date, or technical detail, but a reader should not have to check the references section just to reach a resource being discussed. + ### Reference when possible
📚
We want to avoid spreading misinformation as much as possible, which can be tricky when researching old software tools since there can be conflicting information. Please reference sources so readers can verify whether the information is correct. You may use Wikipedia as a source but only as a last resort if no other websites have the information. @@ -73,6 +84,7 @@ Some general rules are below: * **Non-linear order** - Never assume the reader will follow a linear order. Each section should be standalone so they can read only the parts they are interested in. * **Present then explain** - Present terminology as factual first, then justify or contextualize ("What is it?" then "Why is it useful?"). * **Encourage hands-on experimentation** - Suggest trying tools, running commands, or inspecting files. +* **Use stable site wording** - Avoid wording that implies a recent site change, such as "we now have", "now has its own page", or "this now lives". Prefer stable phrases such as "For the full history, see:" or "A dedicated page covers this in more detail:". ### Character Rules
🔤
@@ -256,6 +268,10 @@ You don't need to reference posts from RetroReversing.com. Instead, just link to {% endraw %} ``` +When mentioning a company or console that already has a dedicated category page, link the first meaningful mention in that section to the relevant RetroReversing page. +For example, use `[Datel](/companies/datel)`, `[Sony PlayStation](/ps1)`, or `[PlayStation 2](/ps2)` in normal prose. +Do not link headings just to satisfy this rule; keep headings plain and link the first suitable mention in the body text instead. + --- ## Rules for Code Examples For code that could be useful to run interactively in the browser, provide the example in TypeScript. Otherwise, use Python for any scripts intended to run locally. @@ -341,6 +357,21 @@ Text for the section... This saves hosting all the images in this Git repository and links back to **MobyGames**, whose bandwidth we are using for the images. +## Pasted VS Code images +VS Code can create generic files such as `image.png` when an image is pasted into Markdown. +Always replace pasted images named only `image.png` with a descriptive filename. +Do not leave generic `image.png` files in the repository. + +For screenshots and photos, always convert generic pasted PNG files to JPG unless the image needs transparency or lossless pixel-perfect detail. +Use the Markdown alt text as the preferred filename source when it has been filled in. +If the alt text is blank or generic, name the file from the nearest useful heading, caption, or surrounding paragraph context. + +For example, `![Trainer Option in Game Studio](image/image.png)` should become a JPG with a meaningful path such as: + +```markdown +![Trainer Option in Game Studio](/public/images/GameBoy/Trainer Option in Game Studio.jpg) +``` + --- ## Emoji on left after heading
💡
diff --git a/README.md b/README.md index 13cb38b10..67e98bad5 100644 --- a/README.md +++ b/README.md @@ -122,4 +122,38 @@ To preview the site on your machine: For faster local iteration while writing content: * Run `jekyll serve --config _config.yml,_config.dev.yml --livereload` -* This disables redirect generation locally, which reduces rebuild fan-out. \ No newline at end of file +* This disables redirect generation locally, which reduces rebuild fan-out. + +--- +# Visual Regression Testing +BackstopJS is configured for a small set of representative pages and viewport sizes. Use it when changing CSS, layouts, includes, or page structures that could affect rendered output. + +Install the npm dependencies first: +```bash +npm install +``` + +Start the local Jekyll server in another terminal: +```bash +jekyll serve --config _config.yml,_config.dev.yml --livereload +``` + +Create or update the approved screenshot baselines: +```bash +npm run backstop:reference +``` + +Run a visual regression check: +```bash +npm run backstop:test +``` + +If the differences are intentional, approve the new screenshots: +```bash +npm run backstop:approve +``` + +By default BackstopJS targets `http://localhost:4000`. To test a different preview URL, set `BACKSTOP_BASE_URL`: +```bash +BACKSTOP_BASE_URL=https://example-preview-url npm run backstop:test +``` diff --git a/_config.dev.yml b/_config.dev.yml index 3fd6849d3..ea61af2ca 100644 --- a/_config.dev.yml +++ b/_config.dev.yml @@ -13,7 +13,9 @@ exclude: - _site.backup - templates - node_modules + - vendor - tmp + - test - .cache - .DS_Store - scripts diff --git a/_config.yml b/_config.yml index 0a3a238e0..02ea3c096 100644 --- a/_config.yml +++ b/_config.yml @@ -41,7 +41,9 @@ exclude: - _site.backup - templates - node_modules + - vendor - tmp + - test - .cache - .DS_Store - scripts @@ -381,4 +383,3 @@ references: name: segaretro.org link: https://www.segaretro.org image: https://segaretro.org/skins/foreground/assets/img/segaretro_logo.png - diff --git a/_data/valid-tags.json b/_data/valid-tags.json index 61834e955..20d90ecc6 100644 --- a/_data/valid-tags.json +++ b/_data/valid-tags.json @@ -1,16 +1,16 @@ { "summary": { - "totalFiles": 300, - "filesWithTags": 300, + "totalFiles": 295, + "filesWithTags": 295, "uniqueTags": 108, - "generatedAt": "2026-04-11T15:02:54.945Z" + "generatedAt": "2026-07-18T18:36:42.014Z" }, "tags": { "2d": 1, "32x": 1, "3d": 6, "3do": 1, - "3ds": 8, + "3ds": 9, "amiga": 1, "android": 3, "arcade": 2, @@ -29,18 +29,19 @@ "crossproducts": 5, "datel": 2, "debug": 6, - "devkit": 29, + "delphi": 1, + "devkit": 28, "disassemblers": 1, "documentary": 2, - "dos": 4, + "dos": 3, "dreamcast": 8, - "ds": 12, - "emulation": 2, + "ds": 15, + "emulation": 3, "fileformats": 15, "gameboy": 20, "gamecom": 1, "gamecube": 10, - "gameengines": 13, + "gameengines": 12, "gamegear": 4, "games": 25, "gba": 17, @@ -49,12 +50,12 @@ "gigaleak": 2, "graphics": 1, "hacking": 1, - "handhelds": 4, - "hardware": 38, + "handhelds": 5, + "hardware": 37, "homebrew": 2, - "incomplete": 1, - "industry": 21, - "introduction": 56, + "incomplete": 2, + "industry": 19, + "introduction": 60, "ios": 1, "ique": 2, "jaguar": 1, @@ -65,51 +66,51 @@ "mastersystem": 4, "maths": 4, "megadrive": 6, - "memory": 3, - "middleware": 11, + "memory": 2, + "middleware": 10, "msx": 3, - "n64": 32, - "nes": 15, + "n64": 30, + "nes": 14, "news": 2, "nintendo": 3, - "pc": 13, + "pc": 12, "pc-88": 1, "pc-engine": 1, "pokemon": 1, - "pokemonmini": 3, + "pokemonmini": 4, "programming-languages": 1, "ps1": 17, "ps2": 10, "ps3": 3, "ps4": 2, - "psp": 7, + "psp": 10, "psyq": 3, "radare2": 1, - "reverseengineering": 5, + "reverseengineering": 6, "reversingemulator": 5, "saturn": 17, - "sdk": 48, + "sdk": 51, "sega": 27, "segasaturn": 1, "sg1000": 1, "snes": 25, "snsystems": 12, - "software": 5, - "sourcecode": 24, + "software": 6, + "sourcecode": 26, "starfox2": 1, "superfx": 1, "switch": 3, "symbols": 13, - "tools": 18, + "tools": 19, "tutorial": 10, "tutorials": 1, - "unity3d": 3, + "unity3d": 2, "vectrex": 1, - "vita": 5, + "vita": 6, "wii": 12, "wiiu": 8, "windows": 1, - "wonderswan": 1, + "wonderswan": 2, "xbox": 8, "xbox360": 2, "zxspectrum": 1 @@ -192,6 +193,7 @@ ], "categories/consoles/GameBoy.md": [ "gameboy", + "introduction", "gba", "ds" ], @@ -208,11 +210,17 @@ "gamecube" ], "categories/consoles/Handhelds.md": [ + "3ds", + "ds", "gba", "gameboy", "gamecom", "gamegear", - "handhelds" + "handhelds", + "pokemonmini", + "psp", + "vita", + "wonderswan" ], "categories/consoles/Intellivision.md": [ "console", @@ -228,7 +236,8 @@ "sega" ], "categories/consoles/N64.md": [ - "n64" + "n64", + "introduction" ], "categories/consoles/Nes.md": [ "nes", @@ -261,6 +270,7 @@ "psp" ], "categories/consoles/PokemonMini.md": [ + "pokemonmini", "pokemonmini" ], "categories/consoles/SG1000.md": [ @@ -352,17 +362,10 @@ "books", "industry" ], - "pages/Industry/Books.md": [ - "industry" - ], "categories/misc/Conferences.md": [ "industry", "introduction" ], - "pages/Industry/GDC.md": [ - "industry", - "introduction" - ], "categories/misc/Hacking.md": [ "industry", "hacking", @@ -390,9 +393,6 @@ "categories/misc/Maths.md": [ "maths" ], - "categories/misc/Museums.md": [ - "industry" - ], "categories/tools/3DSoftware.md": [ "3d", "software", @@ -432,6 +432,9 @@ "pages/About.md": [ "introduction" ], + "pages/Industry/Books.md": [ + "industry" + ], "pages/Industry/Copyright.md": [ "industry", "legal", @@ -443,14 +446,15 @@ "magazines", "introduction" ], + "pages/Industry/GDC.md": [ + "industry", + "introduction" + ], "pages/Industry/GameMakerMagazine.md": [ "industry", "magazines", "introduction" ], - "pages/Industry/IndieDevelopment.md": [ - "industry" - ], "pages/Industry/RetroGamer.md": [ "industry", "magazines", @@ -526,6 +530,11 @@ "3d", "introduction" ], + "pages/SourceCode/mame/GameAndWatchEmulation.md": [ + "sourcecode", + "emulation", + "handhelds" + ], "pages/SourceCode/mame/MAMESourceCode.md": [ "sourcecode", "emulation", @@ -638,7 +647,8 @@ "pages/consoles/gameboy/PokemonRedBlue.md": [ "gameboy", "games", - "assembly" + "assembly", + "sourcecode" ], "pages/consoles/gameboy/SuperGameBoySDK.md": [ "gameboy", @@ -712,11 +722,6 @@ "gba", "sdk" ], - "pages/consoles/n64/Bung-Doctor-v64.md": [ - "n64", - "devkit", - "hardware" - ], "pages/consoles/n64/Datel-Action-Replay-Professional.md": [ "n64", "memory", @@ -746,14 +751,15 @@ "pages/consoles/n64/Mupen64SourceCodeAnalysis.md": [ "n64" ], - "pages/consoles/n64/Mystical-Ninja-N64-Memory-Rom-Editing.md": [ - "n64", - "memory" - ], "pages/consoles/n64/N64BootCode.md": [ "n64", "assembly" ], + "pages/consoles/n64/N64DevelopmentKitHardware.md": [ + "n64", + "devkit", + "hardware" + ], "pages/consoles/n64/N64HardwareArchitecture.md": [ "n64", "hardware", @@ -762,7 +768,8 @@ "pages/consoles/n64/N64MipsAssembly.md": [ "n64", "assembly", - "tutorial" + "tutorial", + "incomplete" ], "pages/consoles/n64/N64RDP.md": [ "n64" @@ -786,11 +793,6 @@ "3d", "tools" ], - "pages/consoles/n64/OfficialN64Devkit.md": [ - "n64", - "devkit", - "hardware" - ], "pages/consoles/n64/OmanArchive.md": [ "n64", "sdk", @@ -850,10 +852,6 @@ "nes", "ghidra" ], - "pages/consoles/nes/NesProgrammingIntro.md": [ - "nes", - "introduction" - ], "pages/consoles/nes/Super-Mario-Bros.md": [ "nes", "games" @@ -864,10 +862,6 @@ "sourcecode", "games" ], - "pages/consoles/pc/RetroCityRampage.md": [ - "pc", - "dos" - ], "pages/consoles/pc/WindowsExecutables.md": [ "pc", "windows", @@ -1230,13 +1224,23 @@ "introduction" ], "pages/general/maths/Matrix.md": [ - "maths" + "maths", + "psp", + "ds", + "sdk", + "introduction" ], "pages/general/maths/Quaternions.md": [ - "maths" + "maths", + "psp", + "ds", + "introduction", + "sdk" ], "pages/general/maths/Vectors.md": [ - "maths" + "maths", + "introduction", + "sdk" ], "pages/hardware/Verilog.md": [ "hardware", @@ -1453,6 +1457,12 @@ "sdk", "tools" ], + "pages/tools/Delphi.md": [ + "delphi", + "tools", + "software", + "reverseengineering" + ], "pages/tools/GameMaker.md": [ "gameengines", "middleware" @@ -1482,11 +1492,6 @@ "middleware", "unity3d" ], - "pages/tools/Unity3d_additions2.md": [ - "gameengines", - "middleware", - "unity3d" - ], "pages/tools/engines/KromeMerkuryEngine.md": [ "middleware", "gameengines" diff --git a/_includes/homepage/post-playlist.html b/_includes/homepage/post-playlist.html index 65d2df47e..a49829b60 100644 --- a/_includes/homepage/post-playlist.html +++ b/_includes/homepage/post-playlist.html @@ -9,6 +9,7 @@ Optional parameters: * tag: Filter pages by this tag (default is no filter) * limit: Number of pages to show (default: 10) + * description: Short helper text shown under the heading * hide_show_all: Whether to hide the default show all button * pinned_permalink: Permalink to "pin" at the start of the list Notes: @@ -33,7 +34,12 @@