diff --git a/emulator/render-bridge/pod_render_joints.conf b/emulator/render-bridge/pod_render_joints.conf new file mode 100644 index 00000000..e366695b --- /dev/null +++ b/emulator/render-bridge/pod_render_joints.conf @@ -0,0 +1,86 @@ +# pod_render_norio.conf -- pod_render_rec with the RIO serial port OFF. +# +# The emulator log shows a steady stream of serial1 RX OVERRUN errors on +# the RIO pipe, and controls post their events at HighEventPriority +# UNCONDITIONALLY (CONTROLS.HPP:250) -- so a chattering RIO port would +# flood a priority the background pump always serves first, starving the +# priority-0 renderer events the load gate waits on. This conf tests that +# by removing the port entirely. Everything else is identical to the rec +# conf, so a launch here and a hang there isolates the RIO. +# +[sdl] +output=opengl +# higher,higher not highest: HIGH_PRIORITY_CLASS starved the host desktop; +# with the retry patches a rare dropout self-recovers (see gauge_rio.conf). +priority=higher,higher +[dosbox] +memsize=32 +machine=svga_s3 +[cpu] +core=dynamic +cputype=pentium +cycles=max +[sblaster] +sbtype=sb16 +sbbase=220 +irq=5 +dma=1 +hdma=5 +[mixer] +# match the EMU8000s' native rate (no resample) and buffer ~60ms so brief +# emulation-thread stalls (RIO retry recovery) don't audibly chop +rate=44100 +blocksize=1024 +prebuffer=60 +[serial] +# RIO on COM1 with the low-latency options (rxpollus/rxburst) so the board's +# few-ms ACK deadline is met; plasma display on COM2 (real pod has both). +# VWE fork namedpipe backend (com0com/realport retired -- COM1/COM2 gone): +# DOSBox = pipe client (retry), vRIO/vPLASMA apps = servers; an unconnected +# pipe behaves as an unplugged cable so the mission still runs. serialnamedpipe.h +serial1=disabled +serial2=namedpipe pipe:vplasma +# live UNBUFFERED game output: DOS char devices are not buffered, so +# redirecting stdout to COM3 lands every line immediately. A normal +# '> file' redirect stays 0 bytes until the process exits, which hides +# all progress on a run that does NOT crash. +serial3=file file:C:\VWE\TeslaRel410\emulator\render-bridge\podlog.txt +[autoexec] +mount c "C:\VWE\TeslaRel410\ALPHA_1" +c: +cd \REL410\BT +set VIDEOFORMAT=svga +rem production pod card init (PARAMETR.BAT:181-186): DIAGNOSE + AWEUTIL per +rem card -- AWEUTIL /S does the EMU8000 bring-up and DRAM detect the HMI SOS +rem driver relies on; skipping it left the cards uninitialized (silent). +rem aweutil /s SKIPPED for now: it verifies the AWE32 GM ROM, which the +rem emulated cards lack (hangs in a retry loop) -- restore once the ROM is +rem dumped from a real card. diagnose /s kept (passes, sets mixer config). +set BLASTER=A220 I5 D1 H5 P330 T6 +c:\sb16\diagnose /s +set BLASTER=A240 I7 D3 H6 P300 T6 +c:\sb16\diagnose /s +set BLASTER=A220 I5 D1 H5 P330 T6 +set TEMP=c:\ +rem arena1 city mission (TESTARN.EGG: map=arena1, time=day) with the RIO +rem attached; stdout redirected so mission-load progress survives kills. +set BT_JOINTS=1 +set BT_MECH_LOG=1 +set BT_MAP_LOG=1 +set BT_MER_LOG=1 +set BT_VID_LOG=1 +set BT_LAUNCH_LOG=1 +set BT_STACK_LOG=1 +set BT_FORCE_THROTTLE=0.6 +set BT_FORCE_TURN=0.25 +set HEAPSIZE=15000000 +set L4GAUGE=640x480x16 +call setenv.bat r s n p +32rtm.exe -x +BTL4REC.EXE -egg testarn.egg > COM3 + +echo GAME-RC=%errorlevel% >> RC.TXT +32rtm.exe -u +echo ALPHA1-RUN-DONE +pause + diff --git a/restoration/source410/BT_L4/BTL4VID.CPP b/restoration/source410/BT_L4/BTL4VID.CPP index 7a0f02e2..a971e0cc 100644 --- a/restoration/source410/BT_L4/BTL4VID.CPP +++ b/restoration/source410/BT_L4/BTL4VID.CPP @@ -40,6 +40,164 @@ extern Entity *Entity_Being_Created; +//########################################################################### +//###################### BTL4HingeRenderable ############################ +//########################################################################### +// +// A HingeRenderable that flushes the joint as a FULL MATRIX instead of a +// single-axis sin/cos pair. +// +// THE PROBLEM. L4VIDRND.CPP:1028 sets SINGLE_AXIS_HINGE True, so both the +// ctor and Execute push the hinge with dpl_SetDCSXAxis / YAxis / ZAxis -- +// three distinct libDPL entry points, each handed only (sine, cosine). The +// AXIS is therefore carried by WHICH function was called, and libDPL does not +// put it on the wire. Confirmed against a live capture +// (emulator/render-bridge/joints_check.fifodump): every articulation record +// is 12 bytes, [dcs handle][sin][cos], 26133 of them, and there is no axis +// field anywhere in the message. Nor can the renderer recover it from +// context -- the wire names nothing (zero action-0x22 name records in that +// capture), so a handle cannot be matched back to a .SKL node. +// +// The consequence is visible: vrboard parses those records, cannot apply +// them, and leaves the flushed matrix standing. The mech renders with a +// live root but frozen limbs. +// +// THE FIX is the archive's own alternative. The #else half of that same +// #if (L4VIDRND.CPP:1153) builds a Quaternion from the Hinge and assigns it +// over the DCS matrix, which encodes the axis in the matrix itself and +// flushes as a 12-float pose. The renderer already applies those -- it is +// exactly what BallJointRenderable::Execute does unconditionally, with no +// #if at all, which is why ball joints were never affected by this. +// +// So this is not a new mechanism. It is the branch the original authors +// wrote and did not take, applied to hinges so they behave like the ball +// joints beside them. +// +// Cost: 48 wire bytes per changed hinge per frame instead of 8. With ~22 +// articulated nodes that is under 1KB/frame, against captures that already +// run to megabytes. +// +// Why a subclass and not a shadow of L4VIDRND.CPP: this needs one method, +// and Component::Execute is virtual (CMPNNT.HPP:40), so an override +// dispatches normally. Shadowing the engine file to flip one #define would +// fork 1900 lines of it to change two. +// +// WHY THIS DERIVES FROM ChildOffsetRenderable AND NOT FROM HingeRenderable. +// The first attempt did subclass HingeRenderable and override only Execute. +// It built, ran, and DID NOT WORK -- the wire still carried 12-byte records. +// What it did change was their contents: the pair went from (sin, cos) to +// (0.9990, 0.0000), which is m00 and m01 of the matrix being written. +// +// That is the whole answer. The DCS remembers HOW IT WAS LAST SET. The base +// HingeRenderable ctor calls dpl_SetDCSXAxis/YAxis/ZAxis before we get +// control, which puts the DCS in single-axis mode, and the flush then +// serialises two floats out of it no matter what we write through +// dpl_GetDCSMatrix. Writing a full matrix into an axis-mode DCS just means +// the first two cells of the matrix get sent. +// +// So the axis-mode setter must never run on this DCS. Deriving from +// ChildOffsetRenderable -- whose ctor builds the offset DCS and touches no +// axis setter -- is what avoids it. That makes this class the exact hinge +// analogue of BallJointRenderable: same base, same shape, holds its own +// attribute pointer plus a previous-value copy, writes a full matrix in +// Execute. Which is presumably how it would have been written had the +// single-axis optimisation not been there. +// +// NOTE on the archive's stale comment: the ctor there says the quaternion +// hop exists "because the math library doesn't support direct assignment of +// hinge to matrix yet". Still true, and worth recording because the header +// suggests otherwise -- Matrix4x4::operator=(const Hinge&) is DECLARED at +// MATRIX.HPP:94 but implemented nowhere, so taking the direct route is a +// link error. Quaternion::operator=(const Hinge&) is real +// (MUNGA/ROTATION.CPP:707, correct half-angle about axisNumber) and +// Matrix4x4::operator=(const Quaternion&) is real (MATRIX.CPP:260). +// + class BTL4HingeRenderable : + public ChildOffsetRenderable + { + public: + BTL4HingeRenderable( + Entity *entity, + ExecutionType execution_type, + dpl_OBJECT *graphical_object, + dpl_ZONE *this_zone, + dpl_ISECT_MODE intersect_mode, + uint32 intersect_mask, + dpl_DCS *parent_DCS, + LinearMatrix *offset_matrix, + const Hinge *my_hinge + ): + ChildOffsetRenderable( + entity, execution_type, graphical_object, this_zone, + intersect_mode, intersect_mask, parent_DCS, offset_matrix + ) + { + Check(my_hinge); + + myHinge = my_hinge; + oldHinge = *my_hinge; + // + // Push the initial pose, exactly as both stock joint renderables + // do in their ctors -- otherwise the DCS holds the identity the + // base installed until the joint first moves. + // + FlushAsMatrix(); + } + + void + Execute(); + + protected: + void + FlushAsMatrix(); + + const Hinge + *myHinge; // the joint attribute we watch + Hinge + oldHinge; // last value seen, so we only flush on change + }; + +void + BTL4HingeRenderable::FlushAsMatrix() +{ + Quaternion + temp_quaternion; + float32 + *temp_matrix; + + temp_matrix = dpl_GetDCSMatrix(myDCS); + Check_Pointer(temp_matrix); + temp_quaternion = oldHinge; + *(Matrix4x4 *)temp_matrix = temp_quaternion; + + // + // This is what DPL_FLUSH_DCS expands to (L4VIDRND.CPP:73). The macro is + // file-private to L4VIDRND.CPP, so spell it out rather than redefine it + // here and risk the two drifting. It matters that this is the DELAYED + // flush and not a bare dpl_FlushDCS: the batch is what the renderer + // coalesces per frame, and going around it would put every joint on the + // wire immediately. + // + myRenderer->DPLDelayDCSFlush(myDCS); +} + +void + BTL4HingeRenderable::Execute() +{ + Check(this); + + if (oldHinge != *myHinge) + { + oldHinge = *myHinge; + FlushAsMatrix(); + } + // + // Chain the GRANDPARENT, not HingeRenderable::Execute -- that would run + // the single-axis write we are replacing and re-flush the same DCS. + // + ChildOffsetRenderable::Execute(); +} + BTL4VideoRenderer::BTL4VideoRenderer( RendererRate calibration_rate, RendererComplexity calibration_complexity, @@ -366,7 +524,13 @@ dpl_DCS * case Joint::HingeXJointType: case Joint::HingeYJointType: case Joint::HingeZJointType: - joint_renderable = new HingeRenderable( + // + // BTL4HingeRenderable, not HingeRenderable: the stock one + // flushes (sin,cos) with the axis implied by which libDPL + // entry point it called, and the axis never reaches the wire. + // See the class comment at the top of this file. + // + joint_renderable = new BTL4HingeRenderable( entity, VideoRenderable::Dynamic, object, zone, dpl_isect_mode_obj, 0, parent_dcs, &offset, &joint->GetHinge());