Commit Graph
219 Commits
Author SHA1 Message Date
MrBruh a3277d94dc Fewer draws and GL calls
Decals and light volumes batched, web index and vertex array caches, no FULL_ES3. Prisoner, 15 players: 115 to 172 FPS (A/B).
2026-10-11 22:28:34 +13:00
MrBruh acfa9a2a4d Web build 2026-10-11 12:46:28 +13:00
MrBruh 1f48f0555d Shield glow: one light per unit in split screen, light show not clamped (#247) 2026-10-11 12:18:47 +13:00
MrBruh 79bcc86066 Merge pull request #247 from jcompto95-tech/shield-glow 2026-10-11 12:14:25 +13: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
MrBruh 098ca66a72 Merge pull request #249 from innerhat-dev/vehicle-teleporter-name 2026-10-11 11:59:05 +13:00
MrBruh fb97e52663 Stale player handles forgotten in the port's loop (#250) 2026-10-11 11:54:50 +13:00
eltanschauung 6ea142ceee Fix co-op stale player handle crash 2026-10-10 15:30:31 -04: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 1463c87090 Energy shields glow as they flare, in their own color (display.shield_glow)
Video Setup > Graphics > Shield Glow, off by default; Glow Intensity:
DEFAULT or LIGHT SHOW (three times as far and as bright).

As a unit's shield flares (hit, charging, falling), it lights what is around
it, and itself, as a plasma bolt does: a Spartan's gold, an Elite's blue,
any unit's its shield shader's own edge color. The glow's strength is the
flare's, the intensity the shield plasma is drawn with (its intensity
source, an object function, to its exponent), so it rises and falls with
the flare seen on the body, and fades with active camouflage.

port/linux/game/shield_glow.c chooses up to 8 units within 25 world units of
the camera each frame. object_lights.c gains port glows: ordinary dynamic
lights attached to an object's node, kept alive, colored and sized each
frame by their maker (light_port_glow_set) and gone a moment after it stops,
drawn with a light definition the map already has (one without a lens flare
or a flashlight's flags, a plasma one where there is one): no new assets.

Only the drawing changes: nothing the game simulates, nothing sent between
machines. Checked by compiling the changed units.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtk9vPUKbWd48oBMzXuzd
2026-10-10 13:56:38 -04: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 400d62bb76 Merge pull request #123 from startupfoundry/split-halo-android
Split HALO_ANDROID into HALO_ARM64_GUEST, HALO_GLES and HALO_ANDROID

Conflict resolved: main's render-target eviction and mip composites are kept, with the PR's HALO_GLES guard on the ES blit fallback after them.
2026-10-10 15:41:32 +13:00
Milenko f2a0f91e48 Antennas and cloth flags follow a jump of more than one unit
Their attachment's movement was truncated to whole units before the
test, so it took two units of movement in one update, not one, to move
the vertices along with it. Visual only.

(cherry picked from commit 99c2db2d4da3d6d66b6ec28d459c756aeab55a64)
(cherry picked from commit eb0667bcc81ed1d970422c7856162e8aadff6fd7)
2026-10-10 13:59:04 +13:00
Paul Hontz fb9f03cf2c Split HALO_ANDROID into the guest's ABI, the ES renderer and the app
HALO_ANDROID meant three things at once. The port's code now names each:
HALO_ARM64_GUEST for the ILP32 AArch64 guest's code paths (the inline
assembly's C, the bss pragmas, the CRT's), HALO_GLES for the OpenGL ES
renderer (its GL header and function list, shaders and capabilities), and
HALO_ANDROID for the app alone (its display, input, files and lifecycle).

The Android build defines all three, so nothing changes for it, nor for
Linux and Windows, which define none. A build for another platform can
then take one part without the others: the ES renderer, or the guest's
ABI, without the Android app.

port/android/README.md also no longer says that the widescreen changes are
in #ifdef HALO_ANDROID: none have been since 50eae19b.
2026-10-09 09:05:44 -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 65a6bb7edd Grow the powerups' render spheres in a port file
The powerup bounds of #117 move from source/models/models.c to port/linux/game/powerup_render_bounds.c, called from scenario_tags_load beside the other tags-loaded hooks, so the game's models.c no longer takes <xtl.h> and D3D buffer locks. It uses square_root and a relative margin in place of sqrtf and nextafterf, which strict gnu89 does not declare, and the objects.h scale change is marked as the port's. tools/test_powerup_bounds.py is dropped: it redeclares the engine's types by hand and CI does not run it.

