Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ src/
AudioManager.js / sounds.js
public/ # browser runtime assets only
tests/
game.spec.js / site.spec.js # 251 Playwright browser tests
game.spec.js / site.spec.js # 261 Playwright browser tests
```

See [docs/reference-data.md](docs/reference-data.md) for sprite coordinates, wonder IDs, city screen layout, and map file format.
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,41 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).

## [Unreleased]

## [0.6.1] - 2026-08-28

### Added

- Added **Game → Report Bug...**, which creates one ZIP containing a PNG of the
current game view, a restorable game-state snapshot, renderer/view state,
browser diagnostics, and handling instructions.
- Added Download, system Share/Email, email-draft fallback, and prefilled GitHub
issue routes for bug reports without storing GitHub credentials.
- Added a responsive in-game preview to the project landing page and refreshed
its gameplay and project-page screenshots from the corrected renderer.

### Changed

- Restored the MGE-sized city production chooser, original silver listbox,
Times headings, row geometry, unit/building icons, and fixed button layout.
- Size-one cities continue to offer Settlers and Engineers, matching MGE, but
retain the completed production and shields until the city grows.
- Active units in cities now flash at the original 200ms cadence over the city
and garrison flag instead of using a modern selection ring.
- Expanded browser coverage to 261 Playwright tests.

### Fixed

- Terrain and fog masks now respect transparent chroma-key pixels, removing the
black dotted diamonds that appeared around every map square.
- Reveal-map mode no longer applies fog-edge dithering, and its minimap now
shows the complete terrain, including blue ocean tiles, without fog.
- Corrected minimap terrain lookup so ocean and land use their intended MGE
colours in normal play as well as reveal mode.
- Research chooser headings now widen the compact MGE dialog only when needed,
preventing “What discovery shall our wise men pursue?” from overflowing.
- City garrison flags use the original marker offset and active units visibly
animate over occupied cities.

## [0.6.0] - 2026-08-28

### Added
Expand Down
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ ownership attestation locally in your browser.
- Original city, research, Civilopedia, throne-room, advisor, score, and map UI
- Save/load, scenarios, diplomacy, research, combat, AI, Wonders, spaceship,
government, trade, and late-game mechanics
- 251 Playwright browser regression tests
- 261 Playwright browser regression tests
- Production build deployable beneath the GitHub Pages `/civ2/` project path

This is a playable public beta, not a claim that every undocumented MGE edge
Expand Down Expand Up @@ -52,6 +52,22 @@ npm run build:pages
npm run preview:pages
```

## Reporting bugs

Choose **Game → Report Bug...** inside a running game. The app creates one ZIP
containing a screenshot, a restorable game-state snapshot, the current camera
and open-screen state, and browser/viewport diagnostics.

- **Download ZIP** saves the report locally.
- **Share / Email** uses the browser or operating system share sheet when file
sharing is supported. Otherwise it downloads the ZIP and opens an email draft
that tells you which file to attach.
- **GitHub Issue** downloads the same ZIP and opens a prepared issue. Attach the
downloaded file before submitting it.

The static app never asks for or stores GitHub credentials. Bug-report ZIPs can
contain original MGE artwork, so they should be shared only for diagnosis.

## Repository layout

