[Scummvm-git-logs] scummvm master -> 3a7b808800bb90fc3ec748f01894a5eb2ccf88e6

neuromancer noreply at scummvm.org
Tue Sep 1 10:07:50 UTC 2026


This automated email contains information about 5 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
0d74b4e06c COLONY: make sure dos rendering follows the original source
6bb6a93d98 COLONY: fix DOS polygon fills and eye rendering
5de262b804 COLONY: fix aspect ratio rendering for DOS release
f5786b13ba COLONY: better rendering of stair going down
3a7b808800 COLONY: improved instructions in the reactor console


Commit: 0d74b4e06c5b686396f7d1617797ca8ce968723f
    https://github.com/scummvm/scummvm/commit/0d74b4e06c5b686396f7d1617797ca8ce968723f
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-01T12:07:23+02:00

Commit Message:
COLONY: make sure dos rendering follows the original source

Changed paths:
    engines/colony/colony.cpp
    engines/colony/render.cpp
    engines/colony/render_internal.h


diff --git a/engines/colony/colony.cpp b/engines/colony/colony.cpp
index 7586914bf0b..47320c98dbd 100644
--- a/engines/colony/colony.cpp
+++ b/engines/colony/colony.cpp
@@ -150,7 +150,8 @@ ColonyEngine::ColonyEngine(OSystem *syst, const ADGameDescription *gd) : Engine(
 	_widescreen = ConfMan.getBool("widescreen_mod");
 	_invertY = ConfMan.getBool("invert_y");
 
-	// Render mode: EGA (DOS wireframe default) or Macintosh (filled polygons)
+	// Render mode: EGA or Macintosh. The shipped DOS launcher passes /fill,
+	// so both platforms start with filled polygons.
 	if (!ConfMan.hasKey("render_mode") || ConfMan.get("render_mode").empty())
 		_renderMode = Common::kRenderDefault;
 	else
@@ -163,7 +164,7 @@ ColonyEngine::ColonyEngine(OSystem *syst, const ADGameDescription *gd) : Engine(
 			_renderMode = Common::kRenderEGA;
 	}
 
-	_wireframe = !isMacRenderMode();
+	_wireframe = false;
 	_fullscreen = false;
 	_speedShift = 2; // DOS default: speedshift=1, but 2 feels better with our frame rate
 	_moveForward = false;
diff --git a/engines/colony/render.cpp b/engines/colony/render.cpp
index aa9b9fb8e25..1176e970289 100644
--- a/engines/colony/render.cpp
+++ b/engines/colony/render.cpp
@@ -43,24 +43,23 @@ const int g_indexTable[4][10] = {
 	{0, 0,  0, 1,  1,  0,  0, -1,  2, 1}
 };
 
-// DOS lsColor table from ROBOCOLR.C  maps color index to rendering attributes.
-// Fields: monochrome (MONOCHROME  Mac B&W dither pattern, matches MacPattern enum),
-//         backColor (BACKCOLOR), fillColor (FILLCOLOR), lineFillColor (LINEFILLCOLOR),
-//         lineColor (LINECOLOR), pattern (PATTERN).
+// DOS lsColor table from ROBOCOLR.C maps color index to rendering attributes.
+// Source column order: MONOCHROME, LINEFILLCOLOR, FILLCOLOR, BACKCOLOR,
+//                      LINECOLOR, PATTERN.
 // DrawPrism uses: polyfill ON → fill with fillColor/backColor/pattern, outline with lineFillColor.
 //                 polyfill OFF → outline only with lineColor.
 // Mac B&W uses: monochrome field for dither pattern (WHITE/LTGRAY/GRAY/DKGRAY/BLACK/CLEAR).
 struct DOSColorEntry {
 	uint8 monochrome;
-	uint8 backColor;
-	uint8 fillColor;
 	uint8 lineFillColor;
+	uint8 fillColor;
+	uint8 backColor;
 	uint8 lineColor;
 	uint8 pattern;
 };
 
 const DOSColorEntry g_dosColors[79] = {
-	//                     MONO BK  FILL LFIL LINE PAT
+	//                     MONO LFIL FILL BK  LINE PAT
 	/* 0  cCLEAR      */ { 5,  0,  0,  0,  0, 1},
 	/* 1  cBLACK      */ { 4,  0,  0,  0,  0, 1},
 	/* 2  cBLUE       */ { 2,  0,  1,  1,  1, 1},
@@ -181,6 +180,37 @@ const DOSColorEntry &lookupDOSColor(int colorIdx, int level) {
 	}
 }
 
+// Reproduce the DOS driver's SetFill/FILLPATTERN material setup. Patterns 3,
+// 4 and 5 are respectively 50%, 25% and 12.5% foreground coverage; pattern 1
+// is a solid FILLCOLOR fill.
+const byte *setupDOSMaterial(Renderer *gfx, const DOSColorEntry &color) {
+	const byte *stipple = nullptr;
+	switch (color.pattern) {
+	case 3:
+		stipple = kStippleGray;
+		break;
+	case 4:
+		stipple = kStippleLtGray;
+		break;
+	case 5:
+		stipple = kDOSStipplePattern5;
+		break;
+	default:
+		break;
+	}
+
+	if (stipple) {
+		gfx->setMacColors(color.fillColor, color.backColor);
+		gfx->setStippleData(stipple);
+		gfx->setWireframe(true, color.backColor);
+	} else {
+		gfx->setStippleData(nullptr);
+		gfx->setWireframe(true, color.fillColor);
+	}
+
+	return stipple;
+}
+
 // Map ObjColor → Mac B&W dither pattern (from ROBOCOLR.C MONOCHROME field).
 // The monochrome field in the DOS table matches MacPattern enum values directly:
 // WHITE=0, LTGRAY=1, GRAY=2, DKGRAY=3, BLACK=4, CLEAR=5.
@@ -613,17 +643,15 @@ void ColonyEngine::draw3DPrism(Thing &obj, const PrismPartDef &def, bool useLook
 					_gfx->draw3DPolygon(px, py, pz, count, 0); // black outline
 					_gfx->setStippleData(nullptr);
 				} else {
-					// EGA: per-surface colors from DOS lsColor table.
-					// polyfill ON  → B&W fill (from MONOCHROME field), colored LINECOLOR outline.
+					// EGA: per-surface materials from the DOS lsColor table.
+					// polyfill ON  → FILLCOLOR/BACKCOLOR/PATTERN fill, LINEFILLCOLOR outline.
 					// polyfill OFF → outline only with LINECOLOR.
 					const DOSColorEntry &dc = lookupDOSColor(colorIdx, _level);
 					if (!_wireframe) {
-						// Polyfill mode: B&W fill + colored LINECOLOR outline.
-						// LINECOLOR (not LINEFILLCOLOR)  has proper contrast against B&W fills.
-						if (dc.monochrome == kPatternClear)
-							continue;
-						_gfx->setWireframe(true, 7); // all surfaces white; colored outlines provide distinction
-						_gfx->draw3DPolygon(px, py, pz, count, (uint32)dc.lineColor);
+						const byte *stipple = setupDOSMaterial(_gfx, dc);
+						_gfx->draw3DPolygon(px, py, pz, count, (uint32)dc.lineFillColor);
+						if (stipple)
+							_gfx->setStippleData(nullptr);
 					} else {
 						// Wireframe only: LINECOLOR outline, no fill.
 						_gfx->draw3DPolygon(px, py, pz, count, (uint32)dc.lineColor);
@@ -945,9 +973,9 @@ void ColonyEngine::renderCorridor3D() {
 			ceilColor = wallFill;
 		}
 	} else {
-		// IBM_DISP.C: lit → BackColor(vWHITE)=15, color_wall=vwall_Light=0 (black lines on white bg)
+		// IBM_DISP.C: lit → BackColor(vWHITE)=7, color_wall=vwall_Light=0 (black lines on white bg)
 		//             dark → BackColor(vBLACK)=0, color_wall=vINTWHITE=15 (white lines on black bg)
-		wallFill = lit ? (macMode ? 255 : 15) : 0;
+		wallFill = lit ? (macMode ? 255 : 7) : 0;
 		wallLine = lit ? 0 : (macMode ? 255 : 15);
 		floorColor = macMode ? (lit ? 255 : 0) : wallFill;
 		ceilColor  = macMode ? (lit ? 255 : 0) : wallFill;
@@ -1021,9 +1049,9 @@ void ColonyEngine::renderCorridor3D() {
 	// Full depth range  objects beat walls and features at the same distance.
 	_gfx->setDepthRange(0.0f, 1.0f);
 
-	// F7 toggles object fill.
-	// EGA: default is filled (wall background); F7 = outline-only (see-through).
-	// Mac: default is per-surface fill; F7 = "Fast mode" (outline-only).
+	// F8 toggles object fill.
+	// EGA: default is filled (wall background); F8 = outline-only (see-through).
+	// Mac: default is per-surface fill; F8 = "Fast mode" (outline-only).
 	if (_wireframe) {
 		_gfx->setWireframe(true); // No fill = outline-only objects
 	}
diff --git a/engines/colony/render_internal.h b/engines/colony/render_internal.h
index 3d61af62c9f..0e0bd948502 100644
--- a/engines/colony/render_internal.h
+++ b/engines/colony/render_internal.h
@@ -84,6 +84,19 @@ static const byte kStippleGray[128] = {
 	0xAA,0xAA,0xAA,0xAA, 0x55,0x55,0x55,0x55, 0xAA,0xAA,0xAA,0xAA, 0x55,0x55,0x55,0x55,
 };
 
+// DOS FILLPATTERN 5 from METAWNDO.EXE: 12.5% foreground coverage.
+// The driver's 8x8 rows (88 00 22 00 88 00 22 00) are tiled to 32x32.
+static const byte kDOSStipplePattern5[128] = {
+	0x88,0x88,0x88,0x88, 0x00,0x00,0x00,0x00, 0x22,0x22,0x22,0x22, 0x00,0x00,0x00,0x00,
+	0x88,0x88,0x88,0x88, 0x00,0x00,0x00,0x00, 0x22,0x22,0x22,0x22, 0x00,0x00,0x00,0x00,
+	0x88,0x88,0x88,0x88, 0x00,0x00,0x00,0x00, 0x22,0x22,0x22,0x22, 0x00,0x00,0x00,0x00,
+	0x88,0x88,0x88,0x88, 0x00,0x00,0x00,0x00, 0x22,0x22,0x22,0x22, 0x00,0x00,0x00,0x00,
+	0x88,0x88,0x88,0x88, 0x00,0x00,0x00,0x00, 0x22,0x22,0x22,0x22, 0x00,0x00,0x00,0x00,
+	0x88,0x88,0x88,0x88, 0x00,0x00,0x00,0x00, 0x22,0x22,0x22,0x22, 0x00,0x00,0x00,0x00,
+	0x88,0x88,0x88,0x88, 0x00,0x00,0x00,0x00, 0x22,0x22,0x22,0x22, 0x00,0x00,0x00,0x00,
+	0x88,0x88,0x88,0x88, 0x00,0x00,0x00,0x00, 0x22,0x22,0x22,0x22, 0x00,0x00,0x00,0x00,
+};
+
 static const byte kStippleDkGray[128] = {
 	// 0x77,0xDD alternating rows = 75% coverage (dense dots)
 	0x77,0x77,0x77,0x77, 0xDD,0xDD,0xDD,0xDD, 0x77,0x77,0x77,0x77, 0xDD,0xDD,0xDD,0xDD,


Commit: 6bb6a93d9807fb9e2591f9759a5d9e6e7aa76194
    https://github.com/scummvm/scummvm/commit/6bb6a93d9807fb9e2591f9759a5d9e6e7aa76194
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-01T12:07:23+02:00

Commit Message:
COLONY: fix DOS polygon fills and eye rendering

Changed paths:
    engines/colony/colony.h
    engines/colony/render.cpp
    engines/colony/render_features.cpp
    engines/colony/render_internal.h
    engines/colony/render_objects.cpp


diff --git a/engines/colony/colony.h b/engines/colony/colony.h
index a8178fd0114..c063e59b80a 100644
--- a/engines/colony/colony.h
+++ b/engines/colony/colony.h
@@ -203,8 +203,21 @@ enum ObjectType {
 enum ObjColor {
 	kColorClear = 0,
 	kColorBlack = 1,
+	kColorBlue = 2,
+	kColorGreen = 3,
+	kColorCyan = 4,
+	kColorRed = 5,
+	kColorMagenta = 6,
+	kColorBrown = 7,
+	kColorWhite = 8,
 	kColorDkGray = 9,
+	kColorLtBlue = 10,
 	kColorLtGreen = 11,
+	kColorLtCyan = 12,
+	kColorLtRed = 13,
+	kColorLtMagenta = 14,
+	kColorYellow = 15,
+	kColorIntWhite = 16,
 	kColorBath = 17,
 	kColorWater = 18,
 	kColorSilver = 19,
@@ -264,6 +277,8 @@ enum ObjColor {
 	kColorBottomSnoop = 60,
 	kColorUPyramid = 68,
 	kColorShadow = 74,
+	kColorLtGray = 75,
+	kColorGray = 76,
 	// Animated reactor/power suit colors (Mac: c_hcore1..c_hcore4, c_ccore, c_color0..c_color3)
 	kColorHCore1 = 100,
 	kColorHCore2 = 101,
@@ -696,10 +711,13 @@ private:
 	void draw3DPrism(Thing &obj, const PrismPartDef &def, bool useLook, int colorOverride = -1, bool accumulateBounds = false, bool forceVisible = false);
 	void draw3DLeaf(const Thing &obj, const PrismPartDef &def);
 	void draw3DSphere(Thing &obj, int pt0x, int pt0y, int pt0z,
-		int pt1x, int pt1y, int pt1z, uint32 fillColor, uint32 outlineColor, bool accumulateBounds = false);
-	void drawPrismOval3D(Thing &thing, const PrismPartDef &def, bool useLook, int colorOverride, bool forceVisible = false);
+		int pt1x, int pt1y, int pt1z, uint32 fillColor, uint32 outlineColor,
+		bool accumulateBounds = false, bool dosFill = true);
+	void drawPrismOval3D(Thing &thing, const PrismPartDef &def, bool useLook, int colorOverride,
+		bool forceVisible = false, bool dosFill = true);
 	void drawEyeOverlays3D(Thing &thing, const PrismPartDef &irisDef, int irisColorOverride,
-		const PrismPartDef &pupilDef, int pupilColorOverride, bool useLook);
+		const PrismPartDef &pupilDef, int pupilColorOverride, bool useLook, bool dosFill = true);
+	void drawDOSEyeSlit3D(Thing &thing, const PrismPartDef &irisDef, bool useLook);
 	void drawBodyEye3D(Thing &obj, int eyeballColor, int pupilColor, float pull);
 	void drawEnemyEye3D(Thing &obj, Thing &eye, int eyeballColor, int irisColor, int pupilColor);
 	void pullTowardCamera(float *px, float *py, float *pz, int count) const;
diff --git a/engines/colony/render.cpp b/engines/colony/render.cpp
index 1176e970289..3628d03fa90 100644
--- a/engines/colony/render.cpp
+++ b/engines/colony/render.cpp
@@ -180,12 +180,11 @@ const DOSColorEntry &lookupDOSColor(int colorIdx, int level) {
 	}
 }
 
-// Reproduce the DOS driver's SetFill/FILLPATTERN material setup. Patterns 3,
-// 4 and 5 are respectively 50%, 25% and 12.5% foreground coverage; pattern 1
-// is a solid FILLCOLOR fill.
-const byte *setupDOSMaterial(Renderer *gfx, const DOSColorEntry &color) {
+// Reproduce the DOS driver's SetFill/FILLPATTERN setup. Patterns 3, 4 and 5
+// are respectively 50%, 25% and 12.5% foreground coverage; pattern 1 is solid.
+void setupDOSFill(Renderer *gfx, uint32 fillColor, uint32 backColor, int pattern) {
 	const byte *stipple = nullptr;
-	switch (color.pattern) {
+	switch (pattern) {
 	case 3:
 		stipple = kStippleGray;
 		break;
@@ -200,15 +199,19 @@ const byte *setupDOSMaterial(Renderer *gfx, const DOSColorEntry &color) {
 	}
 
 	if (stipple) {
-		gfx->setMacColors(color.fillColor, color.backColor);
+		gfx->setMacColors(fillColor, backColor);
 		gfx->setStippleData(stipple);
-		gfx->setWireframe(true, color.backColor);
+		gfx->setWireframe(true, backColor);
 	} else {
 		gfx->setStippleData(nullptr);
-		gfx->setWireframe(true, color.fillColor);
+		gfx->setWireframe(true, fillColor);
 	}
+}
 
-	return stipple;
+uint32 setupDOSMaterial(Renderer *gfx, int colorIdx, int level) {
+	const DOSColorEntry &color = lookupDOSColor(colorIdx, level);
+	setupDOSFill(gfx, color.fillColor, color.backColor, color.pattern);
+	return color.lineFillColor;
 }
 
 // Map ObjColor → Mac B&W dither pattern (from ROBOCOLR.C MONOCHROME field).
@@ -648,10 +651,9 @@ void ColonyEngine::draw3DPrism(Thing &obj, const PrismPartDef &def, bool useLook
 					// polyfill OFF → outline only with LINECOLOR.
 					const DOSColorEntry &dc = lookupDOSColor(colorIdx, _level);
 					if (!_wireframe) {
-						const byte *stipple = setupDOSMaterial(_gfx, dc);
-						_gfx->draw3DPolygon(px, py, pz, count, (uint32)dc.lineFillColor);
-						if (stipple)
-							_gfx->setStippleData(nullptr);
+						const uint32 outlineColor = setupDOSMaterial(_gfx, colorIdx, _level);
+						_gfx->draw3DPolygon(px, py, pz, count, outlineColor);
+						_gfx->setStippleData(nullptr);
 					} else {
 						// Wireframe only: LINECOLOR outline, no fill.
 						_gfx->draw3DPolygon(px, py, pz, count, (uint32)dc.lineColor);
@@ -731,7 +733,7 @@ void ColonyEngine::pullTowardCamera(float *px, float *py, float *pz, int count)
 
 void ColonyEngine::draw3DSphere(Thing &obj, int pt0x, int pt0y, int pt0z,
 	int pt1x, int pt1y, int pt1z,
-	uint32 fillColor, uint32 outlineColor, bool accumulateBounds) {
+	uint32 fillColor, uint32 outlineColor, bool accumulateBounds, bool dosFill) {
 	// Original Colony eye/ball primitives store the bottom pole in pt0 and the
 	// sphere center in pt1. The classic renderer builds the oval from the
 	// screen-space delta between those two projected points, so the world-space
@@ -840,25 +842,30 @@ void ColonyEngine::draw3DSphere(Thing &obj, int pt0x, int pt0y, int pt0z,
 		_gfx->draw3DPolygon(px, py, pz, N, line);
 		if (stipple)
 			_gfx->setStippleData(nullptr);
-	} else if (lit) {
-		if (isMacRenderMode()) {
-			int pattern = lookupMacPattern((int)fillColor, _level);
-			if (pattern == kPatternClear)
-				pattern = kPatternGray;
-			if (!_wireframe) {
-				_gfx->setWireframe(true, pattern == kPatternBlack ? 0 : 255);
-			}
-			_gfx->setStippleData(kMacStippleData[pattern]);
-			_gfx->draw3DPolygon(px, py, pz, N, lookupDOSColor((int)outlineColor, _level).lineColor);
+	} else if (!isMacRenderMode()) {
+		// DOS eye ovals are independent of the polygon-fill toggle. FillOval()
+		// uses the selected material while FrameOval() uses the caller's pen.
+		const DOSColorEntry &fill = lookupDOSColor((int)fillColor, _level);
+		const DOSColorEntry &outline = lookupDOSColor((int)outlineColor, _level);
+		if (dosFill)
+			setupDOSFill(_gfx, fill.fillColor, fill.backColor, fill.pattern);
+		else {
 			_gfx->setStippleData(nullptr);
-		} else {
-			const DOSColorEntry &fill = lookupDOSColor((int)fillColor, _level);
-			const DOSColorEntry &outline = lookupDOSColor((int)outlineColor, _level);
-			if (!_wireframe) {
-				_gfx->setWireframe(true, fill.fillColor);
-			}
-			_gfx->draw3DPolygon(px, py, pz, N, outline.lineColor);
+			_gfx->setWireframe(true);
 		}
+		_gfx->draw3DPolygon(px, py, pz, N, outline.lineColor);
+		_gfx->setStippleData(nullptr);
+		if (_wireframe)
+			_gfx->setWireframe(true);
+	} else if (lit) {
+		int pattern = lookupMacPattern((int)fillColor, _level);
+		if (pattern == kPatternClear)
+			pattern = kPatternGray;
+		if (!_wireframe)
+			_gfx->setWireframe(true, pattern == kPatternBlack ? 0 : 255);
+		_gfx->setStippleData(kMacStippleData[pattern]);
+		_gfx->draw3DPolygon(px, py, pz, N, lookupDOSColor((int)outlineColor, _level).lineColor);
+		_gfx->setStippleData(nullptr);
 	} else {
 		// Unlit: black fill, white outline.
 		if (!_wireframe) {
diff --git a/engines/colony/render_features.cpp b/engines/colony/render_features.cpp
index 7d5ca1af7e2..50f90d4f5b3 100644
--- a/engines/colony/render_features.cpp
+++ b/engines/colony/render_features.cpp
@@ -512,7 +512,7 @@ void ColonyEngine::drawCellFeature3D(int cellX, int cellY) {
 	const bool macMode = isMacRenderMode();
 	const bool macColors = isMacColorMode();
 
-	// Helper lambda: draw a filled hole polygon with Mac color or B&W fallback
+	// Helper lambda: draw a filled hole polygon with the platform material.
 	auto drawHolePoly = [&](const float *u, const float *v, int cnt, int macIdx) {
 		if (macColors) {
 			uint32 fg = packMacColor(_macColors[macIdx].fg);
@@ -526,7 +526,12 @@ void ColonyEngine::drawCellFeature3D(int cellX, int cellY) {
 			_gfx->setStippleData(kStippleGray);
 			wallPolygon(corners, u, v, cnt, 0);
 			_gfx->setStippleData(nullptr);
+		} else if (!_wireframe) {
+			const uint32 outline = setupDOSMaterial(_gfx, kColorLtGray, _level);
+			wallPolygon(corners, u, v, cnt, outline);
+			_gfx->setStippleData(nullptr);
 		} else {
+			_gfx->setWireframe(true);
 			wallPolygon(corners, u, v, cnt, holeColor);
 		}
 	};
@@ -554,6 +559,10 @@ void ColonyEngine::drawCellFeature3D(int cellX, int cellY) {
 		float v[4] = {0.0f, 0.0f, 1.0f, 1.0f};
 		if (macMode) {
 			drawHolePoly(u, v, 4, 31); // c_hotplate
+		} else if (!_wireframe) {
+			const uint32 outline = setupDOSMaterial(_gfx, kColorTele, _level);
+			wallPolygon(corners, u, v, 4, outline);
+			_gfx->setStippleData(nullptr);
 		} else {
 			// DOS non-polyfill: X pattern (two diagonals)
 			wallLine(corners, 0.0f, 0.0f, 1.0f, 1.0f, holeColor);
@@ -564,6 +573,12 @@ void ColonyEngine::drawCellFeature3D(int cellX, int cellY) {
 	default:
 		break;
 	}
+
+	_gfx->setStippleData(nullptr);
+	const uint32 wallFill = macColors
+		? packMacColor(_macColors[8 + _level - 1].fg)
+		: (macMode ? 255u : 7u);
+	_gfx->setWireframe(true, wallFill);
 }
 
 float stairStepHeight(const float *vf, const float *vc, int d, int s) {
@@ -620,9 +635,24 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
 			_gfx->setStippleData(nullptr);
 	};
 
+	// DOS SuperPoly(): material fill followed by its LINEFILLCOLOR frame.
+	auto dosFillPoly = [&](const float *u, const float *v, int cnt, int colorIdx) {
+		const uint32 outline = setupDOSMaterial(_gfx, colorIdx, _level);
+		wallPolygon(corners, u, v, cnt, outline);
+		_gfx->setStippleData(nullptr);
+	};
+
+	auto dosFillRecess = [&](const float nearC[4][3], const float farC[4][3],
+			const float *u, const float *v, const float *d, int cnt, int colorIdx) {
+		const uint32 outline = setupDOSMaterial(_gfx, colorIdx, _level);
+		recessQuad(nearC, farC, u, v, d, cnt, outline);
+		_gfx->setStippleData(nullptr);
+	};
+
 	switch (map[0]) {
 	case kWallFeatureDoor: {
-		const uint32 doorColor = macColors ? (uint32)0xFF000000 : (macMode ? 0 : 8);
+		const uint32 doorColor = macColors ? (uint32)0xFF000000 :
+			(macMode ? 0u : (_wireframe ? 8u : 0u));
 		bool shipLevel = (_level == 1 || _level == 5 || _level == 6);
 
 		if (shipLevel) {
@@ -645,15 +675,7 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
 					wallPolygon(corners, uSs, vSs, 8, 0);
 				}
 			} else if (!_wireframe) {
-				if (map[1] != 0) {
-					const byte *stipple = setupMacPattern(_gfx, kPatternLtGray, 0, 15);
-					wallPolygon(corners, uSs, vSs, 8, 0);
-					if (stipple)
-						_gfx->setStippleData(nullptr);
-				} else {
-					_gfx->setWireframe(true, 0);
-					wallPolygon(corners, uSs, vSs, 8, 0);
-				}
+				dosFillPoly(uSs, vSs, 8, map[1] != 0 ? kColorSilver : kColorBlack);
 			} else {
 				_gfx->setWireframe(true);
 				wallPolygon(corners, uSs, vSs, 8, 8);
@@ -671,10 +693,10 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
 		} else {
 			const float xl = 0.25f, xr = 0.75f;
 			const float yb = 0.0f, yt = 0.875f;
+			float ud[4] = {xl, xr, xr, xl};
+			float vd[4] = {yb, yb, yt, yt};
 
 			if (macMode) {
-				float ud[4] = {xl, xr, xr, xl};
-				float vd[4] = {yb, yb, yt, yt};
 				if (map[1] != 0) {
 					if (macColors) {
 						macFillPoly(ud, vd, 4, 16); // c_door
@@ -689,6 +711,16 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
 					wallPolygon(corners, ud, vd, 4, 0);
 					_gfx->setWireframe(true, 255);
 				}
+			} else if (!_wireframe) {
+				dosFillPoly(ud, vd, 4, map[1] != 0 ? kColorLtRed : kColorBlack);
+				if (map[1] == 0) {
+					float farC[4][3];
+					getWallRecess3D(corners, farC);
+					const float floorU[4] = {xl, xl, xr, xr};
+					const float floorV[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+					const float floorD[4] = {0.0f, 1.0f, 1.0f, 0.0f};
+					dosFillRecess(corners, farC, floorU, floorV, floorD, 4, kColorWhite);
+				}
 			}
 
 			wallLine(corners, xl, yb, xl, yt, doorColor);
@@ -697,21 +729,22 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
 			wallLine(corners, xr, yb, xl, yb, doorColor);
 
 			if (map[1] != 0) {
-				wallLine(corners, 0.3125f, 0.4375f, 0.6875f, 0.4375f, doorColor);
+				const uint32 detailColor = macMode ? doorColor : 8u;
+				wallLine(corners, 0.3125f, 0.4375f, 0.6875f, 0.4375f, detailColor);
 			}
 		}
 		break;
 	}
 	case kWallFeatureWindow: {
-		const uint32 winColor = macColors ? (uint32)0xFF000000 : (macMode ? 0 : 8);
+		const uint32 winColor = macColors ? (uint32)0xFF000000 : 0;
 		float xl = 0.25f, xr = 0.75f;
 		float yb = 0.25f, yt = 0.75f;
 		float xc = 0.5f, yc = 0.5f;
+		float uw[4] = {xl, xr, xr, xl};
+		float vw[4] = {yb, yb, yt, yt};
 
 		// Mac: fill window pane
 		if (macMode) {
-			float uw[4] = {xl, xr, xr, xl};
-			float vw[4] = {yb, yb, yt, yt};
 			if (macColors) {
 				macFillPoly(uw, vw, 4, 17); // c_window
 			} else {
@@ -719,6 +752,8 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
 				wallPolygon(corners, uw, vw, 4, 0);
 				_gfx->setStippleData(nullptr);
 			}
+		} else if (!_wireframe) {
+			dosFillPoly(uw, vw, 4, kColorSilver);
 		}
 
 		wallLine(corners, xl, yb, xl, yt, winColor);
@@ -803,6 +838,29 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
 			const float vb[4] = {hgt[6], hgt[6], hgt[6], hgt[6]};
 			const float db[4] = {dep[6], 1.0f, 1.0f, dep[6]};
 			macFillRecess(corners, farC, ub, vb, db, 4, 19, macColors);
+		} else if (!_wireframe) {
+			const float uw[4] = {0.0f, 1.0f, 1.0f, 0.0f};
+			const float vw[4] = {0.0f, 0.0f, 1.0f, 1.0f};
+			const float dw[4] = {1.0f, 1.0f, 1.0f, 1.0f};
+			dosFillRecess(corners, farC, uw, vw, dw, 4, kColorGray);
+
+			for (int i = 6; i >= 0; i--) {
+				const float lowV = (i > 0) ? hgt[i - 1] : 0.0f;
+				const float backD = (i > 0) ? dep[i - 1] : 0.0f;
+				const float uq[4] = {0.0f, 0.0f, 1.0f, 1.0f};
+				const float vRiser[4] = {hgt[i], lowV, lowV, hgt[i]};
+				const float dRiser[4] = {dep[i], dep[i], dep[i], dep[i]};
+				dosFillRecess(corners, farC, uq, vRiser, dRiser, 4, kColorGray);
+
+				const float vTread[4] = {lowV, lowV, lowV, lowV};
+				const float dTread[4] = {backD, dep[i], dep[i], backD};
+				dosFillRecess(corners, farC, uq, vTread, dTread, 4, kColorLtGray);
+			}
+
+			const float ub[4] = {0.0f, 0.0f, 1.0f, 1.0f};
+			const float vb[4] = {hgt[6], hgt[6], hgt[6], hgt[6]};
+			const float db[4] = {dep[6], 1.0f, 1.0f, dep[6]};
+			dosFillRecess(corners, farC, ub, vb, db, 4, kColorLtGray);
 		}
 
 		// Back landing and far mouth
@@ -907,11 +965,11 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
 		const uint32 elevColor = macColors ? (uint32)0xFF000000 : (macMode ? 0 : 8);
 		float xl = 0.2f, xr = 0.8f;
 		float yb = 0.1f, yt = 0.9f;
+		float ue[4] = {xl, xr, xr, xl};
+		float ve[4] = {yb, yb, yt, yt};
 
 		// Mac: fill elevator door
 		if (macMode) {
-			float ue[4] = {xl, xr, xr, xl};
-			float ve[4] = {yb, yb, yt, yt};
 			if (macColors) {
 				macFillPoly(ue, ve, 4, 23); // c_elevator
 			} else {
@@ -919,6 +977,8 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
 				wallPolygon(corners, ue, ve, 4, 0);
 				_gfx->setStippleData(nullptr);
 			}
+		} else if (!_wireframe) {
+			dosFillPoly(ue, ve, 4, kColorGray);
 		}
 
 		wallLine(corners, xl, yb, xl, yt, elevColor);
@@ -940,8 +1000,11 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
 				wallPolygon(corners, uT, vT, 6, 0);
 				_gfx->setStippleData(nullptr);
 			}
+		} else if (!_wireframe) {
+			dosFillPoly(uT, vT, 6, kColorBlack);
 		} else {
-			wallPolygon(corners, uT, vT, 6, 0); // vBLACK outline
+			_gfx->setWireframe(true);
+			wallPolygon(corners, uT, vT, 6, 8); // vDKGRAY outline
 		}
 		break;
 	}
@@ -957,11 +1020,15 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
 
 		if (map[1] == 0) {
 			// Original drawALOpen: solid black opening on both DOS and Mac.
-			if (macMode || !_wireframe)
+			if (macMode) {
 				_gfx->setWireframe(true, 0);
-			else
+				wallPolygon(corners, u, v, 8, 0);
+			} else if (!_wireframe) {
+				dosFillPoly(u, v, 8, kColorBlack);
+			} else {
 				_gfx->setWireframe(true);
-			wallPolygon(corners, u, v, 8, 0);
+				wallPolygon(corners, u, v, 8, 0);
+			}
 		} else {
 			// Mac: fill airlock when closed
 			if (macMode) {
@@ -973,10 +1040,7 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
 					_gfx->setStippleData(nullptr);
 				}
 			} else if (!_wireframe) {
-				const byte *stipple = setupMacPattern(_gfx, kPatternLtGray, 0, 15);
-				wallPolygon(corners, u, v, 8, 0);
-				if (stipple)
-					_gfx->setStippleData(nullptr);
+				dosFillPoly(u, v, 8, kColorLtGray);
 			}
 
 			const uint32 airlockColor = macColors ? (uint32)0xFF000000 : (macMode ? 0 : 8);
@@ -1042,12 +1106,47 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
 					}
 				}
 			}
+		} else if (!_wireframe) {
+			for (int i = 0; i < 4; i++) {
+				const int value = map[i + 1];
+				int material = -1;
+				if (value <= 4) {
+					switch (value) {
+					case 1: material = kColorLtRed;   break;
+					case 2: material = kColorLtBlue;  break;
+					case 3: material = kColorMagenta; break;
+					case 4: material = kColorBlue;    break;
+					default: break; // WHITE leaves the corridor background intact
+					}
+				} else {
+					const int color = (value + _displayCount / 6) % 5;
+					switch (color) {
+					case 1: material = kColorLtRed;   break;
+					case 2: material = kColorMagenta; break;
+					case 3: material = kColorRed;     break;
+					case 4: material = kColorCyan;    break;
+					default: break;
+					}
+				}
+
+				if (material >= 0) {
+					const float vb = (3 - i) / 4.0f;
+					const float vt = (4 - i) / 4.0f;
+					const float ub[4] = {0.0f, 1.0f, 1.0f, 0.0f};
+					const float vb4[4] = {vb, vb, vt, vt};
+					dosFillPoly(ub, vb4, 4, material);
+				}
+			}
+		} else if (map[1] || map[2] || map[3] || map[4]) {
+			_gfx->setWireframe(true);
+			for (int i = 1; i <= 3; i++) {
+				const float v = (float)i / 4.0f;
+				wallLine(corners, 0.0f, v, 1.0f, v, 0);
+			}
 		}
 
-		// EGA / Mac B&W: colored lines at band boundaries.
-		// DOS non-polyfill draws 3 lines; we animate line colors for bands > 4.
-		// Mac color mode uses macFillPoly for band fills instead.
-		if (!macColors) {
+		// Preserve the Mac B&W boundary treatment after its pattern fills.
+		if (macMode && !macColors) {
 			for (int i = 1; i <= 3; i++) {
 				int val = map[i];
 				if (val > 4)
diff --git a/engines/colony/render_internal.h b/engines/colony/render_internal.h
index 0e0bd948502..48b1acdb354 100644
--- a/engines/colony/render_internal.h
+++ b/engines/colony/render_internal.h
@@ -39,6 +39,12 @@ void projectCorridorPointClamped(const Common::Rect &screenR, int look, int look
 	float worldX, float worldY, float worldZ,
 	int &screenX, int &screenY);
 
+// DOS MetaWINDOW polygon material setup. setupDOSFill() accepts EGA palette
+// indices directly; setupDOSMaterial() resolves an lsColor material and returns
+// the LINEFILLCOLOR used to frame the filled polygon.
+void setupDOSFill(Renderer *gfx, uint32 fillColor, uint32 backColor, int pattern);
+uint32 setupDOSMaterial(Renderer *gfx, int colorIdx, int level);
+
 // Mac Classic dither patterns (from colorize.c cColor[].pattern).
 // Mac Classic was a 1-bit B&W display. QuickDraw used 8x8 dither patterns
 // to simulate grayscale: WHITE, LTGRAY, GRAY, DKGRAY, BLACK, CLEAR.
@@ -122,7 +128,7 @@ static const byte *kMacStippleData[] = {
 // Helper: set up Mac color stipple rendering for a given cColor[] pattern.
 // Configures setMacColors/setStippleData/setWireframe for the pattern type.
 // Returns the stipple pointer (null for solid patterns)  caller must clear with setStippleData(nullptr).
-static const byte *setupMacPattern(Renderer *gfx, int pattern, uint32 fg, uint32 bg) {
+inline const byte *setupMacPattern(Renderer *gfx, int pattern, uint32 fg, uint32 bg) {
 	const byte *stipple = (pattern >= 1 && pattern <= 3) ? kMacStippleData[pattern] : nullptr;
 	if (stipple) {
 		gfx->setMacColors(fg, bg);
diff --git a/engines/colony/render_objects.cpp b/engines/colony/render_objects.cpp
index e0dbf38e32e..caa7a692fbd 100644
--- a/engines/colony/render_objects.cpp
+++ b/engines/colony/render_objects.cpp
@@ -50,26 +50,26 @@ int mapEyeOverlayColorToMacColor(int colorIdx, int level) {
 	}
 }
 
-uint8 mapEyeOverlayColorToDOSFill(int colorIdx, int level) {
+uint32 setupDOSEyeOverlayMaterial(Renderer *gfx, int colorIdx, int level) {
 	switch (colorIdx) {
 	case kColorBlack:
 	case kColorPupil:
+		setupDOSFill(gfx, 0, 0, 1); // solid vBLACK
 		return 0;
-	case kColorEyeball:
-		return 15;
-	case kColorIris:
-	case kColorEyeIris:
-	case kColorMiniEyeIris:
 	case kColorDroneEye:
 	case kColorSoldierEye:
 	case kColorQueenEye:
-		return 1;
+		// QUEEN.C draweyes(): vGREEN over vYELLOW with pattern 4 on
+		// level 7; otherwise vGREEN over vINTWHITE with pattern 3.
+		setupDOSFill(gfx, 2, level == 7 ? 14 : 15, level == 7 ? 4 : 3);
+		return 2;
+	case kColorIris:
+	case kColorEyeIris:
+	case kColorMiniEyeIris:
 	default:
-		if (colorIdx >= 0 && colorIdx <= 15)
-			return (uint8)colorIdx;
-		if (colorIdx == kColorQueenBody && level == 7)
-			return 15;
-		return 7;
+		// EYE.C/PYRAMID.C: vBLUE over vINTWHITE with pattern 3.
+		setupDOSFill(gfx, 1, 15, 3);
+		return 1;
 	}
 }
 
@@ -1318,7 +1318,8 @@ void ColonyEngine::drawStaticObjects() {
 	_insight = _weapons > 0 && hasAimedRobotTarget();
 }
 
-void ColonyEngine::drawPrismOval3D(Thing &thing, const PrismPartDef &def, bool useLook, int colorOverride, bool forceVisible) {
+void ColonyEngine::drawPrismOval3D(Thing &thing, const PrismPartDef &def, bool useLook,
+		int colorOverride, bool forceVisible, bool dosFill) {
 	if (def.pointCount < 4 || def.surfaceCount < 1)
 		return;
 
@@ -1412,21 +1413,25 @@ void ColonyEngine::drawPrismOval3D(Thing &thing, const PrismPartDef &def, bool u
 		_gfx->draw3DPolygon(px, py, pz, kSegments, line);
 		if (stipple)
 			_gfx->setStippleData(nullptr);
-	} else if (lit) {
-		if (isMacRenderMode()) {
-			int pattern = mapEyeOverlayColorToMacPattern(fillColorIdx);
-			if (pattern == kPatternClear)
-				return;
-			if (!_wireframe)
-				_gfx->setWireframe(true, pattern == kPatternBlack ? 0 : 255);
-			_gfx->setStippleData(kMacStippleData[pattern]);
-			_gfx->draw3DPolygon(px, py, pz, kSegments, 0);
+	} else if (!isMacRenderMode()) {
+		const uint32 outline = setupDOSEyeOverlayMaterial(_gfx, fillColorIdx, _level);
+		if (!dosFill) {
 			_gfx->setStippleData(nullptr);
-		} else {
-			if (!_wireframe)
-				_gfx->setWireframe(true, mapEyeOverlayColorToDOSFill(fillColorIdx, _level));
-			_gfx->draw3DPolygon(px, py, pz, kSegments, 0);
+			_gfx->setWireframe(true);
 		}
+		_gfx->draw3DPolygon(px, py, pz, kSegments, outline);
+		_gfx->setStippleData(nullptr);
+		if (_wireframe)
+			_gfx->setWireframe(true);
+	} else if (lit) {
+		int pattern = mapEyeOverlayColorToMacPattern(fillColorIdx);
+		if (pattern == kPatternClear)
+			return;
+		if (!_wireframe)
+			_gfx->setWireframe(true, pattern == kPatternBlack ? 0 : 255);
+		_gfx->setStippleData(kMacStippleData[pattern]);
+		_gfx->draw3DPolygon(px, py, pz, kSegments, 0);
+		_gfx->setStippleData(nullptr);
 	} else {
 		if (!_wireframe)
 			_gfx->setWireframe(true, 0);
@@ -1439,25 +1444,94 @@ void ColonyEngine::drawPrismOval3D(Thing &thing, const PrismPartDef &def, bool u
 	thing.where.zmx = MAX(thing.where.zmx, bottom);
 }
 
+void ColonyEngine::drawDOSEyeSlit3D(Thing &thing, const PrismPartDef &irisDef, bool useLook) {
+	if (irisDef.pointCount < 4)
+		return;
+
+	const uint8 ang = (useLook ? thing.where.look : thing.where.ang) + 32;
+	const int32 rotCos = _cost[ang];
+	const int32 rotSin = _sint[ang];
+	float eye[4][3];
+	for (int i = 0; i < 4; i++) {
+		const int32 rx = ((int32)irisDef.points[i][0] * rotCos -
+			(int32)irisDef.points[i][1] * rotSin) >> 7;
+		const int32 ry = ((int32)irisDef.points[i][0] * rotSin +
+			(int32)irisDef.points[i][1] * rotCos) >> 7;
+		eye[i][0] = (float)(rx + thing.where.xloc);
+		eye[i][1] = (float)(ry + thing.where.yloc);
+		eye[i][2] = (float)(irisDef.points[i][2] - 160);
+	}
+
+	float center[3];
+	float halfWidth[3];
+	float halfHeight[3];
+	for (int axis = 0; axis < 3; axis++) {
+		center[axis] = (eye[0][axis] + eye[1][axis] + eye[2][axis] + eye[3][axis]) * 0.25f;
+		// QUEEN.C uses an iris-width/8 pen and leaves 1/16 of the iris
+		// height clear at each end of the vertical slit.
+		halfWidth[axis] = (eye[1][axis] - eye[3][axis]) / 16.0f;
+		halfHeight[axis] = (eye[2][axis] - eye[0][axis]) * (7.0f / 16.0f);
+	}
+
+	float px[4];
+	float py[4];
+	float pz[4];
+	const float widthSign[4] = {-1.0f, 1.0f, 1.0f, -1.0f};
+	const float heightSign[4] = {-1.0f, -1.0f, 1.0f, 1.0f};
+	for (int i = 0; i < 4; i++) {
+		px[i] = center[0] + widthSign[i] * halfWidth[0] + heightSign[i] * halfHeight[0];
+		py[i] = center[1] + widthSign[i] * halfWidth[1] + heightSign[i] * halfHeight[1];
+		pz[i] = center[2] + widthSign[i] * halfWidth[2] + heightSign[i] * halfHeight[2];
+	}
+
+	pullTowardCamera(px, py, pz, 4);
+	setupDOSFill(_gfx, 0, 0, 1);
+	_gfx->draw3DPolygon(px, py, pz, 4, 0);
+	_gfx->setStippleData(nullptr);
+	if (_wireframe)
+		_gfx->setWireframe(true);
+}
+
 void ColonyEngine::drawEyeOverlays3D(Thing &thing, const PrismPartDef &irisDef, int irisColorOverride,
-		const PrismPartDef &pupilDef, int pupilColorOverride, bool useLook) {
+		const PrismPartDef &pupilDef, int pupilColorOverride, bool useLook, bool dosFill) {
 	if (!isProjectedPrismSurfaceVisible(_screenR, thing, irisDef, useLook, 0,
 			_me.look, _me.lookY, _me.xloc, _me.yloc, _sint, _cost)) {
 		return;
 	}
 
-	// Original Mac eye rendering uses the iris quad as the single visibility
-	// gate, then draws both iris and pupil unconditionally.
-	drawPrismOval3D(thing, irisDef, useLook, irisColorOverride, true);
-	drawPrismOval3D(thing, pupilDef, useLook, pupilColorOverride, true);
+	// The original uses the iris quad as the single visibility gate, then draws
+	// both overlays unconditionally.
+	drawPrismOval3D(thing, irisDef, useLook, irisColorOverride, true, dosFill);
+
+	const int irisColor = irisColorOverride >= 0 ? irisColorOverride : irisDef.surfaces[0][0];
+	const bool dosEnemyEye = !isMacRenderMode() &&
+		(irisColor == kColorQueenEye || irisColor == kColorDroneEye || irisColor == kColorSoldierEye);
+	if (dosEnemyEye && _level == 7) {
+		drawDOSEyeSlit3D(thing, irisDef, useLook);
+	} else {
+		const int pupilColor = isMacRenderMode() ? pupilColorOverride : kColorPupil;
+		drawPrismOval3D(thing, pupilDef, useLook, pupilColor, true, dosFill);
+	}
 }
 
 // draweyes() of Queen/Drone/Soldier. Writes off: a turned eye leaves the iris
 // quad behind the camera-facing ball, which would then clip it.
 void ColonyEngine::drawEnemyEye3D(Thing &obj, Thing &eye, int eyeballColor, int irisColor, int pupilColor) {
+	bool dosFill = true;
+	if (!isMacRenderMode()) {
+		const uint8 eyeAng = obj.where.ang + 32;
+		const int32 eyeBaseX = obj.where.xloc + (_cost[eyeAng] >> 1);
+		const int32 eyeBaseY = obj.where.yloc + (_sint[eyeAng] >> 1);
+		const int64 dx = eyeBaseX - _me.xloc;
+		const int64 dy = eyeBaseY - _me.yloc;
+		dosFill = dx * dx + dy * dy > 64 * 64;
+	}
+
 	_gfx->setDepthState(true, false);
-	draw3DSphere(eye, 0, 0, 130, 0, 0, 155, eyeballColor, kColorBlack, true);
-	drawEyeOverlays3D(eye, kQIrisDef, irisColor, kQPupilDef, pupilColor, true);
+	const int ballColor = isMacRenderMode() ? eyeballColor : kColorRed;
+	const int ballOutline = isMacRenderMode() ? kColorBlack : kColorIntWhite;
+	draw3DSphere(eye, 0, 0, 130, 0, 0, 155, ballColor, ballOutline, true, dosFill);
+	drawEyeOverlays3D(eye, kQIrisDef, irisColor, kQPupilDef, pupilColor, true, dosFill);
 	_gfx->setDepthState(true, true);
 	mergeObjectBounds(obj.where, eye.where);
 }
@@ -1468,7 +1542,8 @@ void ColonyEngine::drawEnemyEye3D(Thing &obj, Thing &eye, int eyeballColor, int
 void ColonyEngine::drawBodyEye3D(Thing &obj, int eyeballColor, int pupilColor, float pull) {
 	_eyeDepthPull = pull;
 	_gfx->setDepthState(true, false);
-	draw3DSphere(obj, 0, 0, 175, 0, 0, 200, eyeballColor, kColorBlack, true);
+	const int ballColor = isMacRenderMode() ? eyeballColor : kColorEye;
+	draw3DSphere(obj, 0, 0, 175, 0, 0, 200, ballColor, kColorBlack, true);
 	drawEyeOverlays3D(obj, kPIrisDef, -1, kPPupilDef, pupilColor, false);
 	_gfx->setDepthState(true, true);
 	_eyeDepthPull = 0.0f;
@@ -1586,7 +1661,9 @@ void ColonyEngine::drawInterpolatedGrowEye(Thing &obj, int fromStage, int toStag
 	const int z0 = interpolatedRobotPoint(sphereZ[fromStage][0], sphereZ[toStage][0], progress);
 	const int z1 = interpolatedRobotPoint(sphereZ[fromStage][1], sphereZ[toStage][1], progress);
 	_gfx->setDepthState(true, false);
-	draw3DSphere(obj, 0, 0, z0, 0, 0, z1, eyeballColor, kColorBlack, true);
+	const int ballColor = isMacRenderMode() ? eyeballColor : kColorEye;
+	const int ballOutline = isMacRenderMode() ? kColorBlack : kColorEye;
+	draw3DSphere(obj, 0, 0, z0, 0, 0, z1, ballColor, ballOutline, true);
 
 	const PrismPartDef &fromIris = growEyeIrisDefForStage(fromStage);
 	const PrismPartDef &toIris = growEyeIrisDefForStage(toStage);
@@ -1649,6 +1726,8 @@ bool ColonyEngine::drawInterpolatedGrowRobot(Thing &obj, int eyeballColor, int p
 bool ColonyEngine::drawStaticObjectPrisms3D(Thing &obj) {
 	const int eyeballColor = (_level == 1 || _level == 7) ? kColorPupil : kColorEyeball;
 	const int pupilColor = (_level == 1 || _level == 7) ? kColorEyeball : kColorPupil;
+	const int standaloneBallColor = isMacRenderMode() ? eyeballColor : kColorEye;
+	const int standaloneBallOutline = isMacRenderMode() ? kColorBlack : kColorEye;
 
 	switch (obj.type) {
 	case kObjConsole:
@@ -1933,7 +2012,8 @@ bool ColonyEngine::drawStaticObjectPrisms3D(Thing &obj) {
 		}
 		// Writes off, as in drawEnemyEye3D().
 		_gfx->setDepthState(true, false);
-		draw3DSphere(obj, 0, 0, 100, 0, 0, 200, eyeballColor, kColorBlack, true);
+		draw3DSphere(obj, 0, 0, 100, 0, 0, 200,
+			standaloneBallColor, standaloneBallOutline, true);
 		drawEyeOverlays3D(obj, kEyeIrisDef, -1, kEyePupilDef, pupilColor, false);
 		_gfx->setDepthState(true, true);
 		break;
@@ -1965,7 +2045,8 @@ bool ColonyEngine::drawStaticObjectPrisms3D(Thing &obj) {
 		if (drawInterpolatedGrowRobot(obj, eyeballColor, pupilColor))
 			break;
 		_gfx->setDepthState(true, false);
-		draw3DSphere(obj, 0, 0, 0, 0, 0, 100, eyeballColor, kColorBlack, true);
+		draw3DSphere(obj, 0, 0, 0, 0, 0, 100,
+			standaloneBallColor, standaloneBallOutline, true);
 		drawEyeOverlays3D(obj, kFEyeIrisDef, -1, kFEyePupilDef, pupilColor, false);
 		_gfx->setDepthState(true, true);
 		break;
@@ -1988,7 +2069,8 @@ bool ColonyEngine::drawStaticObjectPrisms3D(Thing &obj) {
 		if (drawInterpolatedGrowRobot(obj, eyeballColor, pupilColor))
 			break;
 		_gfx->setDepthState(true, false);
-		draw3DSphere(obj, 0, 0, 0, 0, 0, 50, eyeballColor, kColorBlack, true);
+		draw3DSphere(obj, 0, 0, 0, 0, 0, 50,
+			standaloneBallColor, standaloneBallOutline, true);
 		drawEyeOverlays3D(obj, kSEyeIrisDef, -1, kSEyePupilDef, pupilColor, false);
 		_gfx->setDepthState(true, true);
 		break;
@@ -2011,7 +2093,8 @@ bool ColonyEngine::drawStaticObjectPrisms3D(Thing &obj) {
 		if (drawInterpolatedGrowRobot(obj, eyeballColor, pupilColor))
 			break;
 		_gfx->setDepthState(true, false);
-		draw3DSphere(obj, 0, 0, 0, 0, 0, 25, eyeballColor, kColorBlack, true);
+		draw3DSphere(obj, 0, 0, 0, 0, 0, 25,
+			standaloneBallColor, standaloneBallOutline, true);
 		drawEyeOverlays3D(obj, kMEyeIrisDef, kColorMiniEyeIris, kMEyePupilDef, pupilColor, false);
 		_gfx->setDepthState(true, true);
 		break;


Commit: 5de262b804e39b72977b9b3ca6ff214e0553e39c
    https://github.com/scummvm/scummvm/commit/5de262b804e39b72977b9b3ca6ff214e0553e39c
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-01T12:07:23+02:00

Commit Message:
COLONY: fix aspect ratio rendering for DOS release

Changed paths:
    engines/colony/animation.cpp
    engines/colony/battle.cpp
    engines/colony/gfx.cpp
    engines/colony/intro.cpp
    engines/colony/render.cpp
    engines/colony/render_objects.cpp
    engines/colony/renderer.h
    engines/colony/renderer_opengl.cpp
    engines/colony/renderer_opengl_shaders.cpp


diff --git a/engines/colony/animation.cpp b/engines/colony/animation.cpp
index fa1cffc3b5e..3bc87683dd6 100644
--- a/engines/colony/animation.cpp
+++ b/engines/colony/animation.cpp
@@ -409,6 +409,9 @@ void ColonyEngine::playAnimation() {
 	_coderWin = Common::Rect();
 	_coderPressed = -1;
 	_coderPressInside = false;
+	const bool useSquarePixelViewport = !isMacRenderMode();
+	if (useSquarePixelViewport)
+		_gfx->setSquarePixelViewport(true);
 	_system->lockMouse(false);
 	warpMouseLogical(_centerX, _centerY);
 	const char *cursorName = "default arrow cursor";
@@ -599,7 +602,7 @@ void ColonyEngine::playAnimation() {
 		while (_system->getEventManager()->pollEvent(event)) {
 			if (event.type == Common::EVENT_QUIT || event.type == Common::EVENT_RETURN_TO_LAUNCHER) {
 				_animationRunning = false;
-				return;
+				break;
 			} else if (event.type == Common::EVENT_SCREEN_CHANGED) {
 				_gfx->computeScreenViewport();
 				needsDraw = true;
@@ -700,6 +703,8 @@ void ColonyEngine::playAnimation() {
 				}
 			}
 		}
+		if (!_animationRunning || shouldQuit())
+			break;
 
 		// updateAnimation has its own 50ms throttle; only redraw when we know
 		// the visible state changed (click feedback) or the cadence is due.
@@ -720,6 +725,8 @@ void ColonyEngine::playAnimation() {
 		_system->delayMillis(2);
 	}
 
+	if (useSquarePixelViewport)
+		_gfx->setSquarePixelViewport(false);
 	_system->lockMouse(true);
 	CursorMan.showMouse(false);
 	CursorMan.popAllCursors();
diff --git a/engines/colony/battle.cpp b/engines/colony/battle.cpp
index b019fe1b1cf..90ecaf7c7db 100644
--- a/engines/colony/battle.cpp
+++ b/engines/colony/battle.cpp
@@ -46,7 +46,6 @@ const int kBattleSize = 150;   // BSIZE: collision/spawn radius
 const int kMaxQuad = 15;       // pyramids per quadrant
 const int kTankMax = 24;       // turret pincer animation range
 const int kFloor = 160;        // ground z-offset
-const float kBattleFovY = 75.0f;
 
 // =====================================================================
 // Battle color constants (original Mac QuickDraw pattern indices)
@@ -78,9 +77,8 @@ int battleHorizonY(const Common::Rect &screenR, int lookY) {
 	const float centerY = screenR.top + halfHeight;
 	const float clampedLookY = CLIP<float>((float)lookY, -63.5f, 63.5f);
 	const float pitchRad = clampedLookY * 2.0f * (float)M_PI / 256.0f;
-	const float focalY = halfHeight / tanf(kBattleFovY * (float)M_PI / 360.0f);
 
-	return (int)roundf(centerY - focalY * tanf(pitchRad));
+	return (int)roundf(centerY - kProjectionFocalLength * tanf(pitchRad));
 }
 
 int battlePowerLevel(int32 power) {
@@ -126,12 +124,11 @@ bool battleProjectPoint(const Common::Rect &screenR, uint8 look, int8 lookY, con
 	if (eyeZ >= -1.0f)
 		return false;
 
-	const float focal = (screenR.height() * 0.5f) / tanf(kBattleFovY * (float)M_PI / 360.0f);
 	const float centerX = screenR.left + screenR.width() * 0.5f;
 	const float centerY = screenR.top + screenR.height() * 0.5f;
 
-	screenX = (int)roundf(centerX + (eyeX * focal / -eyeZ));
-	screenY = (int)roundf(centerY - (eyeY * focal / -eyeZ));
+	screenX = (int)roundf(centerX + (eyeX * kProjectionFocalLength / -eyeZ));
+	screenY = (int)roundf(centerY - (eyeY * kProjectionFocalLength / -eyeZ));
 	return true;
 }
 
diff --git a/engines/colony/gfx.cpp b/engines/colony/gfx.cpp
index 2525477cad5..cc0f721ff41 100644
--- a/engines/colony/gfx.cpp
+++ b/engines/colony/gfx.cpp
@@ -99,7 +99,11 @@ static Graphics::RendererType pickRendererType() {
 
 Renderer *createRenderer(OSystem *system, int width, int height) {
 	const Graphics::RendererType type = pickRendererType();
-	initGraphics3d(width, height);
+	// The DOS widescreen canvas keeps its original 350-line EGA coordinate
+	// system, but its display aspect is still 16:9 after pixel-aspect
+	// correction. Request a 16:9 window instead of the raw 853:350 canvas.
+	const int displayHeight = ConfMan.getBool("widescreen_mod") ? (width * 9 + 8) / 16 : height;
+	initGraphics3d(width, displayHeight);
 
 #if defined(USE_OPENGL_SHADERS)
 	if (type == Graphics::kRendererTypeOpenGLShaders) {
diff --git a/engines/colony/intro.cpp b/engines/colony/intro.cpp
index dc00baa3688..ba15bac6f9e 100644
--- a/engines/colony/intro.cpp
+++ b/engines/colony/intro.cpp
@@ -445,8 +445,10 @@ void ColonyEngine::playIntro() {
 			_sound->play(Sound::kStars1);
 			_gfx->clear(_gfx->black());
 			if (loadAnimation("logo2")) {
+				_gfx->setSquarePixelViewport(true);
 				drawAnimation();
 				_gfx->copyToScreen();
+				_gfx->setSquarePixelViewport(false);
 			}
 			qt = makeStars(_screenR, 0);
 			_gfx->clear(_gfx->black());
@@ -457,8 +459,10 @@ void ColonyEngine::playIntro() {
 			_sound->stop();
 			_sound->play(Sound::kStars2);
 			if (loadAnimation("logo1")) {
+				_gfx->setSquarePixelViewport(true);
 				drawAnimation();
 				_gfx->copyToScreen();
+				_gfx->setSquarePixelViewport(false);
 			}
 			qt = makeStars(_screenR, 0);
 			_gfx->clear(_gfx->black());
diff --git a/engines/colony/render.cpp b/engines/colony/render.cpp
index 3628d03fa90..a35f1ae59e0 100644
--- a/engines/colony/render.cpp
+++ b/engines/colony/render.cpp
@@ -324,12 +324,11 @@ void projectCorridorPointClamped(const Common::Rect &screenR, int look, int look
 	const float eyeZ = dz * sinPitch - forward * cosPitch;
 	const float depth = MAX(-eyeZ, 1.0f);
 
-	const float focal = (screenR.height() * 0.5f) / tanf(75.0f * (float)M_PI / 360.0f);
 	const float centerX = screenR.left + screenR.width() * 0.5f;
 	const float centerY = screenR.top + screenR.height() * 0.5f;
 
-	screenX = (int)roundf(centerX + (eyeX * focal / depth));
-	screenY = (int)roundf(centerY - (eyeY * focal / depth));
+	screenX = (int)roundf(centerX + (eyeX * kProjectionFocalLength / depth));
+	screenY = (int)roundf(centerY - (eyeY * kProjectionFocalLength / depth));
 }
 
 bool isSurfaceVisible(const int *surface, int pointCount, const int *screenX, const int *screenY) {
@@ -481,12 +480,11 @@ bool projectCorridorPoint(const Common::Rect &screenR, uint8 look, int8 lookY,
 	if (eyeZ >= -1.0f)
 		return false;
 
-	const float focal = (screenR.height() * 0.5f) / tanf(75.0f * (float)M_PI / 360.0f);
 	const float centerX = screenR.left + screenR.width() * 0.5f;
 	const float centerY = screenR.top + screenR.height() * 0.5f;
 
-	screenX = (int)roundf(centerX + (eyeX * focal / -eyeZ));
-	screenY = (int)roundf(centerY - (eyeY * focal / -eyeZ));
+	screenX = (int)roundf(centerX + (eyeX * kProjectionFocalLength / -eyeZ));
+	screenY = (int)roundf(centerY - (eyeY * kProjectionFocalLength / -eyeZ));
 	return true;
 }
 
diff --git a/engines/colony/render_objects.cpp b/engines/colony/render_objects.cpp
index caa7a692fbd..8ddf4e03dcf 100644
--- a/engines/colony/render_objects.cpp
+++ b/engines/colony/render_objects.cpp
@@ -114,12 +114,11 @@ bool projectCorridorPointRaw(const Common::Rect &screenR, uint8 look, int8 lookY
 	if (eyeZ >= -1.0f)
 		return false;
 
-	const float focal = (screenR.height() * 0.5f) / tanf(75.0f * (float)M_PI / 360.0f);
 	const float centerX = screenR.left + screenR.width() * 0.5f;
 	const float centerY = screenR.top + screenR.height() * 0.5f;
 
-	screenX = (int)roundf(centerX + (eyeX * focal / -eyeZ));
-	screenY = (int)roundf(centerY - (eyeY * focal / -eyeZ));
+	screenX = (int)roundf(centerX + (eyeX * kProjectionFocalLength / -eyeZ));
+	screenY = (int)roundf(centerY - (eyeY * kProjectionFocalLength / -eyeZ));
 	return true;
 }
 
diff --git a/engines/colony/renderer.h b/engines/colony/renderer.h
index eec455a4ba6..47ed2aae323 100644
--- a/engines/colony/renderer.h
+++ b/engines/colony/renderer.h
@@ -35,6 +35,9 @@
 
 namespace Colony {
 
+// CALCROBO.C projects both axes with (coordinate << 8) / depth.
+const float kProjectionFocalLength = 256.0f;
+
 class Renderer {
 public:
 	virtual ~Renderer() {}
@@ -73,10 +76,10 @@ public:
 	// features.c clipped each wall feature to its own wall (ClipRect(&rClip)).
 	virtual void setFeatureClipX(int left, int right) {}
 	virtual void clearFeatureClipX() {}
+	virtual void setSquarePixelViewport(bool enable) = 0;
 	virtual void computeScreenViewport() = 0;
 
-	// Window-pixel rect the logical canvas is drawn into: the whole window
-	// with the widescreen mod on, pillar/letterboxed otherwise.
+	// Window-pixel rect the logical canvas is currently drawn into.
 	const Common::Rect &screenViewport() const { return _screenViewport; }
 
 	// Overlay a RGBA software surface onto the GL framebuffer (for Mac menu bar).
diff --git a/engines/colony/renderer_opengl.cpp b/engines/colony/renderer_opengl.cpp
index d967daafff9..88312b5807a 100644
--- a/engines/colony/renderer_opengl.cpp
+++ b/engines/colony/renderer_opengl.cpp
@@ -117,6 +117,7 @@ public:
 	void clearFeatureClipX() override {
 		applyScissorRect(_active3DViewport);
 	}
+	void setSquarePixelViewport(bool enable) override;
 	void computeScreenViewport() override;
 	void drawSurface(const Graphics::Surface *surf, int x, int y) override;
 	Graphics::Surface *getScreenshot() override;
@@ -137,6 +138,7 @@ private:
 	Common::Rect _active3DViewport;
 	int _width = 0;
 	int _height = 0;
+	bool _squarePixelViewport = false;
 	byte _palette[256 * 3] = {};
 	bool _wireframe = true;
 	int64_t _wireframeFillColor = 0; // -1 = no fill (outline only)
@@ -308,15 +310,12 @@ void OpenGLRenderer::begin3D(int camX, int camY, int camZ, int angle, int angleY
 	glMatrixMode(GL_PROJECTION);
 	glLoadIdentity();
 	
-	// Use a proper FOV-based perspective projection (like Freescape).
-	// A fixed vertical FOV ensures the forward direction stays centered
-	// regardless of viewport dimensions (e.g. when dashboard narrows it).
-	float aspectRatio = (float)viewport.width() / (float)viewport.height();
-	float fov = 75.0f; // vertical FOV in degrees
-	float nearClip = 1.0f;
-	float farClip = 10000.0f;
-	float ymax = nearClip * tanf(fov * M_PI / 360.0f);
-	float xmax = ymax * aspectRatio;
+	// The original projection is center + (coordinate << 8) / depth, so
+	// derive the frustum from its fixed 256-logical-pixel focal length.
+	const float nearClip = 1.0f;
+	const float farClip = 10000.0f;
+	const float xmax = nearClip * viewport.width() * 0.5f / kProjectionFocalLength;
+	const float ymax = nearClip * viewport.height() * 0.5f / kProjectionFocalLength;
 	glFrustum(-xmax, xmax, -ymax, ymax, nearClip, farClip);
  
 	glMatrixMode(GL_MODELVIEW);
@@ -575,14 +574,38 @@ void OpenGLRenderer::end3D() {
 	glOrtho(0, _width, _height, 0, -1, 1);
 }
 
+void OpenGLRenderer::setSquarePixelViewport(bool enable) {
+	if (_squarePixelViewport == enable)
+		return;
+
+	_squarePixelViewport = enable;
+	computeScreenViewport();
+}
+
 void OpenGLRenderer::computeScreenViewport() {
 	int32 screenWidth = _system->getWidth();
 	int32 screenHeight = _system->getHeight();
 
 	bool widescreen = ConfMan.getBool("widescreen_mod");
 
-	if (widescreen) {
-		_screenViewport = Common::Rect(screenWidth, screenHeight);
+	if (_squarePixelViewport) {
+		// Animation bitmaps use square pixels. Fit the uncorrected logical
+		// canvas so their authored 416x264 geometry matches the Mac version.
+		const int32 viewportWidth = MIN<int32>(screenWidth,
+			(screenHeight * _width + _height / 2) / _height);
+		const int32 viewportHeight = MIN<int32>(screenHeight,
+			(screenWidth * _height + _width / 2) / _width);
+		_screenViewport = Common::Rect(viewportWidth, viewportHeight);
+		_screenViewport.translate((screenWidth - viewportWidth) / 2,
+			(screenHeight - viewportHeight) / 2);
+	} else if (widescreen) {
+		// Widescreen is a 16:9 presentation even when fullscreen uses a
+		// taller or wider display. Keep it centered instead of stretching it.
+		const int32 viewportWidth = MIN<int32>(screenWidth, (screenHeight * 16 + 4) / 9);
+		const int32 viewportHeight = MIN<int32>(screenHeight, (screenWidth * 9 + 8) / 16);
+		_screenViewport = Common::Rect(viewportWidth, viewportHeight);
+		_screenViewport.translate((screenWidth - viewportWidth) / 2,
+			(screenHeight - viewportHeight) / 2);
 	} else if (_system->getFeatureState(OSystem::kFeatureAspectRatioCorrection)) {
 		// Aspect ratio correction (4:3)
 		int32 viewportWidth = MIN<int32>(screenWidth, screenHeight * 4 / 3);
diff --git a/engines/colony/renderer_opengl_shaders.cpp b/engines/colony/renderer_opengl_shaders.cpp
index e97a898c68e..bdd1034bebf 100644
--- a/engines/colony/renderer_opengl_shaders.cpp
+++ b/engines/colony/renderer_opengl_shaders.cpp
@@ -83,6 +83,7 @@ public:
 	void setFeatureClipX(int left, int right) override;
 	void clearFeatureClipX() override;
 	void applyScissorRect(const Common::Rect &logical);
+	void setSquarePixelViewport(bool enable) override;
 	void computeScreenViewport() override;
 
 	void drawSurface(const Graphics::Surface *surf, int x, int y) override;
@@ -122,6 +123,7 @@ private:
 	Common::Rect _active3DViewport;
 	int _width = 0;
 	int _height = 0;
+	bool _squarePixelViewport = false;
 	byte _palette[256 * 3] = {};
 
 	OpenGL::Shader *_solidShader = nullptr;
@@ -630,13 +632,35 @@ void OpenGLShaderRenderer::setPalette(const byte *palette, uint start, uint coun
 	memcpy(_palette + start * 3, palette, count * 3);
 }
 
+void OpenGLShaderRenderer::setSquarePixelViewport(bool enable) {
+	if (_squarePixelViewport == enable)
+		return;
+
+	_squarePixelViewport = enable;
+	computeScreenViewport();
+}
+
 void OpenGLShaderRenderer::computeScreenViewport() {
 	const int32 screenWidth = _system->getWidth();
 	const int32 screenHeight = _system->getHeight();
 	const bool widescreen = ConfMan.getBool("widescreen_mod");
 
-	if (widescreen) {
-		_screenViewport = Common::Rect(screenWidth, screenHeight);
+	if (_squarePixelViewport) {
+		// Animation bitmaps use square pixels. Fit the uncorrected logical
+		// canvas so their authored 416x264 geometry matches the Mac version.
+		const int32 vpW = MIN<int32>(screenWidth,
+			(screenHeight * _width + _height / 2) / _height);
+		const int32 vpH = MIN<int32>(screenHeight,
+			(screenWidth * _height + _width / 2) / _width);
+		_screenViewport = Common::Rect(vpW, vpH);
+		_screenViewport.translate((screenWidth - vpW) / 2, (screenHeight - vpH) / 2);
+	} else if (widescreen) {
+		// Widescreen is a 16:9 presentation even when fullscreen uses a
+		// taller or wider display. Keep it centered instead of stretching it.
+		const int32 vpW = MIN<int32>(screenWidth, (screenHeight * 16 + 4) / 9);
+		const int32 vpH = MIN<int32>(screenHeight, (screenWidth * 9 + 8) / 16);
+		_screenViewport = Common::Rect(vpW, vpH);
+		_screenViewport.translate((screenWidth - vpW) / 2, (screenHeight - vpH) / 2);
 	} else if (_system->getFeatureState(OSystem::kFeatureAspectRatioCorrection)) {
 		const int32 vpW = MIN<int32>(screenWidth, screenHeight * 4 / 3);
 		const int32 vpH = MIN<int32>(screenHeight, screenWidth * 3 / 4);
@@ -773,14 +797,12 @@ void OpenGLShaderRenderer::begin3D(int camX, int camY, int camZ, int angle, int
 	glDepthFunc(GL_LEQUAL);
 	glDepthMask(GL_TRUE);
 
-	// Perspective: 75° vertical FOV, near=1, far=10000 — matches the
-	// fixed-function path so geometry lands at the same screen positions.
-	const float aspectRatio = (float)viewport.width() / (float)viewport.height();
-	const float fov = 75.0f;
+	// Match CALCROBO.C's center + (coordinate << 8) / depth projection and
+	// the fixed-function path by using a 256-logical-pixel focal length.
 	const float nearClip = 1.0f;
 	const float farClip = 10000.0f;
-	const float ymax = nearClip * tanf(fov * (float)M_PI / 360.0f);
-	const float xmax = ymax * aspectRatio;
+	const float xmax = nearClip * viewport.width() * 0.5f / kProjectionFocalLength;
+	const float ymax = nearClip * viewport.height() * 0.5f / kProjectionFocalLength;
 
 	// Build perspective frustum directly. Math::makeFrustumMatrix exists in
 	// math/glmath.h, but its sign convention requires a final transpose to


Commit: f5786b13ba79a6b3f4be58e88965885c0718bed6
    https://github.com/scummvm/scummvm/commit/f5786b13ba79a6b3f4be58e88965885c0718bed6
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-01T12:07:23+02:00

Commit Message:
COLONY: better rendering of stair going down

Changed paths:
    engines/colony/render_features.cpp


diff --git a/engines/colony/render_features.cpp b/engines/colony/render_features.cpp
index 50f90d4f5b3..b8d1a07382f 100644
--- a/engines/colony/render_features.cpp
+++ b/engines/colony/render_features.cpp
@@ -889,45 +889,106 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
 		break;
 	}
 	case kWallFeatureDnStairs: {
-		// wallftrs.c draw_dn_stairs(): the same well, dropping away.
+		// A full storey drop over one cell projects through the corridor floor
+		// when viewed from above. Use a shallower render-only drop so the same
+		// tread/riser construction as the up stairs remains visible in the well.
 		const uint32 col = macColors ? (uint32)0xFF000000 : 0; // vBLACK
 		float farC[4][3];
 		getWallRecess3D(corners, farC);
 		clipToWallFace(corners);
 
 		float dep[7];
-		for (int i = 0; i < 7; i++)
+		float hgt[7];
+		for (int i = 0; i < 7; i++) {
 			dep[i] = (float)(i + 1) / 8.0f;
+			hgt[i] = -(float)(i + 1) / 32.0f;
+		}
+
+		// Fill the shaft sides without framing each panel, so their shared edges
+		// form one continuous wall rather than a row of transparent-looking bars.
+		if (isMacRenderMode() || !_wireframe) {
+			_gfx->setStippleData(nullptr);
+			_gfx->setWireframe(false);
+			for (int i = 1; i <= 7; i++) {
+				const float nearD = dep[i - 1];
+				const float farD = (i < 7) ? dep[i] : 1.0f;
+				const float bottomV = hgt[i - 1];
+				const float sideV[4] = {0.0f, bottomV, bottomV, 0.0f};
+				const float sideD[4] = {nearD, nearD, farD, farD};
+				const float leftU[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+				const float rightU[4] = {1.0f, 1.0f, 1.0f, 1.0f};
+				recessQuad(corners, farC, leftU, sideV, sideD, 4, wallFeatureFill);
+				recessQuad(corners, farC, rightU, sideV, sideD, 4, wallFeatureFill);
+			}
+		}
 
 		if (isMacRenderMode()) {
 			const float uw[4] = {0.0f, 1.0f, 1.0f, 0.0f};
-			const float vw[4] = {0.0f, 0.0f, 1.0f, 1.0f};
+			const float vw[4] = {hgt[6], hgt[6], 1.0f, 1.0f};
 			const float dw[4] = {1.0f, 1.0f, 1.0f, 1.0f};
 			macFillRecess(corners, farC, uw, vw, dw, 4, 21, macColors); // c_dnstairs
 
-			// Well floor
-			const float uq[4] = {0.0f, 0.0f, 1.0f, 1.0f};
-			const float vFloor[4] = {0.0f, 0.0f, 0.0f, 0.0f};
-			const float dFloor[4] = {0.0f, 1.0f, 1.0f, 0.0f};
-			macFillRecess(corners, farC, uq, vFloor, dFloor, 4, 21, macColors);
+			// Match the up-stair materials: dark risers and lighter treads.
+			for (int i = 6; i >= 0; i--) {
+				const float nearV = (i > 0) ? hgt[i - 1] : 0.0f;
+				const float nearD = (i > 0) ? dep[i - 1] : 0.0f;
+				const float uq[4] = {0.0f, 0.0f, 1.0f, 1.0f};
+				const float vRiser[4] = {nearV, hgt[i], hgt[i], nearV};
+				const float dRiser[4] = {dep[i], dep[i], dep[i], dep[i]};
+				macFillRecess(corners, farC, uq, vRiser, dRiser, 4, 20, macColors);
+
+				const float vTread[4] = {nearV, nearV, nearV, nearV};
+				const float dTread[4] = {nearD, dep[i], dep[i], nearD};
+				macFillRecess(corners, farC, uq, vTread, dTread, 4, 19, macColors);
+			}
+
+			const float ub[4] = {0.0f, 0.0f, 1.0f, 1.0f};
+			const float vb[4] = {hgt[6], hgt[6], hgt[6], hgt[6]};
+			const float db[4] = {dep[6], 1.0f, 1.0f, dep[6]};
+			macFillRecess(corners, farC, ub, vb, db, 4, 19, macColors);
+		} else if (!_wireframe) {
+			const float uw[4] = {0.0f, 1.0f, 1.0f, 0.0f};
+			const float vw[4] = {hgt[6], hgt[6], 1.0f, 1.0f};
+			const float dw[4] = {1.0f, 1.0f, 1.0f, 1.0f};
+			dosFillRecess(corners, farC, uw, vw, dw, 4, kColorGray);
+
+			for (int i = 6; i >= 0; i--) {
+				const float nearV = (i > 0) ? hgt[i - 1] : 0.0f;
+				const float nearD = (i > 0) ? dep[i - 1] : 0.0f;
+				const float uq[4] = {0.0f, 0.0f, 1.0f, 1.0f};
+				const float vRiser[4] = {nearV, hgt[i], hgt[i], nearV};
+				const float dRiser[4] = {dep[i], dep[i], dep[i], dep[i]};
+				dosFillRecess(corners, farC, uq, vRiser, dRiser, 4, kColorGray);
+
+				const float vTread[4] = {nearV, nearV, nearV, nearV};
+				const float dTread[4] = {nearD, dep[i], dep[i], nearD};
+				dosFillRecess(corners, farC, uq, vTread, dTread, 4, kColorLtGray);
+			}
+
+			const float ub[4] = {0.0f, 0.0f, 1.0f, 1.0f};
+			const float vb[4] = {hgt[6], hgt[6], hgt[6], hgt[6]};
+			const float db[4] = {dep[6], 1.0f, 1.0f, dep[6]};
+			dosFillRecess(corners, farC, ub, vb, db, 4, kColorLtGray);
 		}
 
-		// Ceiling, then the slant to the far mouth
-		recessLine(corners, farC, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, dep[3], col);
-		recessLine(corners, farC, 0.0f, 1.0f, dep[3], 1.0f, 1.0f, dep[3], col);
-		recessLine(corners, farC, 1.0f, 1.0f, dep[3], 1.0f, 1.0f, 0.0f, col);
-		recessLine(corners, farC, 0.0f, 1.0f, dep[3], 0.0f, 0.5f, 1.0f, col);
-		recessLine(corners, farC, 0.0f, 0.5f, 1.0f, 1.0f, 0.5f, 1.0f, col);
-		recessLine(corners, farC, 1.0f, 0.5f, 1.0f, 1.0f, 1.0f, dep[3], col);
-
-		// Side walls down to the well floor
-		recessLine(corners, farC, 0.0f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, col);
-		recessLine(corners, farC, 1.0f, 0.5f, 1.0f, 1.0f, 0.0f, 1.0f, col);
-		recessLine(corners, farC, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, col);
-		recessLine(corners, farC, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, col);
-
-		// First step
-		recessLine(corners, farC, 0.0f, 0.0f, dep[0], 1.0f, 0.0f, dep[0], col);
+		// Lower landing and closed far mouth.
+		recessLine(corners, farC, 0.0f, hgt[6], dep[6], 0.0f, hgt[6], 1.0f, col);
+		recessLine(corners, farC, 0.0f, hgt[6], 1.0f, 1.0f, hgt[6], 1.0f, col);
+		recessLine(corners, farC, 1.0f, hgt[6], 1.0f, 1.0f, hgt[6], dep[6], col);
+		recessLine(corners, farC, 0.0f, hgt[6], 1.0f, 0.0f, 1.0f, 1.0f, col);
+		recessLine(corners, farC, 1.0f, hgt[6], 1.0f, 1.0f, 1.0f, 1.0f, col);
+
+		// Treads and risers.
+		for (int i = 6; i >= 0; i--) {
+			const float nearV = (i > 0) ? hgt[i - 1] : 0.0f;
+			const float nearD = (i > 0) ? dep[i - 1] : 0.0f;
+			recessLine(corners, farC, 0.0f, nearV, nearD, 0.0f, nearV, dep[i], col);
+			recessLine(corners, farC, 1.0f, nearV, nearD, 1.0f, nearV, dep[i], col);
+			recessLine(corners, farC, 0.0f, nearV, dep[i], 1.0f, nearV, dep[i], col);
+			recessLine(corners, farC, 0.0f, nearV, dep[i], 0.0f, hgt[i], dep[i], col);
+			recessLine(corners, farC, 1.0f, nearV, dep[i], 1.0f, hgt[i], dep[i], col);
+			recessLine(corners, farC, 0.0f, hgt[i], dep[i], 1.0f, hgt[i], dep[i], col);
+		}
 
 		// Handrails
 		recessLine(corners, farC, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, dep[3], col);


Commit: 3a7b808800bb90fc3ec748f01894a5eb2ccf88e6
    https://github.com/scummvm/scummvm/commit/3a7b808800bb90fc3ec748f01894a5eb2ccf88e6
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-01T12:07:23+02:00

Commit Message:
COLONY: improved instructions in the reactor console

Changed paths:
    engines/colony/animation.cpp


diff --git a/engines/colony/animation.cpp b/engines/colony/animation.cpp
index 3bc87683dd6..217fb1ae837 100644
--- a/engines/colony/animation.cpp
+++ b/engines/colony/animation.cpp
@@ -1043,7 +1043,7 @@ void ColonyEngine::drawColonyCoder(int animOx, int animOy) {
 	const int contentW = 172;
 	const char *instrText = (_animationName == "security")
 		? "Press the symbols as shown on the display to determine the correct value."
-		: "Press the symbols from the desk in reverse order to determine the correct value.";
+		: "Enter the appropriate symbol sequence to determine this reactor's code.";
 	Common::Array<Common::U32String> instrLines;
 	instrFont->wordWrapText(Common::U32String(instrText), contentW - 16, instrLines);
 	const int instrLineH = instrFont->getFontHeight() + 1;




More information about the Scummvm-git-logs mailing list