On bloodgulch the active camouflage's render radius grows from 0.1 to 0.23 and the overshield's from 0.1 to 0.21; no other equipment changes.
2026-10-08 14:15:15 +13:00
MrBruh ec23cfcf24 Merge pull request #117 from eltanschauung/fix/powerup-render-bounds 2026-10-08 14:08:53 +13: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 f41c8e977a Merge pull request #145 from addisonbair/widget-pools
Fix invisible plasma bolts in large network games
2026-10-08 10:56:45 +13:00
eltanschauung 2875b505fc Merge remote-tracking branch 'upstream/main' into resolve/pr117-2026-10-07
# Conflicts:
#	source/cache/cache_files.c
2026-10-07 02:09:33 -04:00
MrBruh f823a18d59 Load, check and run Halo Custom Edition and OpenSauce maps 2026-10-07 19:02:01 +13:00
Addison Bair 35f643f376 Grow the widget and light volume pools for large games
A plasma bolt is drawn by its light volume, a widget, and both pools were
still the Xbox's: 64 widgets and 256 light volumes. Every assault rifle
holds a light volume for its flashlight beam from when it is made, held or
dropped. In network co-op the rifles left lying about are kept as garbage
in proportion to the players, and garbage that any player can see is never
collected, so a large game fills the widget pool and keeps it full; a client
that joins a game in progress makes all the rifles at once and takes every
slot. From then on widgets_new finds the pool full and skips the widget, so
every bolt flies and hits unseen.

- widgets 64 -> 2048 (HALO_PORT_MAXIMUM_WIDGETS)
- light volumes 256 -> 2048 (HALO_PORT_MAXIMUM_LIGHT_VOLUMES); with only the
  widgets grown, the light volumes run out next

At 128 players a game can hold about 1,000: the rifles in hand, the ones on
the ground (garbage collection starts at 400 and keeps those in view) and the
bolts and needles in the air. The CPU part of the game state grows by 38,144
bytes, to 18,242,828 (2,466,548 free). As with any pool size, saved games of
earlier builds no longer load.
2026-10-07 05:23:34 +00:00
MrBruh 5f1abf161c Survive and report a broken lights array 2026-10-07 15:05:11 +13: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
eltanschauung 77efb01b00 Grow culling spheres with enlarged objects 2026-10-06 01:29:48 -04: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
MrBruh 58aa352705 Merge pull request #107 from xshxdex98/sync-glass-and-scenery 2026-10-06 08:40:24 +13:00
MrBruh 59c4b2160e Merge pull request #106 from xshxdex98/coop-scripts-for-everyone 2026-10-06 08:40:24 +13:00
MrBruh 197c199473 Improve performance by ~80% with many enemies
The Silent Cartographer with 32x co-op enemies (484 actors) went from 53
to about 97 fps on the Linux PC, with the same game and the same picture:
cluster list removals start where the reference was put, full data arrays
are not walked again, object lighting keeps each cluster's drawn lights
and passes over far ones without their distance, the per-texture profile
timer only runs while something reads it, and the renderer binds streams
once per draw, caches its texture, target and shader lookups, and no
longer makes Mesa's GL thread wait on large uploads or visibility tests.
2026-10-06 08:13:44 +13:00
xshxdex98 7a1ffca28b Network: glass and destructible scenery break the same for everyone
Breakable surfaces (glass) are broken by each machine's own projectiles
with its own random damage, so a pane could shatter on the host and stay
whole, still blocking, on a client. In co-op the host now sends each break
(and a few again every refresh, for late joiners), and a client breaks the
same surface with its shards.

Destructible scenery and devices (lights, panels) are the map's, at the
same index on every machine, but only damage to units was synced: a
client's shots never damaged them, and what the host broke didn't break
or play its effects on the clients. A client now reports its hits on them
like any other, the host checks and deals them, and every client deals the
host's damage to its own copy, which breaks, plays its effects and is
destroyed as the host's was.

Both are new message kinds that a client without them ignores, so the
network version is unchanged.
2026-10-05 13:00:01 -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
MrBruh 01586cfebf Merge pull request #89 from xshxdex98/coop-campaign 2026-10-05 20:29:19 +13:00
MrBruh 3f6c0c37a4 Friendly fire stops teammates' instant kills 2026-10-05 18:47:39 +13: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
MrBruh 2853a42253 Clear an antenna's shape record when it is created 2026-10-05 17:13:27 +13: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 c9ee319ab5 Add the PC menus 2026-10-04 01:56:45 +13:00
MrBruh 4f3ce00a8e Fix third netcode review 2026-10-01 04:57:51 +13:00
MrBruh e7d1ed3a67 Harden and fix the netcode 2026-10-01 01:22:46 +13:00
MrBruh 4adc3a87a5 Remove matching tooling and HALO_LINUX ifdefs 2026-09-30 21:49:46 +13:00
MrBruh a17d89c6f1 Merge bnunu/halo-1 main 2026-09-30 18:13:05 +13:00
Jonas Volman 6bfd956737 Reconcile approved Halo fidelity packets and bounded data credit
Recover transparent geometry submit (+1,285 meaningful code, zero exact losses); reconcile COMMON ownership and Q10 85+EXT1 credit, actions/main data, and separately disclosed denominator corrections. Preserve canonical-only repairs and all held research. C09 is ordinary disclosed cleanup, not a claim of proven scaffolding intent. Clean rebuild, 1,663 tests, parks, audits, provider links and independent EXT1 check pass. Local reconciliation only.
2026-09-29 13:52:37 -07:00