Files
TeslaRel410/emulator/NET-NOTES.md
T
CydandClaude Fable 5 9536d4cbc0 NET: full networked mission runs end-to-end; fix blank VDB heads (NIC/VDB port clash)
Console queues a mission -> egg over the wire -> pod loads + runs it -> all
cockpit heads render -> mission ends on the console timer. Four fixes:

1. ne2000.cpp: don't BX_PANIC on the TCR inhibit-CRC / auto-tx-disable bits.
   A full production boot runs the packet driver's internal loopback
   self-test, which sets those bits; Bochs' NE2000 aborted the whole
   emulator. Record them in TCR state instead (harmless for an emulated NIC;
   pcap/host does framing+CRC). Also fixes a latent coll_prio bit-4 vs bit-3
   round-trip bug. Committed copy in vpx-device/.

2. Confirmed the mission-load page fault (00FF:219D) was RIO-OFF; booting
   with the RIO live (net_full.conf) loads and runs the mission cleanly.

3. net_loop.conf + net-boot/loop.bat: GO.BAT-style loop so the pod stays
   connected to the console and picks up missions as they're queued (the
   real pod relaunches netnub after each mission/idle disconnect).

4. HEADLINE: blank MFD/radar heads were an I/O port collision. The VDB
   video splitter is hardwired at 0x300-0x31A (palettes 0x300/0x308/0x310);
   the NE2000 at nicbase=300 swallowed the game's VDB palette writes ->
   vdb_pal stayed zero -> pal_draw decoded every head to black. Same VDB
   spam corrupted NIC RX, dropping the console EndMission (mission overran
   its timer). Fix: move the NIC to 0x340 (DOSBox nicbase=340 + DOS NET.CFG
   PORT 340; must agree). VDB keeps 0x300. Config-only, no rebuild.

Adds: net_full.conf, net_loop.conf, net-boot/ (ODI drivers, NET.CFG@340,
loop.bat, README), vpx-device/{ne2000,ethernet_pcap}.cpp; updates NET-NOTES.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 21:18:42 -05:00

24 KiB
Raw Blame History

Networking subsystem — recon & bring-up plan

Goal: bring the pod network up under the emulator so a pod boots the production way (netnub -f BTL4OPT, mission egg delivered over the wire) instead of the -egg test.egg dev bypass. This is also the substrate the ops-console port will plug into.

What the pod network actually is (recon 2026-07-04)