```text
Expand Down
Binary file modified docs/screenshots/project-page.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 14 additions & 8 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,20 @@
<a class="wordmark" href="./">CIVILIZATION II</a>
<div><a href="#about">About</a><a href="https://github.com/wan0net/civ2">Source</a></div>
</nav>
<div class="hero-copy">
<p class="eyebrow">Multiplayer Gold Edition · Recreated for the browser</p>
<h1>Build a civilization<br>to stand the test of time.</h1>
<p class="lede">A graphics-first recreation of the 1998 classic, preserving its original interface, rules, music, movies, and unmistakable Windows-era character.</p>
<div class="actions">
<a class="primary" href="./game.html">Open the game</a>
<a class="secondary" href="#ownership">Before you play</a>
<div class="hero-content">
<div class="hero-copy">
<p class="eyebrow">Multiplayer Gold Edition · Recreated for the browser</p>
<h1>Build a civilization<br>to stand the test of time.</h1>
<p class="lede">A graphics-first recreation of the 1998 classic, preserving its original interface, rules, music, movies, and unmistakable Windows-era character.</p>
<div class="actions">
<a class="primary" href="./game.html">Open the game</a>
<a class="secondary" href="#ownership">Before you play</a>
</div>
</div>
<figure class="gameplay-preview">
<img src="./site/gameplay.jpg" alt="Gameplay view showing the city of Rome and three Roman warrior units on the map">
<figcaption>Current browser build · In-game view</figcaption>
</figure>
</div>
<div class="hero-grid" aria-hidden="true"></div>
</header>
Expand All @@ -38,7 +44,7 @@ <h1>Build a civilization<br>to stand the test of time.</h1>
</section>

<section class="stats" aria-label="Project status">
<div><strong>251</strong><span>browser tests</span></div>
<div><strong>261</strong><span>browser tests</span></div>
<div><strong>59</strong><span>original movies supported</span></div>
<div><strong>7</strong><span>world maps</span></div>
<div><strong>BSD-3</strong><span>project code</span></div>
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "civ2-web",
"version": "0.6.0",
"version": "0.6.1",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
Binary file added public/site/gameplay.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions src/engine/GameState.js
Original file line number Diff line number Diff line change
Expand Up @@ -2068,9 +2068,8 @@ export class GameState {
} else {
city.shields += yields.shields;
const cost = this._productionCost(city.production);
if (city.shields >= cost) {
if (city.shields >= cost && this._completeProduction(city)) {
city.shields = Math.max(0, city.shields - cost);
this._completeProduction(city);
}
}
}
Expand Down Expand Up @@ -2532,7 +2531,9 @@ export class GameState {
if (type === 'unit') {
let spawnCol = city.col, spawnRow = city.row;
const unitData = UNITS[id];
if (unitData.role === 5 && city.size <= 1) return;
// MGE keeps Settlers/Engineers in the production chooser at size 1,
// but holds the completed item (and its shields) until the city grows.
if (unitData.role === 5 && city.size <= 1) return false;
if (unitData.domain === 2) {
// Sea units must spawn on an adjacent ocean tile (coastal city)
const oceanAdj = neighbours(city.col, city.row, this.mapCols).find(n => {
Expand Down Expand Up @@ -2670,6 +2671,7 @@ export class GameState {
} else {
city.production = null;
}
return true;
}

// ─── Research ──────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -2723,8 +2725,6 @@ export class GameState {
if (u.obsoletedBy !== -1 && civ.advances.has(u.obsoletedBy)) continue;
// Nuclear Missile (id=45) requires Manhattan Project (62) to have been built by any civ
if (u.id === 45 && !this._manhattanBuilt) continue;
// Settlers/Engineers (role 5) require city size > 1 (axx0 UnitProductionOrder.cs:28-31)
if (u.role === 5 && city.size <= 1) continue;
items.push({ type: 'unit', id: u.id, name: u.name, cost: u.cost * 10 });
}

Expand Down
46 changes: 45 additions & 1 deletion src/render/MapRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { applyWizardMixin } from './mixins/WizardMixin.js';
import { applyCityScreenMixin } from './mixins/CityScreenMixin.js';
import { applySidebarMixin } from './mixins/SidebarMixin.js';
import { applyTerrainMixin } from './mixins/TerrainMixin.js';
import { applyBugReportMixin } from './mixins/BugReportMixin.js';
import { GameState } from '../engine/GameState.js';
import { Civ2SaveLoader } from '../engine/Civ2SaveLoader.js';
import { MapLoader } from '../engine/MapLoader.js';
Expand Down Expand Up @@ -53,6 +54,8 @@ const MENUS = [
{ label: 'Save As MGE .SAV', action: 'game_save_sav' },
{ label: 'Load Game', action: 'game_load', shortcut: 'Ctrl+L' },
null,
{ label: 'Report Bug...', action: 'game_reportbug' },
null,
{ label: 'Retire', action: 'game_retire', shortcut: 'Ctrl+R' },
{ label: 'Quit', action: 'game_quit', shortcut: 'Ctrl+Q' },
]},
Expand Down Expand Up @@ -371,6 +374,7 @@ export class MapRenderer {
this._menuBarRects = []; // [{x, w, menuIdx}] — populated during draw
this._menuItemRects = []; // [{x,y,w,h,action,disabled}] — current dropdown
this._menuHoverIdx = null; // index into _menuItemRects
this._bugReportDialog = null;

// Title screen state
this._titleScreen = false;
Expand Down Expand Up @@ -925,6 +929,7 @@ export class MapRenderer {
if (this._titleScreen) { this._handleTitleScreenKey(e); return; }
// ── Wizard intercepts all keys when active ──────────────────────────────
if (this._wizard) { this._handleWizardKey(e); return; }
if (this._bugReportDialog) { this._handleBugReportKey(e); return; }
if (this._editCityDialog) { this._handleEditCityKey(e); return; }
if (this._editTechsDialog) { this._handleEditTechsKey(e); return; }
if (this._editUnitDialog) { this._handleEditUnitKey(e); return; }
Expand Down Expand Up @@ -965,6 +970,7 @@ export class MapRenderer {
const gs = this.gameState;

// Modal dialogs first
if (this._bugReportDialog) { this._bugReportDialog = null; return true; }
if (this._civilopedia) { this._civilopedia = null; return true; }
if (this._cityScreen && this._cityScreenProdList) {
this._cityScreenProdList = false;
Expand Down Expand Up @@ -1443,6 +1449,11 @@ export class MapRenderer {
return;
}

if (this._bugReportDialog) {
this._handleBugReportClick(px, py);
return;
}

// ── Retirement flow (POWERgraph → Score → HoF) ──────────────────────────
if (this._retireStage) {
this._handleRetireFlowClick(px, py);
Expand Down Expand Up @@ -2468,6 +2479,20 @@ export class MapRenderer {
// Collect cities for name rendering in a separate last pass
if (!this._cityNameQueue) this._cityNameQueue = [];
this._cityNameQueue.push({ city, x, y });

// In MGE the city and garrison flag remain in the base map, while
// the ready unit flashes on top as a separate animation frame.
// Queue it for the final map pass. The original MGE waiting frames
// then redraw city names over the unit.
const active = this.gameState.activeUnit;
const combatAtCity = this._combatAnim &&
((col === this._combatAnim.atkCol && row === this._combatAnim.atkRow) ||
(col === this._combatAnim.defCol && row === this._combatAnim.defRow));
if (v === 2 && active?.col === col && active?.row === row &&
(!this._moveAnim || this._moveAnim.unit !== active) && !combatAtCity) {
if (!this._activeCityUnitQueue) this._activeCityUnitQueue = [];
this._activeCityUnitQueue.push({ unit: active, x, y });
}
}

if (v === 2) {
Expand Down Expand Up @@ -2563,7 +2588,17 @@ export class MapRenderer {
ctx.strokeRect(fx - 2, fy - 2, tileWS + 4, tileHS + 4);
}

// Pass LAST — city names drawn on top of everything (axx0 Draw.Map.cs:135-140)
// Active units in cities are animation elements in MGE: their sprite and
// shield blink over the otherwise-static city and flag.
if (this._activeCityUnitQueue) {
for (const { unit, x: ux, y: uy } of this._activeCityUnitQueue) {
this._drawUnit(ctx, unit, ux, uy, spritesReady);
}
this._activeCityUnitQueue = null;
}

// Pass LAST — city names remain readable above units, exactly as MGE's
// WaitingAnimation redraws them after its active-unit frame.
if (this._cityNameQueue) {
for (const { city, x: cx, y: cy } of this._cityNameQueue) {
this._drawCityName(ctx, city, cx, cy);
Expand Down Expand Up @@ -2635,6 +2670,10 @@ export class MapRenderer {

// Game-over screen
if (!this._wizard && !this._retireStage && this.gameState.gameOver) this._drawGameOver(ctx, canvasW, canvasH);

// Modern support feature, intentionally topmost so its controls cannot be
// obscured by the MGE screens included in the captured report.
if (this._bugReportDialog) this._drawBugReportDialog(ctx, canvasW, canvasH);
}

_drawGameOver(ctx, canvasW, canvasH) {
Expand Down Expand Up @@ -3136,6 +3175,9 @@ export class MapRenderer {
case 'game_load':
this._loadGame();
return true;
case 'game_reportbug':
this._openBugReportDialog();
return true;
case 'game_options':
this._gameOptionsDialog = true;
this._gameOptionsRects = [];
Expand Down Expand Up @@ -3829,6 +3871,7 @@ export class MapRenderer {
this._negotiationScreen = null;
this._aiPeaceProposal = null;
this._captureDialog = null;
this._bugReportDialog = null;
this._stopWonderVideo();
this._wonderSplash = null;
this._wonderVideo = null;
Expand Down Expand Up @@ -5047,3 +5090,4 @@ applyWizardMixin(MapRenderer);
applyDialogsMixin(MapRenderer);
applyAdvisorsMixin(MapRenderer);
applyInfoScreensMixin(MapRenderer);
applyBugReportMixin(MapRenderer);
Loading
Loading