Collection of unsorted information posted by George Moromisato on the inner workings of Transcendence
See the next_version page
There is a chain of gates leading from Sol to St. Katharine's, but it is not included in v1 (it probably will be in future version).Forum Link
The Kuiper stargate leads to the Centauri system (which is not included in v1). Forum Link
Planets and stars use an inverse exponential scale:
| Object | Size (Km) | Image (pixels) | Example |
| Ringed planet | 300,000 | 512 | Saturn |
| Large gas giant | 150,000 | 430 | Jupiter |
| Medium gas giant | 50,000 | 330 | Neptune |
| Large rocky planet | 13,000 | 240 | Earth |
| Medium rocky planet | 7,000 | 200 | Mars |
| Small rocky planet | 5,000 | 180 | Mercury |
| Large dwarf planet | 3,500 | 170 | Earth’s Moon |
| Medium dwarf planet | 2,250 | 150 | Pluto |
| Small dwarf planet | 1,000 | 128 | Ceres |
| Giant asteroid | 500 | 105 | Vesta |
| Large asteroid | 250 | 90 | Hyperion |
| Medium asteroid | 100 | 64 | Prometheus |
| Small asteroid | 50 | 50 | Eros |
| Tiny asteroid | 10 | 32 | Deimos |
The formula is (roughly)
pixel size = ( (km size) ^ (0.25) ) * 22.25
When hitting a large station, only WMD counts. WMD is some percentage of normal weapon damage, using this table:
* WMD0 = 0% * WMD1 = 4% * WMD2 = 10% * WMD3 = 20% * WMD4 = 34% * WMD5 = 52% * WMD6 = 74% * WMD7 = 100%
For example, if 10 points of WMD3 damage hit a station, then the station only takes 20% of the damage or 2 points. Regardless of the above, any hit on a station causes at least 1 point of damage, so even weapons with no WMD do some damage (this may or may not change in the future).
Note that only stations with multiHull=“true” count as “large stations”
Ships generally take full damage (regardless of WMD) but for ships that have “non critical” armor segments, WMD comes into play. The chance that a hit to a non critical segment will destroy the ship increases for WMD weapons.
When hitting wrecks, etc. only WMD counts.
Time in Transcendence is quantized. Bullet-time doubly so. Remember that the universe updates every 1/30th of a second (real-time) which means that time cannot be subdivided below that quantum.
Since motion is just (velocity X time), motion in Transcendence is also quantized–that is, objects will appear to “jump” from one point in space to another.
Bullets in Transcendence obey these quantum laws. Sometimes, bullets appear to “jump” across a solid barrier. This is not an illusion, but rather a property of the quantum laws of Transcendence.
Ships never seem to pass through walls because they are large and (relatively) slow macroscopic objects; but bullets are small and fast and subject to these quantum effects.
Why don't I just fix this (quantum) bug?
Yeah, I don't have a good answer for that :)
To position the camera and the lights, imagine that the object is at the origin: The camera is 6 units away and 12 units above the object. The light source is 3 units to the left (as seen from the camera), 5 units behind [Note: THE LIGHT COMES FROM THE TOP LEFT OF THE IMAGE, “BEHIND” translates to “behind the ship from the camera's perspective” NOT “behind the ship from the ship's perspective”] and 5 units above the object (Note: treat the light source as being at infinity).
Acceptable facings for ships: 1 2 3 4 5 6 8 9 10 12 15 18 20 24 30 36 40 45 60 72 90 120 180 360
When running the game with /debugvideo, parameters are found printed in the debug.log
Paint = The time (in milliseconds) that it takes to paint all of the objects (ships, stations, effects, etc.) to an offscreen buffer. If this is high, then it means that there are a ton of objects on the screen OR that some of the effects (explosions, etc.) are taking a long time to paint.
Blt = The time (in milliseconds) that it takes to blt the offscreen buffer to the video card. If this is high, then it means that the videocard can't keep up. This might improve with /dx. This number is constant no matter how many objects are on screen or in the system (but might vary if other apps are accessing the videocard).
Update = The time (in milliseconds) that it takes to compute the game state. If this is high then it means that there are a lot of objects doing a lot of things. For example, this time increases with lots of Dwargs. This also increases if you are in autopilot (since we update multiple times per frame).
If you do the math, each frame has to process in a little more than 33 milliseconds to achieve 30 frames per second. If the sum of the values above exceeds 33 milliseconds, then you won't get smooth game-play.
On a Dell M1330, my times in Eridani look like this:
12/22/2008 11:31:36 Frames: 30 Paint: 2 Blt: 7 Update: 0 12/22/2008 11:31:46 Frames: 30 Paint: 3 Blt: 10 Update: 0 12/22/2008 11:31:56 Frames: 30 Paint: 3 Blt: 9 Update: 0
Deeper in the game, Paint and Update start to climb.
1. If it's a command, use sentence capitalization:
Buy items Replace armor Talk to station boss
2. If it's the name of a place, then use title capitalization:
Dock Services Ringer Lab Hall of Mirrors
3. If it's something the player says, put it in quotes and punctuate:
“Who is Domina?” “OK, you have a deal.” “He says you betrayed the Fleet.”
I have no idea if this will help (or confuse) but here is the code that I use to calculate the intercept time for a missile of a given speed to hit a target moving at a certain (constant) speed.
Once you've calculated the intercept time, you can use simple trigonometry to calculate where to aim (since you will know where the ship will be when the missile hits).
Good luck!
Metric CSystem::CalcInterceptTime
(
const Vector &vTarget,
const Vector &vTargetVel,
Metric rMissileSpeed,
Metric *retrRange
)
// CalcInterceptTime
//
// Returns the time that it would take to intercept a target
// at vTarget, moving with velocity vTargetVel, with
// a missile of speed rMissileSpeed. Returns < 0.0 if the missile cannot
// intercept the target.
//
// The formula for interception is:
//
// A +- B sqrt(C)
// t = --------------
// D
//
// Where:
// A = B * rVi
// B = 2 * rRange
// C = rMissileSpeed^2 - rVj^2
// D = 2 * (C - rVi^2)
//
// rVi = the target speed along the position vector
// rVj = the target speed tangential to the position vector
// rRange = the (initial) distance to the target
// rMissileSpeed = the speed of the missile
{
Metric rRange = vTarget.Length();
Vector vPosNormal = vTarget / rRange;
if (retrRange)
*retrRange = rRange;
// Compute the orthogonals of the velocity along the position vector
Metric rVi, rVj;
vTargetVel.GenerateOrthogonals(vPosNormal, &rVi, &rVj);
// Figure out the inside of the square root. If this value is negative
// then we don't have an interception course.
Metric C = rMissileSpeed * rMissileSpeed - rVj * rVj;
if (C < 0.0)
return -1.0;
// Figure out the denominator. If this value is 0 then we don't
// have an interception course.
Metric D = 2 * (C - rVi * rVi);
if (D == 0.0)
return -1.0;
// Compute A and B
Metric B = 2 * rRange;
Metric A = B * rVi;
// Compute both roots
Metric Z = B * sqrt(C);
Metric R1 = (A + Z) / D;
Metric R2 = (A - Z) / D;
// If the first root is positive then return it
if (R1 > 0.0)
return R1;
// Otherwise we return the second root, which may or may not
// be positive
return R2;
}
The truth is that (objCommunicate) is probably not the right way to implement autons.
A bit of history:
As you know, all ship AIs (regardless of controller) honor a sequential list of orders. Example:
Code:
… (shpOrder theShip 'attack theTarget) (shpOrder theShip 'gate) …
The snippet of code above orders a ship to first attack theTarget and after the target is destroyed, to gate out. Ideally, what I should have done, is to use that mechanism for autons. For example, in the <Communications> code, when you order an auton to attack, the code should just call something like:
Code:
(shpOrder theAuton 'attack theTarget)
Unfortunately, one of the orders that a ship obeys is the escort order:
Code:
(shpOrder theAuton 'escort gPlayerShip)
So when an auton is following you around, it is in the middle of the escort order. If you give it another order, it will not obey the order until the escort order has completed (which it never will).
One possible solution is to cancel the escort order before giving the attack order. Unfortunately, the problem there is that the escort order is the only way that we know that the auton is following the player. If we cancel the escort order, we won't know that the auton should follow the player (for example, if the player enters a stargate).
Anyway, to overcome these problems, I created a special controller to implement the auton. The auton is always in running the escort order. But it can change its behavior based on messages from the player. Thus I added (objCommunicate) to tell the auton that it should attack.
I think this was a mistake. Instead, I should have added a separate variable on a ship that indicates that it is escorting another ship. Then we could use the standard order machinery to command autons and wingmen.
In this time-period (2400's) humans do NOT have the technology for FTL anything. The stargates (and alien jumpdrive) are all they've got (and they don't know how those work). All interstellar communications is via messenger ships passing through stargates.
The alien races of the galaxy (e.g., Iocrym) DO have FTL technology, but even that is not instantaneous (e.g., it takes non-zero time to pass through a stargate–time proportional to distance). Comms is the same way–FLT but not instantaneous.
Domina and Oracus do have instantaneous communication, but only when they communicate with a sentient being.
int CShipClass::ComputeScore (const CDeviceDescList &Devices,
int iArmorLevel,
int iPrimaryWeapon,
int iSpeed,
int iThrust,
int iManeuver,
bool bPrimaryIsLauncher)
// ComputeScore
//
// Compute the score of the class based on equipment
{
int i;
int iSpecial = 0;
int iExceptional = 0;
int iDrawback = 0;
int iStdLevel = iArmorLevel;
int iWeaponLevel = (iPrimaryWeapon == -1 ? 0 : ComputeDeviceLevel(Devices.GetDeviceDesc(iPrimaryWeapon)));
// If our weapon is better than our armor then adjust the level
// depending on the difference.
if (iWeaponLevel > iArmorLevel)
{
switch (iWeaponLevel - iArmorLevel)
{
case 1:
iStdLevel = iWeaponLevel;
iDrawback++;
break;
case 3:
iStdLevel = iWeaponLevel - 2;
iSpecial += 2;
break;
default:
iStdLevel = (iWeaponLevel + iArmorLevel) / 2;
}
}
// If our best weapon is 2 or more levels below our standard
// level then take drawbacks exponentially.
if (iStdLevel > iWeaponLevel + 1)
iDrawback += min(16, (1 << (iStdLevel - (iWeaponLevel + 2))));
else if (iStdLevel > iWeaponLevel)
iDrawback++;
// If all movement stats are high then this counts as an
// exceptional ability
if (iSpeed == enumHigh && iThrust == enumHigh && iManeuver == enumHigh)
iExceptional++;
// Otherwise, treat them as special abilities or drawbacks
else
{
if (iSpeed == enumLow)
iDrawback++;
else if (iSpeed == enumHigh)
iSpecial++;
if (iThrust == enumLow)
iDrawback++;
else if (iThrust == enumHigh)
iSpecial++;
if (iManeuver == enumLow)
iDrawback++;
else if (iManeuver == enumHigh)
iSpecial++;
}
// 1 armor segment is a drawback
int iArmorSections = GetHullSectionCount();
if (iArmorSections <= 1)
iDrawback++;
// 2-3 armor segments is normal
else if (iArmorSections < 4)
;
// 4 or more armor segments is special
else if (iArmorSections < 8 )
iSpecial++;
else if (iArmorSections < 16)
iSpecial += 2;
else if (iArmorSections < 32)
iSpecial += 3;
else if (iArmorSections < 64)
iSpecial += 4;
else
iSpecial += 5;
// Checkout all the devices
bool bDirectionalBonus = false;
bool bGoodSecondary = false;
int iDirectionalBonus = 0;
for (i = 0; i < Devices.GetCount(); i++)
{
const SDeviceDesc &Dev = Devices.GetDeviceDesc(i);
CDeviceClass *pDevice = Dev.Item.GetType()->GetDeviceClass();
int iDeviceLevel = ComputeDeviceLevel(Dev);
// Specific devices
switch (pDevice->GetCategory())
{
case itemcatWeapon:
case itemcatLauncher:
{
int iWeaponAdj = (iDeviceLevel - iStdLevel);
// If this is a secondary weapon, then add it to the score
if (i != iPrimaryWeapon)
{
// Calculate any potential bonus based on the weapon level
// compared to the base level
iSpecial += max(iWeaponAdj + 3, 0);
}
// Compute fire arc
int iFireArc = (Dev.bOmnidirectional ? 360 : AngleRange(Dev.iMinFireArc, Dev.iMaxFireArc));
// Adjust for turret-mount
iDirectionalBonus += (max(iWeaponAdj + 3, 0) * iFireArc);
break;
}
case itemcatReactor:
// Reactors don't count as improvements
break;
default:
{
// Other devices are special abilities depending on level
if (iDeviceLevel > iStdLevel+1)
iExceptional++;
else if (iDeviceLevel > iStdLevel)
iSpecial += 4;
else if (iDeviceLevel >= iStdLevel-1)
iSpecial += 2;
else
iSpecial++;
}
}
}
// If we have no weapons then we have some drawbacks
if (iPrimaryWeapon == -1)
iDrawback += 3;
// Add bonus if weapon is omnidirectional
iSpecial += (int)((iDirectionalBonus / 270.0) + 0.5);
// Checkout AI settings
const SAISettings &AI = GetAISettings();
int iFireAccuracyScore, iFireRateScore;
if (AI.iFireAccuracy > 97)
iFireAccuracyScore = 5;
else if (AI.iFireAccuracy >= 93)
iFireAccuracyScore = 4;
else if (AI.iFireAccuracy >= 90)
iFireAccuracyScore = 3;
else if (AI.iFireAccuracy < 75)
iFireAccuracyScore = 1;
else
iFireAccuracyScore = 2;
if (AI.iFireRateAdj <= 10)
iFireRateScore = 5;
else if (AI.iFireRateAdj <= 20)
iFireRateScore = 4;
else if (AI.iFireRateAdj <= 30)
iFireRateScore = 3;
else if (AI.iFireRateAdj >= 60)
iFireRateScore = 1;
else
iFireRateScore = 2;
int iFireControlScore = iFireRateScore * iFireAccuracyScore;
if (iFireControlScore >= 20)
iExceptional++;
else if (iFireControlScore > 6)
iSpecial += ((iFireControlScore - 5) / 2);
else if (iFireControlScore < 2)
iDrawback += 4;
else if (iFireControlScore < 4)
iDrawback += 2;
// Compute final score
ScoreDesc *pBase = &g_XP[iStdLevel-1];
int iScore = pBase->iBaseXP
+ iSpecial * pBase->iSpecialXP
+ iExceptional * pBase->iExceptionalXP
+ iDrawback * pBase->iDrawbackXP;
return iScore;
}
static ScoreDesc g_XP[] =
{
// Base Score
// Special Ability
// Exceptional Ability
// Drawback
// Level Score
{ 20, 5, 20, 0, 50 }, // I
{ 50, 10, 50, -5, 100 }, // II
{ 115, 15, 100, -10, 200 }, // III
{ 200, 20, 170, -20, 350 }, // IV
{ 340, 30, 260, -35, 600 }, // V
{ 500, 45, 370, -50, 900 }, // VI
{ 750, 60, 500, -65, 1400 }, // VII
{ 1050, 80, 650, -85, 1900 }, // VIII
{ 1450, 100, 820, -105, 2600 }, // IX
{ 1900, 125, 1010, -130, 3250 }, // X
{ 2400, 150, 1220, -155, 4200 }, // XI
{ 3000, 180, 1450, -185, 5500 }, // XII
{ 3600, 210, 1700, -215, 6750 }, // XIII
{ 4250, 245, 1970, -250, 8250 }, // XIV
{ 5000, 280, 2260, -285, 10000 }, // XV
{ 6000, 320, 2570, -325, 11500 }, // XVI
{ 7000, 360, 2900, -365, 13250 }, // XVII
{ 8000, 405, 3250, -410, 15000 }, // XVIII
{ 9000, 450, 3620, -455, 16750 }, // XIX
{ 10000, 500, 4010, -505, 18500 }, // XX
{ 11000, 550, 4420, -555, 20500 }, // XXI
{ 12000, 605, 4850, -610, 22500 }, // XXII
{ 13000, 660, 5300, -665, 25000 }, // XXIII
{ 14000, 720, 5770, -725, 26500 }, // XXIV
{ 15000, 780, 6260, -785, 30000 }, // XXV
};