// 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 #include #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