fs2port/port/include/sceneryVm.h
2026-05-13 21:32:05 -05:00

147 lines
6.7 KiB
C

// Scenery interpreter VM. Drives a stream of opcoded records the
// same way the original FS2 chunk5 dispatcher does, but writes into
// the modern framebuffer via the renderer instead of poking hires
// bytes directly.
#ifndef SCENERY_VM_H
#define SCENERY_VM_H
#include <stdint.h>
#include <stdbool.h>
#include "renderer.h"
#include "sceneryProjection.h"
#include "types.h"
#define SCENERY_VERTEX_CAP 64
// FS2 cached-vertex pool lives at $0140 in zero-page-extended memory,
// 8 bytes per vertex slot. Opcodes $31/$32/$33/$35/$42 reference these
// by 1-byte index from the stream. The pool holds up to 80 vertices
// (= $140..$540 = 0x400 / 8).
#define SCENERY_CACHED_POOL_BASE 0x0140
#define SCENERY_CACHED_POOL_CAP 80
// Forward decl so we don't pull in camera.h here.
struct CameraT;
struct SceneryStateT;
typedef enum SceneryStationTypeE {
SCENERY_STATION_ADF = 'A',
SCENERY_STATION_NAV = 'N',
SCENERY_STATION_COM = 'C'
} SceneryStationTypeE;
// Decoded station record passed to the optional callback. `freq` is
// the raw little-endian word from the record (BCD-packed); `x`/`y` are
// 24-bit signed scenery coordinates; `z` is 0 for ADF/COM and a 24-bit
// signed altitude/north for NAV. `name` is the COM record's airport
// name or NULL.
typedef struct SceneryStationT {
SceneryStationTypeE type;
uint16_t freq;
int32_t x;
int32_t y;
int32_t z;
const char *name;
} SceneryStationT;
typedef void (*SceneryStationCbF)(struct SceneryStateT *state, const SceneryStationT *station);
// Working state for a single scenery interpretation pass.
typedef struct SceneryStateT {
const uint8_t *stream;
const uint8_t *cursor;
const uint8_t *streamEnd;
// When non-NULL, $1A (WriteWord) and $25 (StoreImmWord) opcodes
// patch this buffer at the bytecode-supplied target addresses.
// The full 64K RAM image is treated as one flat address space;
// chunk5 SceneryOpStoreImmWord stores into $0846/$0848 zero-page
// slots and similar, all of which live inside the same 64K
// buffer when we're driving from a RAM dump.
uint8_t *writableRam;
// Raw .SD scenery file used by HEADER's demand-load. The file
// is indexed in 256-byte sectors; chunk5 HEADER stores
// (sectionId, count) at $08E5/$08E6 and triggers a copy of
// count*256 bytes from sceneryFile[sectionId*256] to the
// relocated dest at $08E7/$08E8 -- mirrors chunk5 LA63A.
const uint8_t *sceneryFile;
uint32_t sceneryFileSize;
RenderStateT *renderer;
const struct CameraT *camera; // NULL for 2D streams
uint8_t subDepth;
SceneryStationCbF stationCb; // NULL = ignore station records
void *userData; // forwarded to stationCb
SceneryPipelineT pipeline; // 3D vertex / projection state
// Drawing mode flags toggled by the $1B/$1C opcodes. dayOnlySkip
// is set by SceneryOpDayOnly when night, suppressing line draws
// for ground-only objects until SceneryOpModeWhite restores.
bool dayOnlySkip;
// Set by main.c (or the time-of-day step) so $1C can decide
// whether to suppress draws.
bool isNight;
// Offline extraction mode: every conditional opcode walks BOTH
// branches (recursively) instead of evaluating the predicate.
// The visited[] array bounds the total work. Used by tools that
// want to extract every reachable polygon / station regardless
// of the aircraft's runtime position.
bool walkAllPaths;
uint8_t visited[200000]; // cycle guard for offline walks
// Polygon vertex accumulator. Each $40/$41 (xform-B) emit appends
// its projected screen coords. The $29 (CopyToD2) opcode triggers
// rendererFillPolygon over these coords, then resets the buffer.
// Mirrors chunk5's PrimVert*/SecVert* polygon arrays at $0AF9+
// that the L7826 scan-line rasterizer fills from.
int16_t polyXs[64];
int16_t polyYs[64];
int polyCount;
// 3D-vertex accumulator that mirrors chunk5's PrimVerts array
// BEFORE the 4-pass Sutherland-Hodgman clipping at L6F98.
// Vertices live in camera-space (post-TransformVertex, pre-
// PerspectiveDivide). The clipper introduces new vertices at
// frustum-edge intersections, then projection expands the
// resulting screen-Y range -- without this 3D-then-clip path
// a polygon whose vertices all map to a narrow screen-Y range
// (because their Z's are all similar) collapses to a thin
// sliver instead of the real wedge shape chunk5 produces.
// The clipper output lives in a second array (`polyV3DOut`)
// and we ping-pong between the two per pass.
SceneryVertexT polyV3D[64];
SceneryVertexT polyV3DOut[64];
int polyV3DCount;
// MAME-patched chunk5 EmitClippedLine ends with RTS, so each
// $41/$02 op (= line-emit) terminates its parent's
// SceneryInterpreterStep iteration. SubInvoke ($18) calls JSR
// $6751 which then RTSes when a line is emitted, returning
// control to the SubInvoke handler that restores the parent
// cursor and continues. Mirror this with an exitDispatch flag:
// setting it tells sceneryRun to stop iterating.
bool exitDispatch;
} SceneryStateT;
// Initialise an interpreter pointing at the given byte stream. Pass
// `camera = NULL` for legacy 2D fixture streams.
void sceneryInit(SceneryStateT *state, const uint8_t *stream, uint32_t length, RenderStateT *renderer);
void sceneryAttachCamera(SceneryStateT *state, const struct CameraT *cam);
// Install a station-record callback. Pass NULL to clear. Used by the
// offline scenery dump tool to collect ADF/NAV/COM records without
// rendering anything.
void sceneryAttachStationCb(SceneryStateT *state, SceneryStationCbF cb, void *userData);
// Run the interpreter until it hits a stream-terminator record.
void sceneryRun(SceneryStateT *state);
// Walk every reachable record from the given entry offset, collecting
// stations into the provided callback. Used by extractstations as a
// drop-in replacement for the old hand-rolled walker. The visited
// array tracks already-walked positions to bound work and let the
// caller invoke from many entry points cheaply.
void sceneryWalkFrom(SceneryStateT *state, uint32_t entryOffset);
#endif