Note: I validated this playing on a 2.17.0 server, and playing netrek without seeing anyone else's torps is suboptimal. I delegated the writeup, so it is more comprehensive that I would have been on my own. I'm guessing that this behavior was not encountered before because most of the clients implemented short packets long before full weapon resolution was incorporated, but future new clients will likely hit it if they delay implementation of short packets and get full weapon resolution working earlier in development.
BTW: Thanks for adding PROTOCOL.MD. It is helpful.
Summary
Two client-negotiated options — the FULL_WEAPON_RESOLUTION feature (CP_FEATURE) and
short/variable packets (CP_S_REQ / SP_S_REPLY) — are negotiated independently and the
server accepts either without reference to the other. However, the torpedo emitter code
assumes that FULL_WEAPON_RESOLUTION implies short packets: with the feature on,
updateTorps() skips every torp that is not the client's own, on the stated assumption that
those torps will be carried by SupdateTorps() instead — but SupdateTorps() is only ever
called inside the send_short branch of the per-update dispatch.
The consequence is that a client which enables FULL_WEAPON_RESOLUTION but does not enable
short packets receives no torpedo packets at all for any player other than itself. Other
players' torpedoes exist server-side and damage and kill normally, but are never transmitted,
so they are invisible to that client. Nothing in the negotiation reports this: the feature
reply is a plain accept, and no warning, error, or degraded reply indicates that a category of
gameplay-critical data will be withheld for the life of the connection.
This is deterministic from the code path, not timing-dependent, and it is reachable by any
client that reads the feature list and enables an advertised feature without also implementing
short packets — a plausible position for a new or third-party client, since short packets are a
substantial separate protocol layer.
Trigger conditions
All of the following, and nothing else, are required:
- The server's feature file advertises
FULL_WEAPON_RESOLUTION as a server-side ('S') feature
with value ON. The shipped sample does this: docs/sample_features:79 —
FULL_WEAPON_RESOLUTION S ON.
- The client sends a
CP_FEATURE request for FULL_WEAPON_RESOLUTION with value == 1.
- The client never sends
CP_S_REQ with SPK_VON (or sends SPK_VOFF), so send_short
remains 0. send_short is 0 by default (ntserv/data.c:213, int send_short = 0;).
- The client is a normal player, i.e.
me->p_status != POBSERV. Observers are not
affected — see the observer special case below.
There is no ordering requirement between (2) and (3); the state is read fresh on every update.
Mechanism
1. The feature is accepted with no precondition
ntserv/feature.c:62 registers the feature against the server flag:
{"FULL_WEAPON_RESOLUTION", &F_full_weapon_resolution, NULL},
getFeature() (ntserv/feature.c:175) sets the flag directly from the client's requested
value, with no reference to send_short or to any other feature (ntserv/feature.c:212-221):
for(i=0; feature_vars[i].name ; i++){
if(feature_cmp(cpack->name, feature_vars[i].name)) {
if(feature_vars[i].var){
if(cpack->value == 1)
*feature_vars[i].var = (spack->value == 1);
handleFeature() (ntserv/socket.c:2149-2166) calls getFeature() and returns the reply
immediately; there is no validation stage and no deferred check. The reply the client receives
is an ordinary accept with value 1. The default is off: ntserv/data.c:400,
int F_full_weapon_resolution = 0;.
2. updateTorps() drops all non-own torps when the flag is set
ntserv/genspkt.c:1400 updateTorps(). Own torps (and observers) are emitted first
(:1414-1419):
if (myTorp(t)
|| (F_full_weapon_resolution && me->p_status == POBSERV)
) {
sndTorp(tpi, tp, t, i, UPDT_ALL);
continue;
}
Everything else is then discarded outright when the flag is on, before any of the in-view /
out-of-view logic runs (:1421-1427):
/*
* Not my torp.
* If not in view, only send limited information
* Full weapon resolution torps are skipped, they are instead
* sent via SupdateTorps */
if (F_full_weapon_resolution)
continue;
The comment is the design assumption made explicit: non-own torps are expected to be picked up
by SupdateTorps(). Note also that this continue precedes the UPDT_LITTLE path used to
signal TFREE/out-of-view, so not even torp-teardown information is sent.
The observer case at :1415 short-circuits before the skip, so an observer with the feature on
and short packets off still receives all torps at UPDT_ALL. The failure is specific to normal
players.
3. SupdateTorps() is unreachable without short packets
ntserv/genspkt.c:1926 SupdateTorps() is the short-packet torp emitter, and it is the
mirror image of the above — it skips the client's own torps when the feature is on
(:1969-1973):
if (torp->t_owner==me->p_no) {
/* With full weapon resolution, self torps sent via
updateTorps */
if (F_full_weapon_resolution)
continue;
So the two emitters are designed as a matched pair, each covering what the other skips. But the
per-update dispatch in updateClient() (ntserv/socket.c:611-642) only ever pairs them inside
the short-packet branch:
if(send_short) {
updatePlasmas();
updateSelf(FALSE);
updatePhasers();
updateShips();
if (F_full_weapon_resolution) {
SupdateTorps();
updateTorps();
}
else
SupdateTorps();
...
} else {
updateTorps();
updatePlasmas();
...
}
The else branch — the one taken when send_short == 0 — calls updateTorps() and never
SupdateTorps(). SupdateTorps() has no other call site. With F_full_weapon_resolution == 1
and send_short == 0, non-own torps are skipped by updateTorps() at genspkt.c:1426 and
there is no second emitter to pick them up. They are never written to either the TCP or the UDP
buffer, in any update, for the duration of the connection or until the client turns the feature
off or short packets on.
4. How a client reaches send_short == 0
send_short is only assigned in handleShortReq() (ntserv/socket.c:3114), on CP_S_REQ:
SPK_VOFF sets it to 0 (:3120), SPK_VON sets it to 2 or 1 depending on the client's
declared short-protocol version (:3136-3139), and a version mismatch on SPK_VON is rejected
back to SPK_VOFF (:3126-3131). A client that never sends CP_S_REQ at all simply keeps the
data.c:213 default of 0. PROTOCOL.md:437 states this expectation directly: "The server
defaults to old fixed-size packets (send_short starts 0 in ntserv/socket.c); short/variable
packets are strictly opt-in via CP_S_REQ/CP_FEATURE, so a client that never negotiates them
… is never sent one."
Scope of affected data
Read against the emitters, the withholding is confined to torpedoes:
- Torpedoes, other players' — withheld. As above. Both position/direction updates
(SP_TORP) and status/war info (SP_TORP_INFO) are produced by the same sndTorp() calls
that are skipped, so no torp state of any kind is emitted for other players.
- Torpedoes, own — sent normally, via the
myTorp(t) branch at genspkt.c:1414.
- Plasma torpedoes — not affected.
updatePlasmas() (genspkt.c:1445) references
F_full_weapon_resolution only in the observer special case at :1454-1456; it has no
equivalent skip for non-own plasmas, and there is no SupdatePlasmas() anywhere in the tree.
updatePlasmas() is called in both dispatch branches (socket.c:612 and :633), so plasmas
are emitted normally in all combinations.
- Phasers — not affected.
updatePhasers() (genspkt.c:1481) uses the flag only to widen
the view test for observers (:1501).
- Ships, planets, self, status, messages, player stats — not affected; none of these
emitters is gated on F_full_weapon_resolution in a way that removes data, and all are called
in both branches.
So the affected set is exactly "torpedoes owned by players other than the receiving client, for
a non-observer client."
Silence and existing guards
- The
CP_FEATURE reply is a normal accept. No new_warning() or other diagnostic is emitted
at negotiation time or at any point afterward.
- There is no re-check when the state later becomes inconsistent.
handleShortReq()'s
SPK_VOFF path (socket.c:3119-3123) sets send_short = 0 without consulting
F_full_weapon_resolution, so a client that had both enabled can turn short packets off and
enter the same state mid-game, again silently.
- The server does emit a diagnostic for an analogous dependency elsewhere in the same function:
SPK_SALL and SPK_ALL respond "Activate SHORT Packets first!" when send_short is 0
(socket.c:3177-3195). No comparable check exists for the feature.
- I could not find the dependency documented anywhere outside the code.
PROTOCOL.md does not
mention FULL_WEAPON_RESOLUTION at all. docs/README.SHORT describes SupdateTorps() and
the short-packet update ordering but predates the feature and does not mention it.
docs/sample_features:79 lists the feature with no annotation. NEWS:561-562 and NEWS:656
record its introduction ("Full weapon resolution, reduce bandwidth use", "Add full weapon
resolution exception to UDP packet size check", "FULL_WEAPON_RESOLUTION [balcerski]") without
stating a prerequisite. The only statement of the dependency in the tree is the code comment
at genspkt.c:1424-1425.
- The dependency is therefore neither documented in the protocol material nor enforced at
negotiation time; it exists only as an assumption inside the emitter pair.
Nothing here is timing- or load-dependent: the branch is evaluated from two boolean globals on
every update, so the outcome is fully determined by the negotiated state.
Server revision examined
- Tree:
netrek-server (vanilla), local read-only checkout.
- Git HEAD:
9d31c99dc220331b941b887b9bebd7dc18497bba — "Add client-to-server protocol test
harness (join, outfit, orbit)", 2026-07-12.
- Most recent release marker in
ChangeLog: netrek-server-vanilla-2.19.0 released
(2016-12-06, James Cameron).
- Files cited:
ntserv/feature.c, ntserv/data.c, ntserv/genspkt.c, ntserv/socket.c,
include/data.h, docs/sample_features, docs/README.SHORT, PROTOCOL.md, NEWS.
Line numbers are from this revision.
Note: I validated this playing on a 2.17.0 server, and playing netrek without seeing anyone else's torps is suboptimal. I delegated the writeup, so it is more comprehensive that I would have been on my own. I'm guessing that this behavior was not encountered before because most of the clients implemented short packets long before full weapon resolution was incorporated, but future new clients will likely hit it if they delay implementation of short packets and get full weapon resolution working earlier in development.
BTW: Thanks for adding PROTOCOL.MD. It is helpful.
Summary
Two client-negotiated options — the
FULL_WEAPON_RESOLUTIONfeature (CP_FEATURE) andshort/variable packets (
CP_S_REQ/SP_S_REPLY) — are negotiated independently and theserver accepts either without reference to the other. However, the torpedo emitter code
assumes that
FULL_WEAPON_RESOLUTIONimplies short packets: with the feature on,updateTorps()skips every torp that is not the client's own, on the stated assumption thatthose torps will be carried by
SupdateTorps()instead — butSupdateTorps()is only evercalled inside the
send_shortbranch of the per-update dispatch.The consequence is that a client which enables
FULL_WEAPON_RESOLUTIONbut does not enableshort packets receives no torpedo packets at all for any player other than itself. Other
players' torpedoes exist server-side and damage and kill normally, but are never transmitted,
so they are invisible to that client. Nothing in the negotiation reports this: the feature
reply is a plain accept, and no warning, error, or degraded reply indicates that a category of
gameplay-critical data will be withheld for the life of the connection.
This is deterministic from the code path, not timing-dependent, and it is reachable by any
client that reads the feature list and enables an advertised feature without also implementing
short packets — a plausible position for a new or third-party client, since short packets are a
substantial separate protocol layer.
Trigger conditions
All of the following, and nothing else, are required:
FULL_WEAPON_RESOLUTIONas a server-side ('S') featurewith value ON. The shipped sample does this:
docs/sample_features:79—FULL_WEAPON_RESOLUTION S ON.CP_FEATURErequest forFULL_WEAPON_RESOLUTIONwithvalue == 1.CP_S_REQwithSPK_VON(or sendsSPK_VOFF), sosend_shortremains 0.
send_shortis 0 by default (ntserv/data.c:213,int send_short = 0;).me->p_status != POBSERV. Observers are notaffected — see the observer special case below.
There is no ordering requirement between (2) and (3); the state is read fresh on every update.
Mechanism
1. The feature is accepted with no precondition
ntserv/feature.c:62registers the feature against the server flag:{"FULL_WEAPON_RESOLUTION", &F_full_weapon_resolution, NULL},getFeature()(ntserv/feature.c:175) sets the flag directly from the client's requestedvalue, with no reference to
send_shortor to any other feature (ntserv/feature.c:212-221):handleFeature()(ntserv/socket.c:2149-2166) callsgetFeature()and returns the replyimmediately; there is no validation stage and no deferred check. The reply the client receives
is an ordinary accept with value 1. The default is off:
ntserv/data.c:400,int F_full_weapon_resolution = 0;.2.
updateTorps()drops all non-own torps when the flag is setntserv/genspkt.c:1400updateTorps(). Own torps (and observers) are emitted first(
:1414-1419):Everything else is then discarded outright when the flag is on, before any of the in-view /
out-of-view logic runs (
:1421-1427):The comment is the design assumption made explicit: non-own torps are expected to be picked up
by
SupdateTorps(). Note also that thiscontinueprecedes theUPDT_LITTLEpath used tosignal
TFREE/out-of-view, so not even torp-teardown information is sent.The observer case at
:1415short-circuits before the skip, so an observer with the feature onand short packets off still receives all torps at
UPDT_ALL. The failure is specific to normalplayers.
3.
SupdateTorps()is unreachable without short packetsntserv/genspkt.c:1926SupdateTorps()is the short-packet torp emitter, and it is themirror image of the above — it skips the client's own torps when the feature is on
(
:1969-1973):So the two emitters are designed as a matched pair, each covering what the other skips. But the
per-update dispatch in
updateClient()(ntserv/socket.c:611-642) only ever pairs them insidethe short-packet branch:
The
elsebranch — the one taken whensend_short == 0— callsupdateTorps()and neverSupdateTorps().SupdateTorps()has no other call site. WithF_full_weapon_resolution == 1and
send_short == 0, non-own torps are skipped byupdateTorps()atgenspkt.c:1426andthere is no second emitter to pick them up. They are never written to either the TCP or the UDP
buffer, in any update, for the duration of the connection or until the client turns the feature
off or short packets on.
4. How a client reaches
send_short == 0send_shortis only assigned inhandleShortReq()(ntserv/socket.c:3114), onCP_S_REQ:SPK_VOFFsets it to 0 (:3120),SPK_VONsets it to 2 or 1 depending on the client'sdeclared short-protocol version (
:3136-3139), and a version mismatch onSPK_VONis rejectedback to
SPK_VOFF(:3126-3131). A client that never sendsCP_S_REQat all simply keeps thedata.c:213default of 0.PROTOCOL.md:437states this expectation directly: "The serverdefaults to old fixed-size packets (
send_shortstarts 0 inntserv/socket.c); short/variablepackets are strictly opt-in via
CP_S_REQ/CP_FEATURE, so a client that never negotiates them… is never sent one."
Scope of affected data
Read against the emitters, the withholding is confined to torpedoes:
(
SP_TORP) and status/war info (SP_TORP_INFO) are produced by the samesndTorp()callsthat are skipped, so no torp state of any kind is emitted for other players.
myTorp(t)branch atgenspkt.c:1414.updatePlasmas()(genspkt.c:1445) referencesF_full_weapon_resolutiononly in the observer special case at:1454-1456; it has noequivalent skip for non-own plasmas, and there is no
SupdatePlasmas()anywhere in the tree.updatePlasmas()is called in both dispatch branches (socket.c:612and:633), so plasmasare emitted normally in all combinations.
updatePhasers()(genspkt.c:1481) uses the flag only to widenthe view test for observers (
:1501).emitters is gated on
F_full_weapon_resolutionin a way that removes data, and all are calledin both branches.
So the affected set is exactly "torpedoes owned by players other than the receiving client, for
a non-observer client."
Silence and existing guards
CP_FEATUREreply is a normal accept. Nonew_warning()or other diagnostic is emittedat negotiation time or at any point afterward.
handleShortReq()'sSPK_VOFFpath (socket.c:3119-3123) setssend_short = 0without consultingF_full_weapon_resolution, so a client that had both enabled can turn short packets off andenter the same state mid-game, again silently.
SPK_SALLandSPK_ALLrespond"Activate SHORT Packets first!"whensend_shortis 0(
socket.c:3177-3195). No comparable check exists for the feature.PROTOCOL.mddoes notmention
FULL_WEAPON_RESOLUTIONat all.docs/README.SHORTdescribesSupdateTorps()andthe short-packet update ordering but predates the feature and does not mention it.
docs/sample_features:79lists the feature with no annotation.NEWS:561-562andNEWS:656record its introduction ("Full weapon resolution, reduce bandwidth use", "Add full weapon
resolution exception to UDP packet size check", "FULL_WEAPON_RESOLUTION [balcerski]") without
stating a prerequisite. The only statement of the dependency in the tree is the code comment
at
genspkt.c:1424-1425.negotiation time; it exists only as an assumption inside the emitter pair.
Nothing here is timing- or load-dependent: the branch is evaluated from two boolean globals on
every update, so the outcome is fully determined by the negotiated state.
Server revision examined
netrek-server(vanilla), local read-only checkout.9d31c99dc220331b941b887b9bebd7dc18497bba— "Add client-to-server protocol testharness (join, outfit, orbit)", 2026-07-12.
ChangeLog:netrek-server-vanilla-2.19.0 released(2016-12-06, James Cameron).
ntserv/feature.c,ntserv/data.c,ntserv/genspkt.c,ntserv/socket.c,include/data.h,docs/sample_features,docs/README.SHORT,PROTOCOL.md,NEWS.Line numbers are from this revision.