diff --git a/ALPHA_1/REL410/BT/FAILURE.LOG b/ALPHA_1/REL410/BT/FAILURE.LOG index df61c2a..e69de29 100644 --- a/ALPHA_1/REL410/BT/FAILURE.LOG +++ b/ALPHA_1/REL410/BT/FAILURE.LOG @@ -1,44 +0,0 @@ -[RIOBoardErrors] -error=missing_board@Slot=2:Address=0 -error=missing_board@Slot=3:Address=1 -error=missing_board@Slot=3:Address=2 -error=missing_board@Slot=7:Address=0 -error=missing_board@Slot=5:Address=0 -[RIODeadLamps] -lamp=AuxLowerLeft8 -lamp=AuxLowerLeft3 -lamp=AuxLowerLeft2 -lamp=AuxLowerLeft1 -lamp=Secondary2 -lamp=Secondary3 -lamp=Secondary6 -lamp=TeslaRelay3 -lamp=undefined_0x17 -lamp=Secondary7 -lamp=Secondary10 -lamp=Secondary12 -lamp=TeslaRelay1 -lamp=AuxUpperCenter8 -lamp=AuxUpperCenter7 -lamp=AuxUpperCenter6 -lamp=AuxUpperCenter3 -lamp=AuxUpperCenter3 -lamp=AuxUpperLeft7 -lamp=AuxUpperLeft6 -lamp=AuxUpperLeft5 -lamp=AuxUpperLeft4 -lamp=AuxUpperLeft3 -lamp=AuxUpperLeft2 -lamp=AuxUpperLeft1 -lamp=AuxUpperRight8 -lamp=AuxUpperRight7 -lamp=AuxUpperRight6 -lamp=AuxUpperRight5 -lamp=AuxUpperRight5 -lamp=AuxUpperRight3 -lamp=AuxUpperRight2 -lamp=AuxUpperRight1 -lamp=PanicButton -lamp=IcomIncRelay -lamp=IcomPTTRelay -lamp=FloorEntry diff --git a/ALPHA_1/REL410/BT/LAST.EGG b/ALPHA_1/REL410/BT/LAST.EGG index f81d173..dc93dfc 100644 --- a/ALPHA_1/REL410/BT/LAST.EGG +++ b/ALPHA_1/REL410/BT/LAST.EGG @@ -1,6 +1,6 @@ [mission] adventure=BattleTech -map=arena1 +map=arena2 scenario=freeforall time=day weather=clear @@ -156,70 +156,70 @@ x=128 y=32 width=8 [pilots] -pilot=200.0.0.96 -[200.0.0.96] +pilot=200.0.0.113 +[200.0.0.113] hostType=0 advancedDamage=1 loadzones=1 -name=Aeolus +name=cyd bitmapindex=1 -experience=expert +experience=veteran badge=VGL patch=Yellow role=Role::Default dropzone=one -vehicle=bhk1 -vehicleValue=1000 +vehicle=madcat +vehicleValue=0 color=White [largebitmap] -bitmap=BitMap::Large::Aeolus -[BitMap::Large::Aeolus] +bitmap=BitMap::Large::cyd +[BitMap::Large::cyd] bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 -bitmap=000003FE0000000001E0000000000000 -bitmap=000007FF0000000001E0000000000000 -bitmap=00000F8F8000000001E0000000000000 -bitmap=00000F078000000001E0000000000000 -bitmap=00000F0781FF00FF81E0F0781FC00000 -bitmap=00000F0783FF81FFC1E0F0783FE00000 -bitmap=00000F0787C7C3E3E1E0F0783C700000 -bitmap=00000F078783C3C1E1E0F07878300000 -bitmap=00000F078783C3C1E1E0F07878000000 -bitmap=00000FFF8783C3C1E1E0F0787C000000 -bitmap=00000FFF8783C3C1E1E0F0787F000000 -bitmap=00000F0787FFC3C1E1E0F0783FC00000 -bitmap=00000F0787FFC3C1E1E0F0781FE00000 -bitmap=00000F07878003C1E1E0F07807F00000 -bitmap=00000F07878003C1E1E0F07801F00000 -bitmap=00000F07878003C1E1E0F07800F00000 -bitmap=00000F078780C3C1E1E0F07860F00000 -bitmap=00000F0787C1C3E3E1E078F871E00000 -bitmap=00000F0783FF81FFC1E07FF83FE00000 -bitmap=00000F0781FF00FF81E03F781FC00000 -bitmap=00000000000000000000000000000000 -bitmap=00000000000000000000000000000000 -bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 +bitmap=000000000000000000003C0000000000 +bitmap=000000000000000000003C0000000000 +bitmap=000000000000000000003C0000000000 +bitmap=000000000000000000003C0000000000 +bitmap=000000000000FCF00E0FBC0000000000 +bitmap=000000000003FE781C1FFC0000000000 +bitmap=000000000007FE781C3C7C0000000000 +bitmap=00000000000F82783C3C3C0000000000 +bitmap=00000000001F003C38783C0000000000 +bitmap=00000000001E003C78783C0000000000 +bitmap=00000000001E003C70783C0000000000 +bitmap=00000000001E003E70783C0000000000 +bitmap=00000000001E001EE0783C0000000000 +bitmap=00000000001E001EE0783C0000000000 +bitmap=00000000001F001FE0783C0000000000 +bitmap=00000000000F020FC07C7C0000000000 +bitmap=000000000007FE0FC03FFC0000000000 +bitmap=000000000003FE0F803FFC0000000000 +bitmap=000000000000FC07800F3C0000000000 +bitmap=00000000000000070000000000000000 +bitmap=00000000000000070000000000000000 +bitmap=000000000000000F0000000000000000 +bitmap=000000000000000E0000000000000000 bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 x=128 y=32 width=8 [smallbitmap] -bitmap=BitMap::Small::Aeolus -[BitMap::Small::Aeolus] -bitmap=00000000000000000000000000000000 -bitmap=0000000000000000003E00000C000000 -bitmap=006300000C00000000631F0F8CC63C00 -bitmap=00633198CCC66200007F3198CCC66000 -bitmap=00633F98CCC6780000633018CCC61E00 -bitmap=00633018CCC6060000633098CCC64600 -bitmap=00631F0F8C7E3C000000000000000000 +bitmap=BitMap::Small::cyd +[BitMap::Small::cyd] bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000600000 +bitmap=00000000006000000000000000600000 +bitmap=000001EC33E000000000030666600000 +bitmap=000003066660000000000303C6600000 +bitmap=00000303C66000000000030186600000 +bitmap=000001E183E000000000000300000000 +bitmap=00000003000000000000000000000000 x=64 y=16 width=4 diff --git a/ALPHA_1/REL410/BT/TESTAR2.EGG b/ALPHA_1/REL410/BT/TESTAR2.EGG new file mode 100644 index 0000000..dc93dfc --- /dev/null +++ b/ALPHA_1/REL410/BT/TESTAR2.EGG @@ -0,0 +1,229 @@ +[mission] +adventure=BattleTech +map=arena2 +scenario=freeforall +time=day +weather=clear +temperature=27 +length=600 +[ordinals] +bitmap=Ordinal::BitMap::1 +bitmap=Ordinal::BitMap::2 +bitmap=Ordinal::BitMap::3 +bitmap=Ordinal::BitMap::4 +[Ordinal::BitMap::1] +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=000038000003C000001FF00000000F00 +bitmap=0000F8000003C000003FF80000000F00 +bitmap=0001F8000003C00000707C0000000F00 +bitmap=0001F8000003C00000603C0000000F00 +bitmap=00007801FC0FF00000003C3DF807FF00 +bitmap=00007803FE0FF00000003C3FFC0FFF00 +bitmap=00007803C703C00000003C3E3C1F0F00 +bitmap=000078078303C0000000783C1E1E0F00 +bitmap=000078078003C0000000783C1E1E0F00 +bitmap=00007807C003C0000000F03C1E1E0F00 +bitmap=00007807F003C0000001E03C1E1E0F00 +bitmap=00007803FC03C0000003C03C1E1E0F00 +bitmap=00007801FE03C0000007803C1E1E0F00 +bitmap=000078007F03C000000F003C1E1E0F00 +bitmap=000078001F03C000001E003C1E1E0F00 +bitmap=000078000F03C000003C003C1E1E0F00 +bitmap=000078060F03C0000078003C1E1E0F00 +bitmap=000078071E03E0000078003C1E1F1F00 +bitmap=00007803FE01F000007FFC3C1E0FFF00 +bitmap=00007801FC00F000007FFC3C1E07EF00 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +x=128 +y=32 +width=8 +[Ordinal::BitMap::2] +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=001FFF0000003C0000003C03C0780000 +bitmap=001FFF0000003C0000007C03C0780000 +bitmap=00000E0000003C000000FC03C0780000 +bitmap=00003C0000003C000001BC03C0780000 +bitmap=0000700F3E1FFC000003BC0FF07BF000 +bitmap=0001E00F7E3FFC0000073C0FF07FF800 +bitmap=0003800FFE7C3C0000063C03C07C7800 +bitmap=0007F80FFE783C00000C3C03C0783C00 +bitmap=0007FE0F80783C0000183C03C0783C00 +bitmap=00001E0F00783C0000383C03C0783C00 +bitmap=00000F0F00783C0000703C03C0783C00 +bitmap=00000F0F00783C00007FFF03C0783C00 +bitmap=00000F0F00783C00007FFF03C0783C00 +bitmap=00000F0F00783C0000003C03C0783C00 +bitmap=00000F0F00783C0000003C03C0783C00 +bitmap=00000F0F00783C0000003C03C0783C00 +bitmap=00180F0F00783C0000003C03C0783C00 +bitmap=001C1F0F007C7C0000003C03E0783C00 +bitmap=000FFE0F003FFC0000003C01F0783C00 +bitmap=0007FC0F001FBC0000003C00F0783C00 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +x=128 +y=32 +width=8 +[Ordinal::BitMap::3] +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=001FFF03C07800000000FC03C0780000 +bitmap=001FFF03C07800000003FC03C0780000 +bitmap=001E0003C07800000007C003C0780000 +bitmap=001E0003C0780000000F0003C0780000 +bitmap=001E000FF07BF000000F000FF07BF000 +bitmap=001E000FF07FF800001E000FF07FF800 +bitmap=001E0003C07C7800001E0003C07C7800 +bitmap=001FFC03C0783C00001EFC03C0783C00 +bitmap=001FFE03C0783C00001FFE03C0783C00 +bitmap=00001F03C0783C00001F1F03C0783C00 +bitmap=00000F03C0783C00001E0F03C0783C00 +bitmap=00000F03C0783C00001E0F03C0783C00 +bitmap=00000F03C0783C00001E0F03C0783C00 +bitmap=00000F03C0783C00001E0F03C0783C00 +bitmap=00000F03C0783C00001E0F03C0783C00 +bitmap=00000F03C0783C00001E0F03C0783C00 +bitmap=00180F03C0783C00001E0F03C0783C00 +bitmap=001C1F03E0783C00001F1F03E0783C00 +bitmap=000FFE01F0783C00000FFE01F0783C00 +bitmap=0007FC00F0783C000007FC00F0783C00 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +x=128 +y=32 +width=8 +[Ordinal::BitMap::4] +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=001FFF03C07800000007FC03C0780000 +bitmap=001FFF03C0780000000FFE03C0780000 +bitmap=00000F03C0780000001F1F03C0780000 +bitmap=00000F03C0780000001E0F03C0780000 +bitmap=00000F0FF07BF000001E0F0FF07BF000 +bitmap=00001F0FF07FF800001E0F0FF07FF800 +bitmap=00001E03C07C7800001E0F03C07C7800 +bitmap=00003E03C0783C00001E0F03C0783C00 +bitmap=00003C03C0783C00000F1E03C0783C00 +bitmap=00003C03C0783C000007FC03C0783C00 +bitmap=00007803C0783C000007FC03C0783C00 +bitmap=00007803C0783C00000F1E03C0783C00 +bitmap=00007803C0783C00001E0F03C0783C00 +bitmap=0000F003C0783C00001E0F03C0783C00 +bitmap=0000F003C0783C00001E0F03C0783C00 +bitmap=0000F003C0783C00001E0F03C0783C00 +bitmap=0000F003C0783C00001E0F03C0783C00 +bitmap=0000F003E0783C00000F1E03E0783C00 +bitmap=0000F001F0783C00000FFE01F0783C00 +bitmap=0000F000F0783C000007FC00F0783C00 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +x=128 +y=32 +width=8 +[pilots] +pilot=200.0.0.113 +[200.0.0.113] +hostType=0 +advancedDamage=1 +loadzones=1 +name=cyd +bitmapindex=1 +experience=veteran +badge=VGL +patch=Yellow +role=Role::Default +dropzone=one +vehicle=madcat +vehicleValue=0 +color=White +[largebitmap] +bitmap=BitMap::Large::cyd +[BitMap::Large::cyd] +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=000000000000000000003C0000000000 +bitmap=000000000000000000003C0000000000 +bitmap=000000000000000000003C0000000000 +bitmap=000000000000000000003C0000000000 +bitmap=000000000000FCF00E0FBC0000000000 +bitmap=000000000003FE781C1FFC0000000000 +bitmap=000000000007FE781C3C7C0000000000 +bitmap=00000000000F82783C3C3C0000000000 +bitmap=00000000001F003C38783C0000000000 +bitmap=00000000001E003C78783C0000000000 +bitmap=00000000001E003C70783C0000000000 +bitmap=00000000001E003E70783C0000000000 +bitmap=00000000001E001EE0783C0000000000 +bitmap=00000000001E001EE0783C0000000000 +bitmap=00000000001F001FE0783C0000000000 +bitmap=00000000000F020FC07C7C0000000000 +bitmap=000000000007FE0FC03FFC0000000000 +bitmap=000000000003FE0F803FFC0000000000 +bitmap=000000000000FC07800F3C0000000000 +bitmap=00000000000000070000000000000000 +bitmap=00000000000000070000000000000000 +bitmap=000000000000000F0000000000000000 +bitmap=000000000000000E0000000000000000 +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000000000 +x=128 +y=32 +width=8 +[smallbitmap] +bitmap=BitMap::Small::cyd +[BitMap::Small::cyd] +bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000600000 +bitmap=00000000006000000000000000600000 +bitmap=000001EC33E000000000030666600000 +bitmap=000003066660000000000303C6600000 +bitmap=00000303C66000000000030186600000 +bitmap=000001E183E000000000000300000000 +bitmap=00000003000000000000000000000000 +x=64 +y=16 +width=4 +[Role::Default] +model=dfltrole +[Role::NoReturn] +model=noretun diff --git a/ALPHA_1/REL410/BT/TESTARN.EGG b/ALPHA_1/REL410/BT/TESTARN.EGG index f81d173..e254d63 100644 --- a/ALPHA_1/REL410/BT/TESTARN.EGG +++ b/ALPHA_1/REL410/BT/TESTARN.EGG @@ -156,70 +156,70 @@ x=128 y=32 width=8 [pilots] -pilot=200.0.0.96 -[200.0.0.96] +pilot=200.0.0.113 +[200.0.0.113] hostType=0 advancedDamage=1 loadzones=1 -name=Aeolus +name=cyd bitmapindex=1 -experience=expert +experience=veteran badge=VGL patch=Yellow role=Role::Default dropzone=one -vehicle=bhk1 -vehicleValue=1000 +vehicle=madcat +vehicleValue=0 color=White [largebitmap] -bitmap=BitMap::Large::Aeolus -[BitMap::Large::Aeolus] +bitmap=BitMap::Large::cyd +[BitMap::Large::cyd] bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 -bitmap=000003FE0000000001E0000000000000 -bitmap=000007FF0000000001E0000000000000 -bitmap=00000F8F8000000001E0000000000000 -bitmap=00000F078000000001E0000000000000 -bitmap=00000F0781FF00FF81E0F0781FC00000 -bitmap=00000F0783FF81FFC1E0F0783FE00000 -bitmap=00000F0787C7C3E3E1E0F0783C700000 -bitmap=00000F078783C3C1E1E0F07878300000 -bitmap=00000F078783C3C1E1E0F07878000000 -bitmap=00000FFF8783C3C1E1E0F0787C000000 -bitmap=00000FFF8783C3C1E1E0F0787F000000 -bitmap=00000F0787FFC3C1E1E0F0783FC00000 -bitmap=00000F0787FFC3C1E1E0F0781FE00000 -bitmap=00000F07878003C1E1E0F07807F00000 -bitmap=00000F07878003C1E1E0F07801F00000 -bitmap=00000F07878003C1E1E0F07800F00000 -bitmap=00000F078780C3C1E1E0F07860F00000 -bitmap=00000F0787C1C3E3E1E078F871E00000 -bitmap=00000F0783FF81FFC1E07FF83FE00000 -bitmap=00000F0781FF00FF81E03F781FC00000 -bitmap=00000000000000000000000000000000 -bitmap=00000000000000000000000000000000 -bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 +bitmap=000000000000000000003C0000000000 +bitmap=000000000000000000003C0000000000 +bitmap=000000000000000000003C0000000000 +bitmap=000000000000000000003C0000000000 +bitmap=000000000000FCF00E0FBC0000000000 +bitmap=000000000003FE781C1FFC0000000000 +bitmap=000000000007FE781C3C7C0000000000 +bitmap=00000000000F82783C3C3C0000000000 +bitmap=00000000001F003C38783C0000000000 +bitmap=00000000001E003C78783C0000000000 +bitmap=00000000001E003C70783C0000000000 +bitmap=00000000001E003E70783C0000000000 +bitmap=00000000001E001EE0783C0000000000 +bitmap=00000000001E001EE0783C0000000000 +bitmap=00000000001F001FE0783C0000000000 +bitmap=00000000000F020FC07C7C0000000000 +bitmap=000000000007FE0FC03FFC0000000000 +bitmap=000000000003FE0F803FFC0000000000 +bitmap=000000000000FC07800F3C0000000000 +bitmap=00000000000000070000000000000000 +bitmap=00000000000000070000000000000000 +bitmap=000000000000000F0000000000000000 +bitmap=000000000000000E0000000000000000 bitmap=00000000000000000000000000000000 bitmap=00000000000000000000000000000000 x=128 y=32 width=8 [smallbitmap] -bitmap=BitMap::Small::Aeolus -[BitMap::Small::Aeolus] -bitmap=00000000000000000000000000000000 -bitmap=0000000000000000003E00000C000000 -bitmap=006300000C00000000631F0F8CC63C00 -bitmap=00633198CCC66200007F3198CCC66000 -bitmap=00633F98CCC6780000633018CCC61E00 -bitmap=00633018CCC6060000633098CCC64600 -bitmap=00631F0F8C7E3C000000000000000000 +bitmap=BitMap::Small::cyd +[BitMap::Small::cyd] bitmap=00000000000000000000000000000000 +bitmap=00000000000000000000000000600000 +bitmap=00000000006000000000000000600000 +bitmap=000001EC33E000000000030666600000 +bitmap=000003066660000000000303C6600000 +bitmap=00000303C66000000000030186600000 +bitmap=000001E183E000000000000300000000 +bitmap=00000003000000000000000000000000 x=64 y=16 width=4 diff --git a/ALPHA_1/REL410/BT/cavern.egg b/ALPHA_1/REL410/BT/cavern.egg new file mode 100644 index 0000000..7e62762 Binary files /dev/null and b/ALPHA_1/REL410/BT/cavern.egg differ diff --git a/dpl3-revive/patha/vrboard.py b/dpl3-revive/patha/vrboard.py index bc96259..86a4338 100644 --- a/dpl3-revive/patha/vrboard.py +++ b/dpl3-revive/patha/vrboard.py @@ -106,6 +106,9 @@ class VirtualBoard: self.morphs = {} # morphed geom -> (a, b, alpha) self.names = {} # 0x8000xxxx name id -> handle (0x22) self.sfx = {} # SPECIALFX defs by code (0x1c) + self.psfx = [] # active PSFX effects (0x1d in + # MUNGA): list of decoded bursts + # = weapon bolts / muzzle / impacts self.dl2d = {} # 2D display lists (0x29/0x2b): handle -> [tag words] self._dl2d_acc = None # in-progress chunked 0x2b flush self.munga = False # game-build dialect (rpl4opt/btl4opt) @@ -147,11 +150,43 @@ class VirtualBoard: return self.do_add_connections(msg.payload) if msg.action == 0x1a: return self.do_texels(msg.payload) - if msg.action == 0x1d and len(msg.payload) >= 56: - # per-frame transform (shipped flush_artics): - # [dcs_type=1 matrix][dcs handle][3x3 row-major][tx ty tz] - h = struct.unpack_from('= 160: + # vr_psfx_action = dpl_Effect(particlestart|explosion, DCS, + # &info): a glowing particle burst. Wire = [DCS handle] + # [dpl_PARTICLESTART_EFFECT_INFO] (DPLTYPES.H), so struct + # field K is at word K+1: [5..7]=px,py,pz release position, + # [8]=pv, [9..11]=vel, [15]=rad, [17]=exp (expansion), + # [29..31]=start INTERIOR rgb, [32]=start alpha, [45..47]= + # end INTERIOR rgb. DCS!=0 -> emitter rides that projectile + # DCS (weapon bolt, px,py,pz is a ~0 local offset); DCS==0 + # -> one-shot WORLD-space burst at (px,py,pz) (muzzle flash, + # impact spark, explosion -- big rad). MUNGA articulates via + # 0x1f, so a >=160B 0x1d is always a psfx, never a transform. + n = len(p) // 4 + f = struct.unpack_from('<%df' % n, p) + dcs = struct.unpack_from(' 47 else (0.0, 0.0, 0.0) + rad = abs(f[15]) if n > 15 else 1.0 + exp = abs(f[17]) if n > 17 else 0.0 + alpha = min(1.0, abs(f[32])) if n > 32 else 1.0 + ttl = 30 if rad >= 3.0 else (14 if dcs == 0 else 48) + self.psfx.append({'dcs': dcs, 'pos': pos, 'col': col, + 'ecol': ecol, 'rad': rad, 'exp': exp, + 'alpha': alpha, 'ttl': ttl, 'maxttl': ttl, + 'age': 0}) + if len(self.psfx) > 256: # bound it; renderer expires + del self.psfx[:-256] + return b'' + if len(p) >= 56: + # FLYK per-frame transform (shipped flush_artics): + # [dcs_type=1 matrix][dcs handle][3x3 row-major][tx ty tz] + h = struct.unpack_from('= 8: # MUNGA batched flush_artics: [n_records] then n records of diff --git a/dpl3-revive/patha/vrview.py b/dpl3-revive/patha/vrview.py index 2e24682..3dc177b 100644 --- a/dpl3-revive/patha/vrview.py +++ b/dpl3-revive/patha/vrview.py @@ -32,6 +32,18 @@ TYPE = {2:'zone',3:'view',4:'instance',5:'dcs',6:'lmodel',7:'object',8:'lod', 0xe:'light'} +def inst_visible(nodes, inst): + """Draw-time gate for 'gated' (hidden-until-armed, w3 0/1) instances: + stored word 4 of the CURRENT instance body is the live visibility field + (dpl_SetInstanceVisibility). Checked per draw because a flush bumps no + node/edge counts, so the cache never rebuilds on a visibility pulse + (BT laser beams arm for ~4 frames per shot).""" + if not inst.get('gated'): + return True + b = nodes.get(inst['handle'], {}).get('body') or b'' + return len(b) >= 20 and struct.unpack_from(' field slices # 3 = [x y z] (vtype 0x01) @@ -688,7 +718,9 @@ class Renderer: d = d / dn best_t, best_h = np.inf, 0 for inst in self.cache.instances: - M = self.chain_matrix(board, inst['chain']) + if not inst_visible(board.nodes, inst): + continue + M = self.chain_matrix(board, inst['chain'], fix_degenerate=True) R, T = M[:3, :3], M[3, :3] for gh in inst['geoms']: mesh = self.cache._mesh(board, gh) @@ -789,9 +821,19 @@ class Renderer: FIX[:3, :3] = [[0, 0, -1], [0, 1, 0], [1, 0, 0]] for inst in c.instances: - Mw = self.chain_matrix(board, inst['chain']) + if not inst_visible(board.nodes, inst): + continue + # fix_degenerate: BTL4 vehicle rigs contain pass-through DCSs + # whose flushed rotation is rank-2 (shifted body variant); the + # laser-beam chain rides four of them and composed rank-2 = beam + # drawn away from the gun. Same treatment the camera chain gets. + Mw = self.chain_matrix(board, inst['chain'], fix_degenerate=True) # NOVIEWMATRIX instances live in camera space: no view transform M = Mw if inst.get('hud') else Mw @ V + # first-person canopy cull (see SceneCache radius note) + if (inst.get('gated') and not inst.get('hud') + and np.linalg.norm(M[3, :3]) + inst.get('radius', 0) < 10.0): + continue if inst['chain'] and inst['chain'][0] in board.anim: M = FIX @ M if inst['billboard']: diff --git a/dpl3-revive/patha/vrview_gl.py b/dpl3-revive/patha/vrview_gl.py index 6dd3918..f0f2ea8 100644 --- a/dpl3-revive/patha/vrview_gl.py +++ b/dpl3-revive/patha/vrview_gl.py @@ -463,8 +463,16 @@ class GLRenderer(vrview.Renderer): FIX[:3, :3] = [[0, 0, -1], [0, 1, 0], [1, 0, 0]] for inst in c.instances: - Mw = self.chain_matrix(board, inst['chain']) + if not vrview.inst_visible(board.nodes, inst): + continue + # fix_degenerate: see vrview draw loop (rank-2 rig DCSs skewed + # the laser beam off the gun) + Mw = self.chain_matrix(board, inst['chain'], fix_degenerate=True) M = Mw if inst.get('hud') else Mw @ V + # first-person canopy cull (see SceneCache radius note in vrview) + if (inst.get('gated') and not inst.get('hud') + and np.linalg.norm(M[3, :3]) + inst.get('radius', 0) < 10.0): + continue if inst['chain'] and inst['chain'][0] in board.anim: M = FIX @ M if inst['billboard']: @@ -488,6 +496,7 @@ class GLRenderer(vrview.Renderer): self._draw_geom(board, c, gh, mesh, M, Mw) self._draw_particles(board, V, vp, self.skip / 60.0) + self._draw_psfx(board, V) self._draw_hud2d_gl(board, vp) # present: FBO -> window with the DAC gamma @@ -686,3 +695,82 @@ class GLRenderer(vrview.Renderer): self._points_vao(data).render(mgl.POINTS, vertices=len(data)) self._fbo.depth_mask = True ctx.disable(mgl.BLEND) + + def _draw_psfx(self, board, V): + """Triggered PSFX (0x1d) as glowing additive discs, two kinds: + + - DCS-attached bolts (weapon fire): the emitter rides the flying + projectile DCS, so the glow tracks the shot. We keep a short trail of + recent world positions and draw a fading tail, so the continuous + particle stream (rate ~300/s) reads as a streak, not a lone dot. The + bolt lives until the projectile DCS is deleted (impact). + - DCS=0 world bursts (muzzle flash, impact spark, explosion): a fixed + world-space burst at (px,py,pz); big-radius explosions expand and the + colour lerps from the start interior toward the end interior over the + burst's ttl, then it fades out.""" + if not board.psfx: + return + cache = {} + rows, live = [], [] + for e in board.psfx: + dcs = e['dcs'] + if dcs: + nd = board.nodes.get(dcs) + if not nd or nd.get('type') != 5: + continue # projectile consumed (impact) + m = cache.get(dcs) + if m is None: + try: + m = self.chain_matrix(board, self._chain(board, dcs, links=True)) + except Exception: + m = self.dcs_matrix(board, dcs) + cache[dcs] = m + pos = np.asarray(m[3, :3], float) + else: + e['ttl'] -= 1 + if e['ttl'] <= 0: + continue + pos = np.asarray(e['pos'], float) + if not np.all(np.isfinite(pos)): + continue + e['age'] += 1 + live.append(e) + frac = e['age'] / max(e['maxttl'], 1) # 0..1 over life + col = np.clip((1.0 - frac) * np.array(e['col']) + + frac * np.array(e['ecol']), 0.0, 2.0) + a0 = min(e['alpha'], 1.0) + if dcs: # bolt + trail + tr = e.setdefault('trail', []) + tr.append(pos) + if len(tr) > 9: + del tr[0] + nt = len(tr) + for k, tp in enumerate(tr): + w = (k + 1) / nt # head brightest + rows.append([tp[0], tp[1], tp[2], + max(e['rad'] * (0.35 + 0.65 * w), 1.2), + col[0], col[1], col[2], a0 * w * w]) + else: # world burst + grow = 1.0 + min(e['exp'], 12.0) * 0.09 * frac # explosions swell + fade = min(1.0, e['ttl'] / 10.0) + rows.append([pos[0], pos[1], pos[2], max(e['rad'] * grow, 1.5), + col[0], col[1], col[2], a0 * fade]) + board.psfx = live + if not rows: + return + data = np.array(rows, 'console runs use a pcap-enabled build (net_full.conf). +ne2000=true +nicbase=340 +nicirq=3 +backend=slirp +[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). +serial1=directserial realport:COM1 rxpollus:100 rxburst:16 +serial2=directserial realport:COM2 +[autoexec] +mount c "C:\VWE\TeslaRel410\ALPHA_1" +mount d "C:\VWE\TeslaRel410\emulator\net-boot" +d: +echo === loading NE2000 packet-driver stack (as production AUTOEXEC did) === +d:\lsl +d:\ne2000 +d:\odipkt +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), RIO attached, +rem bare -egg launch (no netnub) = the user's real-world test-egg setup. +set HEAPSIZE=15000000 +set L4GAUGE=640x480x16 +call setenv.bat r s s p +32rtm.exe -x +rem stdout -> log: this optimized build still emits DEBUG_STREAM lines (the +rem RIO retry spam proved it) -- capture the weapon fire-refusal reason. +rem DOS flushes in 4KB chunks; the tail lands on clean exit (mission timer). +btl4opt.exe -egg testarn.egg > c:\weaponlog.txt +32rtm.exe -u +echo ALPHA1-RUN-DONE +pause diff --git a/emulator/render-bridge/gauge_arena_sound.conf b/emulator/render-bridge/gauge_arena_sound.conf index d17f2a2..c4dd4a1 100644 --- a/emulator/render-bridge/gauge_arena_sound.conf +++ b/emulator/render-bridge/gauge_arena_sound.conf @@ -24,9 +24,9 @@ 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 COM2 later. +# few-ms ACK deadline is met; plasma display on COM2 (real pod has both). serial1=directserial realport:COM1 rxpollus:100 rxburst:16 -serial2=disabled +serial2=directserial realport:COM2 [autoexec] mount c "C:\VWE\TeslaRel410\ALPHA_1" c: @@ -48,7 +48,7 @@ rem arena1 city mission (TESTARN.EGG: map=arena1, time=day) with the RIO rem attached; stdout redirected so mission-load progress survives kills. set HEAPSIZE=15000000 set L4GAUGE=640x480x16 -call setenv.bat r s s g +call setenv.bat r s s p 32rtm.exe -x btl4opt.exe -egg testarn.egg 32rtm.exe -u diff --git a/emulator/render-bridge/launch_pod.ps1 b/emulator/render-bridge/launch_pod.ps1 index 8c573be..1d80e96 100644 --- a/emulator/render-bridge/launch_pod.ps1 +++ b/emulator/render-bridge/launch_pod.ps1 @@ -57,6 +57,7 @@ if ($NoSound) { } else { $env:VWE_AWE32 = '1' $env:VWE_AWE_ROM = (Resolve-Path "$PSScriptRoot\..\roms\awe32.raw").Path + $env:VWE_AWE_LOG = '1' # 10s reports incl. limiter stats (crackle diagnosis) } $dosbox = (Resolve-Path "$PSScriptRoot\..\src\src\dosbox-x.exe").Path diff --git a/emulator/render-bridge/live_bridge.py b/emulator/render-bridge/live_bridge.py index 229f360..d569c7d 100644 --- a/emulator/render-bridge/live_bridge.py +++ b/emulator/render-bridge/live_bridge.py @@ -85,6 +85,7 @@ def fp_cam(board, cache): h = max(anim, key=lambda k: float(np.abs(np.array(anim[k][9:12])).sum())) if h is None: return None + fp_cam.root = h # player vehicle root DCS (for the pick backchannel) f = anim[h] R = np.array(f[:9]).reshape(3, 3) t = np.array(f[9:12]) @@ -145,6 +146,26 @@ def fp_cam(board, cache): M[0, :3], M[1, :3], M[2, :3], M[3, :3] = right, up, back, eye return M +def send_cam(M): + # Backchannel on the fifosock: hand the device OUR camera (the + # user-validated cockpit view, twist + glance included) so its reticle + # raycast aims where the player actually looks. The device's own view + # decode sits at the static view-node pose, which aimed every pick -- + # and thus every missile volley and laser beam -- at one fixed wrong + # world point (the "missiles fly off to a fixed spot" bug). + if sock is None: + return + eye, fwd = M[3, :3], -M[2, :3] + # 7th field: player vehicle root DCS -- the device skips instances on + # this articulation subtree (own arms/torso/muzzle-flash), which the ray + # grazes intermittently and which retargeted mid-volley missiles. + root = getattr(fp_cam, 'root', 0) or 0 + try: + sock.send(f"CAM {eye[0]:.3f} {eye[1]:.3f} {eye[2]:.3f} " + f"{fwd[0]:.6f} {fwd[1]:.6f} {fwd[2]:.6f} {root:x}\n".encode()) + except OSError: + pass + def render(board): try: pg = r.pygame @@ -161,6 +182,7 @@ def render(board): M = fp_cam(board, r.cache) if M is not None: r.cam_matrix = lambda _b, _M=M: _M + send_cam(M) r.draw(board) except KeyboardInterrupt: raise diff --git a/emulator/vpx-device/vpxlog.cpp b/emulator/vpx-device/vpxlog.cpp index 04a8305..ab8b7ae 100644 --- a/emulator/vpx-device/vpxlog.cpp +++ b/emulator/vpx-device/vpxlog.cpp @@ -265,13 +265,83 @@ static void fifo_sock_write(const unsigned char *p, size_t n) { } else { fifo_sock_drop(); return; } } } + +/* bridge -> device backchannel on the same socket. The bridge streams its + * camera pose (the user-validated cockpit view: cam chain + torso twist + + * hat glance) as text lines: CAM ex ey ez dx dy dz\n + * raycast_pick prefers this over the device's own S.view decode -- S.view + * tracks the static view-node pose (~arena origin, not the player's aim), + * which pointed every reticle pick, and therefore every missile volley and + * laser beam, at one fixed wrong world point. */ +static char brcam_line[192]; +static size_t brcam_len = 0; +static float brcam[6]; +static bool brcam_valid = false; +static DWORD brcam_tick = 0; +static unsigned brcam_root = 0; /* player vehicle root DCS (7th CAM field) */ + +static void fifo_sock_poll(void) { + if (fifo_csock == INVALID_SOCKET) return; + char buf[512]; + for (;;) { + int r = recv(fifo_csock, buf, (int)sizeof buf, 0); + if (r == 0) { fifo_sock_drop(); return; } /* client closed */ + if (r < 0) { + if (WSAGetLastError() != WSAEWOULDBLOCK) fifo_sock_drop(); + return; + } + for (int i = 0; i < r; i++) { + char ch = buf[i]; + if (ch == '\n') { + brcam_line[brcam_len] = 0; + float v[6]; unsigned rt = 0; + int nf = sscanf(brcam_line, "CAM %f %f %f %f %f %f %x", + &v[0], &v[1], &v[2], &v[3], &v[4], &v[5], &rt); + if (nf >= 6) { + memcpy(brcam, v, sizeof brcam); + brcam_valid = true; + brcam_tick = GetTickCount(); + brcam_root = (nf >= 7) ? rt : 0; + } + brcam_len = 0; + } else if (brcam_len + 1 < sizeof brcam_line) { + brcam_line[brcam_len++] = ch; + } else { + brcam_len = 0; /* oversize: resync */ + } + } + } +} #else static bool fifo_sock_active(void) { return false; } static void fifo_sock_init(int) {} static void fifo_sock_accept(void) {} static void fifo_sock_write(const unsigned char *, size_t) {} +static void fifo_sock_poll(void) {} #endif +/* Latest bridge camera, if fresh: the pick must not outlive a dead bridge + * by more than a couple of seconds (stale aim = the fixed-point bug again, + * just slower), so fall back to the device view after 2s of silence. */ +static bool bridge_cam_get(float O[3], float D[3]) { +#ifdef _WIN32 + if (brcam_valid && (GetTickCount() - brcam_tick) < 2000) { + O[0] = brcam[0]; O[1] = brcam[1]; O[2] = brcam[2]; + D[0] = brcam[3]; D[1] = brcam[4]; D[2] = brcam[5]; + return true; + } +#endif + return false; +} + +static unsigned bridge_cam_root(void) { +#ifdef _WIN32 + return brcam_root; +#else + return 0; +#endif +} + static void fifo_buf_push(unsigned char v) { if (fifo_dump_fp == NULL && !vpx_render && !fifo_sock_active()) return; if (fifo_buf_len >= (1u << 20)) return; /* runaway guard */ @@ -295,6 +365,7 @@ static void fifo_flush_record(void) { } if (fifo_sock_active()) { fifo_sock_accept(); + fifo_sock_poll(); /* drain bridge CAM backchannel */ fifo_sock_write(hdr, sizeof hdr); fifo_sock_write(fifo_buf, fifo_buf_len); } @@ -2132,9 +2203,18 @@ static float ray_tri(const float O[3], const float D[3], * geogroup -> geometry -> polys, transformed by the DCS world matrix. */ static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out, unsigned *geom_out, float xyz_out[3]) { - if (!S.view.has_cam) return false; - const float O[3] = { S.view.eye[0], S.view.eye[1], S.view.eye[2] }; - float D[3] = { -S.view.rot[6], -S.view.rot[7], -S.view.rot[8] }; /* cam fwd */ + /* Ray = the player's actual look: prefer the bridge's camera (cam chain + * + torso twist + hat glance, streamed back over the fifosock). The + * device's own S.view decode tracks the static view-node pose, so with + * it every pick -- and every missile/beam -- aimed at one fixed wrong + * world point. Fallback keeps weapons firing bridge-less. */ + float O[3], D[3]; + const bool from_bridge = bridge_cam_get(O, D); + if (!from_bridge) { + if (!S.view.has_cam) return false; + O[0] = S.view.eye[0]; O[1] = S.view.eye[1]; O[2] = S.view.eye[2]; + D[0] = -S.view.rot[6]; D[1] = -S.view.rot[7]; D[2] = -S.view.rot[8]; + } float dl = sqrtf(D[0]*D[0] + D[1]*D[1] + D[2]*D[2]); if (dl < 1e-6f) return false; D[0] /= dl; D[1] /= dl; D[2] /= dl; @@ -2153,6 +2233,36 @@ static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out M16 world; dcs_world(di->first, cache, world); for (size_t ii = 0; ii < di->second.size(); ii++) { unsigned inst = di->second[ii]; + /* Skip hidden-until-armed instances (w3 0/1: laser beams, + * missile models, effects). Picking them is poison: the beam + * stretches to the pick point, the next pick hits the BEAM at + * that point and locks the target there (feedback loop); a + * volley's own missiles get picked and homed on; and a picked + * missile whose DCS deletes on impact hands the game a stale + * handle (the arena2 freeze). The reticle resolves the WORLD: + * terrain, buildings, vehicles. */ + std::map::const_iterator wi = + S.inst_w3.find(inst); + if (wi != S.inst_w3.end() && wi->second != 2 && wi->second != 3) + continue; + /* Never pick the player's OWN articulation subtree (arms, torso, + * muzzle flash, searchlight): the ray grazes own geometry from + * the over-the-shoulder eye and a mid-volley pick of the muzzle + * flash retargets the salvo ("missiles hare off sometimes"). + * Root DCS comes from the bridge's CAM line, 7th field. */ + unsigned own_root = bridge_cam_root(); + if (own_root) { + unsigned a = di->first; + bool own = false; + for (int hop = 0; hop < 64; hop++) { + if (a == own_root) { own = true; break; } + std::map::const_iterator ppi = + S.dcs_parent.find(a); + if (ppi == S.dcs_parent.end() || ppi->second == a) break; + a = ppi->second; + } + if (own) continue; + } std::map::const_iterator oi = S.inst_object.find(inst); if (oi == S.inst_object.end()) continue; @@ -2210,8 +2320,10 @@ static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out if ((best_inst != last_i || best_dcs != last_d) && vpx_fp) { flush_run(); fprintf(vpx_fp, "# raycast pick: inst=%08X dcs=%08X gg=%08X t=%.1f " - "pos(%.1f,%.1f,%.1f)\n", best_inst, best_dcs, best_gg, best_t, - xyz_out[0], xyz_out[1], xyz_out[2]); + "pos(%.1f,%.1f,%.1f) cam=%s eye(%.1f,%.1f,%.1f)\n", + best_inst, best_dcs, best_gg, best_t, + xyz_out[0], xyz_out[1], xyz_out[2], + from_bridge ? "bridge" : "view", O[0], O[1], O[2]); fflush(vpx_fp); last_i = best_inst; last_d = best_dcs; } diff --git a/emulator/vpx-device/vweawe.cpp b/emulator/vpx-device/vweawe.cpp index fed6f99..391baf3 100644 --- a/emulator/vpx-device/vweawe.cpp +++ b/emulator/vpx-device/vweawe.cpp @@ -22,8 +22,10 @@ * from the emulation thread: the real cards were autonomous silicon, so * sustained voices keep sounding even while the emulation thread stalls * (e.g. RIO retry storms during mission staging). Both cards are summed - * into one stereo stream (headset); per-card host routing for the real - * 4-speaker cockpit comes later (see SOUND-NOTES.md). + * into one stereo stream (headset) through a peak limiter (the raw voice + * accumulator routinely exceeds int16; hard-clamping it was the "generator + * out" speech crackle -- the SBK samples themselves are clean); per-card + * host routing for the real 4-speaker cockpit comes later (SOUND-NOTES.md). * * Environment (host-side, like the VPX device): * VWE_AWE32=1 enable the device (inert otherwise) @@ -217,15 +219,28 @@ static volatile LONG awe_stop = 0; #define AWE_SLOTS 28 /* absolute pool size */ static WAVEHDR awe_hdr[AWE_SLOTS]; static int16_t awe_slotbuf[AWE_SLOTS][AWE_SLOT_FRAMES * 2]; -static int16_t awe_cardbuf[2][AWE_SLOT_FRAMES * 2]; +static int32_t awe_cardbuf[2][AWE_SLOT_FRAMES * 2]; + +/* Output limiter (always on). The EMU8000 core sums its 32 voices into a raw + * int32 accumulator; a busy mix (speech over the engine loop, weapon hits) + * easily exceeds int16 and the old hard clamp flat-topped it -- the user's + * "generator out" crackle (baseline WAV from the SBK proved the sample itself + * is clean). Instead of clipping, duck: instant attack to keep the summed + * peak under AWE_LIM_TARGET, ~46ms release back to unity. Stereo-linked so + * the image doesn't wander. The safety clamp after it should never engage. */ +#define AWE_LIM_TARGET 32000 +static int32_t awe_lim_gain = 1 << 16; /* Q16, <= 1.0 */ +static unsigned long awe_lim_engaged = 0; /* frames ducked (diagnostic) */ +static int awe_prelim_peak[2]; /* per-card pre-limit |peak| */ static void awe_render_chunk(emu8k_t *card, int ci, unsigned n) { wavetable_pos_global = (int)n; emu8k_update(card); for (unsigned i = 0; i < n * 2; i++) { int32_t v = card->buffer[i] >> awe_shift; - if (v > 32767) v = 32767; else if (v < -32768) v = -32768; - awe_cardbuf[ci][i] = (int16_t)v; + awe_cardbuf[ci][i] = v; /* full-range; limited at sum */ + if (v < 0) v = -v; + if (v > awe_prelim_peak[ci]) awe_prelim_peak[ci] = v; } memset(card->buffer, 0, n * 2 * sizeof(int32_t)); memset(card->chorus_in_buffer, 0, n * sizeof(int32_t)); @@ -264,27 +279,55 @@ static DWORD WINAPI awe_thread_proc(LPVOID) { awe_last_render_qpc = now.QuadPart; LeaveCriticalSection(&awe_lock); int16_t *out = awe_slotbuf[free_slot]; - for (unsigned i = 0; i < AWE_SLOT_FRAMES * 2; i++) { - int32_t v = (int32_t)awe_cardbuf[0][i] + awe_cardbuf[1][i]; - if (v > 32767) v = 32767; else if (v < -32768) v = -32768; - out[i] = (int16_t)v; - int a0 = awe_cardbuf[0][i] < 0 ? -awe_cardbuf[0][i] - : awe_cardbuf[0][i]; - int a1 = awe_cardbuf[1][i] < 0 ? -awe_cardbuf[1][i] - : awe_cardbuf[1][i]; - if (a0 > peak[0]) peak[0] = a0; - if (a1 > peak[1]) peak[1] = a1; + for (unsigned i = 0; i < AWE_SLOT_FRAMES; i++) { + int32_t l = awe_cardbuf[0][i * 2] + awe_cardbuf[1][i * 2]; + int32_t r = awe_cardbuf[0][i * 2 + 1] + awe_cardbuf[1][i * 2 + 1]; + /* stereo-linked peak limiter: instant attack, ~46ms release */ + int32_t pk = l < 0 ? -l : l; + int32_t pr = r < 0 ? -r : r; + if (pr > pk) pk = pr; + if (pk > AWE_LIM_TARGET) { + int32_t need = (int32_t)(((int64_t)AWE_LIM_TARGET << 16) / pk); + if (need < awe_lim_gain) awe_lim_gain = need; + } + if (awe_lim_gain < (1 << 16)) { + awe_lim_engaged++; + l = (int32_t)(((int64_t)l * awe_lim_gain) >> 16); + r = (int32_t)(((int64_t)r * awe_lim_gain) >> 16); + /* release: exponential toward unity, min step 1 so it + * actually GETS there (>>11 alone stalls 2048 short) */ + int32_t step = ((1 << 16) - awe_lim_gain) >> 11; + awe_lim_gain += step ? step : 1; + if (awe_lim_gain > (1 << 16)) awe_lim_gain = 1 << 16; + } + if (l > 32767) l = 32767; else if (l < -32768) l = -32768; + if (r > 32767) r = 32767; else if (r < -32768) r = -32768; + out[i * 2] = (int16_t)l; + out[i * 2 + 1] = (int16_t)r; } + if (awe_prelim_peak[0] > peak[0]) peak[0] = awe_prelim_peak[0]; + if (awe_prelim_peak[1] > peak[1]) peak[1] = awe_prelim_peak[1]; + awe_prelim_peak[0] = awe_prelim_peak[1] = 0; WAVEHDR *h = &awe_hdr[free_slot]; memset(h, 0, sizeof(WAVEHDR)); h->lpData = (LPSTR)out; h->dwBufferLength = AWE_SLOT_FRAMES * 4; waveOutPrepareHeader(awe_wo, h, sizeof(WAVEHDR)); waveOutWrite(awe_wo, h, sizeof(WAVEHDR)); - if (awe_dump_front) - fwrite(awe_cardbuf[0], 4, AWE_SLOT_FRAMES, awe_dump_front); - if (awe_dump_rear) - fwrite(awe_cardbuf[1], 4, AWE_SLOT_FRAMES, awe_dump_rear); + if (awe_dump_front || awe_dump_rear) { + /* dumps stay s16le: clamp the raw int32 card stream per file */ + static int16_t dump16[AWE_SLOT_FRAMES * 2]; + for (int ci = 0; ci < 2; ci++) { + FILE *df = ci ? awe_dump_rear : awe_dump_front; + if (!df) continue; + for (unsigned i = 0; i < AWE_SLOT_FRAMES * 2; i++) { + int32_t v = awe_cardbuf[ci][i]; + if (v > 32767) v = 32767; else if (v < -32768) v = -32768; + dump16[i] = (int16_t)v; + } + fwrite(dump16, 4, AWE_SLOT_FRAMES, df); + } + } for (int ci = 0; ci < 2; ci++) { frames[ci] += AWE_SLOT_FRAMES; if (awe_log_on > 0 && frames[ci] - last_report[ci] >= 441000) { @@ -294,11 +337,12 @@ static DWORD WINAPI awe_thread_proc(LPVOID) { for (int i = 0; i < 32; i++) if (card->voice[i].cvcf_curr_volume) voices++; fprintf(stderr, "VWE AWE32%c 10s: wr=%lu wcRd=%lu rd=%lu " - "smldW=%lu voices=%d peak=%d\n", ci ? 'R' : 'F', + "smldW=%lu voices=%d peak=%d limF=%lu\n", ci ? 'R' : 'F', awe_stats[ci].wr, awe_stats[ci].rd_wc, awe_stats[ci].rd, awe_stats[ci].smld_w, - voices, peak[ci]); + voices, peak[ci], awe_lim_engaged); peak[ci] = 0; + if (ci) awe_lim_engaged = 0; } } }