The stack, bottom to top:

  1. Ethernet NIC + Novell ODI (AUTOEXEC.BAT): lsl (Link Support Layer) → lnepci (Lance/PCnet PCI ODI driver) → odipkt (Dan Lanciani's ODI→Packet-Driver shim). The ODI/Netware login side (IPXODI/VLM/NET LOGIN in STARTNET.BAT) is for file-server access; the game itself only needs the packet-driver interface odipkt exposes. NET.CFG: LNEPCI, FRAME Ethernet_II + 802.2.
  2. WATTCP — the TCP/IP stack. Confirmed by WATTCP.CFG in REL410/BT, REL410/RP, and per-pod VGL_LABS/THISPOD:
    my_ip = 200.0.0.113   netmask = 255.255.255.0
    gateway = 200.0.0.1   nameserver = 200.0.0.1
    
    So each pod is a static host on an isolated 200.0.0.0/24 LAN; the ops console is almost certainly 200.0.0.1.
  3. NetNub (netnub.exe, real-mode) — launches the game as a child (netnub -f BTL4OPT) and is the network server for the protected-mode game. Interface (NetNub/NETNUB.HPP): a shared Netcom struct (version 11, 64KB buffer) + a software interrupt. The game sets a Function code (TCP_OPEN=3, TCP_LISTEN=4, TCP_CLOSE=5, RESOLVE_ADDRESS=6, CHECK_SOCKET=7, UDP_*, plus remote file OPEN/READ/WRITE/SEEK/CLOSE 12-19), copies the marked fields to real mode, INTs, copies back. tcp_Socket is ~4300 bytes = classic WATTCP.
  4. L4NetworkManager (L4NET.HPP/.TCP) — the game's net brick. The console is master and connects to the pods; the pod receives ReceiveEggFileMessage (the mission egg), replies AcknowledgeEggFileMessage ("connected, ready, send the next host"), and tracks HostConnected/HostDisconnected. If the console drops, the pod is built to auto-start anyway.

Topology to replicate:

   [ops console 200.0.0.1] --TCP--> [pod 200.0.0.113]  (+ more pods .114..)
        (master, egg source)          (listens, ACKs, runs mission)

Emulator enablers (already in the fork)

  • NE2000 ISA NIC emulated (hardware/ne2000.cpp, Bochs-derived); config [ne2000] ne2000=true, nicbase=, nicirq=, macaddr=, backend=.
  • Two Ethernet backends built: misc/ethernet_pcap.cpp (bridge to a host NIC via npcap) and misc/ethernet_slirp.cpp (user-mode virtual net / NAT). Plus ethernet_nothing.

Key simplification: the emulated card is an NE2000, not a PCI Lance, so lnepci won't bind. We don't need the Novell ODI chain at all — WATTCP finds a packet driver by scanning INT 0x60-0x80 for the PKT DRVR signature, so we load a generic NE2000 packet driver (Crynwr NE2000.COM) directly against the emulated card's base/IRQ. That drops lsl/lnepci/odipkt/VLM entirely and hands NetNub/WATTCP the packet interface they expect.

Bring-up plan

Backend choice. Two viable paths:

  • pcap + host-only adapter (recommended, matches real topology): bridge the NE2000 to a host virtual switch; run the pod at 200.0.0.113 and the stand-in console at 200.0.0.1 on that segment. WATTCP's static IP + LAN assumptions hold exactly; the console connects inbound to the pod with no NAT. Cost: npcap + a host-only/loopback adapter + admin.
  • slirp (fallback, self-contained): no host NIC/admin, but it's NAT and defaults to 10.0.2.0/24 — the pod LISTENS, so inbound needs slirp host-forwarding and a guest-network/IP reconciliation with WATTCP's hard-coded 200.0.0.113. Investigate whether DOSBox-X slirp allows the custom net + static guest IP + inbound forward cleanly.

Milestones

  1. NIC up: [ne2000] on, NE2000.COM packet driver loaded, WATTCP/ NetNub start clean; pod boots via netnub -f BTL4OPT (no -egg) and sits waiting for the console. Verify NetNub reports net address 200.0.0.113 and a TCP_LISTEN is queued. (New scratch conf, mirror the RIO/sound conf pattern.)
  2. L3/L4 reachability: from the host segment, confirm the pod answers ARP/ping at 200.0.0.113 and a raw TCP connect to its listen port completes (proves NE2000↔backend↔host path end-to-end).
  3. Decode the console→pod egg protocol: NetworkPacketHeader + message framing from network.hpp/hostmgr.hpp + the ReceiveEggFileMessage layout, cross-checked with a live capture of the pod's listen/ACK. (Pin the listen port here — not yet found in source; grep NETNUB.EXE strings / capture.)
  4. Eggs over the wire: a minimal host-side stand-in console (Python) connects to the pod, pushes a mission egg, handles the ACK → pod runs the mission with no -egg bypass. This is the headline goal.
  5. (later, joins the console-port workstream): replace the stand-in with the ported/emulated Mac ops console; multi-pod coordination (HostConnected/Disconnected, mission review, camera ship).

Milestone 1 — DONE (2026-07-04): pod boots on the network path

Verified end to end under DOSBox-X (slirp backend), no -egg bypass:

  • [ne2000] ne2000=true, nicbase=300, nicirq=3, backend=slirp → NE2000 emulated at Base=0x300 irq=3; slirp 4.9.1 initialized.
  • The Novell ODI chain works against the emulated NE2000, no external packet driver needed: lslne2000 (Novell/Eagle NE2000 MLID v1.53, from NWCLIENT) → odipkt (FTP Software ODI packet driver). ODIPKT installed at SINT 0x60, MLID NE2000, MAC CE:3D:72:67:38:69, frames Ethernet_II (board 1) + 802.2 (board 2).
  • GOTCHA: the ODI tools read NET.CFG from the directory the .COM loads from, and the stock NWCLIENT\NET.CFG says Link Driver LNEPCI — with no NE2000 section the MLID defaults to 802.2-only and odipkt fails ("An MLID could not be found"). Fix without touching ALPHA_1: keep an emulator NET.CFG (Link Driver NE2000 + FRAME Ethernet_II) beside copies of lsl/ne2000/odipkt on a scratch drive and load from there.
  • netnub -f btl4opt (no egg) launches the game as btl4opt -net 250224, sets up the game↔netnub channel at INT 0x61 (separate from odipkt's 0x60), initializes the network manager ("Changing blocking from 0 to 1"), and the game boots through the VPX handshake to an open (blank) render window — waiting for a console to deliver a mission egg.

Working scratch files: scratchpad/net_stageB.conf, scratchpad/net/{NET.CFG,LSL.COM,NE2000.COM,ODIPKT.COM}. Launch env: VPXLOG + VPX_RESPOND=1 + VPX_RENDER=1 (VPX board must answer or the game exits before networking).

Console side: a Mac emulator stands in for the ops console (user, 2026-07-04)

The user is building a Mac emulator running the real 410console as the console peer (instead of a from-scratch Python stand-in). This merges the networking and console-port workstreams: the real console software will connect to the pod and push eggs. Implication for topology — two separate emulators (DOSBox pod + Mac console) must share an L2 segment, which slirp (NAT, per-process isolation) cannot bridge. Plan: move the pod's NE2000 to backend=pcap on a host-only/loopback adapter; bridge the Mac emulator to the same adapter; pod=200.0.0.113, console=200.0.0.1 on 200.0.0.0/24. Then milestone 3 (protocol) can be captured live from the real console traffic rather than reverse-engineered blind.

Console emulator = SheepShaver (2026-07-04)

The real ops console is a Power Macintosh 6100/66 (PowerPC 601) → emulate with SheepShaver (PPC Mac, Mac OS 7.5.29.0.4). Basilisk II (68k) is out. GOTCHA: the 6100's OWN ROM does NOT work in SheepShaver ("Unsupported ROM type" — SheepShaver emulates a PCI 9500; the 6100 is NuBus). Use a compatible old-world PPC ROM instead (7100/66, 7500, 7600, or 8500) — PPC apps are Toolbox/OS-based, not ROM-specific, so the 410console app runs regardless. Target Mac OS 7.5.57.6.1 (console era). Networking: SheepShaver TAP ↔ DOSBox-X NE2000 pcap, both bridged to a host adapter on 200.0.0.0/24 (console .1, pod .113).

Console 4.10 OS compatibility (from the binary, 2026-07-04): classic PowerPC PowerPlant/CodeWarrior app; PEF imports are only InterfaceLib, MathLib, ObjectSupportLib. NO AppearanceLib (⇒ does NOT need Mac OS 8) and NO Open Transport libs (⇒ classic MacTCP API). Resource fork has the startup check "TCP/IP not installed. Install either MacTCP or OpenTransport…" via Gestalt/SysEnvirons — so it accepts either stack. → Runs System 7.1.2Mac OS 9.0.4; practical floor 7.5 (ObjectSupportLib ships with 7.5). Recommended SheepShaver target: System 7.5.5 / 7.6.1 (period-correct for a 6100, includes Open Transport 1.1.x so the TCP/IP Gestalt check passes, most stable under SheepShaver w/ a 7100/7500/8500 old-world ROM). Set TCP/IP control panel to 200.0.0.1.

Leverage the real 6100 (user has it): (1) image its hard drive → get the exact console software + OS + MacTCP/OpenTransport config → drop into SheepShaver for a faithful reproducible console; (2) fastest path to a first egg + LIVE protocol capture = put the real 6100 on a physical Ethernet with the pcap-bridged pod (needs an AAUI→RJ45 transceiver) and capture the console→pod egg exchange off the wire (hands us milestone 3). Sequence: real 6100 first (seeds image + capture) → SheepShaver as the archival console built from that image.

Console software EXTRACTED + protocol port FOUND (2026-07-04)

The user unstuffed 410consoleArchive.sit (via infinitemac.org) to 4_10extractedConsole/. Contents: Console 4.10 (the app), per-site config, fonts, logs. This means the dead 6100 is NOT a blocker — we have the console software directly; run it in SheepShaver (no disk image needed; fresh Mac OS 7.6.1/8.1 + MacTCP set to 200.0.0.1).

  • App is PowerPC — data fork magic Joy!peffpwpc (PEF/PowerPC), Metrowerks CodeWarrior 1993-95; 3.4MB resource fork. Confirms SheepShaver (needs standard shared libs: InterfaceLib/MathLib, present in any 7.5+ install).
  • Console.ini is the master config: [NetworkEndpoint::Cockpit::*] sections define every pod. THE TCP PORT IS 1501 (defaultPort / localHostPort) — this answers the milestone-3 "listen port unknown" question. Console connects to each pod IP:1501; pod LISTENS on 1501.

Cockpit endpoint roster (base Console.ini) — our emulated pod = "Puck" 200.0.0.113:

cockpit IP hostType
Frequent Flyer 200.0.0.11 (sic) 0
Privateer 200.0.0.112 0
Puck 200.0.0.113 0
Carpe Diem 200.0.0.114 0
Man O' War 200.0.0.115 0
Divine Wind 200.0.0.116 0
Icarus 200.0.0.117 0
Gypsy 200.0.0.118 0
Alpha Mission Review 200.0.0.119 2
Alpha Camera 200.0.0.120 2

hostType 0 = playable cockpit, 2 = special (mission-review / camera ship). ini Folder/ holds real per-venue configs (DBAtlanta/Chicago/Houston/ Toronto/LaZerPark/... — the actual VWE centers), same .11x/1501 scheme.

Revised topology: SheepShaver console @200.0.0.1 → TCP 200.0.0.113:1501 → pod "Puck". Remaining protocol unknown is just the on-stream message framing (NetworkPacketHeader + ReceiveEggFileMessage) — capture it once the console connects, or read it from network.hpp.

Egg-delivery protocol — decoded from source (2026-07-04)

Decoded from CODE/RP/MUNGA/{NETWORK,RECEIVER,HOSTID}.HPP + MUNGA_L4/L4NET.HPP. The real send/receive implementation (framing on the byte stream) did NOT survive in the archive — only headers + a test harness (L4NET.TCP's TestClass, #if 0). So the logical message layout below is solid; two low-level details (stream framing + endianness) need a live capture or a binary disasm to pin — see caveats.

Transport: console → TCP connect to pod IP : 1501 → the pod (NetNub TCP_LISTEN) accepts. All base types are 32-bit (Enumeration=int, size_t, LWord, Time::ticks=long → 4 bytes each).

On-wire unit = NetworkPacket = NetworkPacketHeader + a Receiver::Message.

NetworkPacketHeader (16 bytes):

off field type
0 clientID ClientID enum (0=NetworkMgr,2=HostMgr,5=Console...)
4 gameID Enumeration
8 fromHost HostID (Enumeration)
12 timeStamp Time (long ticks)

Receiver::Message header (12 bytes) that every message starts with:

off field type
0 messageLength size_t (= sizeof the whole message)
4 messageID Enumeration (ReceiveEggFileMessageID etc.)
8 messageFlags LWord (bit0 ReliableFlag=1)

ReceiveEggFileMessage (the egg carrier; messageLength = 1024):

off field type
0 (Receiver::Message header) 12 B
12 sequenceNumber int (chunk index)
16 notationFileLength int (total egg size)
20 thisMessageLength int (bytes valid in this chunk, ≤1000)
24 notationData[1000] char (the egg chunk)

So a full egg packet on the wire = 16 (header) + 1024 (message) = 1040 bytes — matches the nb≤1040 payload cap seen on the VPX/iserver link. Egg-delivery algorithm: the console splits the mission egg (a text "notation file", same INI/notation format as Console.ini) into ceil(len/1000) chunks; sends each as a ReceiveEggFileMessage with sequenceNumber 0..N, notationFileLength=total, thisMessageLength≤1000. The pod's ReceiveEggFileMessageHandler reassembles into eggTempBuffer by sequence, and when notationFileLength bytes have arrived, parses it as the mission notation file. Pod replies AcknowledgeEggFileMessage ("connected, ready, next host"). messageID values start at NetworkClient::NextMessageID; ReceiveEggFileMessageID is the first NetworkManager message ID.

Caveats to confirm with the first live capture (or a binary disasm of Console 4.10 send / BTL4OPT receive):

  1. Stream framing: whether each 1040-B NetworkPacket is one discrete NetNub/WATTCP record, or the receiver frames within the TCP stream via the leading messageLength. (NetNub RECEIVE_PACKET returns up to 1600 B; MAX aligns with one packet.)
  2. Endianness: console is big-endian PPC, pod is little-endian x86 — the multi-byte header/length ints must be byte-swapped by one side (or sent in network order). notationData (egg text) is endian-agnostic. The capture will show which order the length fields use; a stand-in sender must match it.

Everything needed to PARSE a capture and BUILD a stand-in egg-sender is here except those two bytes-on-the-wire details, which the console-connect milestone resolves immediately.

Console VM standup (user, 2026-07-04): SheepShaver + OS 9.0.4 + old-world ROM

Valid combo (old-world ROMs support the full 7.5.29.0.4 range); OS 9's Open Transport provides the MacTCP-API compat the app's Gestalt check wants, so Console 4.10 runs. Networking initially set to "Basilisk II Router" — NOTE that's NAT (assigns a 10.x IP, routes to host), fine for building/updating the VM but it CANNOT reach the pod at 200.0.0.113. For the console↔pod link both must be bridged (TAP) on one L2 segment: console SheepShaver TCP/IP = manual 200.0.0.1 / 255.255.255.0 on a TAP; pod DOSBox-X moves off backend=slirp to backend=pcap bound to the same TAP/bridge (200.0.0.113 already via WATTCP). Then the console's outbound TCP to 200.0.0.113:1501 reaches the pod and we capture the egg exchange (resolves the framing + endianness caveats above).

MILESTONE: real console <-> real pod talking over the wire (2026-07-05)

The emulated SheepShaver console and the emulated DOSBox pod now exchange the live console protocol on TCP 1501. Hard-won setup (all required):

  1. DOSBox-X pcap backend had to be rebuilt + npcap DLL path. config.h had C_PCAP 1 but the stale ethernet.o predated it; force-recompile (rm src/misc/ethernet.o ethernet_pcap.o; make). Runtime: npcap installs its DLLs in C:\Windows\System32\Npcap\ (npcap-only mode), NOT System32, so DOSBox couldn't load wpcap.dll -> launch dosbox-x.exe with C:\Windows\System32\Npcap prepended to PATH.
  2. Two-TAP Windows bridge (single shared TAP fails: SheepShaver holds the user-mode handle, DOSBox rides NDIS, frames don't cross). Console SheepShaver -> TAP1 (ether tap + etherguid); pod -> TAP2; both bridged.
  3. TAP2 media status = Always-Connected (registry MediaStatus=1 on the TAP2 class key, needs elevation; the GUI toggle didn't persist). Without it TAP2 reports "unplugged" and the bridge won't forward to it.
  4. Pod pcap binds to the BRIDGE MINIPORT, not a member TAP. Injecting on a bridge member (TAP2) isn't re-forwarded by the bridge (the pod TX'd but the console never saw it). Bind DOSBox realnic to the bridge miniport = "Microsoft Network Adapter Multiplexor Driver". GOTCHA: DOSBox realnic matches a substring of the pcap device NAME (\Device\NPF_{GUID}) and parses a leading-digit value as an interface index -- the bridge GUID 5DB5521D... starts with 5 -> picked iface #5 (Bluetooth!). Use a letter-leading fragment: realnic=DB5521D.
  5. Diagnostics: added PCAP TX/RX counters to ethernet_pcap.cpp (SendPacket/GetPackets) -- confirmed the pod WAS transmitting all along; the bridge forwarding was the only gap. Capture the wire with dumpcap -i <bridge#> -f "arp or tcp port 1501" -w cap.pcapng.

Verified protocol on the wire (little-endian, matches the source decode):

  • Console->pod StateQuery (32B): hdr clientID=4(App) gameID=0 fromHost=1 ts=.. ; msg len=16 id=3 flags=0 ; body requestingHost=1.
  • Pod->console StateResponse (40B): hdr clientID=5 (ConsoleClientID) gameID=0 fromHost=0 ts=.. ; msg len=24 id=1 flags=1 (Reliable) ; body respondingHostID=0, applicationState=1, application=1 (BTL4/BattleTech). (My earlier synthetic guess had clientID=4/flags=0/host=1/state=0 -- all wrong, which is why the real console rejected the stand-in. These are the correct values for pod_responder.py.)
  • Console polls StateQuery ~every 2s; pod ACKs + StateResponse each time. Clean 3-way handshake; zero-length "dup ACKs" are benign keepalives.

OPEN: with this working, the console still hadn't enabled mission-send in the stripped-down (keyboard-only, no-RIO, no-sound) pod boot -- retrying with a full RIO+sound boot (net_full.conf) so the pod presents its normal state (applicationState may change when fully booted). Need the console UI readout to know what state it wants before it will send the mission egg.

EGG DELIVERED OVER THE WIRE (2026-07-05) — headline goal achieved

The real SheepShaver console sent a real mission egg to the real DOSBox pod over the emulated network, captured + reassembled byte-perfect:

  • Console -> pod, TCP 1501, 8x ReceiveEggFileMessage chunks (1040-B NetworkPackets each = 16 hdr + 1024 msg), 7514 bytes total, declared notationFileLength=7514 (exact match). Reassembled with net-tools/decode_egg.py; saved net-tools/captured_cavern_mission.egg.
  • The egg is a text NOTATION file (same format as Console.ini). Contents = the operator's actual mission: [mission] adventure=BattleTech map=cavern scenario=freeforall time=night weather=clear temperature=27 length=120; [pilots] pilot=200.0.0.113; [200.0.0.113] name=cyd vehicle=madcat experience=veteran badge=VGL dropzone=one color=Grey advancedDamage=1 role=Role::Default + embedded pilot badge bitmaps + ordinal fonts.
  • Confirms the whole decoded protocol end to end: StateQuery/StateResponse poll, then egg chunks, pod ACKs, no -egg bypass. Networking DONE.

BUT the pod CRASHES loading the mission (game-side, NOT network): nn.log shows Reference to a page you don't own / PF cr2=FF008B5B / Unhandled exception 000E at 00FF:219D ErrCode 0004 / NETNUB Munga exited code 14 -- a page fault (wild pointer 0xFF008B5B) in the mission-load path right after the egg arrives. Suspects: (a) RIO was DISABLED for this run (net_pcap.conf) and the mission sets up the player's vehicle (madcat) / controls, which may deref a null RIO/control struct; (b) missing cavern content or a pilot-config/badge path bug. Note: cavern/night via -egg test.egg with RIO ENABLED rendered fine earlier this session, so RIO-off is the leading suspect. Real pods run RIO-on headless (no focus stealing), so this may not reproduce there. NEXT: retry with RIO enabled + DOSBox priority=highest,highest (so clicking the console can't starve the RIO's ACK deadline during load); if it still faults, disassemble around game code 00FF:219D. This joins the Division-renderer/crash workstream.

FULL NETWORKED MISSION WORKS END TO END (2026-07-05, later) — all fixed

A complete networked mission now runs start to finish: console queues a mission -> egg over the wire -> pod loads it -> RIO live -> all cockpit heads render -> mission ends on the console timer. Four fixes got here:

  1. Mission-load crash was RIO-OFF (confirmed). Booting with the RIO enabled (net_full.conf: serial1 COM1 rxpollus:100 rxburst:16, full RIO+sound production boot) — the page fault at 00FF:219D is gone and the mission loads + runs. Real pods run RIO-on, so this was the whole thing.

  2. NE2000 BX_PANIC fixed in the emulator. A full production boot runs the packet driver's internal loopback self-test, which sets the NIC's TCR inhibit-CRC bit; Bochs' ne2000.cpp BX_PANIC'd and aborted the whole emulator. Patched the TCR write handler to record crc_disable/ext_stoptx instead of panicking (harmless for an emulated NIC; pcap/host frames+CRCs). Committed copy: vpx-device/ne2000.cpp.

  3. Pod persistence via GO.BAT loop. When the console has no mission queued, the pod connects, sees nothing, and cleanly exits (not a crash) — the real pod's GO.BAT immediately relaunches netnub. net_loop.conf + net-boot/loop.bat replicate that so the pod stays connected and ready.

  4. HEADLINE FIX — blank cockpit heads = NE2000/VDB I/O port collision. All 5 MFD + radar heads decoded to pure black during a live mission even though the DOSBox SVGA gauge framebuffer was FULL (captured mid-mission: SENSOR CLUSTER/MYOMERS/SRM 4/mech wireframes/pilot name). Root cause: the VDB video splitter board is hardwired at 0x300-0x31A (palettes 0x300/0x308/0x310; VDB_BASE in vpxlog.cpp) and we'd put the NE2000 at nicbase=300 — the NIC swallowed the game's VDB palette writes, so vdb_pal stayed zero and pal_draw mapped every index to black. The VDB spam into 0x300-0x31A also corrupted NIC registers, dropping the console's EndMission -> the mission overran its timer (second symptom, same cause). FIX (config-only): move the NIC to 0x340 in the DOSBox conf (nicbase=340) AND the DOS NET.CFG (PORT 340) — both must agree; the game uses odipkt so it's base-agnostic. VDB keeps 0x300 (game hardwires it; real pod's NIC lived elsewhere for this reason). Non-networked gauge runs never hit this (no NIC). Verified: heads render perfectly (radar with contact blips + SPEED/HEADING/ARMOR + mission clock; MFDs full) AND the mission ends cleanly on the timer (Sending EndMission, score 1000). Diagnostic recipe: New-Item <VPX_DUMPDIR>\DUMP mid-mission dumps win0/3/4.bmp; PrintWindow (flag 2, GL) the DOSBox SVGA window to prove the framebuffer has content; framebuffer-full + heads-black => palette/port.

Committed: net_full.conf, net_loop.conf, net-boot/ (drivers, NET.CFG @340, loop.bat, README), vpx-device/ne2000.cpp, vpx-device/ethernet_pcap.cpp.

Open questions / notes

  • Exact TCP listen port(s) — not in the source grep; get from NETNUB.EXE or a capture at milestone 3.
  • Does WATTCP need a real ARP peer for the gateway at boot, or does it proceed with a static IP and only ARP on connect? Affects whether the stand-in console must answer ARP for 200.0.0.1.
  • NETCLIENT=PNW (PARAMETR.bat) selects Personal NetWare — file-server side, not the game's TCP path; likely irrelevant to egg delivery and can stay unloaded under emulation.
  • RP uses the identical MUNGA net brick + its own WATTCP.CFG — everything here carries over to Red Planet.