process_tmap and process_tmap2 used the texture index from the BSP data
to index Num_polies and Num_verts without checking it, so a corrupt
model wrote past both arrays while its vertex buffers were built. The
collision tree parser already treats such an index as a fatal error, but
it runs afterward. Raise the same error here, and skip the polygon in
case the error is continued past.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The post-parse sanity checks and the bad-version check returned FAIL
without closing the POF. Nothing reads the file after the chunk loop, so
close it there, and close it before the bad-version return too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Directional, point, and cone lights left local_vec, local_vec2, and some of
the type-specific fields uninitialized until something rotated or set them.
The sun copies in Static_light were never rotated at all until the previous
commit, so light_apply_rgb's sun term read uninitialized memory. Zero every
field up front, which also covers what the tube light did by hand.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
light_apply_rgb works in the model's local frame, but nothing rotated the
lights into that frame before insignias were drawn: light_rotate_all() was
only called for the briefing and tech room lights, and even then it skipped
Static_light, the separate copies of the sun lights that light_apply_rgb
reads. So the sun term used a stale, never-set direction and the point
lights were treated as if they sat at the model's origin.
Rotate the lights, including the sun copies, once the insignia's instance
is set up, and light each vertex at its actual position, which includes
the insignia's offset.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
In debug builds, subsystems found in a model but missing from ships.tbl
are meant to be dumped to data/tables/<model>.subsystems as stub table
entries. Since subsystem matching was deferred until after the model
file is read (#4590), the dump file was already closed by the time
do_new_subsystem ran, so it was always empty and deleted. Worse, ss_fp
was left pointing at a closed CFILE slot, which another file could reuse
before do_new_subsystem wrote to it, and the dump file leaked whenever a
load failed.
Open the dump file on the first unmatched subsystem instead, and close it
once the deferred subsystems have been matched. This also means no file
is created and deleted for models without unmatched subsystems. Reset
the once-per-model warning flags there as well, rather than only when
the dump file opened successfully.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The insignia normal array was declared with the vertex limit, filled in by
face index, and read by vertex index. An insignia with more than 81 faces
therefore wrote past the array, into the following insignia or, for the
sixth and final slot, past the insignia list into the rest of the
polymodel. And the renderer lit each vertex with the normal of whichever
face shared its index, or with a zero normal (ambient light only) when
there was no such face.
A normal belongs to a face, so size the array by the face limit and light
all three corners of each face with that face's normal.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A subobject that named itself, or one of its own descendants, as its parent
made the depth walk loop forever, and would have done the same to every
later walk up the hierarchy. Since each SOBJ chunk adds only one link, any
cycle must lead back to the subobject being read, so detect that during the
walk and treat the subobject as parentless.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
When a ship flying into a fighterbay has to route around the carrier,
create_model_path puts tangent and bisect points ahead of the bay path.
The departure speed ramp treated the first point of the AI path as the
start of the bay path: it started once the ship passed that point and
measured the path length from there. On a detoured path the ramp
therefore began slowing the ship while it was still flying around the
hull, and the ship entered the bay slower than intended. The ramp
fraction was also not clamped, so the target speed could exceed max
speed.
Under the new ai_profiles flag "$fix fighterbay speed ramp:"
(Fix_bay_speed_ramp), the departure ramp starts at the first point of
the bay path itself and the ramp fraction is clamped to [0, 1]. The
flag is enabled by default for mods targeting 26.2.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Settings in game_settings.tbl were logged as they were parsed, so a setting was only logged if it appeared in a table. A setting enabled through $Target Version was never logged, and a setting that appeared in more than one table was logged once per table, possibly with different values.
The settings are now logged by mod_table_log_settings, which runs at the end of mod_table_init. The version-gated settings that previously had no log line now have one: framerate independent turning, scripted velocity, asteroid bounding box checks, the expensive turret target check, skybox depth consistency, zero-radius explosions, and insignias as decals.
Messages that describe the table text itself, such as deprecated options, removed file extensions, and out-of-range values, are still logged at parse time.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
get_I_inv converts an object's inverse moment of inertia tensor from body to world coordinates for collision physics. It was written as though vm_matrix_x_matrix(dest, a, b) computes a * b, but that function computes b * a, so the result was (R)(I_inv_body)(Rt) rather than the (Rt)(I_inv_body)(R) stated in the comment. The collision response therefore used the tensor of an object facing the inverse of its actual orientation. This dates back to retail.
The result is only correct for an isotropic tensor or an unrotated object. Every retail ship has an anisotropic tensor, with a median principal-axis ratio of about 2.5 and as high as 13 for capital ships, so the spin imparted by a collision was typically about the wrong axis.
Since this changes collision behavior for every mod, the fix is controlled by the new game_settings.tbl option "$Fix collision moment of inertia:", which defaults to true for mods targeting 26.2 or later. The retail path is otherwise unchanged.
PR #2575 fixed a related bug in physics_ship_init: retail scaled the inverse moment of inertia by $Density rather than by its inverse, so a denser ship was easier to rotate rather than harder. That fix was merged without a flag. For consistency it is now controlled by "$Fix density moment of inertia:", which defaults to true for mods targeting 21.0 or later.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
set_accel_for_docking took the model path from the first point of the
AI path. When a departing ship has to route around the carrier,
create_model_path prepends detour points whose path_num is -1, so the
path name was read from pm->paths[-1]. That usually yields junk and a
missed metadata lookup, but crashes when the memory before the paths
array is unmapped. Use aip->mp_index, which both bay path builders set
to the model path index.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
When the ship a path leads into moves, modify_model_path_points
re-globalizes the path's model points in place.
It always rewrote nverts points, but create_model_exit_path can build
a path with fewer, in which case the rewrite would run past the end of
the path into Path_points entries that may belong to other ships. No
current caller builds such a path, but the rewrite now covers only the
points the path actually holds.
A subsystem path gives its second-to-last point a random offset when
created, but the refresh did not pass that point back in, so the
offset was lost the first time the target moved. Record the
randomized vertex in ai_info and, under the new ai_profiles flag
"$fix subsystem path randomization on refresh:"
(Fix_model_path_refresh_randomization), reapply it on refresh. The
flag is enabled by default for mods targeting 26.2.
Also rename a parameter in `get_base_path_info` (which is currently
unused) for clarity.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
A subsystem's looping sounds are now switched by ship_update_subsys_sounds, split out of ship_recalc_subsys_strength, which the support-ship and automatic repair paths now also call when a subsystem comes back from zero. Previously those paths left the subsystem playing its dead sound.
Both the recalc and do_subobj_destroyed_stuff also cleared the dead-sound flag immediately after assigning the dead sound, so a revived subsystem never stopped it and a destroyed one gained another copy on each recalc. They now set the flag.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
When a destroyed subsystem was repaired, its submodels were only restored if the repair was done through a SEXP. A subsystem repaired by a support ship, by automatic repair, by a script, or through the debug console regained its hitpoints but not its submodels, so a ship could have a working turret with no model. Any -destroyed form also remained visible.
All of these now call restore_subsystem_submodels, which brings back the subsystem's submodels and hides the -destroyed submodel linked to a special-point subsystem. The -destroyed form of an ordinary submodel is hidden when the submodels are next replicated. The SEXP code uses the same function, with no change in its behavior.
Destruction outside do_subobj_destroyed_stuff now goes through a new blow_off_subsystem_submodels function, which do_subobj_destroyed_stuff also uses. It blows off both of the subsystem's submodels and shows any linked -destroyed submodel, unless the subsystem has "no disappear". This handles the cases where a subsystem appears destroyed without going through do_subobj_destroyed_stuff:
- the subsystem is destroyed at the start of the mission
- the subsystem was destroyed in the previous mission of a red-alert sequence
- the ship changes class while the subsystem's submodels are blown off
Previously the first two blew off only the first submodel, ignored "no disappear" for it, and never showed a linked -destroyed submodel; the third did nothing. For red alert, subsystems with no hitpoints at all are not treated as destroyed. For a class change, the visible state is carried over rather than inferred from hitpoints, so a subsystem set to zero without being blown up, or repaired without its submodels, keeps its appearance.
Red alert also restores the submodels of a subsystem that the mission file starts destroyed if the previous mission's data gives it hitpoints. Ship-copy-damage now shows or hides the -destroyed submodel linked to a special-point subsystem to match the copied state.
As before, the link between special-point subsystems and -destroyed submodels requires "$Link special-point subsystems to -destroyed submodels:" in game_settings.tbl.
The multiplayer hitpoint-update path is not changed. A client receives subsystem hitpoints separately from the notification that a subsystem was destroyed, so a hitpoint update sent just before the destruction could arrive after it and wrongly restore the submodels.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Add generate_ship_usage_list_team alongside the weaponry twin that already existed in missioneditor/common, so both loadout editors can drop their TVT/non-TVT branches and simply iterate Num_teams.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
An empty list such as `( )` leaves get_sexp() with no start node, but it still passed that -1 to get_operator_const() for the ai-chase-any-except compatibility check and, outside FRED, for preloading. This tripped the out-of-range assertion in debug builds. Return the empty list directly instead.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Element counts were taken straight from the file, so a corrupt POF could
supply a negative count (stored alongside a null array) or one larger than
the fixed-size array it feeds. Add pof_read_count() to floor every count at
zero and clamp it to the destination's capacity, reading past the discarded
entries where more data follows in the same chunk.
The upper bounds that were checked at all were checked with debug-only
Asserts, or with an Error() that returns if the user picks Debug, so the
overflowing loop after it was reachable in both cases.
Also validate the submodel indices in SOBJ and TGUN/TMIS, the vertex indices
in insignia faces, and bounds-check the split plane array before writing to
it rather than after.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Current_filename_sub dates back to the use of *_ex() parsing functions for parsing .eff files, which were superseded by the Bookmarks feature. Today it is entirely redundant to Current_filename. A read that throws now leaves Current_filename naming the file that failed, rather than the file parsed before it, as it should.
Also clear Current_filename in stop_parse, so that a warning issued between the end of one parse and the start of the next no longer blames the last file parsed, and null out Mp there so it stops dangling into the freed parse buffer. Also delete the vestigial Mp_save.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
* Fix QtFRED root notifications when inserting nested operators
Only emit rootNodeFormulaChanged when the wrapped node is a formula
root. Inserting above a nested expression previously reported a child
as an event, goal, or cutscene root, causing assertions and out-of-bounds
accesses in the receiving dialog model.
Co-Authored-By: OpenAI Codex
* Give each QtFRED ship independently owned arrival and departure cues
Pass the tree model to the cue update handlers and serialize a separate
expression for each eligible ship. Sharing one formula across selected
ships caused double frees on subsequent edits and left other ships
referencing freed nodes when only one ship was changed.
This also avoids allocating unused expressions when cue updates are
disabled or no selected ships are eligible.
Co-Authored-By: OpenAI Codex
* Synchronize QtFRED item editability after SEXP node changes
Update native Qt editability alongside the shared model flags so
variable and container references cannot be edited as plain text.
Restore inline editing when a reference is replaced with ordinary data,
and preserve item flags when moving or copying branches.
Reject edit requests for protected items before setting the editing
state, preventing later programmatic changes from being treated as
user edits.
Co-Authored-By: OpenAI Codex
When parsing a SEXP, a variable reference's node type was decided solely by whether it was quoted: "@Var" became a string variable and @Var a numeric variable, regardless of how the variable was declared. A hand-edited SEXP with the wrong quoting therefore produced either a misleading "Not a number" error or a silently mistyped node that was saved back with the wrong quoting.
get_sexp now takes the node type from the variable's declared type, and flags a reference whose quoting contradicts it. check_sexp_syntax reports such references (or any variable node whose type disagrees with its variable) as the new recoverable error SEXP_CHECK_VARIABLE_TYPE_MISMATCH: FRED flags it, and the game warns but still loads the mission, which runs correctly because the node type has been corrected. Saving from FRED writes the corrected quoting.
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
Check each 3D shockwave model when it is loaded and warn if none of its textures has a glow map. The check is skipped when glow maps are disabled, since they are not loaded then.
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
The overflow flag and screen position were read from a temp vertex
already returned to the pool, which only worked because nothing
allocated in between. Copy them out first.
The clamps that keep the indicator away from the top and left of the
clip rectangle subtracted the clip origin instead of adding it, and
dropped it from the clamped value. Inert while the origin is zero, but
wrong for any other clip rectangle.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
A 3D shockwave's glow map already takes its frame from the shockwave's
elapsed time so the animation spans the shockwave's life. Do the same
for an animated base texture, and skip the radius fade for it, since the
artist's frames then control the brightness the way they do for the glow.
A static base texture still fades with the radius.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Models drawn without lighting under deferred rendering copy their base
colour into the emissive buffer and zero the ambient occlusion factor,
but still write their real diffuse and specular data to the G-buffer.
Each direct light then adds diffuse and specular on top of the emissive
copy, and the environment map adds its specular term, so the "no
lighting" flag only appeared to work in dark scenes.
Zero the diffuse and specular outputs after the emissive copy. The
specular alpha is the Fresnel factor, so clearing the whole vector also
removes the grazing-angle sheen.
Fixes#3999
Co-Authored-By: Claude Fable 5.1 <[email protected]>
3D shockwaves pass `1 - radius/outer_radius` as the warp alpha, which `model_render_determine_color` turns into the material colour for additive draws. Since a481999a2 (SamuelCho's OpenGL Code commit in August 2016), the model fragment shader has not multiplied the diffuse sample by that colour in the unlit branch, so the base texture stayed at full brightness and vanished abruptly. The same path carries the warp effect's radius fade and the alpha of unlit transparent weapons.
Clamp the alpha before converting it to a byte, as the pre-2016 code did; thrusters pass 1.2, and without the clamp they would now render dim.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
- Introduced `shadow_ray_params` to simplify ray origin and cull mask management.
- Shifted shadow ray origins to TLAS-relative space for improved precision and consistency, especially for cockpit rendering.
- Refactored cascade bind functions, adding `shadow_cascade_params_bind_deferred()` for consolidated offset/count handling in deferred lighting.
- Expanded TLAS to include cockpits and additional object types, ensuring parity with rasterized shadows.
- Introduced per-instance cull masks to address cockpit self-shadowing issues.
- Improved Vulkan depth isolation for cockpit rendering via backup/restore mechanism, ensuring correct depth sampling in later passes.
- Extracted reusable helper functions for cockpit rendering state logic.
Follow-up to #7606. Restore the MaxGuardRadius property, restore the ability to set specific ranges using set-guard-range, and add scripting functions for setting the specific ranges.
Fixes#7756
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
Neither editor had a teardown to match FSO's game_shutdown(), so the engine
data they set up was never freed. Add editor_free_engine_data() to free it,
and call it from a new fred_shutdown() in FRED and from qtFRED's shutdown(),
before the graphics system closes.
Also:
- Delete FRED's modeless editors if they are still open at exit.
- Make FRED's dialog tooltips and sexp tree image lists members, since
they were leaked or depended on OnDestroy() being called.
- Parent qtFRED's item models so they are deleted with their widgets.
- Enable the CRT leak report in qtFRED, which previously had none.
- In qtFRED, call shutdown() last in closeEvent(), since it destroys the
window.
- Make the preferences path and user SID strings function-level statics,
so MFC's leak dump no longer reports them as leaks.
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
HudGaugeOffscreen::calculatePosition could return without writing its
outputs, either when the eye vertex overflowed or when the clipped
point landed a pixel or two inside the clip rectangle. The caller drew
anyway, so the indicator appeared at a random spot with a random
orientation that changed every frame. This showed up when targeting a
subsystem on a distant ship, where the clip loses enough precision to
hit the second case.
calculatePosition now returns whether it found a position, and both
callers skip drawing when it did not. The inside case falls back to
the nearest screen edge, since the point is already known to be off
screen, so the indicator stays steady instead of blinking.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
The projected point was the subsystem but the stored position was the
hull centre, so the offscreen indicator sized its triangles for a
different point than the one it clipped against.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
The expression used & instead of |, so neither PF_OVERFLOW nor
PF_PROJECTED was ever set on that path.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
swap_bsp_sortnorm2 swapped the bounding box at offsets 8 and 20, which
re-swapped the front list offset and left the real box (at 16 and 28)
unswapped. swap_bsp_tmap2poly never swapped the chunk's bounding box
or normal. Both corrupted models using these chunks on big-endian
builds.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Report leaks after static destruction via _CRTDBG_LEAK_CHECK_DF instead of calling _CrtDumpMemoryLeaks() at the end of main(), which incorrectly reported every global container as a memory leak.
Also fix the three remaining real leaks which were discovered after all the noise was cleared up:
- Mc_point_list was never freed, because its atexit cleanup was only registered by code that no longer runs. But the list was only a temporary array of pointers used while building the collision tree, so remove it and have model_collide_parse_bsp_defpoints() copy the vertices directly into the tree's point list.
- model_batch_buffer's Mem_alloc copy was never freed. But the copy is actually not necessary, so remove it and pass Submodel_matrices directly.
- Goal_target_names strings were only freed when the next mission was parsed, so the last mission's names leaked at exit. Hold them in SCP_vm_unique_ptr so they are freed with the vector.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>