Second operator report, same day: "what is that yellow orange artifact on the
display, it too shows up here and in bt411 but not in the original" -- a
solid vertical bar beside the RANGE readout on the colour head.
It is the same defect as 5.3.83's MFD bleed. Not a similar one: the same
676 pixels.
HOW IT WAS CAUGHT. 5.3.83 shipped a fix that could not be verified, because
the fix makes something INVISIBLE and the A/B rig's boot cockpit already
showed zero MFD extras -- the unfixed build looked perfectly clean. So this
commit adds a POSITIVE CONTROL instead of another argument:
BT_TRANS_PROVOKE fills the colour head's uninitialised
translationTable[64..255] with 0xFF00 -- every high-byte head bit --
rather than the zero the fix installs. Any draw that indexes the tail
then lights ALL the mono heads at once.
On the boot cockpit that lights exactly 1030 pixels: Eng1/Eng2/Eng3 +1030
each, Mfd1 +684, Mfd2 +704, Mfd3 +1074, Comm +676, against 0 extras with the
fix. The bug was firing the whole time. Our heap simply happened to hold
zeros in that tail -- the same luck the shipped binary has been having, which
is exactly why the operator sees the artifact and the A/B rig does not.
THE OFFENDER, NAMED. oormask.py renders which pixels those are and prints
their horizontal run lengths. The mask is a glyph cluster plus 52 runs of
13px -- a SOLID 13x52 BAR at screen (526,228)-(538,279). Solid means a
rectangle in the source art, so decode the art:
BTSEC1.PCX, the colour head's own 480x640 background, contains exactly
ONE out-of-range value in the entire image: index 254, exactly 676
pixels, a solid 52x13 rectangle at (199,526)-(250,538).
The sec port is configured at ROTATION 270, mapping source (x,y) ->
screen (y, 479-x). That puts the rectangle at screen x 526..538,
y 229..280. Measured mask: x 526..538, y 228..279. Same rectangle, to
the pixel.
ONE READ, TWO SYMPTOMS. translationTable[254] is never written (
BuildSecondaryTranslation fills only 1<<numberOfBits = 64 entries), and
DrawPoint ORs the result in unmasked. Whatever the heap left there decides
which symptom the operator sees:
low 6 bits set -> a coloured block on the COLOUR head, beside the RANGE
readout. The yellow-orange bar.
high 8 bits set -> garbage in the MFD / ENG / COMM planes. The bleed.
Both reports, one uninitialised int. It also explains the "not in the
original" asymmetry without needing the original to differ in code: it does
not differ, it is just getting zeros there. And it explains BT411 showing it
too -- both reconstructions inherit the read from the archive.
5.3.83's zero-fill therefore cures both, and turns a heap-lottery into a
guarantee. Still not DIRECTLY observed cured, because no rig we have was
showing the artifact to begin with; that honesty is recorded in the file
header rather than smoothed over.
ALSO IN: oormask.py, barbox.py, vis_provoke.conf, and a README section on
positive controls -- when a fix replaces garbage with a benign value, build
the variant that replaces it with a maximally LOUD value, because that turns
"I see no difference" into a number and separates "the fix works" from "this
screen never exercised the path".
METHOD NOTE, recorded in the README because it nearly cost the fix: three
grabs of one running instance score IDENTICALLY, so the within-boot noise
floor is zero -- and that is the wrong floor. Judging a rebuild needs the
ACROSS-boot floor (~6px on the MFD heads). A single bad grab, caught
mid-draw, read as a 2700px regression and nearly got a correct change
reverted.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1002 lines
28 KiB
C++
1002 lines
28 KiB
C++
//===========================================================================//
|
|
// File: l4grend.cc //
|
|
// Project: MUNGA Brick: LBE4 Gauge Renderer //
|
|
// Contents: //
|
|
//---------------------------------------------------------------------------//
|
|
// Date Who Modification //
|
|
// -------- --- ---------------------------------------------------------- //
|
|
// 02/10/95 CPB Initial coding. //
|
|
// 04/12/95 CPB Moved BuildGraphicsPorts in from RP/BTL4GRND.CPP. //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1995, Virtual World Entertainment, Inc. All rights reserved //
|
|
// PROPRIETARY and CONFIDENTIAL //
|
|
//===========================================================================//
|
|
|
|
#include <mungal4.hpp>
|
|
#pragma hdrstop
|
|
|
|
#if !defined(L4APP_HPP)
|
|
# include <l4app.hpp>
|
|
#endif
|
|
|
|
#if !defined(TIME_HPP)
|
|
# include <time.hpp> // for profiling
|
|
#endif
|
|
|
|
#if !defined(L4GREND_HPP)
|
|
# include <l4grend.hpp>
|
|
#endif
|
|
|
|
#if !defined(L4GAUGE_HPP)
|
|
# include <l4gauge.hpp>
|
|
#endif
|
|
|
|
#if !defined(MISSION_HPP)
|
|
# include <mission.hpp>
|
|
#endif
|
|
|
|
#if !defined(L4PLASMA_HPP)
|
|
# include <l4plasma.hpp>
|
|
#endif
|
|
|
|
#if !defined(NOTATION_HPP)
|
|
# include <notation.hpp>
|
|
#endif
|
|
|
|
// #define LOCAL_TEST
|
|
|
|
#if defined(LOCAL_TEST)
|
|
# define Test_Tell(n) cout << n
|
|
#else
|
|
# define Test_Tell(n)
|
|
#endif
|
|
|
|
//
|
|
//#############################################################################
|
|
// L4MethodDescription list
|
|
//#############################################################################
|
|
//
|
|
MethodDescription
|
|
ChainToPrevious = METHOD_DESCRIPTION_CHAIN(NULL); // Null:end of chain
|
|
|
|
MethodDescription
|
|
*L4MethodDescription[] =
|
|
{
|
|
&MakeConfigMethodDescription,
|
|
&MakeExternConfigMethodDescription,
|
|
&BackgroundReconfig::methodDescription,
|
|
&BackgroundLine::methodDescription,
|
|
&BackgroundRect::methodDescription,
|
|
&BackgroundFilledRect::methodDescription,
|
|
&BackgroundPixelmap::methodDescription,
|
|
&BackgroundBitmap::methodDescription,
|
|
&NumericDisplayScalar::methodDescription,
|
|
&NumericDisplaySpeed::methodDescription,
|
|
&NumericDisplayInteger::methodDescription,
|
|
&DigitalClock::methodDescription,
|
|
&RankAndScore::methodDescription,
|
|
&BarGraphSolidScalar::methodDescription,
|
|
&BarGraphPixelMapScalar::methodDescription,
|
|
&ColorState::methodDescription,
|
|
&TwoState::methodDescription,
|
|
&SegmentArcNormalized::methodDescription,
|
|
&ChainToPrevious
|
|
};
|
|
|
|
//
|
|
//#############################################################################
|
|
// L4GaugeRenderer
|
|
//#############################################################################
|
|
//
|
|
L4GaugeRenderer::L4GaugeRenderer():
|
|
GaugeRenderer()
|
|
{
|
|
//
|
|
//---------------------------------------------------------------------
|
|
// Ensure that graphicsDisplay is NULL
|
|
//---------------------------------------------------------------------
|
|
//
|
|
graphicsDisplay = NULL;
|
|
externalDisplay = NULL;
|
|
|
|
palettesAreActive = True;
|
|
scrambleVideoFlag = False;
|
|
|
|
char
|
|
*mode_string = getenv("L4GAUGE");
|
|
//
|
|
//---------------------------------------------------------------------
|
|
// NOTE that L4Application::Initialize() searches for the environment
|
|
// variable "L4GAUGE" to determine whether or not to create
|
|
// a gauge renderer.
|
|
//---------------------------------------------------------------------
|
|
//
|
|
if (mode_string != NULL)
|
|
{
|
|
//
|
|
//---------------------------------------------------------------------
|
|
// Attempt to initialize external display
|
|
//---------------------------------------------------------------------
|
|
//
|
|
char
|
|
*plasma_string = getenv("L4PLASMA");
|
|
|
|
if (plasma_string != NULL)
|
|
{
|
|
if (strnicmp(plasma_string, "COM2", 4) == 0)
|
|
{
|
|
Tell("Plasma display created on COM2\n");
|
|
externalDisplay = new PlasmaDisplay(PCS_COM2);
|
|
}
|
|
else
|
|
{
|
|
Tell("Plasma display created on COM1\n");
|
|
externalDisplay = new PlasmaDisplay(PCS_COM1);
|
|
}
|
|
|
|
if (externalDisplay != NULL)
|
|
{
|
|
Register_Object(externalDisplay);
|
|
}
|
|
}
|
|
//
|
|
//---------------------------------------------------------------------
|
|
// Attempt to initialize main display
|
|
//---------------------------------------------------------------------
|
|
//-------------------------------------------------
|
|
// Search notation file for specified resolution
|
|
//-------------------------------------------------
|
|
//
|
|
NotationFile
|
|
notation_file("GAUGE\\L4GAUGE.INI");
|
|
|
|
if (!notation_file.PageExists(mode_string))
|
|
{
|
|
Tell("L4GaugeRenderer::L4GaugeRenderer - '" <<
|
|
mode_string <<
|
|
"' not found in L4GAUGE.INI!\n"
|
|
);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
//-------------------------------------------------
|
|
// Requested resolution exists, read parameters
|
|
//-------------------------------------------------
|
|
//
|
|
int
|
|
mode(0),
|
|
width(640),
|
|
height(480),
|
|
page_size(64),
|
|
gran(64),
|
|
bpl(640*2),
|
|
pfp(0),
|
|
special(0),
|
|
q(0);
|
|
|
|
q += notation_file.GetEntry(mode_string, "mode", &mode);
|
|
q += notation_file.GetEntry(mode_string, "width", &width);
|
|
q += notation_file.GetEntry(mode_string, "height", &height);
|
|
q += notation_file.GetEntry(mode_string, "sizeInKB", &page_size);
|
|
q += notation_file.GetEntry(mode_string, "granularityInKB", &gran);
|
|
q += notation_file.GetEntry(mode_string, "bytesPerLine", &bpl);
|
|
q += notation_file.GetEntry(mode_string, "pageFcnPtr", &pfp);
|
|
q += notation_file.GetEntry(mode_string, "special", &special);
|
|
|
|
if (q == 8)
|
|
{
|
|
graphicsDisplay = new
|
|
SVGA16(
|
|
mode,
|
|
width,
|
|
height,
|
|
page_size,
|
|
gran,
|
|
bpl,
|
|
pfp,
|
|
special
|
|
);
|
|
if (graphicsDisplay != NULL)
|
|
{
|
|
Register_Object(graphicsDisplay);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ~L4GaugeRenderer
|
|
//#############################################################################
|
|
//
|
|
L4GaugeRenderer::~L4GaugeRenderer()
|
|
{
|
|
# if defined (LOCAL_TEST)
|
|
Tell("L4GaugeRenderer::~L4GaugeRenderer()\n");
|
|
# endif
|
|
Check(this);
|
|
|
|
int i;
|
|
|
|
//----------------------------------------------------
|
|
// Make sure all gauges removed first
|
|
//----------------------------------------------------
|
|
Remove(0);
|
|
//----------------------------------------------------
|
|
// Delete the graphics ports
|
|
//----------------------------------------------------
|
|
for(i=0; i<maximumGraphicsPorts; ++i)
|
|
{
|
|
if (graphicsPort[i] != NULL)
|
|
{
|
|
Check(graphicsPort[i]);
|
|
Unregister_Object(graphicsPort[i]);
|
|
delete graphicsPort[i];
|
|
}
|
|
}
|
|
|
|
if (graphicsDisplay != NULL)
|
|
{
|
|
Check(graphicsDisplay);
|
|
Unregister_Object(graphicsDisplay);
|
|
delete graphicsDisplay;
|
|
}
|
|
|
|
if (externalDisplay != NULL)
|
|
{
|
|
Check(externalDisplay);
|
|
Unregister_Object(externalDisplay);
|
|
delete externalDisplay;
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// LocalEmergencyShutdown - restore the ABSOLUTE MINIMUM to get back
|
|
// to the system!
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4GaugeRenderer::LocalEmergencyShutdown()
|
|
{
|
|
if (graphicsDisplay != NULL)
|
|
{
|
|
// It's an EMERGENCY: a 'verify' somewhere may have already failed.
|
|
// Therefore don't check it, just kill it!
|
|
delete graphicsDisplay;
|
|
}
|
|
|
|
GaugeRenderer::LocalEmergencyShutdown();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// TestInstance
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
L4GaugeRenderer::TestInstance() const
|
|
{
|
|
return GaugeRenderer::TestInstance();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// StartImplementation
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4GaugeRenderer::LoadMissionImplementation(Mission *mission)
|
|
{
|
|
Check(this);
|
|
# if defined (LOCAL_TEST)
|
|
Tell("L4GaugeRenderer::LoadMissionImplementation()\n");
|
|
# endif
|
|
|
|
GaugeRenderer::LoadMissionImplementation(mission);
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// StopImplementation
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4GaugeRenderer::ShutdownImplementation()
|
|
{
|
|
Check(this);
|
|
# if defined (LOCAL_TEST)
|
|
Tell("L4GaugeRenderer::ShutdownImplementation()\n");
|
|
# endif
|
|
|
|
// EVENTUALLY kill all interrupt-driven processes (if they exist)
|
|
|
|
GaugeRenderer::ShutdownImplementation();
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// SuspendImplementation
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4GaugeRenderer::SuspendImplementation()
|
|
{
|
|
Check(this);
|
|
# if defined (LOCAL_TEST)
|
|
Tell("L4GaugeRenderer::SuspendImplementation()\n");
|
|
# endif
|
|
|
|
GaugeRenderer::SuspendImplementation();
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ResumeImplementation
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4GaugeRenderer::ResumeImplementation()
|
|
{
|
|
Check(this);
|
|
# if defined (LOCAL_TEST)
|
|
Tell("L4GaugeRenderer::ResumeImplementation()\n");
|
|
# endif
|
|
|
|
GaugeRenderer::ResumeImplementation();
|
|
Check_Fpu();
|
|
}
|
|
|
|
|
|
//
|
|
//#############################################################################
|
|
// ExecuteForeground
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4GaugeRenderer::ExecuteForeground()
|
|
{
|
|
Check(this);
|
|
//---------------------------------------
|
|
// We must call the ancestral method!
|
|
//---------------------------------------
|
|
GaugeRenderer::ExecuteForeground();
|
|
//---------------------------------------
|
|
// Process special effects
|
|
//---------------------------------------
|
|
if (graphicsDisplay != NULL)
|
|
{
|
|
Check(graphicsDisplay);
|
|
|
|
ProcessVideoEffects();
|
|
((SVGA16 *) graphicsDisplay)->UpdatePalette();
|
|
}
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ExecuteImplementation
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
L4GaugeRenderer::ExecuteBackgroundDisplayUpdate()
|
|
{
|
|
Check(this);
|
|
|
|
Logical
|
|
result = False; // False == 'all done'
|
|
|
|
//--------------------------------------------------------
|
|
// Update external annunciator
|
|
//
|
|
// Allow the plasma display to continue if there's
|
|
// more background time available.
|
|
//
|
|
// This method is still called if taskmode is set to
|
|
// 'foreground'. If it's called in 'foreground' mode,
|
|
// it really means that we're still waiting for foreground
|
|
// to begin processing, so we might as well update the
|
|
// external display (if needed).
|
|
//--------------------------------------------------------
|
|
if (externalDisplay != NULL)
|
|
{
|
|
Check(externalDisplay);
|
|
result = externalDisplay->Update(False); // returns 'False' if done
|
|
}
|
|
//-------------------------------------------------------
|
|
// Update display ONLY in 'copy' mode
|
|
//--------------------------------------------------------
|
|
if (taskMode == copy)
|
|
{
|
|
if (graphicsDisplay != NULL)
|
|
{
|
|
Check(graphicsDisplay);
|
|
|
|
if (graphicsDisplay->Update(False) == False)
|
|
{
|
|
//--------------------------------------------------------
|
|
// Tell executionImplementation that we're basically done.
|
|
//--------------------------------------------------------
|
|
taskMode = foreground;
|
|
}
|
|
else
|
|
{
|
|
result = True; // We're not done yet!
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// NotifyOfNewInterestingEntity
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4GaugeRenderer::NotifyOfNewInterestingEntity(
|
|
Entity *interesting_entity
|
|
)
|
|
{
|
|
SET_GAUGE_RENDERER();
|
|
Check(this);
|
|
Check(interesting_entity);
|
|
//----------------------------------------------
|
|
// Make sure gaugeImage exists for entity
|
|
//----------------------------------------------
|
|
Check(warehousePointer);
|
|
|
|
((L4Warehouse*)warehousePointer)->gaugeImageBin.Get(
|
|
interesting_entity->GetResourceID()
|
|
);
|
|
//----------------------------------------------
|
|
// Update gaugeRenderer
|
|
//----------------------------------------------
|
|
GaugeRenderer::NotifyOfNewInterestingEntity(interesting_entity);
|
|
Check_Fpu();
|
|
CLEAR_GAUGE_RENDERER();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// NotifyOfBecomingUninterestingEntity
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4GaugeRenderer::NotifyOfBecomingUninterestingEntity(
|
|
Entity *uninteresting_entity
|
|
)
|
|
{
|
|
SET_GAUGE_RENDERER();
|
|
Check(this);
|
|
Check(uninteresting_entity);
|
|
//----------------------------------------------
|
|
// Remove gaugeImage if no longer used
|
|
//----------------------------------------------
|
|
Check(warehousePointer);
|
|
|
|
((L4Warehouse*)warehousePointer)->gaugeImageBin.Release(
|
|
uninteresting_entity->GetResourceID()
|
|
);
|
|
//----------------------------------------------
|
|
// Update gaugeRenderer
|
|
//----------------------------------------------
|
|
GaugeRenderer::NotifyOfBecomingUninterestingEntity(uninteresting_entity);
|
|
Check_Fpu();
|
|
CLEAR_GAUGE_RENDERER();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// BuildGraphicsPort
|
|
//#############################################################################
|
|
|
|
//===========================================================================//
|
|
// RECONSTRUCTION SHADOW (source410 overrides CODE per build410.sh srcfor).
|
|
//
|
|
// Verbatim copy of CODE/RP/MUNGA_L4/L4GREND.CPP with ONE change: the port
|
|
// construction below builds a BTL4GraphicsPort instead of an L4GraphicsPort,
|
|
// to close the MFD-bleed artifact. Everything else is the archive's.
|
|
//
|
|
// THE ARTIFACT (operator report 2026-07-29: "artifacts bleeding over from the
|
|
// radar display into the MFDs ... I have noticed this in the BT411 builds as
|
|
// well, but does not happen in the original"):
|
|
//
|
|
// * The colour head is SIX BITS (L4GAUGE.CFG:4395, sec mask 0x003F) and the
|
|
// MFDs are bits 8-15 of the SAME 16-bit words -- one plane-packed
|
|
// framebuffer, so a colour-head defect can only surface as MFD garbage.
|
|
// * L4GraphicsPort::BuildSecondaryTranslation (L4VB16.CPP:5419) enumerates
|
|
// only the ACTIVE-bit combinations, filling 1<<numberOfBits entries of
|
|
// int translationTable[256] -- 64 for this port. Entries 64..255 are
|
|
// never written. Note the asymmetry: BuildAuxiliaryTranslation nests an
|
|
// IncrementInactive loop inside IncrementActive and so fills all 256,
|
|
// which is why the MONO MFD ports are safe by construction.
|
|
// * DrawPixelMap8 (L4VB16.CPP:2708) indexes that table with an 8-BIT source
|
|
// pixel and DrawPoint ORs the result in UNMASKED, so an out-of-range
|
|
// entry puts arbitrary bits into other ports' planes.
|
|
// * The colour head's own art carries out-of-range SENTINELS: AVACRIT.PCC
|
|
// is 94% <= 63 with 6% at exactly 231; BTSEC1.PCX is 99.8% <= 63 with
|
|
// 0.2% at 254. Drawn transparent they are skipped; drawn OPAQUE every
|
|
// pixel goes through the table.
|
|
//
|
|
// Why the original is PROBABLY clean (hypothesis, not proven): the port is
|
|
// heap-allocated, and an allocation that happens to arrive zeroed makes those
|
|
// reads colour 0 -- black, and invisible on a black head. Our heap has a
|
|
// different history. That would also explain why the artifact is
|
|
// INTERMITTENT and why the operator sees it in the BT411 builds too: it
|
|
// depends on heap CONTENTS, not on any drawing logic, and both reconstructions
|
|
// inherit the same latent read from the archive.
|
|
//
|
|
// The fix does not change drawing behaviour -- it only guarantees the
|
|
// out-of-range case draws colour 0, i.e. contributes no bits under DrawPoint's
|
|
// OR. That is the most conservative possible outcome and matches what the
|
|
// shipped binary is observed to do.
|
|
//
|
|
// THE OFFENDER IS NAMED (2026-07-29). BT_TRANS_PROVOKE (below) fills the
|
|
// tail with 0xFF00 instead of 0, which makes every out-of-range draw light
|
|
// all eight high-byte heads -- turning an invisible bug into a measurable
|
|
// one. On the boot cockpit it lights exactly 1030 pixels, and the mask
|
|
// splits into a glyph cluster plus ONE SOLID 13x52 BAR at screen
|
|
// (526,228)-(538,279).
|
|
//
|
|
// Decoding the art finds the source of that bar with no ambiguity left:
|
|
//
|
|
// BTSEC1.PCX, the colour head's own 480x640 background, contains exactly
|
|
// ONE out-of-range value in the whole image -- index 254, exactly 676
|
|
// pixels, a SOLID 52x13 RECTANGLE at (199,526)-(250,538).
|
|
//
|
|
// The sec port is configured at ROTATION 270 (L4GAUGE.CFG), which maps
|
|
// source (x,y) -> screen (y, 479-x). That places the rectangle at screen
|
|
// x 526..538, y 229..280. The measured mask is x 526..538, y 228..279.
|
|
// Same rectangle, to the pixel.
|
|
//
|
|
// So one uninitialised read produces BOTH reported symptoms, depending only
|
|
// on which bits the heap garbage happens to have set:
|
|
//
|
|
// low 6 bits set -> a coloured block on the COLOUR head. This is the
|
|
// operator's second report the same day, "that yellow
|
|
// orange artifact", a vertical bar beside the RANGE
|
|
// readout -- which is precisely where the rotated
|
|
// rectangle lands.
|
|
// high 8 bits set -> garbage in the MFD / ENG / COMM planes. This is the
|
|
// first report, "bleeding over ... into the MFDs".
|
|
//
|
|
// VERIFICATION STATUS:
|
|
//
|
|
// * MECHANISM: proven from source (asymmetric builders) AND live (the
|
|
// provoke control lights 1030 pixels that the fix zeroes).
|
|
// * OFFENDING ART: proven by decode (BTSEC1.PCX index 254, 676px, and the
|
|
// rotation arithmetic lands it on the reported spot).
|
|
// * NON-REGRESSIVE: gauge A/B, three boots (unfixed x2, fixed x1) score
|
|
// identically on every head -- Mfd1 132/0, Mfd2 37/7, Mfd3 23/0 --
|
|
// against a ~6px across-boot noise floor.
|
|
// * NOT DIRECTLY OBSERVED CURED. In all our A/B boots the heap tail
|
|
// happened to hold ZEROS, so the unfixed build already scored 0 extras
|
|
// and there was no visible artifact to remove. That is the same luck
|
|
// the shipped binary has been enjoying. The fix makes it guaranteed
|
|
// rather than lucky; confirming the operator's bar is gone needs a
|
|
// capture from a rig that was showing it.
|
|
//
|
|
// A first "fixed" capture appeared to REGRESS the MFDs by ~2700px. It was a
|
|
// bad grab (caught mid-draw); the rebuild-and-rerun matched the unfixed
|
|
// numbers exactly. Recorded here because the two-runs rule is what caught it.
|
|
//===========================================================================//
|
|
|
|
class BTL4GraphicsPort :
|
|
public L4GraphicsPort
|
|
{
|
|
public:
|
|
BTL4GraphicsPort(
|
|
Video16BitBuffered *graphics_display,
|
|
const char *name,
|
|
int rotation,
|
|
int bit_mask,
|
|
SVGA16::PaletteID palette_ID,
|
|
L4GraphicsPort::ChannelEnableID channel_enable
|
|
):
|
|
L4GraphicsPort(
|
|
graphics_display, name, rotation, bit_mask,
|
|
palette_ID, channel_enable
|
|
)
|
|
{
|
|
//
|
|
// SECONDARY ports only. The low byte non-zero is exactly the
|
|
// test BuildSecondaryTranslation asserts on
|
|
// (Verify((bitMask & 0xFF) != 0)); an AUXILIARY port fills all
|
|
// 256 entries itself and must not be touched.
|
|
//
|
|
if ((bit_mask & 0xFF) != 0)
|
|
{
|
|
int
|
|
first = 1 << numberOfBits,
|
|
i;
|
|
|
|
//
|
|
// POSITIVE CONTROL. Zero is the fix; it is also invisible,
|
|
// so on its own it cannot tell us whether the out-of-range
|
|
// path is ever TAKEN on a given screen. BT_TRANS_PROVOKE
|
|
// fills the same tail with every MFD bit instead: if the
|
|
// path fires, the MFD heads flood with garbage and the A/B
|
|
// scoreboard's "extra" columns explode. If they do not
|
|
// move, the sec port never sees an out-of-range index on
|
|
// that screen and the fix is inert there.
|
|
//
|
|
int
|
|
fill = (getenv("BT_TRANS_PROVOKE") != NULL) ? 0xFF00 : 0;
|
|
|
|
for (i = first; i < 256; ++i)
|
|
{
|
|
translationTable[i] = fill;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
//
|
|
void
|
|
L4GaugeRenderer::BuildGraphicsPort(
|
|
int port_number,
|
|
const char *name,
|
|
int rotation,
|
|
int bitAllocation,
|
|
SVGA16::PaletteID palette_ID,
|
|
L4GraphicsPort::ChannelEnableID enable_ID,
|
|
Palette8 *palette
|
|
)
|
|
{
|
|
Test_Tell("L4GaugeRenderer::BuildGraphicsPort\n");
|
|
Check(this);
|
|
|
|
if (graphicsDisplay != NULL)
|
|
{
|
|
Test_Tell("...non-NULL display\n");
|
|
Verify(port_number < maximumGraphicsPorts);
|
|
Verify(port_number >= 0);
|
|
Test_Tell("...port_number= " << port_number << "\n");
|
|
//
|
|
//---------------------------------------------------------
|
|
// If it already exists, delete it
|
|
//---------------------------------------------------------
|
|
//
|
|
L4GraphicsPort
|
|
*graphics_port = (L4GraphicsPort *) graphicsPort[port_number];
|
|
|
|
if (graphics_port != NULL)
|
|
{
|
|
Test_Tell("...deleting existing port\n");
|
|
Check(graphics_port);
|
|
Unregister_Object(graphics_port);
|
|
delete graphics_port;
|
|
}
|
|
//
|
|
//---------------------------------------------------------
|
|
// Attempt to create it
|
|
//---------------------------------------------------------
|
|
//
|
|
graphicsPort[port_number] = new BTL4GraphicsPort(
|
|
(Video16BitBuffered *) graphicsDisplay,
|
|
name,
|
|
rotation,
|
|
bitAllocation,
|
|
palette_ID,
|
|
enable_ID
|
|
);
|
|
|
|
graphics_port = (L4GraphicsPort *) graphicsPort[port_number];
|
|
|
|
Test_Tell("...new port at " << graphics_port << "\n");
|
|
//
|
|
//---------------------------------------------------------
|
|
// If created, register it and build the palette
|
|
//---------------------------------------------------------
|
|
//
|
|
if (graphics_port != NULL)
|
|
{
|
|
Check(graphics_port);
|
|
Register_Object(graphics_port);
|
|
//
|
|
// Set the palette for the port
|
|
// (Note: secondary uses lowest 8 bits)
|
|
//
|
|
if ((bitAllocation > 0) && (bitAllocation < 0x0100))
|
|
{
|
|
Test_Tell("...secondary\n");
|
|
graphics_port->SetSecondaryPalette(palette);
|
|
}
|
|
else
|
|
{
|
|
Test_Tell("...auxiliary\n");
|
|
graphics_port->SetAuxiliaryPalette();
|
|
}
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// RemapGraphicsPort
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4GaugeRenderer::RemapGraphicsPort(
|
|
int port_number,
|
|
SVGA16::PaletteID palette_ID,
|
|
L4GraphicsPort::ChannelEnableID enable_ID,
|
|
Palette8 *palette
|
|
)
|
|
{
|
|
Check(this);
|
|
Test_Tell("L4GaugeRenderer::RemapGraphicsPort\n");
|
|
|
|
if (graphicsDisplay != NULL)
|
|
{
|
|
Test_Tell("...non-NULL display\n");
|
|
Verify(port_number < maximumGraphicsPorts);
|
|
Verify(port_number >= 0);
|
|
Test_Tell("...port_number= " << port_number << "\n");
|
|
//---------------------------------------------------------
|
|
// If it exists, change the palette ID and set the palette
|
|
//---------------------------------------------------------
|
|
L4GraphicsPort
|
|
*graphics_port = (L4GraphicsPort *) graphicsPort[port_number];
|
|
|
|
if (graphics_port != NULL)
|
|
{
|
|
Check(graphics_port);
|
|
//
|
|
// Set the palette ID
|
|
//
|
|
graphics_port->SetPaletteID(palette_ID);
|
|
graphics_port->SetEnableID(enable_ID);
|
|
|
|
//
|
|
// Set the palette for the port
|
|
// (Note: secondary uses lowest 8 bits)
|
|
//
|
|
|
|
int
|
|
bit_mask = graphics_port->GetBitMask();
|
|
|
|
if (bit_mask & 0x00FF) // must be secondary
|
|
{
|
|
Test_Tell("...secondary\n");
|
|
Verify(palette != NULL);
|
|
graphics_port->SetSecondaryPalette(palette);
|
|
}
|
|
else
|
|
{
|
|
Test_Tell("...auxiliary\n");
|
|
graphics_port->SetAuxiliaryPalette();
|
|
}
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// BuildExternalGraphicsPort
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4GaugeRenderer::BuildExternalGraphicsPort(
|
|
int port_number,
|
|
const char *name,
|
|
int /*bitAllocation*/
|
|
)
|
|
{
|
|
Check(this);
|
|
Test_Tell(
|
|
"L4GaugeRenderer::BuildExternalGraphicsPort(" << port_number << ")\n"
|
|
);
|
|
|
|
if (externalDisplay != NULL)
|
|
{
|
|
Test_Tell("...non-NULL display\n");
|
|
Verify(port_number < maximumGraphicsPorts);
|
|
Verify(port_number >= 0);
|
|
Test_Tell("...port_number= " << port_number << "\n");
|
|
//
|
|
//---------------------------------------------------------
|
|
// If it already exists, delete it
|
|
//---------------------------------------------------------
|
|
//
|
|
L4GraphicsPort
|
|
*graphics_port = (L4GraphicsPort *) graphicsPort[port_number];
|
|
|
|
if (graphics_port != NULL)
|
|
{
|
|
Test_Tell("...deleting existing port\n");
|
|
Check(graphics_port);
|
|
Unregister_Object(graphics_port);
|
|
delete graphics_port;
|
|
}
|
|
//
|
|
//---------------------------------------------------------
|
|
// Attempt to create it
|
|
//---------------------------------------------------------
|
|
//
|
|
graphicsPort[port_number] = new L4BytePort(
|
|
(Video8BitBuffered *) externalDisplay,
|
|
name,
|
|
0 // rotation
|
|
);
|
|
|
|
graphics_port = (L4GraphicsPort *) graphicsPort[port_number];
|
|
|
|
Test_Tell("...new port at " << graphics_port << "\n");
|
|
//
|
|
//---------------------------------------------------------
|
|
// If created, register it
|
|
//---------------------------------------------------------
|
|
//
|
|
if (graphics_port != NULL)
|
|
{
|
|
Check(graphics_port);
|
|
Register_Object(graphics_port);
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// FadeToWhite, FadeToGame
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4GaugeRenderer::FadeToWhite(Scalar number_of_seconds)
|
|
{
|
|
Check(this);
|
|
Verify(number_of_seconds >= 0.0); // zero is allowed (instant change)
|
|
|
|
// cout << "L4GaugeRenderer::FadeToWhite(" << number_of_seconds << ")\n";
|
|
|
|
if (graphicsDisplay != NULL)
|
|
{
|
|
Check(graphicsDisplay);
|
|
((SVGA16 *)graphicsDisplay)->FadeToWhite(number_of_seconds);
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
void
|
|
L4GaugeRenderer::FadeToNormal(Scalar number_of_seconds)
|
|
{
|
|
Check(this);
|
|
Verify(number_of_seconds >= 0.0); // zero is allowed (instant change)
|
|
|
|
// cout << "L4GaugeRenderer::FadeToNormal(" << number_of_seconds << ")\n";
|
|
|
|
if (graphicsDisplay != NULL)
|
|
{
|
|
Check(graphicsDisplay);
|
|
((SVGA16 *)graphicsDisplay)->FadeToPalettes(number_of_seconds);
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// Configure
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4GaugeRenderer::ConfigureForModel(
|
|
const char *configuration_name,
|
|
Entity *entity
|
|
)
|
|
{
|
|
Check(this);
|
|
Check_Pointer(configuration_name);
|
|
|
|
Test_Tell(
|
|
"L4GaugeRenderer::ConfigureForModel(" <<
|
|
configuration_name << ", " <<
|
|
entity << ")\n"
|
|
);
|
|
|
|
Check(application);
|
|
Check(entity);
|
|
|
|
Mission
|
|
*mission = application->GetCurrentMission();
|
|
Check(mission);
|
|
|
|
const char
|
|
*model_name = mission->GetGameModel();
|
|
Check_Pointer(model_name);
|
|
|
|
char
|
|
full_name[64];
|
|
|
|
Str_Copy(full_name, model_name, sizeof(full_name));
|
|
Str_Cat(full_name, configuration_name, sizeof(full_name));
|
|
|
|
Test_Tell(
|
|
"L4GaugeRenderer::ConfigureForModel, full_name=" << full_name <<
|
|
")\n"
|
|
);
|
|
|
|
Configure(full_name, entity);
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// SpecialEffect
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4GaugeRenderer::SpecialEffect(
|
|
VideoEffectType type,
|
|
Scalar duration
|
|
)
|
|
{
|
|
Check(this);
|
|
|
|
switch (type)
|
|
{
|
|
case scrambleVideo:
|
|
if (graphicsDisplay != NULL)
|
|
{
|
|
scrambleVideoFlag = True;
|
|
scrambleVideoTimeout = ((Scalar)Now()) + duration;
|
|
|
|
Check(graphicsDisplay);
|
|
((SVGA16*) graphicsDisplay)->FunkyVideo(True);
|
|
}
|
|
break;
|
|
|
|
// other modes go here
|
|
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
void
|
|
L4GaugeRenderer::ProcessVideoEffects()
|
|
{
|
|
Check(this);
|
|
Check(graphicsDisplay);
|
|
|
|
Time
|
|
now = Now();
|
|
|
|
if (scrambleVideoFlag)
|
|
{
|
|
if (now >= scrambleVideoTimeout)
|
|
{
|
|
scrambleVideoFlag = False;
|
|
((SVGA16*) graphicsDisplay)->FunkyVideo(False);
|
|
}
|
|
}
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// Profiling support
|
|
//--------------------------------------------------------------------
|
|
Scalar
|
|
L4GaugeRenderer::GetCurrentFramePercentage()
|
|
{
|
|
Check(this);
|
|
return Get_Frame_Percent_Used();
|
|
}
|
|
|
|
|
|
|
|
|