sierrahotel/src/main.c
2024-08-09 16:33:09 -05:00

770 lines
20 KiB
C

/*
* Copyright (c) 2024 Scott Duensing, scott@kangaroopunch.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*
* Memory Map:
*
* Far Heap (upper 448k):
*
* 0x7ffff - Top of far heap / End of bitmap page 1
* - Bitmap page is 0x14000 (81920 bytes)
* 0x6c000 - Start of bitmap page 1
* 0x6bfff - End of bitmap page 2
* - Bitmap page is 0x14000 (81920 bytes)
* 0x58000 - Start of bitmap page 2
* 0x57fff - End of A2-3D2 and data to be used in slots 3 and 2/4 below
* - ...
* 0x54000 - Start of A2-3D2 and data to be used in slots 3 and 2/4 below
* - 57344 bytes free
* 0x45fff - End of overlay code
* - ...
* 0x10000 - Bottom of far heap / Start of overlay code
*
*
* Near Heap (lower 64k):
*
* 0xffff - Top of near heap
* - ...
* 0xe000 - Microkernel
* - ...
* 0xc000 - I/O and microkernel
* - ...
* 0xa000 - Overlay code
* 0x9fff - Top of virtual stack
* - ...
* 0x8000 - Program code / 3D/2D data
* - ...
* 0x6000 - Program code / 3D library
* - ...
* 0x0300 - Start of program code
* 0x02ff - End of program arguments / data storage
* - ... (We're going to overwrite this!)
* 0x0200 - Start of program arguments / data storage
* 0x01ff - Top of CPU stack space
* - ...
* 0x0100 - Bottom of CPU stack space
* 0x00ff - End of microkernel use
* - ...
* 0x00f0 - Start of microkernel use
* - 46 bytes free
* 0x00c2 - End of A2-3D2 use
* - ...
* 0x0060 - Start of A2-3D2 use
* - 49 bytes free
* 0x002f - llvm virtual register 31
* - ...
* 0x0010 - llvm virtual register 0
* 0x000f - MMU_MEM_BANK_7
* - ...
* 0x0008 - MMU_MEM_BANK_0
* - 7 bytes free
* 0x0001 - MMU_IO_CTRL
* 0x0000 - MMU_MEM_CTRL
*
*
* Calling the 3D Renderer:
*
* The MMU splits the near heap into 8k "slots" that 8k "blocks" of far heap
* can be mapped into. f256lib, by default, sets the near heap up as
* follows:
*
* 0xe000 - Slot 7 - Microkernel
* 0xc000 - Slot 6 - I/O and Microkernel
* 0xa000 - Slot 5 - Code Overlay Region
* 0x8000 - Slot 4 - Program Code
* 0x6000 - Slot 3 - Program Code
* 0x4000 - Slot 2 - Program Code
* 0x2000 - Slot 1 - Program Code
* 0x0000 - Slot 0 - Zero Page & Program Code
*
* Our 3D library, subLOGIC's A2-3D2, was designed to be run on an Apple ][.
* The Apple ][ has two high-resolution video pages in the middle of the
* memory map and A2-3D2 loads at 0x6000 immediately after the second video
* page. This makes sense on the Apple. On the Foenix, it's in the way at
* slot 3. The library also needs a 3D scene database and outputs an array
* of 2D drawing instructions. Given the size of A2-3D2 this pushes our
* memory usage up into slot 4. This leaves slots 0 to 2 for the simulator.
* That's less than 24k. Not good.
*
* We're going to place the 3D library and related data in the far heap and
* manually swap them in using the MMU when we need to render the scene. To
* do this, we need to ensure we're not currently executing code from
* somewhere in the slot 2 to 4 range. To accomplish this, all the functions
* that deal with A2-3D2 are placed in an overlay. This ensures whatever
* code calls into slots 2 to 4 has a program counter between 0xa000 and
* 0xbfff - well out of the way of the 3D stuff.
*
* When enabling compiler optimizations, both the compiler and A2-3D2 want
* use of the remaining zero page. We maintain copies of the area that
* gets clobbered by each and make sure the right one is in place at the
* right time.
*
* The last thing to worry about is data needed by both the simulator and
* A2-3D2. This is basically only the camera position and rotation. These
* 10 bytes will be stored directly into RAM from 0x200 to 0x20a overwriting
* any command line arguments that may have been in memory.
*
*/
#include <string.h>
#ifdef __F256__
#define WITHOUT_FILE
#define WITHOUT_SPRITE
#define WITHOUT_TILE
#define F256LIB_IMPLEMENTATION
#include "f256lib.h"
/*
* embedded.bin is loaded at 0x54000:
*
* 8k a2-3d2.bin @ 0x54000
* 8k scene.3d @ 0x56000
*
*/
EMBED(embedded, "embedded.bin", 0x54000);
#endif
#include "a23d2.h"
asm (
".text\n"
// " .cpu mosw65c02\n"
// "mos-insns-w65c02\n"
// "EF_MOS_ARCH_W65C02 \n"
".global dmaClear\n"
"dmaClear:\n"
"pha\n"
// "phx\n"
".byte $DA\n"
"php \n" // preserver interrupt state + c
"sei \n" // disable interrupts
"ldy 1 \n" // y = io_ctrl
//"phy \n" // save io_ctrl on stack
".byte $5A\n"
//"stz 1 \n" // map in the dma registers, where we can get them
".byte $64,$01 \n"
//"stz $DF00 \n" // clear dma control
".byte $9c,$00,$DF\n"
"ldy #$05 \n" // DMA_CTRL_ENABLE+DMA_CTRL_FILL
"sty $DF00 \n"
"ldy __rc8 \n" // Fill Color
"sty $DF01 \n" // Fill value
"sta $DF08 \n" // Addr Low
"stx $DF09 \n" // Addr Hi
"lda __rc2 \n" // Addr Bank
"sta $DF0A \n"
"lda __rc4 \n" // length low
"sta $DF0C \n"
"lda __rc5 \n" // length middle
"sta $DF0D \n"
"lda __rc6 \n" // length high
"sta $DF0E \n"
"lda #$80 \n" // DMA_CTRL_START
//"tsb $DF00 \n" // DMA_CTRL
".byte $0C,$00,$DF\n"
//"wait_start: lda $DF01 \n" // DMA_STATUS
// "bpl wait_start \n" // Wait for DMA
"wait_end: lda $DF01 \n" // DMA_STATUS
"bmi wait_end \n" // Wait for DMA
//"stz $DF00 \n" // DMA Off
".byte $9c,$00,$DF\n"
"nop \n"
"nop \n"
"nop \n" // wait
"nop \n"
"nop \n"
"nop \n"
"pla \n" // restore the io_ctrl
"sta 1 \n"
"plp \n"
// "plx\n"
".byte $FA\n"
"pla\n"
"rts \n"
);
void dmaClear(uint32_t address, uint32_t length, byte color);
#if 0
typedef struct airplaneS {
int8_t aileron; // -15 to 15
int8_t elevator; // -15 to 15
int8_t rudder; // -15 to 15
int8_t throttle; // -15 to 15
bool ignition;
bool engine;
int16_t rpm;
float hSpeed;
float vSpeed;
float deltaZ;
float efAOF;
float climbRate;
bool airborne;
bool stall;
bool brake;
int16_t x;
int16_t y;
int16_t z;
double pitch; // 0 to 255
double yaw; // 0 to 255
double roll; // 0 to 255
} airplaneT;
airplaneT _plane;
byte _gamepad;
#endif
#if 0
//#define SEGMENT_MATH
#define PI 3.1415926
//#define Rads(d) (((d) < 0 ? (d) + 360 : (d)) * (PI / 180))
#define Degs(r) ((r) * (180 / PI))
// https://www.reddit.com/r/programming/comments/3e7ghi/discrete_arctan_in_6502/
#define ATAN_SPLINE_C0 (double)(-0.14380550980765507115) /* 3pi/4 - 5/2 */
#define ATAN_SPLINE_C1 (double)(-0.07079632679489661923) /* 3/2 - pi/2 */
double atan(double x){
if (x >= 0) {
if ( x<= 1) {
return x + (ATAN_SPLINE_C0 + ATAN_SPLINE_C1 * x) * x * x;
} else {
x = 1 / x;
return PI / 2 - (x + (ATAN_SPLINE_C0 + ATAN_SPLINE_C1 * x) * x * x);
}
} else {
if (x >= -1) {
return x - (ATAN_SPLINE_C0 - ATAN_SPLINE_C1 * x) * x * x;
} else {
x = -1 / x;
return (x + (ATAN_SPLINE_C0 + ATAN_SPLINE_C1 * x) * x * x) - PI / 2;
}
}
}
float sin(float d) {
_tdata = d;
a23d2Sin();
return _trig;
}
float cos(float d) {
_tdata = d;
a23d2Cos();
return _trig;
}
//#define SEGMENT_INPUT
void getInput(void) {
static byte oneJoy = 0;
static byte twoJoy = 0;
static byte keyJoy = 0;
do {
kernelCall(NextEvent);
// Read real joysticks.
if (kernelEventData.type == kernelEvent(GAME)) {
oneJoy = kernelEventData.game.game0;
twoJoy = kernelEventData.game.game1;
}
//***TODO*** Add SNES/NES once I get some.
// Use keyboard as virtual joystick.
if (kernelEventData.type == kernelEvent(key.PRESSED)) {
switch (kernelEventData.key.ascii) {
case 'w':
case 'W':
keyJoy |= JOY_UP;
break;
case 'a':
case 'A':
keyJoy |= JOY_LEFT;
break;
case 's':
case 'S':
keyJoy |= JOY_DOWN;
break;
case 'd':
case 'D':
keyJoy |= JOY_RIGHT;
break;
case 'j':
case 'J':
keyJoy |= JOY_BUTTON_1;
break;
case 'k':
case 'K':
keyJoy |= JOY_BUTTON_2;
break;
case 'l':
case 'L':
keyJoy |= JOY_BUTTON_3;
break;
}
}
if (kernelEventData.type == kernelEvent(key.RELEASED)) {
switch (kernelEventData.key.ascii) {
case 'w':
case 'W':
keyJoy &= ~JOY_UP;
break;
case 'a':
case 'A':
keyJoy &= ~JOY_LEFT;
break;
case 's':
case 'S':
keyJoy &= ~JOY_DOWN;
break;
case 'd':
case 'D':
keyJoy &= ~JOY_RIGHT;
break;
case 'j':
case 'J':
keyJoy &= ~JOY_BUTTON_1;
break;
case 'k':
case 'K':
keyJoy &= ~JOY_BUTTON_2;
break;
case 'l':
case 'L':
keyJoy &= ~JOY_BUTTON_3;
break;
}
}
} while (kernelGetPending() > 0);
// Merge inputs. Yes, this allows dumb things like LEFT and RIGHT at the same time.
_gamepad = oneJoy | twoJoy | keyJoy;
}
//#define SEGMENT_FLIGHT_MODEL_1
// These didn't use to be global. Ugly.
float tmpX;
float tmpY;
float tmpZ;
float newX;
float newY;
float newZ;
float iSpeed;
float lSpeed;
float hAccel;
float lVeloc;
float gVeloc;
float deltaZ;
float AOA;
float torque;
float torque2;
uint16_t loopTime = 40;
// Used to be static. Need preserved.
double dPitch = 0;
double dYaw = 0;
double dRoll = 0;
float collectX = 0;
float collectY = 0;
float collectZ = 0;
// This flight model is basically the one from "Build Your Own Flight Sim
// in C++" by Michael Radtke and Chris Lampton.
void lightPlane(void) {
// Flight model. Power Dynamics.
if (_plane.ignition) {
// Start engine.
if (!_plane.engine) _plane.engine = true;
// Adjust RPM.
if (_plane.rpm < (375 + (_plane.throttle * 117))) _plane.rpm += loopTime * 0.5; //***TODO*** T
if (_plane.rpm > (375 + (_plane.throttle * 117))) _plane.rpm -= loopTime * 0.5;
} else {
// Stop engine.
if (_plane.engine) _plane.engine = false;
// Run down engine.
if (_plane.rpm > 0) _plane.rpm -= (int16_t)(loopTime / 2); //***TODO*** This will never work.
if (_plane.rpm < 0) _plane.rpm = 0;
}
// Flight model. Flight Dynamics.
// Calculate speed from RPM.
iSpeed = _plane.rpm / 17.5;
// Modify speed by pitch.
iSpeed += (_plane.pitch * 1.5);
// Horizontal acceleration - thrust.
hAccel = ((_plane.rpm * (iSpeed - _plane.hSpeed)) / 10000);
hAccel /= 1000;
hAccel *= loopTime;
if (_plane.brake && !_plane.airborne) {
// Handle brakes.
if (_plane.hSpeed > 0) {
_plane.hSpeed -= 1;
} else {
_plane.hSpeed = 0;
}
} else {
// Accelerate normally.
_plane.hSpeed += hAccel;
}
// Force speed to range -1..1.
lSpeed = (_plane.hSpeed / 65) - 1;
if (lSpeed > 1) lSpeed = 1;
// Lift curve.
lVeloc = Degs(atan(lSpeed));
// Force lift to range 0..90.
lVeloc += 45;
// Shift range to 0..-17.
lVeloc /= 5.29;
// Multiply by pitch modifier.
lVeloc *= (-(_plane.pitch * .157) + 1);
// Time slice.
lVeloc /= 1000;
lVeloc *= loopTime;
// Gravity.
gVeloc = loopTime * (-16.0 / 10000); // -16.0 is ft/sec for gravity.
// Sum vertical velocity.
_plane.vSpeed = gVeloc + lVeloc;
// No vertical speed if we're on the ground.
if (!_plane.airborne && (_plane.vSpeed < 0)) _plane.vSpeed = 0;
// Save climb rate in ft/min.
_plane.climbRate = _plane.vSpeed / loopTime;
_plane.climbRate *= 60000L;
// Expand to ft/hr.
deltaZ = _plane.hSpeed * 5280;
// Get ft/ms.
deltaZ /= 3600000L;
deltaZ *= loopTime;
// Find effective angle of flight.
if (deltaZ) {
_plane.efAOF = -(atan(_plane.vSpeed / deltaZ));
} else {
_plane.efAOF = -(atan(_plane.vSpeed));
}
// Convert to degrees.
AOA = Degs(_plane.efAOF);
// Handle stalling.
if (((_plane.pitch < AOA) && (AOA < 0)) && (_plane.hSpeed < 40)) {
if ((_plane.pitch - AOA) < -20) _plane.stall = true;
}
if (_plane.stall) {
if (_plane.pitch > 30) {
_plane.stall = false;
} else {
_plane.pitch++;
}
}
}
//#define SEGMENT_FLIGHT_MODEL_2
void lightPlane2(void) {
// Flight model. Inertial Damping.
if (dPitch) {
dPitch -= dPitch / 10;
if (((dPitch > 0) && (dPitch < 0.01)) || ((dPitch < 0) && (dPitch > -0.01))) dPitch = 0;
}
if (dYaw) {
dYaw -= dYaw / 10;
if (((dYaw > 0) && (dYaw < 0.01)) || ((dYaw < 0) && (dYaw > -0.01))) dYaw = 0;
}
if (dRoll) {
dRoll -= dRoll / 10;
if (((dRoll > 0) && (dRoll < 0.01)) || ((dRoll < 0) && (dRoll > -0.01))) dRoll = 0;
}
// Flight model. Rate of Change.
if (_plane.airborne) {
if (_plane.aileron != 0) {
torque = ((_plane.hSpeed * _plane.aileron) / 10000);
if (dRoll != (torque * loopTime)) dRoll += torque * 6;
}
}
if (_plane.elevator != 0) {
torque = ((_plane.hSpeed * _plane.elevator) / 10000);
if ((!_plane.airborne) && (torque > 0)) torque = 0; //***FIX*** This is dumb.
if (dPitch != (torque * loopTime)) dPitch += torque * 1.5;
}
if (_plane.hSpeed) {
torque = 0.0;
if (_plane.rudder != 0) torque -= ((_plane.hSpeed * _plane.rudder) / 10000);
torque2 = 0.0;
if ((_plane.roll > 0) && (_plane.roll <= 90)) { //***FIX*** This is dumb.
torque2 = _plane.roll * 0.00050;
} else {
if ((_plane.roll < 0) && (_plane.roll >= -90)) {
torque2 = _plane.roll * 0.00050;
}
}
torque += torque2;
if (dYaw != (torque * loopTime)) dYaw += torque * 1.5;
}
// Flight model. Apply Rotations.
// Transform pitch into components of yaw and pitch based on roll.
_plane.roll += dRoll;
_plane.yaw += dYaw;
_plane.pitch += (dPitch * cos(_plane.roll));
_plane.yaw += -(dPitch * sin(_plane.roll));
if (_plane.roll > 180) {
_plane.roll = -180 + (_plane.roll - 180);
} else {
if (_plane.roll < -180) _plane.roll = 180 + (_plane.roll + 180);
}
if (_plane.yaw > 180) {
_plane.yaw = -180 + (_plane.yaw - 180);
} else {
if (_plane.yaw < -180) _plane.yaw = 180 + (_plane.yaw + 180);
}
// Handle special case when aircraft pitch passes vertical.
if ((_plane.pitch > 90) || (_plane.pitch < -90)) {
if (_plane.roll >= 0) {
_plane.roll -= 180;
} else {
if (_plane.roll < 0) _plane.roll += 180;
}
if (_plane.yaw >= 0) {
_plane.yaw -= 180;
} else {
if (_plane.yaw < 0) _plane.yaw += 180;
}
if (_plane.pitch > 0) {
_plane.pitch = (180 - _plane.pitch);
} else {
if (_plane.pitch < 0) _plane.pitch = (-180 - _plane.pitch);
}
}
// Dampen everything out to 0 if they get close enough.
if ((_plane.pitch > -0.5) && (_plane.pitch < 0.5)) _plane.pitch = 0;
if ((_plane.roll > -0.5) && (_plane.roll < 0.5)) _plane.roll = 0;
if ((_plane.yaw > -0.5) && (_plane.yaw < 0.5)) _plane.yaw = 0;
}
//#define SEGMENT_FLIGHT_MODEL_3
void moveAircraft(void) {
// Calculate new aircraft position. Each coordinate is 1 foot in 3D space.
tmpX = 0;
tmpY = 0;
tmpZ = deltaZ;
// Order of these points is significant.
// Rotate in Z.
newX = (tmpX * cos(_plane.roll)) - (tmpY * sin(_plane.roll));
newY = (tmpX * sin(_plane.roll)) + (tmpY * cos(_plane.roll));
tmpX = newX;
tmpY = newY;
// Rotate in X;
_plane.efAOF = Degs(_plane.efAOF);
newY = (tmpY * cos(_plane.efAOF)) - (tmpZ * sin(_plane.efAOF));
newZ = (tmpY * sin(_plane.efAOF)) + (tmpZ * cos(_plane.efAOF));
tmpY = newY;
tmpZ = newZ;
// Rotate in X;
newX = (tmpZ * sin(_plane.yaw)) + (tmpX * cos(_plane.yaw));
newZ = (tmpZ * cos(_plane.yaw)) - (tmpX * sin(_plane.yaw));
tmpX = newX;
tmpZ = newZ;
// Translate rotated point back to where it should be
// relative to it's last position.
collectX += newX;
if ((collectX > 1) || (collectX < -1)) {
_plane.x -= collectX;
collectX = 0;
}
collectY += newY;
if ((collectY > 1) || (collectY < -1)) {
_plane.y -= collectY;
collectY = 0;
}
collectZ += newZ;
if ((collectY > 1) || (collectY < -1)) {
_plane.z += collectZ;
collectZ = 0;
}
// Are we flying?
if ((!_plane.airborne) && (-_plane.y)) _plane.airborne = true;
}
#endif
#define SEGMENT_MAIN
void runSimulation(void) {
bool pageZero;
// Initialize airplane.
// memset(&_plane, 0, sizeof(airplaneT));
// Which page is visible?
pageZero = true;
// Draw it!
while (true) {
// Flip pages.
if (pageZero) {
// Looking at bitmap 0.
pageZero = false;
bitmapSetActive(1);
bitmapSetVisible(0, true);
bitmapSetVisible(1, false);
_drawlist = DRAWLIST_P1;
} else {
// Looking at bitmap 1.
pageZero = true;
bitmapSetActive(0);
bitmapSetVisible(1, true);
bitmapSetVisible(0, false);
_drawlist = DRAWLIST_P0;
}
#if 0
// Erase old scene.
bitmapSetColor(0);
_useColor = false;
//textPrint("a23d2Draw()\n");
a23d2Draw();
#else
graphicsWaitVerticalBlank();
dmaClear(pageZero ? 0x6c000 : 0x58000, 0x12c00, 0);
#endif
// Draw new scene.
//textPrint("a23d2Render()\n");
a23d2Render();
_useColor = true;
//textPrint("a23d2Draw()\n");
a23d2Draw();
#if 0
// Update player input.
getInput();
// Update aircraft control state.
if ((_gamepad & JOY_BUTTON_1) || (_gamepad & JOY_BUTTON_2) || (_gamepad & JOY_BUTTON_3)) {
// Modified input with button down.
if ((_gamepad & JOY_UP) && (_plane.throttle > -15)) _plane.throttle++;
if ((_gamepad & JOY_DOWN) && (_plane.throttle < 15)) _plane.throttle--;
if (_gamepad & JOY_RIGHT) _plane.brake = !_plane.brake;
} else {
// No button pressed.
if ((_gamepad & JOY_UP) && (_plane.elevator > -15)) _plane.elevator--;
if ((_gamepad & JOY_DOWN) && (_plane.elevator < 15)) _plane.elevator++;
if ((_gamepad & JOY_LEFT) && (_plane.aileron > -15)) _plane.aileron--;
if ((_gamepad & JOY_RIGHT) && (_plane.aileron < 15)) _plane.aileron++;
}
// "Coordinated" flight.
_plane.rudder = _plane.aileron;
// Do the actual flying!
lightPlane();
lightPlane2();
moveAircraft();
#endif
// Move camera.
//_camera->p += 1; // Change pitch angle.
//_camera->b += 2; // Change bank angle.
_camera->h += 1; // Change heading angle.
}
}
int main(int argc, char *argv[]) {
byte c;
byte ega[16][3] = {
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0xaa },
{ 0x00, 0xaa, 0x00 },
{ 0x00, 0xaa, 0xaa },
{ 0xaa, 0x00, 0x00 },
{ 0xaa, 0x00, 0xaa },
{ 0xaa, 0x55, 0x00 },
{ 0xaa, 0xaa, 0xaa },
{ 0x55, 0x55, 0x55 },
{ 0x55, 0x55, 0xff },
{ 0x55, 0xff, 0x55 },
{ 0x55, 0xff, 0xff },
{ 0xff, 0x55, 0x55 },
{ 0xff, 0x55, 0xff },
{ 0xff, 0xff, 0x55 },
{ 0xff, 0xff, 0xff }
};
// Load EGA palette into CLUT0.
for (c=0; c<16; c++) graphicsDefineColor(0, c, ega[c][0], ega[c][1], ega[c][2]);
//textPrint("a23d2Init()\n");
a23d2Init();
_camera->x = 5000;
_camera->y = 1000;
_camera->z = 5000;
_camera->p = 25;
// Set up bitmap planes.
for (c=0; c<2; c++) {
// Clear screen.
bitmapSetActive(c);
bitmapSetColor(0);
bitmapClear();
// Control Panel.
bitmapSetColor(8);
bitmapLine(0, 194, 319, 194); // Leaves 45 pixels at the bottom below this line.
}
bitmapSetVisible(0, true);
bitmapSetVisible(1, false);
//textPrint("runSimulation()\n");
runSimulation();
return 0;
}