53 Commits
Author SHA1 Message Date
MrBruh 9f89f9253d Merge pull request #322 from eltanschauung/feature/coop-extra-pickups-and-vehicles
Add extra pickups and vehicles options for co-op hosts
2026-10-20 01:57:38 +13:00
MrBruh 1081f981fc Merge pull request #390 from eltanschauung/fix/coop-elevator-riders-upstream
Fix co-op elevator riders falling through floors
2026-10-19 21:16:09 +13:00
eltanschauung 1b03880209 Keep co-op elevator riders aligned through reversals
Carry riders by the current collision transform rather than the previous tick's movement. Preserve supported riders' height through stopped-device predictions and delayed client echoes without suppressing normal jumps, departures or authoritative teleports. Local history only; network messages and protocol version are unchanged.

Validation on upstream main: 379 CPU/menu/assembly tests, including 32 focused elevator cases and negative controls; all three changed C files compile for Windows x86. Earlier native integrated-fork probes passed on b40/c40 at simulated 100/200 ms RTT through reversals and phase snaps.

(cherry picked from commit a38b7db0f4eb4ccc66f5445a6e9c4eef2fb0114a)
2026-10-17 04:17:09 -04:00
Paul Hontz 956387d89f Netcode: a client deletes the host's flag or ball on its word without halting
A client on the Steam Frame, in an internet game on carousel, stopped on the
game's exception screen: "objects.c #829: !(weapon_is_flag(object_index))".
The host's object changes had told it to delete a weapon that on the client
is a flag or an oddball (weapon_is_flag reads the weapon tag's "must be
readied" bit, which in the maps only those two have), and
distributed_client_delete deleted it as it deletes any of the host's
objects. The game never deletes a flag or ball while a game type runs, and
asserts so twice: in object_delete_initial_recursive and in weapon_delete.
A release build's assertions only note it in debug.txt, so a release host
can delete one and tell its clients; a client built without --release then
halts, flat or VR alike.

A client follows the host's word: a flag or ball it deletes so is let go of
first (distributed_client_release_item takes it out of its carrier's hands,
as any weapon of the host's), the goal its game type showed at it is cleared
(game_engine_objective_weapon_deleted: CTF's flag and carrier goals,
oddball's ball goal), and the two assertions are not made for an object
distributed_client_delete deletes (network_objects_client_deleting). The
host and the game's own deletions keep them.

On spark, a host told its clients that each flag (one in the client's
player's hands) or the ball was deleted, as a host deleting one does, and
of it again the next tick. Before, a flat client and a VR client halted on
the same assertion as the Frame, in CTF and in oddball; now neither does,
the client's player lets go of the flag, the flag comes back when the host
tells of it again, host and client count the same items, and the player
is killed and respawns after it. A slayer game of deaths and respawns plays
as before.

Files:
  port/linux/game/network_objects.c
  source/objects/objects.c
  source/items/weapons.c
  source/game/game_engine.c, source/game/game_engine.h
  port/linux/NETCODE.md
2026-10-13 23:09:14 -04:00
eltanschauung 1f47875802 Add extra weapon and pickup options for co-op hosts
Offer Per Player, Static Multiplier and None for authored campaign weapons, grenades, health kits, overshields and camouflage. Share amount rows with extra enemies, cap pickups at 8x, retain ammunition and checkpoint counts, and only place copies on clear nearby ground. Use ordinary host-created replicated objects without changing the wire protocol.

(cherry picked from commit 6f3a75507690a03382759f0f1b70c13e38abc851)
2026-10-12 12:43:46 -04:00
eltanschauung 68f0d2d303 Add extra vehicle options for co-op hosts
Offer Per Player, Static Multiplier and None. Register authored campaign vehicles, anchor delivered cargo after ground contact, and replace player-driven vehicles after both bounding spheres clear the home with a 15 percent buffer. Keep host authority, pad collision checks, allocation limits and checkpoint budgets. Replicate ordinary vehicles without changing the wire protocol.

(cherry picked from commit 75abf60ed676b0b5b8a20de4f82068c9a3ba281f)
2026-10-12 12:37:39 -04:00
MrBruh f735ca152d First-person legs: one mesh kept, its waist forgotten on a new map (#243) 2026-10-11 12:11:51 +13:00
MrBruh 03d054aa6e Merge pull request #243 from jcompto95-tech/first-person-legs 2026-10-11 12:06:18 +13:00
justinfilip af7ad15d3c Keep a respawned vehicle on the name its teleporter uses.
The timer was leaving the named vehicle on the pad and creating another on top of it. The home now follows that named vehicle, and a pad that already has one does not get a second.
2026-10-10 13:25:53 -05:00
jcompto95-techandClaude Opus 5.5 d36c15c313 Your own legs in first person, seen looking down (display.first_person_legs)
Video Setup > FOV and Viewmodels > Legs, off by default. In first person the
player's own body, which the game animates as everyone else sees it but does
not draw for its own player, is drawn from the waist down: looking down,
your legs walk, run, strafe and crouch as the body's animation moves them.

How it is drawn (port/linux/game/first_person_legs.c):
- A copy of the body's pose as drawn: everything but the hips and legs (by
  node name: pelvis, thigh, calf, foot, toe) folded into a lid over the
  waist, the root kept.
- The body set back behind the eye, each frame, as far as keeps the waist's
  open rim beyond the bottom plane of the view (the way Halo 2's fp_body
  keeps its open top out of sight): looking ahead, 4 cm; straight down at
  105 degrees, about 67 cm. The legs are seen as they swing forward.
- Drawn with the world's camera, so it keeps its place and size at any FOV;
  not in mirrors, not in the shadow pass, without what it carries.

The waist's rim is found from the model's own triangles: object_mesh.c reads
an object's drawn mesh (its most detailed geometry, in the permutations it
shows) from the vertex and index buffers the game draws it from, as
powerup_render_bounds.c reads them; its cache is forgotten on a new map
(objects_initialize_for_new_map).

Only the drawing changes: the body, its hit boxes, markers, the camera and
everything sent between machines are the game's. Checked by compiling the
changed units; the fold and set-back were checked against a capture of the
cyborg's mesh and pose and played on Windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtk9vPUKbWd48oBMzXuzd
2026-10-10 10:30:16 -04:00
MrBruh ce31f22e6b Merge pull request #175 from ChupathingyCE/upstream-fix/coop-cluster-negative
Take no co-op activating cluster below zero from the host
2026-10-09 20:48:18 +13:00
Milenko ea79111994 Bounded copies and formats of tag names
The scenario's tag name was copied with strcpy into the game state
header's 256-byte map_name, a tag's name was formatted with sprintf into
the out of objects message's 512 bytes, and csprintf bounded its output
at MAXIMUM_STRING_SIZE even when it formats into temporary, which is 256
bytes and where most callers put tag names. All three are bounded by
their buffers' sizes now.
2026-10-08 08:35:50 -04:00
MrBruh bad6534e0c Forget the object bounds cache in the game state load procs
The cache is forgotten before every game state load (beside cluster_partitions_port_forget) and at each new map, after the declarations, rather than at the top of four functions.
2026-10-08 13:46:02 +13:00
Milenko 44da50e411 Take no co-op activating cluster below zero from the host
objects_port_set_activating_cluster takes the cluster a co-op host sends
in its presentation (network_coop.c), and refused only NONE and indices
past the BSP's clusters. Any other negative value (activating_cluster is
sent plus one, so a host sending -5 or 0x8001) was stored, and the client
then read the cluster PVS before the start of its array. Any negative
index now means none, as NONE already did.
2026-10-07 19:26:59 -04:00
Addison Bair 04713926d1 Pass over far objects on a packed copy of their bounding spheres
A collision query (collision_get_features_in_sphere) reads the bounding
sphere of every collideable object in every cluster it touches, to pass
over the far ones. Every biped queries each tick, so a crowded cluster
(co-op's extra enemies, a carrier burst) costs the square of the crowd in
scattered object reads.

object_bounds_cache.c keeps the spheres packed by absolute index, written
by their only writer (object_compute_node_matrices). The loop runs the same
point_in_sphere on the copy, so the same objects are passed over and the
same features gathered, in order. game_state.c invalidates the copy when
the game state is replaced (revert, loaded core or game, new map).

Deterministic offline crowd (c20, legendary, extra enemies x8, 24 carriers
burst, seed pinned): game state identical tick for tick; collision queries
4.2 -> 3.0 us (-28%), object updates -15%.
2026-10-07 22:11:47 +00:00
MrBruh 07302a866f Co-op-only garbage throttle, network version 21 2026-10-07 13:43:50 +13:00
MrBruh 929ae2d5af Merge PR #135 2026-10-07 13:38:30 +13:00
MrBruh d7efd3934a Validate map tags before loading 2026-10-07 12:01:09 +13:00
xshxdex98 b214dc1728 Host performance: the garbage collection no longer runs every tick with many enemies
Garbage (bodies, dropped weapons) is kept while a player may see it: in any player's potentially visible clusters, near them or in a 45 degree cone of where they aim, however far. With many players spread over a level and many bodies (co-op's extra enemies), every body was seen by someone, nothing could go, and the whole collection (each garbage object's visibility, the object memory pool compacted) ran again every tick on the host, which also wrote 'garbage collection critical' to debug.txt each tick while short of memory. A player now sees garbage no further than 30 world units off, a collection of active garbage that left too much is tried again only after a second, nothing collected compacts nothing, and the critical line is logged as often as the warning. Running short of memory or objects still collects at once.
2026-10-06 15:59:43 -05:00
MrBruh 76addf661f Second hardening round 2026-10-07 00:01:28 +13:00
xshxdex98 7ba780ef59 Garbage collection's notices are chatter: on screen only with console_log = "all"
With many enemies (co-op's extra enemies) object memory runs low and the
garbage collector prints "garbage collection critical" and "removing
objects" at every pass, filling the screen for the host and every client.
They are the game's own chatter, so they show as game.console_log says, as
the sound and texture caches' already do; the log keeps every line.
2026-10-05 18:55:53 -05:00
xshxdex98 8e6b9a211e Co-op: triggers, cutscenes and failed missions work for every player
The campaign scripts only run on the host, against every player's unit,
but some of their habits assumed split-screen:

- volume_test_objects_all on the players waited for every player in the
  volume at once: the Maw's bridge cutscene, a30's Pelican pickups. In
  co-op one player arriving is enough now.
- Scripts name players as player0 and player1, so everyone past the
  second was left out of what they did: Pelican loads (a30's pickups,
  insertions), getting out, cutscene teleports and freezes, starting
  profiles. Those players now follow player0 (coop_scripts.c).
- A cutscene filmed far from every player (b30's sword Elite at the
  shaft door) wasn't active on clients: the cluster the scripts force
  active (object_pvs_activate) now rides in the presentation's spare
  bytes, and what the host's camera films is sent to clients as often as
  what is next to their players.
- A mission the scripts fail (d40's timer, a50's Keyes) only respawned the
  players, leaving them in the failure cutscene for good. It now reverts
  like the cutscene skip does; everyone dying still respawns at the last
  checkpoint. The scripts' game_revert reverts the same way.

The network version is unchanged: the presentation keeps its size, and a
host without the active cluster sends zeroes, which read as none.
2026-10-05 12:43:48 -05:00
xshxdex98 8194c5a00b Online co-op: play the campaign together over LAN and the internet
Hosting a co-op level from the Map screen (CO-OP CAMPAIGN, a level and its
difficulty, then Server Setup) makes a network game on the campaign map
with no game engine, which is what co-op is. Only the host runs the
scripts and the AI; port/linux/game/network_coop.c sends clients what the
scripts do (cutscenes, camera, fades, HUD, sounds, devices, named objects,
animations), and network_actors.c drives the host's AI units on every
machine. Players spawn beside each other, respawn beside a safe teammate,
and spectate while dead or waiting. Skipping a cutscene is a vote.
Players can join a game in progress at any time.
2026-10-05 00:31:30 -05:00
Fadi DerouicheandClaude Opus 5 39ece781ee Step the antenna chain in the tick and draw the frames between the ticks
An antenna's chain is a simulation of the Xbox's own step, an update a tick,
and the port was stepping it once a frame with the frame's own time. Its
springs, its carry and the points' physics then acted against the frames'
movement of it, so how far the chain moved per update depended on the frame
rate, and a vehicle at speed swung it far wider than a tick's step does.

- game.c steps the chain in the tick, beside the effects, which are already
  stepped there.
- antenna.c records the shape each tick leaves, relative to the point the
  vehicle's marker carries the base by. A frame drawn between two ticks is the
  part of the way between those two shapes that it is, carried to where the
  marker is now, so the chain stays on the vehicle it hangs from instead of
  lagging a tick behind it. The gate that left a chain that had not been drawn
  for five updates unstepped counted frames; it counts ticks now, which is
  what it counted on the Xbox.
- widgets.c no longer steps the chain from the widget update, which ran once a
  frame.
- game_time.c draws the per-frame effects and widgets that hang off objects
  (game_frame) with the pose between the last two ticks, the pose the renderer
  draws with, so what they place on an object is drawn where they put it.
- objects.c hands markers the matrices the renderer draws with, so an effect,
  a particle system or a contrail attached to a moving object follows it
  between the ticks too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-05 17:13:27 +13:00
MrBruh 4adc3a87a5 Remove matching tooling and HALO_LINUX ifdefs 2026-09-30 21:49:46 +13:00
MrBruh 8fcfe9e653 Finish distributed netcode 2026-09-29 00:27:28 +13:00
MrBruh fef8a582dd Fix release build crash on level load 2026-09-27 19:56:32 +13:00
MrBruh 755f610025 Fix 128-player multiplayer issues 2026-09-27 18:17:24 +13:00
Jonas VolmanandClaude Opus 5.5 9a376181f5 Port: grow the game state and object pools for 128 players
The Xbox game state (0x345000 bytes at 0x80061000) ends exactly where the
map-linked tag cache begins and has 948 bytes free, so a 128-player
session cannot even allocate its player datums. The native builds now
place a 16 MB game state (CPU 0xFC0000, GPU 0x40000) at 0x81A00000, above
the tag cache and below everything the Xbox memory window hands out
top-down, and grow the pools that scale with the number of players:

- objects: 2048 -> 8192 headers, 1 MB -> 8 MB pool; the garbage limits
  keep January's proportions (5/10/20 % of the pool, 2.5/5/10 % of the
  headers, 75/50/30 active garbage objects per 16 players)
- cluster partition reference lists 2048 -> 8192 (collideable and
  noncollideable objects, lights); a full list drops objects out of
  rendering, collision and sphere queries
- effects 256 -> 2048, effect locations 512 -> 4096
- particles 1024 -> 8192 (render_particles' visible list, filled without
  a bound check, follows), particle systems 64 -> 256, their particles
  512 -> 4096
- contrails 256 -> 1024, contrail points 1024 -> 8192
  (MAXIMUM_CONTRAIL_POINTS stays the pool size for the same reason)
- lights 896 -> 4096, object looping sounds 1024 -> 4096
- cached object render states and rendered objects 256 -> 1024
- script object-list references 128 -> 1024, objects one explosion can
  damage 64 -> 256

The values live in port/linux/include/halo_port_capacity.h, included by
halo_port_limits.h. With 128 player datums the CPU part holds 14,256,908
bytes (2,258,164 free). Saved games and cores grow to 16 MB. Every change
is under HALO_LINUX; the MSVC objects are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 10:18:07 -07:00
MrBruh a06ef74eec Detach framerate from gameplay simulation 2026-09-26 21:49:07 +12:00
Jonas VolmanandClaude Opus 5.5 7d92f97c9f Correct objects.obj private symbol ownership (zero credit)
cachebeta.pdb publics show 31 objects.obj symbols were file-static in
January, and no other January object references any of them. Only
symbols.json and our source disagreed with January.
- 29 functions were already static in our source; their symbols.json
  entries gain "static": true.
- object_memory_release_procs becomes static.
- object_update becomes static, and its public prototype leaves the
  shared objects.h. It is defined before every use in objects.c.

objects now matches January in all 225 symbols, all 121 functions stay
strict exact, and its 1,530-byte .data section is identical. It is
still held: five candidate-only helper COMDATs conflict with
hand-written NODUP providers (path_obstacles, action_charge,
player_control, bored_camera). It also emits the three stock D3D SDK
tables that keep rasterizer_xbox_profile held. No
semantic_data_matches entry, no credit.

Verification: full ninja (the objects.h edit rebuilt its 92
consumers); stable diff vs frozen 8,245-function baseline 0 gained /
0 regressions; parks 103/0/0; fake-match scan 0 -> 0; pytest 1154
passed; git diff --check clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 14:43:00 -07:00
Jonas Volman f8e8e6b6fb Complete objects object and harden progress accounting 2026-07-19 12:30:45 -07:00
Jonas Volman 110483d01a Match object garbage collection 2026-07-18 20:19:47 -07:00
Jonas Volman 561be66f78 Correct object creation failure ordering 2026-07-18 18:11:27 -07:00
Jonas Volman 9330763458 Correct objects data ownership and GC initialization 2026-07-18 17:40:20 -07:00
Jonas Volman 655e48fa28 Match object attachment to node 2026-07-16 11:16:32 -07:00
Jonas Volman a7120e7e0e Fix object garbage collection termination 2026-07-15 22:13:40 -07:00
Jonas Volman 5f8c9b3110 Improve point-vector object traversal match 2026-07-15 20:17:39 -07:00
Jonas Volman 07f41911ec Correct object node orientation fallback 2026-07-15 18:03:23 -07:00
Jonas Volman 38e8b5b315 Correct object garbage collection reconstruction 2026-07-15 18:03:22 -07:00
Jonas Volman 96e4002d5a Match objects update profile section ownership 2026-07-15 16:22:42 -07:00
Jonas Volman 50c2ff4d4f Match objects_dump_memory stack layout 2026-07-15 14:12:01 -07:00
Jonas Volman 159ffbb5d8 Match object change color computation 2026-07-15 14:09:21 -07:00
Berthalamew 37e96a1c2f ai_debug work (#37) 2026-07-13 14:34:27 +01:00
Berthalamew 4cb1519b3b objects work (#36)
* match attachments_new

* objects_garbage_collection work
2026-07-11 17:46:57 +01:00
Berthalamew 369b71e72e breakable surface work (#35)
- work on breakable surface functions
- fix `magnitude3d`
2026-07-02 00:31:52 +01:00
Berthalamew bc6663de55 unit additions (#34)
- add unit functions and associated dependencies
2026-07-01 00:15:19 +01:00
Berthalamew 11507e901f match object_export_function_values and attachments_delete (#27) 2026-06-16 14:49:20 +01:00
punpckhdq fb0d814535 Declare .comm data in TUs instead of headers (#26)
* Declare .comm data in TUs instead of headers

* Update files.h

* Include
2026-06-16 12:49:22 +01:00
Berthalamew 7b5b1f67f4 object_compute_node_matrices_work (#23)
* object_compute_node_matrices work

88.71% match

* main changes

* add misc definitions

* name private function

* requested changes
2026-06-08 21:55:40 +01:00