Commit graph

5 commits

Author SHA1 Message Date
Erik
9d4967a461 fix(core): ACME cross-check fixes — normals, placement, scenery
Four fixes from the ACME StaticObjectManager cross-reference:

1. GfxObjMesh: normalize vertex normals (1d). Dat normals may not be
   unit-length; without normalization, lighting is wrong per-vertex.

2. SetupMesh: add third-fallback placement frame (2a). If neither
   Resting nor Default exists, use the first available frame from
   PlacementFrames. Matches ACME's GetDefaultPlacementFrame.

3. SceneryGenerator: building cell exclusion (4d). Compute which
   terrain vertices have buildings (from LandBlockInfo.Objects +
   Buildings), skip scenery spawns in those cells. Prevents trees
   from spawning inside building footprints.

4. SceneryGenerator: slope filter (4e). Compute terrain normal Z at
   each displaced position and check against ObjectDesc.MinSlope /
   MaxSlope bounds. Prevents trees from spawning on cliff faces.

Also confirmed 4f (scenery Z=0) is NOT a bug — GameWindow's hydrator
lifts scenery to terrain Z at line 1213. The Z=0 in SceneryGenerator
is a placeholder correctly overridden at render time.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:52:08 +02:00
Erik
3fd774515a fix(core): emit both sides of double-sided polygons in GfxObjMesh
The lifestone (and likely any weenie with closed shells using NoPos /
Negative / Both stippling) rendered with visible holes where you could
see inside it — confirmed via the user's "see into it" description.
Root cause: GfxObjMesh.Build skipped any polygon whose PosSurface was
out of range, which is exactly what a NoPos-stippled or
negative-only polygon looks like. Backface culling isn't involved
(acdream has it disabled); we were simply dropping triangles.

Ported the pos/neg emission rule from
references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/
ObjectMeshManager.cs (lines 955-971 and 1510-1577):

  pos side: emit when !Stippling.NoPos and PosSurface is valid
  neg side: emit when Stippling.Negative, Stippling.Both, OR
            (!Stippling.NoNeg && SidesType == CullMode.Clockwise)

The "Clockwise CullMode means NegUVIndices are on the wire" rule is
non-obvious but matches how Polygon.Unpack reads NegUVIndices, so
any closed mesh relying on that convention now renders correctly.

Neg-side triangles get the reversed fan winding and a negated vertex
normal. With culling off the winding only matters for lighting
consistency, but keeping the semantics right future-proofs the
fix if we ever enable back-face culling for a perf pass. The
dedup cache is keyed by (posIdx, uvIdx, isNeg) so the same vertex
can carry different normals on the pos and neg sides.

Pos-side winding is preserved at the original (0, i, i+1) order so
the existing single-triangle and fan-triangulation tests still pass
— neg side uses (i+1, i, 0), which is the same shape mirrored.

194 tests green.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 20:59:10 +02:00
Erik
a71db90310 feat(net): Phase 6.6 — parse UpdateMotion (0xF74C) into MotionUpdated event
Server sends UpdateMotion whenever an entity's motion state changes:
NPCs starting a walk cycle, creatures switching to a combat stance,
doors opening, a player waving, etc. Phase 6.1-6.4 already handles
rendering different (stance, forward-command) pairs for the INITIAL
CreateObject, but without this message NPCs freeze in whatever pose
they spawned with and never transition to walking/fighting.

Added UpdateMotion.TryParse with the same ServerMotionState the
CreateObject path uses, reached via a slightly different outer
layout (guid + instance seq + header'd MovementData; the MovementData
starts with the 8-byte sequence/autonomous header this time rather
than being preceded by a length field). Only the (stance, forward-
command) pair is extracted — same subset CreateObject grabs.

WorldSession dispatches MotionUpdated(guid, state) when a 0xF74C
body parses successfully. The App-side wiring (guid→entity lookup
and AnimatedEntity cycle swap) is intentionally deferred to a
separate commit because it touches GameWindow which is currently
being edited by the Phase 9.1 translucent-pass work.

89 Core.Net tests (was 83, +6 for UpdateMotion coverage).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 20:33:26 +02:00
Erik
324abed6eb feat(core): add Vertex.TerrainLayer + LandblockMesh layer map
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 20:16:25 +02:00
Erik
f915a13263 feat(core): add GfxObjMesh.Build multi-surface mesh extractor 2026-04-10 17:52:09 +02:00