[Scummvm-git-logs] scummvm master -> f0a14d295e1d06b9576509780646698534ae7898

mduggan noreply at scummvm.org
Tue Sep 23 10:04:18 UTC 2025


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

Summary:
8a2cba2b63 ACCESS: Remove redundant debug message
d4da7f3a23 ACCESS: small const correctness fixes
41e4007633 ACCESS: Add myself to engine credits
8fb83ad151 ACCESS: Implement MM duct section
28a3a81347 NEWS: Mention Martian Memorandum support
f0a14d295e ACCESS: Mark MM as ready for testing


Commit: 8a2cba2b6354cb52ba03e1a3896b6793dcd33d5f
    https://github.com/scummvm/scummvm/commit/8a2cba2b6354cb52ba03e1a3896b6793dcd33d5f
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2025-09-23T14:15:25+10:00

Commit Message:
ACCESS: Remove redundant debug message

Changed paths:
    engines/access/bubble_box.cpp


diff --git a/engines/access/bubble_box.cpp b/engines/access/bubble_box.cpp
index 494570c3ce8..749fbb7a46b 100644
--- a/engines/access/bubble_box.cpp
+++ b/engines/access/bubble_box.cpp
@@ -336,9 +336,6 @@ void BubbleBox::doBox(int item, int box) {
 void BubbleBox::setCursorPos(int posX, int posY) {
 	Common::Point newPt =  Common::Point(posX * 8, posY * 8 + _rowOff);
 	_vm->_screen->_printStart = _vm->_screen->_printOrg = newPt;
-	// This function (at 0x6803) calculates something from a lookup table, but
-	// never does anything with it.
-	debug("Skipping call to setCursorPos");
 }
 
 void BubbleBox::printString(Common::String msg) {


Commit: d4da7f3a236788ccb07c24029d415cc67ebac2e7
    https://github.com/scummvm/scummvm/commit/d4da7f3a236788ccb07c24029d415cc67ebac2e7
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2025-09-23T14:16:11+10:00

Commit Message:
ACCESS: small const correctness fixes

Changed paths:
    engines/access/asurface.cpp
    engines/access/asurface.h
    engines/access/screen.cpp
    engines/access/screen.h


diff --git a/engines/access/asurface.cpp b/engines/access/asurface.cpp
index f01a5542a60..3224b82bef4 100644
--- a/engines/access/asurface.cpp
+++ b/engines/access/asurface.cpp
@@ -163,7 +163,7 @@ void BaseSurface::sPlotB(const SpriteFrame *frame, const Common::Rect &bounds) {
 	transBlitFrom(*frame, Common::Rect(0, 0, frame->w, frame->h), bounds, TRANSPARENCY, true);
 }
 
-void BaseSurface::copyBlock(BaseSurface *src, const Common::Rect &bounds) {
+void BaseSurface::copyBlock(const BaseSurface *src, const Common::Rect &bounds) {
 	copyRectToSurface(*src, bounds.left, bounds.top, bounds);
 }
 
diff --git a/engines/access/asurface.h b/engines/access/asurface.h
index 81ead365387..d11e7c936a8 100644
--- a/engines/access/asurface.h
+++ b/engines/access/asurface.h
@@ -90,7 +90,7 @@ public:
 	 */
 	void plotB(const SpriteFrame *frame, const Common::Point &pt);
 
-	virtual void copyBlock(BaseSurface *src, const Common::Rect &bounds);
+	virtual void copyBlock(const BaseSurface *src, const Common::Rect &bounds);
 
 	virtual void restoreBlock();
 
diff --git a/engines/access/screen.cpp b/engines/access/screen.cpp
index fbd799369d5..139200ba5ce 100644
--- a/engines/access/screen.cpp
+++ b/engines/access/screen.cpp
@@ -282,7 +282,7 @@ void Screen::restoreScreen() {
 	_screenYOff = _screenSave._screenYOff;
 }
 
-void Screen::copyBlock(BaseSurface *src, const Common::Rect &bounds) {
+void Screen::copyBlock(const BaseSurface *src, const Common::Rect &bounds) {
 	Common::Rect destBounds = bounds;
 	destBounds.translate(_windowXAdd, _windowYAdd + _screenYOff);
 
diff --git a/engines/access/screen.h b/engines/access/screen.h
index 31341a862af..735db92d88d 100644
--- a/engines/access/screen.h
+++ b/engines/access/screen.h
@@ -86,7 +86,7 @@ public:
 	 */
 	void update() override;
 
-	void copyBlock(BaseSurface *src, const Common::Rect &bounds) override;
+	void copyBlock(const BaseSurface *src, const Common::Rect &bounds) override;
 
 	void restoreBlock() override;
 


Commit: 41e40076335002297aeb1d64f51dee1c101dce29
    https://github.com/scummvm/scummvm/commit/41e40076335002297aeb1d64f51dee1c101dce29
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2025-09-23T14:19:26+10:00

Commit Message:
ACCESS: Add myself to engine credits

Changed paths:
    engines/access/credits.pl


diff --git a/engines/access/credits.pl b/engines/access/credits.pl
index bfb19a57267..f0b826ba875 100644
--- a/engines/access/credits.pl
+++ b/engines/access/credits.pl
@@ -1,4 +1,5 @@
 begin_section("Access");
 	add_person("Arnaud Boutonné", "Strangerke", "");
 	add_person("Paul Gilbert", "dreammaster", "");
+	add_person("Matthew Duggan", "stauff", "");
 end_section();


Commit: 8fb83ad151beadcf127a5384353abf92bd8f846c
    https://github.com/scummvm/scummvm/commit/8fb83ad151beadcf127a5384353abf92bd8f846c
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2025-09-23T19:47:07+10:00

Commit Message:
ACCESS: Implement MM duct section

Changed paths:
  A engines/access/martian/martian_duct.cpp
  A engines/access/martian/martian_duct.h
  R engines/access/martian/martian_tunnel.cpp
  R engines/access/martian/martian_tunnel.h
    engines/access/events.cpp
    engines/access/events.h
    engines/access/martian/martian_resources.cpp
    engines/access/martian/martian_resources.h
    engines/access/martian/martian_scripts.cpp
    engines/access/martian/martian_scripts.h
    engines/access/module.mk
    engines/access/room.cpp


diff --git a/engines/access/events.cpp b/engines/access/events.cpp
index 0196e66a257..e68505420a2 100644
--- a/engines/access/events.cpp
+++ b/engines/access/events.cpp
@@ -130,6 +130,12 @@ bool EventsManager::isCursorVisible() {
 	return CursorMan.isVisible();
 }
 
+void EventsManager::delayUntilNextFrame() {
+	while (!checkForNextFrameCounter())
+		delay();
+	nextFrame();
+}
+
 void EventsManager::pollEvents(bool skipTimers) {
 	if (checkForNextFrameCounter()) {
 		nextFrame();
@@ -312,6 +318,7 @@ bool EventsManager::getAction(Common::CustomEventType &action) {
 	}
 }
 
+
 bool EventsManager::isKeyActionPending() const {
 	return (_keyCode != Common::KEYCODE_INVALID || _action != kActionNone);
 }
diff --git a/engines/access/events.h b/engines/access/events.h
index f02a1773dbe..c99a6b5f547 100644
--- a/engines/access/events.h
+++ b/engines/access/events.h
@@ -131,10 +131,16 @@ public:
 
 	bool getAction(Common::CustomEventType &action);
 
+	Common::CustomEventType peekAction() const { return _action; }
+
+	Common::KeyCode peekKeyCode() const { return _keyCode; }
+
 	bool isKeyActionPending() const;
 
 	void delay(int time = 5);
 
+	void delayUntilNextFrame();
+
 	void debounceLeft();
 
 	void clearEvents();
diff --git a/engines/access/martian/martian_duct.cpp b/engines/access/martian/martian_duct.cpp
new file mode 100644
index 00000000000..9d7679728b5
--- /dev/null
+++ b/engines/access/martian/martian_duct.cpp
@@ -0,0 +1,1286 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "access/martian/martian_duct.h"
+#include "access/martian/martian_game.h"
+#include "access/martian/martian_resources.h"
+
+namespace Access {
+
+namespace Martian {
+
+MartianDuct::MartianDuct(MartianEngine *vm) : _vm(vm), _stopMoveLoop(false), _playerX(0), _preYOffset(0), _playerY(0), _moveAngle(kMoveAngleNorth), _drawDistX(100), _drawDistY(500), _threshold1(50), _crawlFrame(0), _xOffset(160), _yOffset(160), _xScale(100), _yScale(160), _moveIntent(kMoveIntentNone), _primArrayIdx(0), _mapLoc(0), _nextPlayerX(0), _nextPlayerY(0) {
+	ARRAYCLEAR(_matrix[0]);
+	ARRAYCLEAR(_matrix[1]);
+	ARRAYCLEAR(_matrix[2]);
+	ARRAYCLEAR(_tempPoints);
+}
+
+MartianDuct::~MartianDuct() {
+}
+
+void MartianDuct::duct2() {
+	_playerX = 550;
+	_preYOffset = 10;
+	_playerY = 850;
+	doDuct();
+}
+
+void MartianDuct::duct4() {
+	_playerX = 2750;
+	_preYOffset = 10;
+	_playerY = 1050;
+	doDuct();
+}
+
+void MartianDuct::doDuct() {
+	_vm->_screen->forceFadeOut();
+
+	_vm->_screen->_windowXAdd = 0;
+	_vm->_screen->_windowYAdd = 0;
+	_vm->_screen->_screenYOff = 0;
+
+	_vm->_events->hideCursor();
+	_vm->_files->loadScreen(20, 0);
+	Resource *res = _vm->_files->loadFile(20, 1);
+	_vm->_objectsTable[20] = new SpriteResource(_vm, res);
+	if (_vm->_inventory->_inv[40]._value == ITEM_IN_INVENTORY) {
+		// Show map if we have it.
+		_vm->_screen->plotImage(_vm->_objectsTable[20], 8, Common::Point(140, 10));
+	}
+	_vm->_events->showCursor();
+	_vm->_screen->forceFadeIn();
+	_crawlFrame = 0;
+	_xOffset = 160;
+	_yOffset = 100;
+	_xScale = 160;
+	_yScale = 100;
+	// The game initialises some drawing window here but it makes no difference
+	_moveAngle = kMoveAngleNorth;
+	// game has Y and Z angles X fraction part which is always 0
+	_drawDistX = 100;
+	_drawDistY = 500;
+	_threshold1 = 50;
+
+	drawArrowSprites();
+	drawArrowSprites2();
+	_vm->_room->_function = FN_NONE;
+
+	// FIXME: Quick HACK: skip this part
+	//g_system->displayMessageOnOSD(Common::U32String("Duct section not implemented yet!"));
+	//_vm->_flags[0x62] = 1;
+	//_vm->_flags[0x55] = 1;
+	//_vm->_room->_function = FN_CLEAR1;
+	// END HACK
+
+	while (!_vm->shouldQuit()) {
+		clearWorkScreenArea();
+		updateMatrix();
+		applyMatrixToMapData();
+		// Draw duct panels
+		updatePrimsAndDraw();
+		// Draw tex over the top
+		_vm->_buffer2.plotImage(_vm->_objectsTable[20], _crawlFrame, Common::Point(140, 94));
+		copyBufBlockToScreen();
+		if (_vm->_room->_function != FN_NONE)
+			break;
+		do {
+			waitForMoveUpdate();
+		} while (!_stopMoveLoop && !_vm->shouldQuitOrRestart());
+	}
+
+	delete _vm->_objectsTable[20];
+	_vm->_objectsTable[20] = nullptr;
+}
+
+void MartianDuct::drawArrowSprites() {
+	int x;
+	int y;
+	int frame;
+
+	_vm->_events->hideCursor();
+	_vm->_screen->plotImage(_vm->_objectsTable[20], 7, Common::Point(4, 0x50));
+	if (_moveAngle == kMoveAngleNorth) { // (highlight up arrow)
+		x = 0x11;
+		y = 0x50;
+		frame = 9;
+	} else if (_moveAngle == kMoveAngleEast) { // highlight right arrow
+		x = 0x19;
+		y = 0x57;
+		frame = 12;
+	} else if (_moveAngle == kMoveAngleSouth) { // highlight down arrow
+		x = 0xe;
+		y = 0x5d;
+		frame = 11;
+	} else { // West or west2 (left arrow)
+		x = 4;
+		y = 0x57;
+		frame = 10;
+	}
+	_vm->_screen->plotImage(_vm->_objectsTable[20], frame, Common::Point(x, y));
+	_vm->_events->showCursor();
+}
+
+void MartianDuct::drawArrowSprites2() {
+	_vm->_events->hideCursor();
+	_vm->_screen->plotImage(_vm->_objectsTable[20], 14, Common::Point(16, 0x58));
+	if (_mapLoc > 3 && _mapLoc < 13) {
+		_vm->_screen->plotImage(_vm->_objectsTable[20], 13, Common::Point(17, 0x59));
+	}
+	_vm->_events->showCursor();
+}
+
+void MartianDuct::clearWorkScreenArea() {
+	_vm->_buffer2.fillRect(Common::Rect(100, 60, 220, 140), 0);
+	// For testing, clear the whole buffer:
+	//_vm->_buffer2.fillRect(Common::Rect(320, 200), 0);
+}
+
+void MartianDuct::copyBufBlockToScreen() {
+	// Start from row 60, 100 across. 100 px by 80 rows.
+	_vm->_screen->copyBlock(&_vm->_buffer2, Common::Rect(100, 60, 220, 140));
+	// For testing, copy everything:
+	//_vm->_screen->copyBuffer(&_vm->_buffer2);
+}
+
+void MartianDuct::updateMatrix() {
+	// The original does a full fixed-point matrix calculation here, but
+	// half the values are always 1 or 0 so it's much simpler than that.
+	float moveAngleRad = (float)(_moveAngle / 256.0f) * 2.0f * M_PI;
+	float cosVal = cos(moveAngleRad);
+	float sinVal = sin(moveAngleRad);
+
+	// 3D rotation through Y axis.  We never rotate through the others.
+	_matrix[0][0] = cosVal;
+	_matrix[0][1] = 0.0f;
+	_matrix[0][2] = sinVal;
+	_matrix[1][0] = 0.0f;
+	_matrix[1][1] = 1.0f;
+	_matrix[1][2] = 0.0f;
+	_matrix[2][0] = -sinVal;
+	_matrix[2][1] = 0.0f;
+	_matrix[2][2] = cosVal;
+}
+
+void MartianDuct::applyMatrixToMapData() {
+	int16 shapeDataIndex;
+	_renderShapes.clear();
+	_renderPoints.clear();
+
+	for (const DuctMapPoint *pMapData = DUCT_MAP_DATA; shapeDataIndex = pMapData->shapeType, shapeDataIndex != -1; pMapData++) {
+		// The original sets the actual drawing x/y here but then uses them
+		// as inputs to the next step so we don't need to set them here.
+		const int16 blockX = pMapData->x;
+		const int16 blockY = pMapData->y;
+		if (abs(blockX + _threshold1 - _playerX) < _drawDistX && abs(blockY + _threshold1 - _playerY) < _drawDistY) {
+			const DuctShape *ptr = DUCT_SHAPE_DATA[shapeDataIndex];
+			const uint startPointNum = _renderPoints.size();
+			const Point3 *pPoint = ptr->points;
+			// The data is the point list followed by the other data.
+			for (int i = 0; i < ptr->numPts; i++) {
+				int16 x = pPoint->x + blockX - _playerX;
+				int16 y = pPoint->y - _preYOffset;
+				int16 z = pPoint->z + blockY - _playerY;
+				pPoint++;
+				doMatrixMulAndAddPoint(x, y, z);
+			}
+
+			const uint16 *dataPtr = ptr->data;
+			for (int j = 0; j < ptr->array2Len; j++) {
+				RenderShape shapeData;
+				// Input struct is byte,ignored,int16,[array of int16]
+				shapeData._col = (byte)dataPtr[0];
+				const int dataCount = dataPtr[1];
+				dataPtr += 2;
+				for (int i = 0; i < dataCount; i++) {
+					shapeData._pointIdxs.push_back(*dataPtr + startPointNum);
+					dataPtr++;
+				}
+				_renderShapes.push_back(shapeData);
+			}
+		}
+	}
+}
+
+void MartianDuct::updatePrimsAndDraw() {
+	int16 shapeZDepth[256];
+	int shapeIndexes[256];
+
+	for (uint i = 0; i < _renderShapes.size(); i++)
+		shapeZDepth[i] = INT16_MIN;
+
+	for (uint shpNum = 0; shpNum < _renderShapes.size(); shpNum++) {
+		const Point3 &pt0 = _renderPoints[_renderShapes[shpNum]._pointIdxs[0]];
+		const Point3 &pt2 = _renderPoints[_renderShapes[shpNum]._pointIdxs[2]];
+		int16 zDepth = ((int)pt0.z + pt2.z) / 2;
+		uint insertPoint = 0;
+		uint i = _renderShapes.size();
+		while (zDepth <= shapeZDepth[insertPoint] && i > 0) {
+			insertPoint++;
+			i--;
+		}
+
+		if (i == 0)
+			continue;
+
+		for (uint j = _renderShapes.size(); j > insertPoint; j--) {
+			shapeZDepth[j] = shapeZDepth[j - 1];
+			shapeIndexes[j] = shapeIndexes[j - 1];
+		}
+
+		shapeZDepth[insertPoint] = zDepth;
+		shapeIndexes[insertPoint] = shpNum;
+	}
+
+	//debug("**** Begin frame ****");
+	for (uint shapeNum = 0; shapeNum < _renderShapes.size(); shapeNum++) {
+		int shapeIdx = shapeIndexes[shapeNum];
+		_vm->_buffer2._lColor = _renderShapes[shapeIdx]._col;
+		int numPoints = _renderShapes[shapeIdx]._pointIdxs.size();
+		_primX1Array.clear();
+		_primY1Array.clear();
+		_primZ1Array.clear();
+		_primX2Array.clear();
+		_primY2Array.clear();
+		_primZ2Array.clear();
+		// Link up the points in order to make a polygon.
+		int ptIdx;
+		for (int pointNum = 0; pointNum < numPoints - 1; pointNum++) {
+			ptIdx = _renderShapes[shapeIdx]._pointIdxs[pointNum];
+			_primX1Array.push_back(_renderPoints[ptIdx].x);
+			_primY1Array.push_back(_renderPoints[ptIdx].y);
+			_primZ1Array.push_back(_renderPoints[ptIdx].z);
+			ptIdx = _renderShapes[shapeIdx]._pointIdxs[pointNum + 1];
+			_primX2Array.push_back(_renderPoints[ptIdx].x);
+			_primY2Array.push_back(_renderPoints[ptIdx].y);
+			_primZ2Array.push_back(_renderPoints[ptIdx].z);
+		}
+		ptIdx = _renderShapes[shapeIdx]._pointIdxs[numPoints - 1];
+		_primX1Array.push_back(_renderPoints[ptIdx].x);
+		_primY1Array.push_back(_renderPoints[ptIdx].y);
+		_primZ1Array.push_back(_renderPoints[ptIdx].z);
+		ptIdx = _renderShapes[shapeIdx]._pointIdxs[0];
+		_primX2Array.push_back(_renderPoints[ptIdx].x);
+		_primY2Array.push_back(_renderPoints[ptIdx].y);
+		_primZ2Array.push_back(_renderPoints[ptIdx].z);
+
+		assert(_primX1Array.size() == (uint)numPoints);
+
+		bool shouldDraw = !doPrimArrayUpdates(numPoints);
+
+		if (!shouldDraw)
+			continue;
+
+		/*
+		debug("** shape %d [%d] color %d ** ", shapeNum, shapeIdx, _vm->_buffer2._lColor);
+		debugN("XYs: ");
+		for (int i = 0; i < numPoints; i++) {
+			int16 x = _primX1Array[i];
+			int16 y = _primY1Array[i];
+			debugN("(%d %d), ", x, y);
+		}
+		debug(".");
+
+		debugN("RBs: ");
+		for (int i = 0; i < numPoints; i++) {
+			int16 x = _primX2Array[i];
+			int16 y = _primY2Array[i];
+			debugN("(%d %d), ", x, y);
+		}
+		debug(".");
+		*/
+
+		doDraw(numPoints);
+	}
+	//debug("**** End frame ****");
+}
+
+
+#define POLY_DRAW_CODE 1
+
+
+void MartianDuct::doDraw(int numPoints) {
+
+#if POLY_DRAW_CODE
+	//
+	// This code is not as efficient as the original game, but it's
+	// easier to understand.
+	//
+	// We have a set of line segments in the _prim**Array arrays and want
+	// to draw the polygons that they form, but they are not necessarily
+	// in order or handedness.  First remove null segments, then sort the
+	// remaining ones by connecting up the start and end points.  Since they
+	// are all simple shapes this works fine.
+	//
+
+	// First remove null lines (same start and end)
+	for (int i = 0; i < numPoints; i++) {
+		if (_primX1Array[i] == _primX2Array[i] && _primY1Array[i] == _primY2Array[i]) {
+			_primX1Array.remove_at(i);
+			_primY1Array.remove_at(i);
+			_primX2Array.remove_at(i);
+			_primY2Array.remove_at(i);
+			numPoints--;
+			i--;
+			continue;
+		}
+	}
+
+	// Sort the line segments
+	Common::Array<int> x1s;
+	Common::Array<int> y1s;
+	Common::Array<int> x2s;
+	Common::Array<int> y2s;
+
+	x1s.push_back(_primX1Array.remove_at(0));
+	y1s.push_back(_primY1Array.remove_at(0));
+	x2s.push_back(_primX2Array.remove_at(0));
+	y2s.push_back(_primY2Array.remove_at(0));
+	int pointsToAdd = numPoints - 1;
+	while (pointsToAdd > 0) {
+		// Find the next line segment to add, could be in either direction.
+		for (int i = 0; i < pointsToAdd; i++) {
+			if (_primX1Array[i] == x2s.back() && _primY1Array[i] == y2s.back()) {
+				x1s.push_back(_primX1Array.remove_at(i));
+				y1s.push_back(_primY1Array.remove_at(i));
+				x2s.push_back(_primX2Array.remove_at(i));
+				y2s.push_back(_primY2Array.remove_at(i));
+				pointsToAdd--;
+				break;
+			} else if (_primX2Array[i] == x2s.back() && _primY2Array[i] == y2s.back()) {
+				x1s.push_back(_primX2Array.remove_at(i));
+				y1s.push_back(_primY2Array.remove_at(i));
+				x2s.push_back(_primX1Array.remove_at(i));
+				y2s.push_back(_primY1Array.remove_at(i));
+				pointsToAdd--;
+				break;
+			}
+		}
+	}
+
+	// Complete the shape to join up to the start again.
+	x1s.push_back(x1s[0]);
+	y1s.push_back(y1s[0]);
+
+	_vm->_buffer2.drawPolygonScan(x1s.data(), y1s.data(), x1s.size(), Common::Rect(320, 200), _vm->_buffer2._lColor);
+
+#else
+
+	int16 maxYVal = 0;
+	int16 minYVal = 0xff;
+	int i = 0;
+
+	// Count of line segments and x1/x2 pairs for each row
+	byte segmentCount[202];
+	int16 segmentCoords[200][16];
+
+	ARRAYCLEAR(segmentCount);
+	for (int y = 0; y < 200; y++)
+		ARRAYCLEAR(segmentCoords[y]);
+
+	do {
+		int16 primitiveX = _primXArray[i];
+		int16 primitiveY = _primYArray[i];
+		int16 primitiveB = _primY2Array[i];
+		int16 primitiveR = _primX2Array[i];
+
+		maxYVal = MAX(maxYVal, primitiveY);
+		maxYVal = MAX(maxYVal, primitiveB);
+		minYVal = MIN(minYVal, primitiveY);
+		minYVal = MIN(minYVal, primitiveB);
+
+		if (primitiveR < primitiveX) {
+			// Flip X and Y
+			SWAP(primitiveX, primitiveR);
+			SWAP(primitiveY, primitiveB);
+		}
+		const int16 width = primitiveR - primitiveX;
+		int16 x = primitiveX;
+		int16 _lastPrimIdx = i;
+
+		if (width == 0) {
+			if (primitiveY != primitiveB) {
+				if (primitiveB <= primitiveY)
+					SWAP(primitiveY, primitiveB);
+
+				int16 y = primitiveY;
+				do {
+					//byte bVar6 = (byte)array_a344[y * 0x10];
+					//*(int16 *)CONCAT11((char)((uint)(array_a344[y * 0x10] >> 8) + CARRY1(bVar6, byteData_bc44[y]),
+					//				 bVar6 + byteData_bc44[y]) = primitiveX;
+					segmentCoords[y][segmentCount[y]] = primitiveX;
+					segmentCoords[y][segmentCount[y] + 1] = primitiveX;
+					segmentCount[y]++;
+					y++;
+				} while (y != primitiveB);
+			}
+		} else {
+			const int height = primitiveB - primitiveY;
+			if (height < 0) {
+				if (-height < width) {
+					i = -width / 2;
+					int16 y = primitiveY;
+					while (true) {
+						do {
+							x++;
+							i -= height;
+						} while (i < 0);
+						int16 nextY = y - 1;
+						if (nextY == primitiveB)
+							break;
+						//byte bVar6 = (byte)array_a344[nextY * 0x10];
+						//*(int16 *)CONCAT11((char)((uint)(array_a344[nextY * 0x10] >> 8) +
+						//			   CARRY1(bVar6,byteData_bc44[y]),
+						//			   bVar6 + byteData_bc44[nextY]) = x;
+						segmentCoords[y][segmentCount[y]] = primitiveX;
+						segmentCoords[y][segmentCount[y] + 1] = x;
+						segmentCount[y]++;
+						i -= width;
+						y = nextY;
+					}
+				} else {
+					i = height / 2;
+					int16 y = primitiveY;
+					int16 nextY;
+					while (nextY = y - 1, nextY != primitiveB) {
+						//byte bVar6 = (byte)array_a344[nextY * 0x10];
+						//*(int16 *)CONCAT11((char)((uint)(array_a344[nextY * 0x10] >> 8) +
+						//			   CARRY1(bVar6,byteData_bc44[primitiveY]),
+						//			   bVar6 + byteData_bc44[primitiveY]) = x;
+						segmentCount[-y]++;
+						i += width;
+						y = nextY;
+						if (-1 < i) {
+							x++;
+							i += height;
+						}
+					}
+				}
+
+				SWAP(primitiveX, primitiveR);
+				SWAP(primitiveY, primitiveB);
+				//byte bVar6 = (byte)array_a344[primitiveY * 0x10];
+				//*(int16 *)CONCAT11((char)((uint)(array_a344[primitiveY * 0x10] >> 8) +
+				//			 CARRY1(bVar6,byteData_bc44[primitiveY]),
+				//			 bVar6 + byteData_bc44[primitiveY]) = primitiveX;
+				segmentCount[primitiveY]++;
+			} else if (height != 0 && height < width) {
+				i = -width / 2;
+				int16 y = primitiveY;
+				while (true) {
+					do {
+						x++;
+						i += height;
+					} while (i < 0);
+					//byte bVar6 = (byte)array_a344[y * 0x10];
+					//*(int16 *)CONCAT11((char)((uint)(array_a344[y * 0x10] >> 8) +
+					//				 CARRY1(bVar6,byteData_bc44[y]),
+					//		 bVar6 + byteData_bc44[y]) = x;
+					segmentCount[y]++;
+					y++;
+					if (y == primitiveB)
+						break;
+					i -= width;
+				}
+			} else if (height != 0) {
+				i = -height / 2;
+				int16 y = primitiveY;
+				while (true) {
+					//byte bVar6 = (byte)array_a344[y * 0x10];
+					//*(int16 *)CONCAT11((char)((uint)(array_a344[y * 0x10] >> 8) +
+					//				 CARRY1(bVar6,byteData_bc44[y]),
+					//				 bVar6 + byteData_bc44[y]) = x;
+					segmentCount[y]++;
+					y++;
+					if (y == primitiveB)
+						break;
+					i += width;
+					if (-1 < i) {
+						x++;
+						i -= height;
+					}
+				}
+			}
+		}
+		i = _lastPrimIdx + 1;
+	} while (i != numPoints);
+
+	segmentCount[200] = segmentCount[36];
+	for (int16 y = minYVal; y <= maxYVal; y++) {
+		if (segmentCount[y] == 0)
+			continue;
+
+		int16 *lineData = segmentCoords[y];
+		/*
+		CHECK ME: This probably is supposed to swap incorrect left/right vals, but seems
+		to just check values against themselves?
+		const int16 numSegs = segmentCount[y];
+		int16 right = 0;
+		int16 left = 0;
+		do {
+			const int16 tmpy = lineData[right];
+			if (tmpy < lineData[left]) {
+				lineData[right] = lineData[left];
+				lineData[left] = tmpy;
+			}
+			right++;
+		} while ((right != numSegs) || (right = left + 1, left = right, right != numSegs));
+		*/
+		for (int16 segNum = 0; segNum < segmentCount[y]; segNum++) {
+			const int16 x2 = *(lineData + 1);
+			const int16 x1 = *lineData;
+			lineData += 2;
+			byte *pdest = (byte *)_vm->_buffer2.getBasePtr(x1, y);
+			for (i = 0; i < (x2 - x1) + 1; i++) {
+				*pdest = _vm->_buffer2._lColor;
+				pdest++;
+			}
+		}
+		segmentCount[y] = 0;
+	}
+#endif
+}
+
+void MartianDuct::doMatrixMulAndAddPoint(int16 x, int16 y, int16 z) {
+	Point3 pt;
+	pt.x = _matrix[0][0] * x + _matrix[1][0] * y + _matrix[2][0] * z;
+	pt.y = _matrix[0][1] * x + _matrix[1][1] * y + _matrix[2][1] * z;
+	pt.z = _matrix[0][2] * x + _matrix[1][2] * y + _matrix[2][2] * z;
+	_renderPoints.push_back(pt);
+}
+
+bool MartianDuct::doPrimArrayUpdates(int &numPoints) {
+	if (checkAndUpdatePrimArray1(numPoints))
+		return true;
+	if (checkAndUpdatePrimArray2(numPoints))
+		return true;
+	if (checkAndUpdatePrimArray3(numPoints))
+		return true;
+	if (checkAndUpdatePrimArray4(numPoints))
+		return true;
+	if (checkAndUpdatePrimArray5(numPoints))
+		return true;
+
+	assert(numPoints > 0);
+	for (int16 idx = 0; idx < numPoints; idx++) {
+		Point3 pt1, pt2;
+		getPointValuesFromArray(idx, pt1, pt2);
+		// The original changes the drawing primitive coordinates here
+		// but we only need them to store so we use a temp rect instead.
+		const Common::Rect r = calcFinalLineSegment(pt1, pt2);
+		_primX1Array[idx] = r.left;
+		_primY1Array[idx] = r.top;
+		_primX2Array[idx] = r.right;
+		_primY2Array[idx] = r.bottom;
+	}
+	return false;
+}
+
+void MartianDuct::getPointValuesFromArray(int idx, Point3 &pt1, Point3 &pt2) const {
+	pt1.x = _primX1Array[idx];
+	pt1.y = _primY1Array[idx];
+	pt1.z = _primZ1Array[idx];
+	pt2.x = _primX2Array[idx];
+	pt2.y = _primY2Array[idx];
+	pt2.z = _primZ2Array[idx];
+}
+
+Common::Rect MartianDuct::calcFinalLineSegment(const Point3 &pt1, const Point3 &pt2) const {
+	Common::Rect result;
+	result.left   = _xOffset + ((int)pt1.x * _xScale) / pt1.z;
+	result.top    = _yOffset - ((int)pt1.y * _yScale) / pt1.z;
+	result.right  = _xOffset + ((int)pt2.x * _xScale) / pt2.z;
+	result.bottom = _yOffset - ((int)pt2.y * _yScale) / pt2.z;
+	return result;
+}
+
+bool MartianDuct::checkAndUpdatePrimArrayForFlag(int &numPoints, DuctFlags flag, int divmulNum) {
+	_primArrayIdx = 0;
+	int tempCount = 0;
+	for (int idx = 0; idx < numPoints; idx++) {
+		Point3 pt1, pt2;
+		DuctFlags xyflags;
+		DuctFlags rbflags;
+		getPointValuesFromArray(idx, pt1, pt2);
+		getXYandRBFlags(xyflags, rbflags, pt1, pt2);
+		if (xyflags == kDuctFlagNone && rbflags == kDuctFlagNone) {
+			storeLastValsToPrimArray(pt1, pt2); // increments _primArrayIdx
+		} else if (((xyflags | rbflags) & flag) == kDuctFlagNone) {
+			storeLastValsToPrimArray(pt1, pt2);
+		} else if ((xyflags & rbflags & flag) == kDuctFlagNone) {
+			if ((rbflags & flag) == kDuctFlagNone) {
+				// Implicitly xyflags & flag != none
+				assert((xyflags & flag) != kDuctFlagNone);
+				SWAP(pt1, pt2);
+			}
+			switch (divmulNum) {
+				case 1: pt2 = divmul1(pt1, pt2); break;
+				case 2: pt2 = divmul2(pt1, pt2); break;
+				case 3: pt2 = divmul3(pt1, pt2); break;
+				case 4: pt2 = divmul4(pt1, pt2); break;
+				case 5: pt2 = divmul5(pt1, pt2); break;
+				default: error("Invalid divmul num");
+			}
+			_tempPoints[tempCount] = pt2;
+			tempCount++;
+			storeLastValsToPrimArray(pt1, pt2);
+		}
+	}
+	numPoints = addPointsToMainPrimArray(tempCount);
+	return numPoints == 0;
+}
+
+bool MartianDuct::checkAndUpdatePrimArray1(int &numPoints) {
+	return checkAndUpdatePrimArrayForFlag(numPoints, kDuctFlagZLessThan2, 1);
+}
+
+bool MartianDuct::checkAndUpdatePrimArray2(int &numPoints) {
+	return checkAndUpdatePrimArrayForFlag(numPoints, kDuctFlagXLessThanNegZ, 2);
+}
+
+bool MartianDuct::checkAndUpdatePrimArray3(int &numPoints) {
+	return checkAndUpdatePrimArrayForFlag(numPoints, kDuctFlagZLessThanY, 3);
+}
+
+bool MartianDuct::checkAndUpdatePrimArray4(int &numPoints) {
+	return checkAndUpdatePrimArrayForFlag(numPoints, kDuctFlagZLessThanX, 4);
+}
+
+bool MartianDuct::checkAndUpdatePrimArray5(int &numPoints) {
+	return checkAndUpdatePrimArrayForFlag(numPoints, kDuctFlagYLessThanNegZ, 5);
+}
+
+//
+// For these functions the original uses some fixed-point stuff, but replaced
+// it with float to be much cleaner.
+//
+Point3 MartianDuct::divmul1(const Point3 &pt1, const Point3 &pt2) {
+	// called for kDuctFlagXLessThanNegZ
+	Point3 out;
+	out.z = 2;
+	float tmp = (2.0f - pt1.z) / (pt2.z - pt1.z);
+	out.x = (int)round((pt2.x - pt1.x) * tmp + pt1.x);
+	out.y = (int)round((pt2.y - pt1.y) * tmp + pt1.y);
+	return out;
+}
+
+Point3 MartianDuct::divmul2(const Point3 &pt1, const Point3 &pt2) {
+	// called for kDuctFlagXLessThanNegZ
+	Point3 out;
+	float tmp = (pt1.z + pt1.x * 2.0f) / ((pt1.x - pt2.x) * 2 - pt2.z + pt1.z);
+	out.z = (int)round((pt2.z - pt1.z) * tmp + pt1.z);
+	out.x = -(out.z / 2);
+	out.y = (int)round((pt2.y - pt1.y) * tmp + pt1.y);
+	return out;
+}
+
+Point3 MartianDuct::divmul3(const Point3 &pt1, const Point3 &pt2) {
+	// called for kDuctFlagZLessThanY
+	Point3 out;
+	float tmp = (pt1.z - pt1.y * 2.0f) / ((pt2.y - pt1.y) * 2 - pt2.z + pt1.z);
+	out.y = (int)round((pt2.z - pt1.z) * tmp + pt1.z);
+	out.z = out.y;
+	out.x = (int)round((pt2.x - pt1.x) * tmp + pt1.x);
+	return out;
+}
+
+Point3 MartianDuct::divmul4(const Point3 &pt1, const Point3 &pt2) {
+	// called for kDuctFlagZLessThanX
+	Point3 out;
+	float tmp = (pt1.z - pt1.x * 2.0f) / ((pt2.x - pt1.x) * 2 - pt2.z + pt1.z);
+	out.z = (int)round((pt2.z - pt1.z) * tmp + pt1.z);
+	out.x = out.z / 2;
+	out.y = (int)round((pt2.y - pt1.y) * tmp + pt1.y);
+	return out;
+}
+
+Point3 MartianDuct::divmul5(const Point3 &pt1, const Point3 &pt2) {
+	// called for kDuctFlagYLessThanNegZ
+	Point3 out;
+	float tmp = (pt1.z + pt1.y * 2.0f) / ((pt1.y - pt2.y) * 2 - pt2.z + pt1.z);
+	out.z = (int)round((pt2.z - pt1.z) * tmp + pt1.z);
+	out.y = -(out.z / 2);
+	out.x = (int)round((pt2.x - pt1.x) * tmp + pt1.x);
+	return out;
+}
+
+
+void MartianDuct::getXYandRBFlags(DuctFlags &xyflags, DuctFlags &rbflags, const Point3 &pt1, const Point3 &pt2) {
+	xyflags = getComparisonFlags(pt1.x * 2, pt1.y * 2, pt1.z);
+	rbflags = getComparisonFlags(pt2.x * 2, pt2.y * 2, pt2.z);
+}
+
+int MartianDuct::addPointsToMainPrimArray(int tempCount) {
+	int dstIdx = _primArrayIdx;
+	// Resize arrays if we need to.
+	if (dstIdx + tempCount > (int)_primX1Array.size()) {
+		_primX1Array.resize(dstIdx + tempCount);
+		_primY1Array.resize(dstIdx + tempCount);
+		_primZ1Array.resize(dstIdx + tempCount);
+		_primX2Array.resize(dstIdx + tempCount);
+		_primY2Array.resize(dstIdx + tempCount);
+		_primZ2Array.resize(dstIdx + tempCount);
+	}
+
+    for (int srcidx = 0; srcidx < tempCount; srcidx += 2) {
+		_primX1Array[dstIdx] = _tempPoints[srcidx].x;
+		_primY1Array[dstIdx] = _tempPoints[srcidx].y;
+		_primZ1Array[dstIdx] = _tempPoints[srcidx].z;
+		_primX2Array[dstIdx] = _tempPoints[srcidx + 1].x;
+		_primY2Array[dstIdx] = _tempPoints[srcidx + 1].y;
+		_primZ2Array[dstIdx] = _tempPoints[srcidx + 1].z;
+		dstIdx++;
+	}
+
+	return dstIdx;
+}
+
+DuctFlags MartianDuct::getComparisonFlags(int16 x, int16 y, int16 z) {
+	enum DuctFlags flags = kDuctFlagNone;
+	if (z < 2)
+		flags = kDuctFlagZLessThan2;
+
+	if (z < y)
+		flags = (DuctFlags)(flags | kDuctFlagZLessThanY);
+
+	if (z < x)
+		flags = (DuctFlags)(flags | kDuctFlagZLessThanX);
+
+	if (x < -z)
+		flags = (DuctFlags)(flags | kDuctFlagXLessThanNegZ);
+
+	if (y < -z)
+		flags = (DuctFlags)(flags | kDuctFlagYLessThanNegZ);
+
+	return flags;
+}
+
+void MartianDuct::waitForMoveUpdate() {
+	// Holding down Insert and O together skips the duct in the original.
+	// Simplify to just accept O.
+	if (_vm->_events->peekKeyCode() == Common::KEYCODE_o) {
+		_vm->_room->_function = FN_CLEAR1;
+		_vm->_flags[0x55] = 1;
+		_vm->_flags[0x62] = 1;
+		_stopMoveLoop = true;
+		return;
+	}
+
+	_stopMoveLoop = false;
+	// Wait for next frame.
+	// Frame rate is set as 100fps, but we really want more like 30.
+	_vm->_events->delayUntilNextFrame();
+	_vm->_events->delayUntilNextFrame();
+	_vm->_events->delayUntilNextFrame();
+	_vm->_events->pollEvents();
+	Common::CustomEventType action = _vm->_events->peekAction();
+
+	Common::Array<Common::Rect> btnCoords;
+	for (int i = 0; Martian::DUCT_ARROW_BUTTON_RANGE[i][0] != -1; i += 2) {
+		// DUCT_ARROW_BUTTON_RANGE is min/max X, min/max Y
+		btnCoords.push_back(Common::Rect(
+			Martian::DUCT_ARROW_BUTTON_RANGE[i][0],
+			Martian::DUCT_ARROW_BUTTON_RANGE[i + 1][0],
+			Martian::DUCT_ARROW_BUTTON_RANGE[i][1],
+			Martian::DUCT_ARROW_BUTTON_RANGE[i + 1][1]));
+	}
+
+	// Note: buttons are 0 = up, 1 = left, 2 = down, 3 = right
+	int hitButton = -1;
+	if (_vm->_events->_leftButton)
+		hitButton = _vm->_events->checkMouseBox1(btnCoords);
+
+	if (action == kActionMoveUp || hitButton == 0) {
+		_moveIntent = kMoveIntentUp;
+		drawArrowSprites2();
+		if (updateMapLocation()) {
+			checkFinished();
+			return;
+		}
+		drawArrowSprites2();
+		if (_moveAngle == kMoveAngleNorth) {
+			_playerY += 7;
+		} else if (_moveAngle == kMoveAngleSouth) {
+			_playerY -= 7;
+		} else if (_moveAngle == kMoveAngleEast) {
+			_playerX += 7;
+		} else if (_moveAngle == kMoveAngleWest) {
+			_playerX -= 7;
+		} else {
+			checkFinished();
+			return;
+		}
+		_stopMoveLoop = true;
+		_crawlFrame++;
+		if (_crawlFrame == 7)
+			_crawlFrame = 0;
+
+		checkFinished();
+		return;
+	} else if (action == kActionMoveDown || hitButton == 2) {
+		_moveIntent = kMoveIntentDown;
+		drawArrowSprites2();
+		if (updateMapLocation()) {
+			checkFinished();
+			return;
+		}
+
+		drawArrowSprites2();
+		if (_moveAngle == kMoveAngleNorth) {
+			_playerY -= 7;
+		} else if (_moveAngle == kMoveAngleSouth) {
+			_playerY += 7;
+		} else if (_moveAngle == kMoveAngleEast) {
+			_playerX -= 7;
+		} else if (_moveAngle == kMoveAngleWest) {
+			_playerX += 7;
+		} else {
+			checkFinished();
+			return;
+		}
+		_stopMoveLoop = true;
+		_crawlFrame--;
+		if (_crawlFrame < 0)
+			_crawlFrame = 6;
+
+		checkFinished();
+		return;
+	} else if (action == kActionMoveLeft || hitButton == 1) {
+		_moveIntent = kMoveIntentLeft;
+		// Action handled, clear it
+		_vm->_events->getAction(action);
+		updateMapLocation();
+	} else if (action == kActionMoveRight || hitButton == 3) {
+		_moveIntent = kMoveIntentRight;
+		// Action handled, clear it
+		_vm->_events->getAction(action);
+		updateMapLocation();
+	} else {
+		checkFinished();
+		return;
+	}
+
+	_moveAngle = (MoveAngle)(_moveAngle & 0xff);
+	drawArrowSprites();
+	drawArrowSprites2();
+	_drawDistX = 200;
+	_drawDistY = 500;
+	if (_moveAngle != kMoveAngleNorth && _moveAngle != kMoveAngleSouth) {
+		_drawDistX = 500;
+		_drawDistY = 200;
+	}
+	_stopMoveLoop = true;
+}
+
+void MartianDuct::checkFinished() {
+	// Check the player position against the exits
+	if (abs(_playerX - 2650) < 50 && abs(_playerY - 1050) < 50) {
+		_vm->_flags[98] = 0;
+		_vm->_room->_function = FN_CLEAR1;
+	}
+	else if (abs(_playerX - 550) < 50 && abs(_playerY - 750) < 50) {
+		// Finished!
+		_vm->_flags[98] = 1;
+		_vm->_flags[85] = 1;
+		_vm->_room->_function = FN_CLEAR1;
+	}
+}
+
+bool MartianDuct::updateMapLocation() {
+	uint16 blk = _threshold1 * 2;
+	_nextPlayerX = (_playerX / blk) * blk + _threshold1;
+	_nextPlayerY = (_playerY / blk) * blk + _threshold1;
+	const DuctMapPoint *mapPt = DUCT_MAP_DATA;
+	do {
+		const int16 pyType = mapPt->ptType;
+		if (pyType == -1)
+			return true;
+
+		if (pyType != 0xff) {
+			_mapLoc = pyType;
+
+			int16 thresh = _threshold1;
+			if (pyType == 6 ||  pyType == 10)
+				thresh = -_threshold1;
+
+			if (abs((mapPt->x + thresh) - _nextPlayerX) <= _threshold1) {
+				thresh = _threshold1;
+				if (pyType == 8)
+					thresh = -_threshold1;
+
+				if (abs((mapPt->y + thresh) - _nextPlayerY) <= _threshold1) {
+					switch (pyType) {
+					case 0: return checkMove0();
+					case 1: return checkMove1();
+					case 2: return checkMove2();
+					case 3: return checkMove3();
+					case 4: return checkMove4();
+					case 5: return checkMove5();
+					case 6: return checkMove6();
+					case 7: return checkMove7();
+					case 8: return checkMove8();
+					case 9: return checkMove9();
+					case 10: return checkMove10();
+					case 11: return checkMove11();
+					case 12: return checkMove12();
+					case 13: return checkMove13_14();
+					case 14: return checkMove13_14();
+					default: error("Unexpected point type in duct map data %d", pyType); return false;
+					}
+				}
+			}
+		}
+		mapPt++;
+	} while( true );
+
+}
+
+void MartianDuct::storeLastValsToPrimArray(const Point3 &pt1, const Point3 &pt2) {
+	_primX1Array[_primArrayIdx] = pt1.x;
+	_primY1Array[_primArrayIdx] = pt1.y;
+	_primZ1Array[_primArrayIdx] = pt1.z;
+	_primX2Array[_primArrayIdx] = pt2.x;
+	_primY2Array[_primArrayIdx] = pt2.y;
+	_primZ2Array[_primArrayIdx] = pt2.z;
+	_primArrayIdx++;
+}
+
+bool MartianDuct::checkMove0() {
+	if (_moveIntent == kMoveIntentRight || _moveIntent == kMoveIntentLeft) {
+		_moveAngle = (MoveAngle)(_moveAngle + kMoveAngleSouth);
+		return false;
+	}
+
+	if (_moveIntent == kMoveIntentUp) {
+		if (_moveAngle == kMoveAngleWest)
+			return true;
+
+	} else if (_moveIntent == kMoveIntentDown && _moveAngle == kMoveAngleEast) {
+		return true;
+	}
+	return false;
+}
+
+bool MartianDuct::checkMove1() {
+	if ((_moveIntent == kMoveIntentRight) || (_moveIntent == kMoveIntentLeft)) {
+		_moveAngle = (MoveAngle)(_moveAngle + kMoveAngleSouth);
+		return false;
+	}
+
+	if (_moveIntent == kMoveIntentUp) {
+		if (_moveAngle == kMoveAngleEast)
+			return true;
+	} else if (_moveIntent == kMoveIntentDown && _moveAngle == kMoveAngleWest) {
+		return true;
+	}
+	return false;
+}
+
+bool MartianDuct::checkMove2() {
+	if (_moveIntent == kMoveIntentRight || _moveIntent == kMoveIntentLeft) {
+		_moveAngle = (MoveAngle)(_moveAngle + kMoveAngleSouth);
+		return false;
+	}
+	if (_moveIntent == kMoveIntentUp) {
+		if (_moveAngle == kMoveAngleSouth)
+			return true;
+	} else if (_moveIntent == kMoveIntentDown && _moveAngle == kMoveAngleNorth) {
+		return true;
+	}
+	return false;
+}
+
+bool MartianDuct::checkMove3() {
+	if (_moveIntent == kMoveIntentRight || _moveIntent == kMoveIntentLeft) {
+		_moveAngle = (MoveAngle)(_moveAngle + kMoveAngleSouth);
+		return false;
+	}
+	if (_moveIntent == kMoveIntentUp) {
+		if (_moveAngle == kMoveAngleNorth)
+			return true;
+
+	} else if (_moveIntent == kMoveIntentDown && _moveAngle == kMoveAngleSouth) {
+		return true;
+	}
+	return false;
+}
+
+bool MartianDuct::checkMove4() {
+	if (_moveIntent == kMoveIntentRight) {
+		_moveAngle = (MoveAngle)(_moveAngle + kMoveAngleEast);
+	} else {
+		if (_moveIntent != kMoveIntentLeft)
+			return false;
+
+		_moveAngle = (MoveAngle)(_moveAngle - kMoveAngleEast);
+	}
+	_playerY = _nextPlayerY;
+	_playerX = _nextPlayerX;
+	return false;
+}
+
+bool MartianDuct::checkMove5() {
+	if (_moveIntent == kMoveIntentRight) {
+		if (_moveAngle == kMoveAngleSouth)
+			return true;
+
+		_moveAngle = (MoveAngle)(_moveAngle + kMoveAngleEast);
+	} else {
+		if (_moveIntent != kMoveIntentLeft) {
+			if (_moveIntent == kMoveIntentUp)
+				return _moveAngle == kMoveAngleWest;
+
+			if (_moveIntent != kMoveIntentDown)
+				return true;
+
+			if (_moveAngle != kMoveAngleEast)
+				return false;
+
+			return true;
+		}
+		if (_moveAngle == kMoveAngleNorth)
+			return true;
+
+		_moveAngle = (MoveAngle)(_moveAngle - kMoveAngleEast);
+	}
+	_playerY = _nextPlayerY;
+	_playerX = _nextPlayerX;
+	return false;
+}
+
+bool MartianDuct::checkMove6() {
+	if (_moveIntent == kMoveIntentRight) {
+		if (_moveAngle == kMoveAngleNorth)
+			return true;
+
+		_moveAngle = (MoveAngle)(_moveAngle + kMoveAngleEast);
+	} else {
+		if (_moveIntent != kMoveIntentLeft) {
+			if (_moveIntent == kMoveIntentUp)
+				return _moveAngle == kMoveAngleEast;
+
+			if (_moveIntent != kMoveIntentDown)
+				return true;
+
+			if (_moveAngle != kMoveAngleWest)
+				return false;
+
+			return true;
+		}
+		if (_moveAngle == kMoveAngleSouth)
+			return true;
+
+		_moveAngle = (MoveAngle)(_moveAngle - kMoveAngleEast);
+	}
+	_playerY = _nextPlayerY;
+	_playerX = _nextPlayerX;
+	return false;
+}
+
+bool MartianDuct::checkMove7() {
+	if (_moveIntent == kMoveIntentRight) {
+		if (_moveAngle == kMoveAngleEast)
+			return true;
+
+		_moveAngle = (MoveAngle)(_moveAngle + kMoveAngleEast);
+	}
+	else {
+		if (_moveIntent != kMoveIntentLeft) {
+			if (_moveIntent == kMoveIntentUp)
+				return _moveAngle == kMoveAngleSouth;
+
+			if (_moveIntent != kMoveIntentDown)
+				return true;
+
+			if (_moveAngle != kMoveAngleNorth)
+				return false;
+
+			return true;
+		}
+		if (_moveAngle == kMoveAngleWest)
+			return true;
+
+		_moveAngle = (MoveAngle)(_moveAngle - kMoveAngleEast);
+	}
+	_playerY = _nextPlayerY;
+	_playerX = _nextPlayerX;
+	return false;
+}
+
+bool MartianDuct::checkMove8() {
+	if (_moveIntent == kMoveIntentRight) {
+		if (_moveAngle == kMoveAngleWest)
+			return true;
+
+		_moveAngle = (MoveAngle)(_moveAngle + kMoveAngleEast);
+	} else {
+		if (_moveIntent != kMoveIntentLeft) {
+			if (_moveIntent == kMoveIntentUp)
+				return _moveAngle == kMoveAngleNorth;
+
+			if (_moveIntent != kMoveIntentDown)
+				return true;
+
+			if (_moveAngle != kMoveAngleSouth)
+				return false;
+
+			return true;
+		}
+
+		if (_moveAngle == kMoveAngleEast)
+			return true;
+
+		_moveAngle = (MoveAngle)(_moveAngle - kMoveAngleEast);
+	}
+	_playerY = _nextPlayerY;
+	_playerX = _nextPlayerX;
+	return false;
+}
+
+bool MartianDuct::checkMove9() {
+	if (_moveIntent == kMoveIntentRight) {
+		if ((_moveAngle != kMoveAngleNorth) && (_moveAngle != kMoveAngleEast)) {
+			return true;
+		}
+		_moveAngle = (MoveAngle)(_moveAngle + kMoveAngleEast);
+	} else {
+		if (_moveIntent != kMoveIntentLeft) {
+			if (_moveIntent == kMoveIntentUp)
+				return _moveAngle == kMoveAngleWest || _moveAngle == kMoveAngleNorth;
+
+			if (_moveIntent != kMoveIntentDown)
+				return true;
+
+			if (_moveAngle != kMoveAngleEast)
+				return _moveAngle == kMoveAngleSouth;
+
+			return true;
+		}
+		if (_moveAngle != kMoveAngleWest && _moveAngle != kMoveAngleSouth)
+			return true;
+
+		_moveAngle = (MoveAngle)(_moveAngle - kMoveAngleEast);
+	}
+	_playerY = _nextPlayerY;
+	_playerX = _nextPlayerX;
+	return false;
+}
+
+bool MartianDuct::checkMove10() {
+	if (_moveIntent == kMoveIntentRight) {
+		if (_moveAngle != kMoveAngleEast && _moveAngle != kMoveAngleSouth)
+			return true;
+
+		_moveAngle = (MoveAngle)(_moveAngle + kMoveAngleEast);
+	} else {
+		if (_moveIntent != kMoveIntentLeft) {
+			if (_moveIntent == kMoveIntentUp)
+				return _moveAngle == kMoveAngleEast || _moveAngle == kMoveAngleNorth;
+
+			if (_moveIntent != kMoveIntentDown)
+				return true;
+
+			if (_moveAngle != kMoveAngleWest)
+				return _moveAngle == kMoveAngleSouth;
+
+			return true;
+		}
+		if (_moveAngle != kMoveAngleNorth && _moveAngle != kMoveAngleWest)
+			return true;
+
+		_moveAngle = (MoveAngle)(_moveAngle - kMoveAngleEast);
+	}
+	_playerY = _nextPlayerY;
+	_playerX = _nextPlayerX;
+	return false;
+}
+
+bool MartianDuct::checkMove11() {
+	if (_moveIntent == kMoveIntentRight) {
+		if (_moveAngle != kMoveAngleWest && _moveAngle != kMoveAngleNorth)
+			return true;
+
+		_moveAngle = (MoveAngle)(_moveAngle + kMoveAngleEast);
+	} else {
+		if (_moveIntent != kMoveIntentLeft) {
+			if (_moveIntent == kMoveIntentUp)
+				return _moveAngle == kMoveAngleWest || _moveAngle == kMoveAngleSouth;
+
+			if (_moveIntent != kMoveIntentDown) {
+				return true;
+			}
+			if (_moveAngle != kMoveAngleNorth) {
+				return _moveAngle == kMoveAngleEast;
+			}
+			return true;
+		}
+		if (_moveAngle != kMoveAngleSouth && _moveAngle != kMoveAngleEast)
+			return true;
+
+		_moveAngle = (MoveAngle)(_moveAngle - kMoveAngleEast);
+	}
+	_playerY = _nextPlayerY;
+	_playerX = _nextPlayerX;
+	return false;
+}
+
+bool MartianDuct::checkMove12() {
+	if (_moveIntent == kMoveIntentRight) {
+		if (_moveAngle != kMoveAngleSouth && _moveAngle != kMoveAngleWest)
+			return true;
+
+		_moveAngle = (MoveAngle)(_moveAngle + kMoveAngleEast);
+	} else {
+		if (_moveIntent != kMoveIntentLeft) {
+			if (_moveIntent == kMoveIntentUp)
+				return _moveAngle == kMoveAngleEast || _moveAngle == kMoveAngleSouth;
+
+			if (_moveIntent != kMoveIntentDown)
+				return true;
+
+			if (_moveAngle != kMoveAngleNorth)
+				return _moveAngle == kMoveAngleWest;
+
+			return true;
+		}
+		if (_moveAngle != kMoveAngleEast && _moveAngle != kMoveAngleNorth)
+			return true;
+
+		_moveAngle = (MoveAngle)(_moveAngle - kMoveAngleEast);
+	}
+	_playerY = _nextPlayerY;
+	_playerX = _nextPlayerX;
+	return false;
+}
+
+bool MartianDuct::checkMove13_14() {
+	if (_moveIntent != kMoveIntentRight && _moveIntent != kMoveIntentLeft)
+		return false;
+
+	_moveAngle = (MoveAngle)(_moveAngle + kMoveAngleSouth);
+	return false;
+}
+
+
+
+}
+
+} // end namespace Access
diff --git a/engines/access/martian/martian_duct.h b/engines/access/martian/martian_duct.h
new file mode 100644
index 00000000000..c4e0cb70c80
--- /dev/null
+++ b/engines/access/martian/martian_duct.h
@@ -0,0 +1,172 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef ACCESS_MARTIAN_MARTIAN_DUCT_H
+#define ACCESS_MARTIAN_MARTIAN_DUCT_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/events.h"
+
+#include "access/martian/martian_resources.h" // For Point3 .. move it?
+
+namespace Access {
+
+namespace Martian {
+
+enum MoveIntent {
+	kMoveIntentNone,
+	kMoveIntentUp,
+	kMoveIntentLeft,
+	kMoveIntentDown,
+	kMoveIntentRight,
+};
+
+// Move angles - in the original these are indexes into sin/cos lookup tables.
+// Add Invalid to ensure at least 2-byte length.
+enum MoveAngle {
+	kMoveAngleNorth = 0,
+	kMoveAngleEast = 0x40,
+	kMoveAngleSouth = 0x80,
+	kMoveAngleWest = 0xC0,
+	kMoveAngleInvalid = 0xffff,
+};
+
+enum DuctFlags {
+	kDuctFlagNone = 0,
+	kDuctFlagZLessThanX = 1,
+	kDuctFlagXLessThanNegZ = 2,
+	kDuctFlagYLessThanNegZ = 4,
+	kDuctFlagZLessThanY = 8,
+	kDuctFlagZLessThan2 = 16,
+};
+
+struct RenderShape {
+	byte _col;
+	Common::Array<uint16> _pointIdxs;
+};
+
+class MartianEngine;
+
+class MartianDuct {
+public:
+	MartianDuct(MartianEngine *vm);
+	~MartianDuct();
+
+	void duct2();
+	void duct4();
+
+private:
+	void doDuct();
+	void drawArrowSprites();
+	void drawArrowSprites2();
+	void clearWorkScreenArea();
+	void copyBufBlockToScreen();
+	void waitForMoveUpdate();
+	void storeLastValsToPrimArray(const Point3 &pt1, const Point3 &pt2);
+	void updatePlayerPos();
+	void updateMatrix();
+	void applyMatrixToMapData();
+	void updatePrimsAndDraw();
+	bool updateMapLocation();
+	void checkFinished();
+	void doMatrixMulAndAddPoint(int16 x, int16 y, int16 z);
+	bool doPrimArrayUpdates(int &tempIdx);
+	void doDraw(int counter);
+
+	void getPointValuesFromArray(int offset, Point3 &pt1, Point3 &pt2) const;
+	Common::Rect calcFinalLineSegment(const Point3 &pt1, const Point3 &pt2) const;
+
+	bool checkAndUpdatePrimArray1(int &offset);
+	bool checkAndUpdatePrimArray2(int &offset);
+	bool checkAndUpdatePrimArray3(int &offset);
+	bool checkAndUpdatePrimArray4(int &offset);
+	bool checkAndUpdatePrimArray5(int &offset);
+	bool checkAndUpdatePrimArrayForFlag(int &offset, DuctFlags flag, int divmulNum);
+
+	static Point3 divmul1(const Point3 &pt1, const Point3 &pt2);
+	static Point3 divmul2(const Point3 &pt1, const Point3 &pt2);
+	static Point3 divmul3(const Point3 &pt1, const Point3 &pt2);
+	static Point3 divmul4(const Point3 &pt1, const Point3 &pt2);
+	static Point3 divmul5(const Point3 &pt1, const Point3 &pt2);
+
+	bool checkMove0();
+	bool checkMove1();
+	bool checkMove2();
+	bool checkMove3();
+	bool checkMove4();
+	bool checkMove5();
+	bool checkMove6();
+	bool checkMove7();
+	bool checkMove8();
+	bool checkMove9();
+	bool checkMove10();
+	bool checkMove11();
+	bool checkMove12();
+	bool checkMove13_14();
+
+	void getXYandRBFlags(DuctFlags &xyflags, DuctFlags &rbflags, const Point3 &pt1, const Point3 &pt2);
+	int addPointsToMainPrimArray(int tempCount);
+	static DuctFlags getComparisonFlags(int16 x, int16 y, int16 z);
+
+	MartianEngine *_vm;
+	int16 _playerX;
+	int16 _preYOffset;
+	int16 _playerY;
+
+	int16 _nextPlayerX;
+	int16 _nextPlayerY;
+	int16 _xOffset;
+	int16 _yOffset;
+	int16 _xScale;
+	int16 _yScale;
+	uint16 _mapLoc;
+	MoveAngle _moveAngle;
+	int16 _crawlFrame;
+	bool _stopMoveLoop;
+	int16 _threshold1;
+	int16 _drawDistX;
+	int16 _drawDistY;
+	MoveIntent _moveIntent;
+
+	// NOTE: Original uses fixed point sin/cos with a lookup table.
+	float _matrix[3][3];
+
+	int16 _primArrayIdx;
+
+	Common::Array<Point3> _renderPoints;
+	Common::Array<RenderShape> _renderShapes;
+
+	Common::Array<int16> _primX1Array;
+	Common::Array<int16> _primY1Array;
+	Common::Array<int16> _primZ1Array;
+	Common::Array<int16> _primX2Array;
+	Common::Array<int16> _primY2Array;
+	Common::Array<int16> _primZ2Array;
+	Point3 _tempPoints[32];
+};
+
+}
+
+} // end namespace Access
+
+#endif // ACCESS_MARTIAN_MARTIAN_DUCT_H
diff --git a/engines/access/martian/martian_resources.cpp b/engines/access/martian/martian_resources.cpp
index 1eaee751437..2be75baacb7 100644
--- a/engines/access/martian/martian_resources.cpp
+++ b/engines/access/martian/martian_resources.cpp
@@ -142,7 +142,7 @@ const byte CAN_TRAVEL_MATRIX[] = {
 	1
 };
 
-const int PICTURERANGE[][2] = {
+const int16 PICTURE_RANGE[][2] = {
 // { min X, max X}, {min Y, max Y}
 	{ 20, 30 },  { 82, 87 },
 	{ 20, 30 },  { 105, 110 },
@@ -153,6 +153,505 @@ const int PICTURERANGE[][2] = {
 	{ -1, -1 }
 };
 
+const int16 DUCT_ARROW_BUTTON_RANGE[][2] = {
+// { min X, max X}, {min Y, max Y}
+	{ 12, 23 }, { 80, 87 }, // up button
+	{ 4, 15 },  { 87, 93 }, // left button
+	{ 14, 26 }, { 93, 104 },// down button
+	{ 25, 36 }, { 87, 93 }, // right button
+	{ -1, -1 }
+};
+
+const DuctMapPoint DUCT_MAP_DATA[] = {
+	// type //shapetype // x    // y
+	{  0x0,     0xB,     0x0,   0x4B0, },
+	{  0xD,    0x10,    0x64,   0x4B0, },
+	{  0xA,     0xD,   0x12C,   0x4B0, },
+	{  0xE,     0xA,    0xC8,   0x44C, },
+	{  0xE,     0xF,    0xC8,   0x3E8, },
+	{  0x5,     0xD,    0xC8,   0x384, },
+	{  0xE,     0xA,    0xC8,   0x320, },
+	{  0xC,     0xC,    0xC8,   0x2BC, },
+	{  0xD,     0xB,    0x64,   0x2BC, },
+	{  0x0,    0x10,     0x0,   0x2BC, },
+	{  0xD,     0xB,   0x12C,   0x384, },
+	{  0xD,    0x10,   0x190,   0x384, },
+	{  0x3,     0xA,   0x1F4,   0x514, },
+	{  0xE,     0xF,   0x1F4,   0x4B0, },
+	{  0xE,     0xA,   0x1F4,   0x44C, },
+	{  0xE,     0xF,   0x1F4,   0x3E8, },
+	{ 0xFF,     0xD,     0x0,   0x4B0, },
+	{  0xE,     0xA,   0x1F4,   0x320, },
+	{  0x2,     0xF,   0x1F4,   0x2BC, },
+	{  0xD,     0xB,   0x258,   0x384, },
+	{  0xD,    0x10,   0x2BC,   0x384, },
+	{  0xD,     0xB,   0x320,   0x384, },
+	{  0xD,    0x10,   0x384,   0x384, },
+	{  0x1,     0xB,   0x578,   0x514, },
+	{  0xD,    0x10,   0x514,   0x514, },
+	{  0xD,     0xB,   0x4B0,   0x514, },
+	{  0xD,    0x10,   0x44C,   0x514, },
+	{  0x9,     0xD,   0x3E8,   0x514, },
+	{  0xE,     0xA,   0x3E8,   0x4B0, },
+	{  0xE,     0xF,   0x3E8,   0x44C, },
+	{  0xE,     0xA,   0x3E8,   0x3E8, },
+	{ 0xFF,     0xD,     0x0,   0x2BC, },
+	{  0xE,     0xA,   0x3E8,   0x320, },
+	{  0xE,     0xF,   0x3E8,   0x2BC, },
+	{  0xE,     0xA,   0x3E8,   0x258, },
+	{  0x5,     0xD,   0x3E8,   0x1F4, },
+	{  0xE,     0xA,   0x3E8,   0x190, },
+	{  0xE,     0xF,   0x3E8,   0x12C, },
+	{  0xE,     0xA,   0x3E8,    0xC8, },
+	{  0xB,     0xD,   0x3E8,    0x64, },
+	{  0xD,     0xB,   0x44C,    0x64, },
+	{  0x8,     0xC,   0x4B0,    0xC8, },
+	{  0x2,     0xA,   0x4B0,     0x0, },
+	{  0xD,     0xB,   0x514,    0x64, },
+	{  0xD,    0x10,   0x578,    0x64, },
+	{  0xD,     0xB,   0x5DC,    0x64, },
+	{  0xD,    0x10,   0x640,    0x64, },
+	{  0x8,     0xC,   0x6A4,    0xC8, },
+	{  0xC,     0xA,   0x6A4,     0x0, },
+	{  0xD,     0xB,   0x708,    0x64, },
+	{  0xD,    0x10,   0x76C,    0x64, },
+	{  0x7,     0xC,   0x7D0,    0x64, },
+	{  0xD,     0xB,   0x834,    0x64, },
+	{  0x8,     0xC,   0x898,    0xC8, },
+	{  0x2,     0xA,   0x898,     0x0, },
+	{  0xD,     0xC,   0x8FC,    0x64, },
+	{  0x7,     0xB,   0x960,    0x64, },
+	{  0xD,    0x10,   0x9C4,    0x64, },
+	{  0xD,     0xB,   0xA28,    0x64, },
+	{  0x1,    0x10,   0xA8C,    0x64, },
+	{  0xE,     0xA,   0x960,    0xC8, },
+	{  0x9,     0xD,   0x960,   0x12C, },
+	{  0xD,     0xB,   0x9C4,   0x12C, },
+	{  0xD,    0x10,   0xA28,   0x12C, },
+	{  0xD,     0xB,   0xA8C,   0x12C, },
+	{  0x1,    0x10,   0x8F0,   0x17C, },
+	{  0xE,     0xA,   0x7D0,    0xC8, },
+	{  0x6,     0xD,   0x834,   0x12C, },
+	{  0xD,     0xA,   0x76C,   0x12C, },
+	{  0x0,     0xF,   0x708,   0x12C, },
+	{  0xE,     0xA,   0x7D0,   0x190, },
+	{  0x9,     0xD,   0x7D0,   0x1F4, },
+	{  0xD,     0xB,   0x834,   0x1F4, },
+	{  0xD,    0x10,   0x898,   0x1F4, },
+	{  0xD,     0xB,   0x8FC,   0x1F4, },
+	{  0xD,    0x10,   0x960,   0x1F4, },
+	{  0xD,     0xB,   0x9C4,   0x1F4, },
+	{  0xD,    0x10,   0xA28,   0x1F4, },
+	{  0xC,     0xC,   0xA8C,   0x1F4, },
+	{  0xE,     0xA,   0xA8C,   0x258, },
+	{  0xE,     0xF,   0xA8C,   0x2BC, },
+	{  0xE,     0xA,   0xA8C,   0x320, },
+	{  0xE,     0xF,   0xA8C,   0x384, },
+	{  0xA,     0xD,   0xAF0,   0x3E8, },
+	{  0xD,     0xB,   0xA28,   0x3E8, },
+	{  0xD,    0x10,   0x44C,   0x1F4, },
+	{  0xD,     0xB,   0x4B0,   0x1F4, },
+	{  0xD,    0x10,   0x514,   0x1F4, },
+	{  0x8,     0xC,   0x578,   0x258, },
+	{  0xE,     0xA,   0x578,   0x190, },
+	{  0x2,     0xF,   0x578,   0x12C, },
+	{  0xD,     0xB,   0x5DC,   0x1F4, },
+	{  0x7,     0xC,   0x640,   0x1F4, },
+	{  0xD,     0xB,   0x6A4,   0x1F4, },
+	{  0x1,    0x10,   0x708,   0x1F4, },
+	{  0xE,     0xA,   0x640,   0x258, },
+	{  0xE,     0xF,   0x640,   0x2BC, },
+	{  0xE,     0xA,   0x640,   0x320, },
+	{  0xD,     0xB,   0x44C,   0x384, },
+	{  0xD,    0x10,   0x4B0,   0x384, },
+	{  0x7,     0xC,   0x514,   0x384, },
+	{  0xE,     0xA,   0x514,   0x3E8, },
+	{  0x3,     0xF,   0x514,   0x44C, },
+	{  0xD,     0xB,   0x578,   0x384, },
+	{  0xD,    0x10,   0x5DC,   0x384, },
+	{ 0xFF,     0xC,   0x3E8,   0x578, },
+	{  0xE,     0xB,   0x640,   0x3E8, },
+	{  0xE,    0x10,   0x640,   0x44C, },
+	{  0xE,     0xB,   0x640,   0x4B0, },
+	{  0x3,    0x10,   0x640,   0x514, },
+	{  0xD,     0xB,   0x6A4,   0x384, },
+	{  0x1,    0x10,   0x708,   0x384, },
+	{ 0xFF,     0xC,   0x1F4,   0x578, },
+	{ 0xFF,     0xC,    0xC8,   0x514, },
+	{ 0xFF,     0xD,   0x12C,   0x2BC, },
+	{ 0xFF,     0xD,   0x5DC,   0x514, },
+	{ 0xFF,     0xC,   0x3E8,    0x64, },
+	{ 0xFF,     0xD,   0xAF0,    0x64, },
+	{ 0xFF,     0xC,   0x960,   0x190, },
+	{ 0xFF,     0xD,   0x708,   0x12C, },
+	{ 0xFF,     0xC,   0x7D0,   0x258, },
+	{ 0xFF,     0xD,   0xAF0,   0x1F4, },
+	{ 0xFF,     0xC,   0xA8C,   0x44C, },
+	{ 0xFF,     0xC,   0x578,   0x12C, },
+	{ 0xFF,     0xD,   0x76C,   0x1F4, },
+	{ 0xFF,     0xC,   0x514,   0x4B0, },
+	{ 0xFF,     0xC,   0x640,   0x578, },
+	{ 0xFF,     0xD,   0x76C,   0x384, },
+	{  0x4,     0xE,   0x1F4,   0x384, },
+	{  0x4,     0xE,   0x3E8,   0x384, },
+	{  0x4,     0xE,   0x640,   0x384, },
+	{ 0xFF,     0xE,    0xC8,   0x384, },
+	{ 0xFF,     0xE,    0xC8,   0x2BC, },
+	{ 0xFF,     0xE,    0xC8,   0x4B0, },
+	{ 0xFF,     0xE,   0x3E8,   0x514, },
+	{ 0xFF,     0xE,   0x3E8,    0x64, },
+	{ 0xFF,     0xE,   0x514,   0x384, },
+	{ 0xFF,     0xE,   0x4B0,     0x0, },
+	{ 0xFF,     0xE,   0x578,   0x1F4, },
+	{ 0xFF,     0xE,   0x640,   0x1F4, },
+	{ 0xFF,     0xE,   0x6A4,     0x0, },
+	{ 0xFF,     0xE,   0x7D0,   0x12C, },
+	{ 0xFF,     0xE,   0x7D0,   0x1F4, },
+	{ 0xFF,     0xE,   0x898,     0x0, },
+	{ 0xFF,     0xE,   0x960,    0x64, },
+	{ 0xFF,     0xE,   0x960,   0x12C, },
+	{ 0xFF,     0xE,   0xA8C,   0x1F4, },
+	{ 0xFF,     0xE,   0xA8C,   0x3E8, },
+	{ 0xFF,     0xE,   0x3E8,   0x1F4, },
+    { -1,  -1,  -1,  -1, },
+};
+
+static const Point3 DUCT_SHAPE_0_POINTS[] = {
+	{    0,  300,    0 }, {    0,  100,    0 },
+	{  100,  400,    0 }, {  100,    0,    0 },
+	{  300,  400,    0 }, {  300,    0,    0 },
+	{  400,  300,    0 }, {  400,  100,    0 },
+	{    0,  300,  500 }, {    0,  100,  500 },
+	{  100,  400,  500 }, {  100,    0,  500 },
+	{  300,  400,  500 }, {  300,    0,  500 },
+	{  400,  300,  500 }, {  400,  100,  500 },
+};
+static const Point3 DUCT_SHAPE_1_POINTS[] = {
+	{    0,  300,  400 }, {    0,  100,  400 },
+	{    0,  400,  300 }, {    0,    0,  300 },
+	{    0,  400,  100 }, {    0,    0,  100 },
+	{    0,  300,    0 }, {    0,  100,    0 },
+	{  500,  300,  400 }, {  500,  100,  400 },
+	{  500,  400,  300 }, {  500,    0,  300 },
+	{  500,  400,  100 }, {  500,    0,  100 },
+	{  500,  300,    0 }, {  500,  100,    0 },
+};
+static const Point3 DUCT_SHAPE_2_POINTS[] = {
+	{    0,  300,  500 }, {    0,  100,  500 },
+	{  100,  400,  500 }, {  100,    0,  500 },
+	{  300,  400,  500 }, {  300,    0,  500 },
+	{  400,  300,  500 }, {  400,  100,  500 },
+	{   40,  300,  300 }, {   40,  100,  300 },
+	{  130,  400,  340 }, {  130,    0,  340 },
+	{  320,  400,  400 }, {  320,    0,  400 },
+	{  410,  300,  450 }, {  410,  100,  450 },
+	{  170,  300,  120 }, {  170,  100,  120 },
+	{  230,  400,  200 }, {  230,    0,  200 },
+	{  380,  400,  340 }, {  380,    0,  340 },
+	{  440,  300,  420 }, {  440,  100,  420 },
+	{  330,  300,   20 }, {  330,  100,   20 },
+	{  360,  400,  120 }, {  360,    0,  120 },
+	{  500,  300,  400 }, {  500,  100,  400 },
+	{  500,  400,  300 }, {  500,    0,  300 },
+	{  500,  400,  100 }, {  500,    0,  100 },
+	{  500,  300,    0 }, {  500,  100,    0 },
+};
+static const Point3 DUCT_SHAPE_3_POINTS[] = {
+	{  500,  300,  500 }, {  500,  100,  500 },
+	{  400,  400,  500 }, {  400,    0,  500 },
+	{  200,  400,  500 }, {  200,    0,  500 },
+	{  100,  300,  500 }, {  100,  100,  500 },
+	{  460,  300,  300 }, {  460,  100,  300 },
+	{  370,  400,  340 }, {  370,    0,  340 },
+	{  180,  400,  400 }, {  180,    0,  400 },
+	{   90,  300,  450 }, {   90,  100,  450 },
+	{  330,  300,  120 }, {  330,  100,  120 },
+	{  270,  400,  200 }, {  270,    0,  200 },
+	{  120,  400,  340 }, {  120,    0,  340 },
+	{   60,  300,  420 }, {   60,  100,  420 },
+	{  170,  300,   20 }, {  170,  100,   20 },
+	{  140,  400,  120 }, {  140,    0,  120 },
+	{    0,  300,  400 }, {    0,  100,  400 },
+	{    0,  400,  300 }, {    0,    0,  300 },
+	{    0,  400,  100 }, {    0,    0,  100 },
+	{    0,  300,    0 }, {    0,  100,    0 },
+};
+static const Point3 DUCT_SHAPE_4_POINTS[] = {
+	{    0,  300,  500 }, {    0,  100,  500 },
+	{    0,  400,  400 }, {    0,    0,  400 },
+	{    0,  400,  200 }, {    0,    0,  200 },
+	{    0,  300,  100 }, {    0,  100,  100 },
+	{  170,  300,  480 }, {  170,  100,  480 },
+	{  140,  400,  380 }, {  140,    0,  380 },
+	{  120,  400,  160 }, {  120,    0,  160 },
+	{   60,  300,   80 }, {   60,  100,   80 },
+	{  330,  300,  380 }, {  330,  100,  380 },
+	{  270,  400,  300 }, {  270,    0,  300 },
+	{  180,  400,  200 }, {  180,    0,  200 },
+	{   90,  300,   50 }, {   90,  100,   50 },
+	{  460,  300,  200 }, {  460,  100,  200 },
+	{  370,  400,  160 }, {  370,    0,  160 },
+	{  100,  300,    0 }, {  100,  100,    0 },
+	{  200,  400,    0 }, {  200,    0,    0 },
+	{  400,  400,    0 }, {  400,    0,    0 },
+	{  500,  300,    0 }, {  500,  100,    0 },
+};
+static const Point3 DUCT_SHAPE_5_6_POINTS[] = {
+	{  500,  300,  500 }, {  500,  100,  500 },
+	{  500,  400,  400 }, {  500,    0,  400 },
+	{  500,  400,  200 }, {  500,    0,  200 },
+	{  500,  300,  100 }, {  500,  100,  100 },
+	{  330,  300,  480 }, {  330,  100,  480 },
+	{  360,  400,  380 }, {  360,    0,  380 },
+	{  380,  400,  160 }, {  380,    0,  160 },
+	{  440,  300,   80 }, {  440,  100,   80 },
+	{  170,  300,  380 }, {  170,  100,  380 },
+	{  230,  400,  300 }, {  230,    0,  300 },
+	{  320,  400,  200 }, {  320,    0,  200 },
+	{  410,  300,   50 }, {  410,  100,   50 },
+	{   40,  300,  200 }, {   40,  100,  200 },
+	{  130,  400,  160 }, {  130,    0,  160 },
+	{  400,  300,    0 }, {  400,  100,    0 },
+	{  300,  400,    0 }, {  300,    0,    0 },
+	{  100,  400,    0 }, {  100,    0,    0 },
+	{    0,  300,    0 }, {    0,  100,    0 },
+};
+static const Point3 DUCT_SHAPE_7_POINTS[] = {
+	{    0,  300,    0 }, {    0,  100,    0 },
+	{  100,  400,    0 }, {  100,    0,    0 },
+	{  300,  400,    0 }, {  300,    0,    0 },
+	{  400,  300,    0 }, {  400,  100,    0 },
+	{    0,  300,  500 }, {    0,  100,  500 },
+	{  100,  400,  500 }, {  100,    0,  500 },
+	{  300,  400,  500 }, {  300,    0,  500 },
+	{  400,  300,  500 }, {  400,  100,  500 },
+};
+static const Point3 DUCT_SHAPE_8_9_POINTS[] = {
+	{    0,  300,  400 }, {    0,  100,  400 },
+	{    0,  400,  300 }, {    0,    0,  300 },
+	{    0,  400,  100 }, {    0,    0,  100 },
+	{    0,  300,    0 }, {    0,  100,    0 },
+	{    0,  300,  400 }, {    0,  100,  400 },
+	{    0,  400,  300 }, {    0,    0,  300 },
+	{    0,  400,  100 }, {    0,    0,  100 },
+	{    0,  300,    0 }, {    0,  100,    0 },
+};
+static const Point3 DUCT_SHAPE_10_POINTS[] = {
+	{    0,  100,    0 }, {    0,    0,    0 },
+	{    0,  100,  100 }, {    0,    0,  100 },
+	{  100,  100,  100 }, {  100,    0,  100 },
+	{  100,  100,    0 }, {  100,    0,    0 },
+};
+static const Point3 DUCT_SHAPE_11_POINTS[] = {
+	{    0,  100,    0 }, {    0,    0,    0 },
+	{    0,  100,  100 }, {    0,    0,  100 },
+	{  100,  100,  100 }, {  100,    0,  100 },
+	{  100,  100,    0 }, {  100,    0,    0 },
+};
+static const Point3 DUCT_SHAPE_12_POINTS[] = {
+	{    0,  100,    0 }, {    0,    0,    0 },
+	{  100,  100,    0 }, {  100,    0,    0 },
+};
+static const Point3 DUCT_SHAPE_13_POINTS[] = {
+	{    0,  100,    0 }, {    0,    0,    0 },
+	{    0,  100,  100 }, {    0,    0,  100 },
+};
+static const Point3 DUCT_SHAPE_14_POINTS[] = {
+	{    0,  100,    0 }, {    0,    0,    0 },
+	{    0,  100,  100 }, {    0,    0,  100 },
+	{  100,  100,  100 }, {  100,    0,  100 },
+	{  100,  100,    0 }, {  100,    0,    0 },
+};
+static const Point3 DUCT_SHAPE_15_POINTS[] = {
+	{    0,  100,    0 }, {    0,    0,    0 },
+	{    0,  100,  100 }, {    0,    0,  100 },
+	{  100,  100,  100 }, {  100,    0,  100 },
+	{  100,  100,    0 }, {  100,    0,    0 },
+};
+static const Point3 DUCT_SHAPE_16_POINTS[] = {
+	{    0,  100,    0 }, {    0,    0,    0 },
+	{    0,  100,  100 }, {    0,    0,  100 },
+	{  100,  100,  100 }, {  100,    0,  100 },
+	{  100,  100,    0 }, {  100,    0,    0 },
+};
+
+static const uint16 DUCT_SHAPE_0_DATA[] = {
+    1, 4, 8, 0xA, 2, 0, 2, 4, 1, 9, 8, 0, 3, 4, 0xB, 3,
+    1, 9, 4, 4, 0xC, 0xA, 2, 4, 5, 4, 0xD, 0xB, 3, 5, 6, 4,
+    0xE, 0xC, 4, 6, 7, 4, 0xF, 0xE, 6, 7, 8, 4, 0xF, 0xD, 5, 7
+};
+static const uint16 DUCT_SHAPE_1_DATA[] = {
+    1, 4, 8, 0xA, 2, 0, 2, 4, 1, 9, 8, 0, 3, 4, 0xB, 3, 1, 9,
+	4, 4, 0xC, 0xA, 2, 4, 5, 4, 0xD, 0xB, 3, 5, 6, 4, 0xE, 0xC,
+	4, 6, 7, 4, 0xF, 0xE, 6, 7, 8, 4, 0xF, 0xD, 5, 7
+};
+static const uint16 DUCT_SHAPE_2_DATA[] = {
+	0x1, 0x4, 0x8, 0xA, 0x2, 0x0, 0x2, 0x4, 0x9, 0xB, 0x3, 0x1,
+	0x3, 0x4, 0x8, 0x9, 0x1, 0x0, 0x4, 0x4, 0x10, 0x12, 0xA, 0x8,
+	0x5, 0x4, 0x11, 0x13, 0xB, 0x9, 0x6, 0x4, 0x11, 0x10, 0x8, 0x9,
+	0x7, 0x4, 0x18, 0x1A, 0x12, 0x10, 0x8, 0x4, 0x19, 0x1B, 0x13, 0x11,
+	0x1, 0x4, 0x19, 0x18, 0x10, 0x11, 0x2, 0x4, 0x22, 0x20, 0x1A, 0x18,
+	0x3, 0x4, 0x23, 0x21, 0x1B, 0x19, 0x4, 0x4, 0x23, 0x22, 0x18, 0x19,
+	0x5, 0x9, 0x2, 0xA, 0x12, 0x1A, 0x20, 0x1E, 0x14, 0xC, 0x4, 0x6,
+	0x9, 0x3, 0xB, 0x13, 0x1B, 0x21, 0x1F, 0x15, 0xD, 0x5, 0x7, 0x4,
+	0x1C, 0x16, 0x14, 0x1E, 0x8, 0x4, 0x1F, 0x1D, 0x17, 0x15, 0x1, 0x4,
+	0x1D, 0x1C, 0x16, 0x17, 0x2, 0x4, 0xC, 0x14, 0x16, 0xE, 0x3, 0x4,
+	0xD, 0x15, 0x17, 0xF, 0x4, 0x4, 0x17, 0x16, 0xE, 0xF, 0x5, 0x4,
+	0x4, 0xC, 0xE, 0x6, 0x6, 0x4, 0xD, 0xF, 0x7, 0x5, 0x7, 0x4,
+	0xF, 0xE, 0x6, 0x7
+};
+static const uint16 DUCT_SHAPE_3_DATA[] = {
+	0x1, 0x4, 0x8, 0xA, 0x2, 0x0, 0x2, 0x4, 0x9, 0xB, 0x3, 0x1,
+	0x3, 0x4, 0x8, 0x9, 0x1, 0x0, 0x4, 0x4, 0x10, 0x12, 0xA, 0x8,
+	0x5, 0x4, 0x11, 0x13, 0xB, 0x9, 0x6, 0x4, 0x11, 0x10, 0x8, 0x9,
+	0x7, 0x4, 0x18, 0x1A, 0x12, 0x10, 0x8, 0x4, 0x19, 0x1B, 0x13, 0x11,
+	0x1, 0x4, 0x19, 0x18, 0x10, 0x11, 0x2, 0x4, 0x22, 0x20, 0x1A, 0x18,
+	0x3, 0x4, 0x23, 0x21, 0x1B, 0x19, 0x4, 0x4, 0x23, 0x22, 0x18, 0x19,
+	0x5, 0x9, 0x2, 0xA, 0x12, 0x1A, 0x20, 0x1E, 0x14, 0xC, 0x4, 0x6,
+	0x9, 0x3, 0xB, 0x13, 0x1B, 0x21, 0x1F, 0x15, 0xD, 0x5, 0x7, 0x4,
+	0x1C, 0x16, 0x14, 0x1E, 0x8, 0x4, 0x1F, 0x1D, 0x17, 0x15, 0x1, 0x4,
+	0x1D, 0x1C, 0x16, 0x17, 0x2, 0x4, 0xC, 0x14, 0x16, 0xE, 0x3, 0x4,
+	0xD, 0x15, 0x17, 0xF, 0x4, 0x4, 0x17, 0x16, 0xE, 0xF, 0x5, 0x4,
+	0x4, 0xC, 0xE, 0x6, 0x6, 0x4, 0xD, 0xF, 0x7, 0x5, 0x7, 0x4,
+	0xF, 0xE, 0x6, 0x7
+};
+static const uint16 DUCT_SHAPE_4_DATA[] = {
+	0x1, 0x4, 0x8, 0xA, 0x2, 0x0, 0x2, 0x4, 0x9, 0xB, 0x3, 0x1,
+	0x3, 0x4, 0x8, 0x9, 0x1, 0x0, 0x4, 0x4, 0x10, 0x12, 0xA, 0x8,
+	0x5, 0x4, 0x11, 0x13, 0xB, 0x9, 0x6, 0x4, 0x11, 0x10, 0x8, 0x9,
+	0x7, 0x4, 0x18, 0x1A, 0x12, 0x10, 0x8, 0x4, 0x19, 0x1B, 0x13, 0x11,
+	0x1, 0x4, 0x19, 0x18, 0x10, 0x11, 0x2, 0x4, 0x22, 0x20, 0x1A, 0x18,
+	0x3, 0x4, 0x23, 0x21, 0x1B, 0x19, 0x4, 0x4, 0x23, 0x22, 0x18, 0x19,
+	0x5, 0x9, 0x2, 0xA, 0x12, 0x1A, 0x20, 0x1E, 0x14, 0xC, 0x4, 0x6,
+	0x9, 0x3, 0xB, 0x13, 0x1B, 0x21, 0x1F, 0x15, 0xD, 0x5, 0x7, 0x4,
+	0x1C, 0x16, 0x14, 0x1E, 0x8, 0x4, 0x1F, 0x1D, 0x17, 0x15, 0x1, 0x4,
+	0x1D, 0x1C, 0x16, 0x17, 0x2, 0x4, 0xC, 0x14, 0x16, 0xE, 0x3, 0x4,
+	0xD, 0x15, 0x17, 0xF, 0x4, 0x4, 0x17, 0x16, 0xE, 0xF, 0x5, 0x4,
+	0x4, 0xC, 0xE, 0x6, 0x6, 0x4, 0xD, 0xF, 0x7, 0x5, 0x7, 0x4,
+	0xF, 0xE, 0x6, 0x7
+};
+static const uint16 DUCT_SHAPE_5_6_DATA[] = {
+	0x1, 0x4, 0x8, 0xA, 0x2, 0x0, 0x2, 0x4, 0x9, 0xB, 0x3, 0x1,
+	0x3, 0x4, 0x8, 0x9, 0x1, 0x0, 0x4, 0x4, 0x10, 0x12, 0xA, 0x8,
+	0x5, 0x4, 0x11, 0x13, 0xB, 0x9, 0x6, 0x4, 0x11, 0x10, 0x8, 0x9,
+	0x7, 0x4, 0x18, 0x1A, 0x12, 0x10, 0x8, 0x4, 0x19, 0x1B, 0x13, 0x11,
+	0x1, 0x4, 0x19, 0x18, 0x10, 0x11, 0x2, 0x4, 0x22, 0x20, 0x1A, 0x18,
+	0x3, 0x4, 0x23, 0x21, 0x1B, 0x19, 0x4, 0x4, 0x23, 0x22, 0x18, 0x19,
+	0x5, 0x9, 0x2, 0xA, 0x12, 0x1A, 0x20, 0x1E, 0x14, 0xC, 0x4, 0x6,
+	0x9, 0x3, 0xB, 0x13, 0x1B, 0x21, 0x1F, 0x15, 0xD, 0x5, 0x7, 0x4,
+	0x1C, 0x16, 0x14, 0x1E, 0x8, 0x4, 0x1F, 0x1D, 0x17, 0x15, 0x1, 0x4,
+	0x1D, 0x1C, 0x16, 0x17, 0x2, 0x4, 0xC, 0x14, 0x16, 0xE, 0x3, 0x4,
+	0xD, 0x15, 0x17, 0xF, 0x4, 0x4, 0x17, 0x16, 0xE, 0xF, 0x5, 0x4,
+	0x4, 0xC, 0xE, 0x6, 0x6, 0x4, 0xD, 0xF, 0x7, 0x5, 0x7, 0x4,
+	0xF, 0xE, 0x6, 0x7
+};
+static const uint16 DUCT_SHAPE_7_DATA[] = {
+	0x2, 0x4, 0x8, 0xA, 0x2, 0x0, 0x3, 0x4, 0x1, 0x9, 0x8, 0x0,
+	0x4, 0x4, 0xB, 0x3, 0x1, 0x9, 0x5, 0x4, 0xC, 0xA, 0x2, 0x4,
+	0x6, 0x4, 0xD, 0xB, 0x3, 0x5, 0x7, 0x4, 0xE, 0xC, 0x4, 0x6,
+	0x8, 0x4, 0xF, 0xE, 0x6, 0x7, 0x1, 0x4, 0xF, 0xD, 0x5, 0x7
+};
+static const uint16 DUCT_SHAPE_8_9_DATA[] = {
+	0x2, 0x4, 0x8, 0xA, 0x2, 0x0, 0x3, 0x4, 0x1, 0x9, 0x8, 0x0,
+	0x4, 0x4, 0xB, 0x3, 0x1, 0x9, 0x5, 0x4, 0xC, 0xA, 0x2, 0x4,
+	0x6, 0x4, 0xD, 0xB, 0x3, 0x5, 0x7, 0x4, 0xE, 0xC, 0x4, 0x6,
+	0x8, 0x4, 0xF, 0xE, 0x6, 0x7, 0x1, 0x4, 0xF, 0xD, 0x5, 0x7
+};
+static const uint16 DUCT_SHAPE_10_DATA[] = {
+	5, 4, 0, 2, 3, 1, 3, 4, 0, 6, 4, 2,
+	4, 4, 1, 7, 5, 3, 6, 4, 6, 7, 5, 4
+};
+static const uint16 DUCT_SHAPE_11_DATA[] = {
+	3, 4, 0, 6, 4, 2, 4, 4, 1, 7, 5, 3,
+	5, 4, 0, 6, 7, 1, 6, 4, 2, 4, 5, 3
+};
+static const uint16 DUCT_SHAPE_12_DATA[] = {
+	7, 4, 0, 1, 3, 2
+};
+static const uint16 DUCT_SHAPE_13_DATA[] = {
+	7, 4, 0, 1, 3, 2
+};
+static const uint16 DUCT_SHAPE_14_DATA[] = {
+	1, 4, 0, 6, 4, 2, 2, 4, 1, 7, 5, 3
+};
+static const uint16 DUCT_SHAPE_15_DATA[] = {
+    6, 4, 0, 2, 3, 1, 4, 4, 0, 6, 4, 2,
+    3, 4, 1, 7, 5, 3, 5, 4, 6, 7, 5, 4
+};
+static const uint16 DUCT_SHAPE_16_DATA[] = {
+	4, 4, 0, 6, 4, 2, 3, 4, 1, 7, 5, 3,
+	6, 4, 0, 6, 7, 1, 5, 4, 2, 4, 5, 3
+};
+
+static const DuctShape DUCT_SHAPE_0[] = {
+	16, 8, DUCT_SHAPE_0_POINTS, DUCT_SHAPE_0_DATA
+};
+
+static const DuctShape DUCT_SHAPE_1[] = {
+	16, 8, DUCT_SHAPE_1_POINTS, DUCT_SHAPE_1_DATA
+};
+
+static const DuctShape DUCT_SHAPE_2[] = {
+	36, 23, DUCT_SHAPE_2_POINTS, DUCT_SHAPE_2_DATA
+};
+
+static const DuctShape DUCT_SHAPE_3[] = {
+	36, 23, DUCT_SHAPE_3_POINTS, DUCT_SHAPE_3_DATA
+};
+
+static const DuctShape DUCT_SHAPE_4[] = {
+	36, 23, DUCT_SHAPE_4_POINTS, DUCT_SHAPE_4_DATA
+};
+
+static const DuctShape DUCT_SHAPE_5_6[] = {
+	36, 23, DUCT_SHAPE_5_6_POINTS, DUCT_SHAPE_5_6_DATA
+};
+
+static const DuctShape DUCT_SHAPE_7[] = {
+	16, 8, DUCT_SHAPE_7_POINTS, DUCT_SHAPE_7_DATA
+};
+
+static const DuctShape DUCT_SHAPE_8_9[] = {
+	16, 8, DUCT_SHAPE_8_9_POINTS, DUCT_SHAPE_8_9_DATA
+};
+
+static const DuctShape DUCT_SHAPE_10[] = {
+	8, 4, DUCT_SHAPE_10_POINTS, DUCT_SHAPE_10_DATA
+};
+
+static const DuctShape DUCT_SHAPE_11[] = {
+	8, 4, DUCT_SHAPE_11_POINTS, DUCT_SHAPE_11_DATA
+};
+
+static const DuctShape DUCT_SHAPE_12[] = {
+	4, 1, DUCT_SHAPE_12_POINTS, DUCT_SHAPE_12_DATA
+};
+
+static const DuctShape DUCT_SHAPE_13[] = {
+	4, 1, DUCT_SHAPE_13_POINTS, DUCT_SHAPE_13_DATA
+};
+
+static const DuctShape DUCT_SHAPE_14[] = {
+	8, 2, DUCT_SHAPE_14_POINTS, DUCT_SHAPE_14_DATA
+};
+
+static const DuctShape DUCT_SHAPE_15[] = {
+	8, 4, DUCT_SHAPE_15_POINTS, DUCT_SHAPE_15_DATA
+};
+
+static const DuctShape DUCT_SHAPE_16[] = {
+	8, 4, DUCT_SHAPE_16_POINTS, DUCT_SHAPE_16_DATA
+};
+
+
+const DuctShape *DUCT_SHAPE_DATA[17] {
+	DUCT_SHAPE_0, DUCT_SHAPE_1, DUCT_SHAPE_2, DUCT_SHAPE_3,
+	DUCT_SHAPE_4, DUCT_SHAPE_5_6, DUCT_SHAPE_5_6, DUCT_SHAPE_7,
+	DUCT_SHAPE_8_9, DUCT_SHAPE_8_9, DUCT_SHAPE_10, DUCT_SHAPE_11,
+	DUCT_SHAPE_12, DUCT_SHAPE_13, DUCT_SHAPE_14, DUCT_SHAPE_15,
+	DUCT_SHAPE_16
+};
+
 static const byte FONT1_WIDTHS[] = {
 	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
 	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
diff --git a/engines/access/martian/martian_resources.h b/engines/access/martian/martian_resources.h
index 30bd8ef6138..1d280b8a4e8 100644
--- a/engines/access/martian/martian_resources.h
+++ b/engines/access/martian/martian_resources.h
@@ -50,7 +50,33 @@ extern const char *const TRAVDATA[];
 extern const char *const SPEC7MESSAGE;
 
 extern const byte CAN_TRAVEL_MATRIX[];
-extern const int PICTURERANGE[][2];
+extern const int16 PICTURE_RANGE[][2];
+extern const int16 DUCT_ARROW_BUTTON_RANGE[][2];
+
+struct DuctMapPoint {
+	int16 ptType;
+	int16 shapeType;
+	int16 x;
+	int16 y;
+};
+
+extern const DuctMapPoint DUCT_MAP_DATA[];
+
+
+struct Point3 {
+	int16 x;
+	int16 y;
+	int16 z;
+};
+
+struct DuctShape {
+	int16 numPts;
+	int16 array2Len;
+	const Point3 *points;
+	const uint16 *data;
+};
+
+extern const DuctShape *DUCT_SHAPE_DATA[];
 
 class MartianResources : public Resources {
 protected:
diff --git a/engines/access/martian/martian_scripts.cpp b/engines/access/martian/martian_scripts.cpp
index f36a852ff3a..41b67874f4a 100644
--- a/engines/access/martian/martian_scripts.cpp
+++ b/engines/access/martian/martian_scripts.cpp
@@ -23,7 +23,7 @@
 #include "access/access.h"
 #include "access/martian/martian_game.h"
 #include "access/martian/martian_resources.h"
-#include "access/martian/martian_tunnel.h"
+#include "access/martian/martian_duct.h"
 #include "access/martian/martian_scripts.h"
 
 namespace Access {
@@ -32,11 +32,11 @@ namespace Martian {
 
 MartianScripts::MartianScripts(AccessEngine *vm) : Scripts(vm) {
 	_game = (MartianEngine *)_vm;
-	_tunnel = new MartianTunnel(_game);
+	_duct = new MartianDuct(_game);
 }
 
 MartianScripts::~MartianScripts() {
-	delete _tunnel;
+	delete _duct;
 }
 
 void MartianScripts::cmdSpecial0() {
@@ -113,7 +113,7 @@ void MartianScripts::cmdSpecial1(int param1, int param2) {
 }
 
 void MartianScripts::cmdSpecial2() {
-	_tunnel->tunnel2();
+	_duct->duct2();
 };
 
 void MartianScripts::cmdSpecial3() {
@@ -128,7 +128,7 @@ void MartianScripts::cmdSpecial3() {
 }
 
 void MartianScripts::cmdSpecial4() {
-	_tunnel->tunnel4();
+	_duct->duct4();
 }
 
 void MartianScripts::doIntro(int param1) {
diff --git a/engines/access/martian/martian_scripts.h b/engines/access/martian/martian_scripts.h
index f31f77f7db8..a1f9f495db5 100644
--- a/engines/access/martian/martian_scripts.h
+++ b/engines/access/martian/martian_scripts.h
@@ -30,12 +30,12 @@ namespace Access {
 namespace Martian {
 
 class MartianEngine;
-class MartianTunnel;
+class MartianDuct;
 
 class MartianScripts : public Scripts {
 private:
 	MartianEngine *_game;
-	MartianTunnel *_tunnel;
+	MartianDuct *_duct;
 
 	void cmdSpecial0();
 	void cmdSpecial1(int param1, int param2);
diff --git a/engines/access/martian/martian_tunnel.cpp b/engines/access/martian/martian_tunnel.cpp
deleted file mode 100644
index 67376039555..00000000000
--- a/engines/access/martian/martian_tunnel.cpp
+++ /dev/null
@@ -1,143 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#include "access/martian/martian_tunnel.h"
-#include "access/martian/martian_game.h"
-
-namespace Access {
-
-namespace Martian {
-
-MartianTunnel::MartianTunnel(MartianEngine *vm) : _vm(vm), _tunnelParam_ca42(0), _tunnelParam_ca44(0), _tunnelParam_ca46(0), _tunnelMoveFlag(0) {
-}
-
-MartianTunnel::~MartianTunnel() {
-}
-
-void MartianTunnel::tunnel2() {
-	_tunnelParam_ca42 = 0x226;
-	_tunnelParam_ca44 = 10;
-	_tunnelParam_ca46 = 0x352;
-	doTunnel();
-}
-
-void MartianTunnel::tunnel4() {
-	_tunnelParam_ca42 = 0xabe;
-	_tunnelParam_ca44 = 10;
-	_tunnelParam_ca46 = 0x41a;
-	doTunnel();
-}
-
-void MartianTunnel::doTunnel() {
-	_vm->_screen->forceFadeOut();
-	_vm->_events->hideCursor();
-	_vm->_files->loadScreen(20, 0);
-	Resource *res = _vm->_files->loadFile(20, 1);
-	_vm->_objectsTable[20] = new SpriteResource(_vm, res);
-	if (_vm->_inventory->_inv[40]._value == ITEM_IN_INVENTORY) {
-		_vm->_screen->plotImage(_vm->_objectsTable[20], 8, Common::Point(140, 10));
-	}
-	_vm->_events->showCursor();
-	_vm->_screen->forceFadeIn();
-	_crawlFrame = 0;
-	_tunnelMoveFlag = 0;
-
-	warning("***TODO***: Init all the other tunnel variables here");
-
-	drawArrowSprites();
-	drawArrowSprites2();
-	_vm->_room->_function = FN_NONE;
-
-	/*
-	while (true) {
-		clearWorkScreenArea();
-		tunnel_17f5c();
-		tunnel_1888a();
-		tunnel_18985();
-		_vm->_buffer2.plotImage(_vm->_objectsTable[20], _crawlFrame, Common::Point(140, 94));
-		copyBufBlockToScreen();
-		if (_vm->_room->_function != FN_NONE)
-			break;
-		do {
-			tunnel_doloop_18c65();
-		} while (_tunnelStopLoop_ca27 == 0);
-	}
-	*/
-	// FIXME: Quick hack skip this part
-	g_system->displayMessageOnOSD(Common::U32String("Duct tunnel section not implemented yet!"));
-	_vm->_flags[0x62] = 1;
-	_vm->_flags[0x55] = 1;
-	_vm->_room->_function = FN_CLEAR1;
-	
-
-
-	delete _vm->_objectsTable[20];
-	_vm->_objectsTable[20] = nullptr;
-}
-
-void MartianTunnel::drawArrowSprites() {
-	int x;
-	int y;
-	int frame;
-
-	_vm->_events->hideCursor();
-	_vm->_screen->plotImage(_vm->_objectsTable[20], 7, Common::Point(4, 0x50));
-	if (_tunnelMoveFlag == 0) {
-		x = 0x11;
-		y = 0x50;
-		frame = 9;
-	} else if (_tunnelMoveFlag == 0x40) {
-		x = 0x19;
-		y = 0x57;
-		frame = 12;
-	} else if (_tunnelMoveFlag == 0x80) {
-		x = 0xe;
-		y = 0x5d;
-		frame = 11;
-	} else {
-		x = 4;
-		y = 0x57;
-		frame = 10;
-	}
-	_vm->_screen->plotImage(_vm->_objectsTable[20], frame, Common::Point(x, y));
-	_vm->_events->showCursor();
-}
-	
-void MartianTunnel::drawArrowSprites2() {
-	_vm->_events->hideCursor();
-	_vm->_screen->plotImage(_vm->_objectsTable[20], 14, Common::Point(16, 0x58));
-	if ((3 < _tunnel_ca20) && (_tunnel_ca20 < 0xd)) {
-		_vm->_screen->plotImage(_vm->_objectsTable[20], 13, Common::Point(17, 0x59));
-	}
-	_vm->_events->showCursor();
-}
-
-void MartianTunnel::clearWorkScreenArea() {
-	_vm->_buffer2.fillRect(Common::Rect(100, 60, 220, 140), 0);
-}
-
-void MartianTunnel::copyBufBlockToScreen() {
-	_vm->_screen->copyBlock(&_vm->_buffer2, Common::Rect(100, 60, 220, 140));
-}
-
-}
-
-} // end namespace Access
diff --git a/engines/access/martian/martian_tunnel.h b/engines/access/martian/martian_tunnel.h
deleted file mode 100644
index 81f77cf186c..00000000000
--- a/engines/access/martian/martian_tunnel.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#ifndef ACCESS_MARTIAN_MARTIAN_TUNNEL_H
-#define ACCESS_MARTIAN_MARTIAN_TUNNEL_H
-
-#include "common/scummsys.h"
-
-namespace Access {
-
-namespace Martian {
-
-class MartianEngine;
-
-class MartianTunnel {
-public:
-	MartianTunnel(MartianEngine *vm);
-	~MartianTunnel();
-
-	void tunnel2();
-	void tunnel4();
-
-private:
-	void doTunnel();
-	void drawArrowSprites();
-	void drawArrowSprites2();
-	void clearWorkScreenArea();
-	void copyBufBlockToScreen();
-	void tunnel_doloop_8c65();
-	void tunnel_17f5c();
-	void tunnel_1888a();
-	void tunnel_18985();
-
-	MartianEngine *_vm;
-	int16 _tunnelParam_ca42;
-	int16 _tunnelParam_ca44;
-	int16 _tunnelParam_ca46;
-	uint16 _tunnel_ca20;
-	byte _tunnelMoveFlag;
-	int16 _crawlFrame;
-	byte _tunnelStopLoop_ca27;
-};
-
-}
-
-} // end namespace Access
-
-#endif // ACCESS_MARTIAN_MARTIAN_TUNNEL_H
diff --git a/engines/access/module.mk b/engines/access/module.mk
index 6e26737b96a..cf861d4d48f 100644
--- a/engines/access/module.mk
+++ b/engines/access/module.mk
@@ -30,7 +30,7 @@ MODULE_OBJS := \
 	martian/martian_game.o \
 	martian/martian_player.o \
 	martian/martian_resources.o \
-	martian/martian_tunnel.o \
+	martian/martian_duct.o \
 	martian/martian_room.o \
 	martian/martian_scripts.o \
 	martian/midiparser_bemd.o \
diff --git a/engines/access/room.cpp b/engines/access/room.cpp
index b1a6e1bc8b7..c93c9253b66 100644
--- a/engines/access/room.cpp
+++ b/engines/access/room.cpp
@@ -91,10 +91,10 @@ void Room::takePicture() {
 	}
 
 	Common::Array<Common::Rect> pictureCoords;
-	for (int i = 0; Martian::PICTURERANGE[i][0] != -1; i += 2) {
-		// PICTURERANGE is min/max X, min/max Y
-		pictureCoords.push_back(Common::Rect(Martian::PICTURERANGE[i][0], Martian::PICTURERANGE[i + 1][0],
-			                                 Martian::PICTURERANGE[i][1], Martian::PICTURERANGE[i + 1][1]));
+	for (int i = 0; Martian::PICTURE_RANGE[i][0] != -1; i += 2) {
+		// PICTURE_RANGE is min/max X, min/max Y
+		pictureCoords.push_back(Common::Rect(Martian::PICTURE_RANGE[i][0], Martian::PICTURE_RANGE[i + 1][0],
+			                                 Martian::PICTURE_RANGE[i][1], Martian::PICTURE_RANGE[i + 1][1]));
 	}
 
 	int result = _vm->_events->checkMouseBox1(pictureCoords);


Commit: 28a3a8134748bac7bf2c5f7e9ced01b416404493
    https://github.com/scummvm/scummvm/commit/28a3a8134748bac7bf2c5f7e9ced01b416404493
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2025-09-23T20:02:04+10:00

Commit Message:
NEWS: Mention Martian Memorandum support

Changed paths:
    NEWS.md


diff --git a/NEWS.md b/NEWS.md
index f0c4abc353a..5fe925a680d 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -26,9 +26,11 @@ For a more comprehensive changelog of the latest experimental code, see:
    - Added support for Adibou 2: Nature & Sciences.
    - Added support for WAGE-based game. More than 160 titles so far.
    - Added support for Penumbra: Overture.
+   - Added support for Tex Murphy: Martian Memorandum
 
  Access:
    - Added keymapper support.
+   - Fixed foodstep sounds.
 
  ADL:
    - Added Text-to-Speech support.


Commit: f0a14d295e1d06b9576509780646698534ae7898
    https://github.com/scummvm/scummvm/commit/f0a14d295e1d06b9576509780646698534ae7898
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2025-09-23T20:02:15+10:00

Commit Message:
ACCESS: Mark MM as ready for testing

Changed paths:
    engines/access/detection_tables.h


diff --git a/engines/access/detection_tables.h b/engines/access/detection_tables.h
index 4a8d35fcdfc..f20427c6725 100644
--- a/engines/access/detection_tables.h
+++ b/engines/access/detection_tables.h
@@ -93,7 +93,7 @@ static const AccessGameDescription gameDescriptions[] = {
 			AD_ENTRY1s("r01.ap", "c081daca9b0cfd710157cf946e343df6", 39352),
 			Common::EN_ANY,
 			Common::kPlatformDOS,
-			ADGF_UNSTABLE,
+			ADGF_TESTING,
 			GUIO1(GUIO_NONE)
 		},
 		kGameMartianMemorandum,




More information about the Scummvm-git-logs mailing list