Skip to content

Training targets, and restyle the character as an irregular fighter - #12

Closed
ralyodio wants to merge 3 commits into
mainfrom
feat/targets-and-irregular-fighter
Closed

Training targets, and restyle the character as an irregular fighter#12
ralyodio wants to merge 3 commits into
mainfrom
feat/targets-and-irregular-fighter

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

There was nothing to shoot at

character.glb was built and then had no consumer — the multiplayer entry point that would draw remote players doesn't exist yet — so the sandbox was an empty yard with a working rifle and no enemy. PRD §40 puts "one enemy and one rifle" ahead of everything else.

Five training targets now stand in the yard. They take hits, topple, and stand back up after three seconds.

Deliberately targets, not AI: there's no animation system and no client-side bot pathing, and a figure sliding around frozen in a standing pose looks worse than one that honestly stands still.

Hit detection is presentation only, like the weapon effects — the server owns registration via resolveHitscan. A target only counts if it's nearer than the world geometry behind it, so one standing behind a container can't be shot through it.

The character read as a Western regular

High-cut ballistic helmet, NVG mount, plate carrier with shoulder yoke, pauldrons. Wrong silhouette for a story whose stated premise is that neither nationality is the default villain, and whose two factions are fictional.

Restyled as an irregular fighter: cloth head wrap with tail and face cloth, field jacket, simple chest rig with rifle pouches and a bandolier, canvas satchel, rolled blanket, cloth knee wraps. No helmet, no optics mount, no armour plate.

Unmarked and fictional — no insignia, no script, no religious or ethnic signifiers. Faction identity is an armband and head-wrap band in the team colour, nothing else. CLAUDE.md gates Iranian/Farsi content on native review that hasn't happened, and a caricature would fail both that and the PRD's §21.12 launch gate.

201 tests pass. Format, lint, typecheck clean.

🤖 Generated with Claude Code

ralyodio and others added 3 commits July 26, 2026 10:45
Launching the Linux build died immediately:

  FATAL:setuid_sandbox_host.cc(163)] The SUID sandbox helper binary was
  found, but is not configured correctly ... chrome-sandbox is owned by
  root and has mode 4755.

Electron sandboxes renderers with unprivileged user namespaces. Ubuntu 23.10
and later restrict those by default, so Chromium falls back to its SUID helper,
which must be root-owned and mode 4755. An AppImage is mounted in /tmp as the
invoking user, so that can never hold, and Chromium aborts rather than run
unsandboxed. Every Ubuntu 23.10+ user was affected; the game had never started
for them.

The launcher now detects the restriction — apparmor_restrict_unprivileged_userns,
max_user_namespaces, or unprivileged_userns_clone — and only then passes
--no-sandbox, printing a one-time notice with the sysctl to restore it.
Detection rather than always passing the flag: dropping the sandbox
unconditionally would weaken every machine, including the majority where it
works. NIGHTCELL7_NO_SANDBOX=1 forces it.

Second bug, in the delivery path: `nightcell7 update` short-circuits on
"Already on the latest version" and never rewrote the launcher, so this fix
would never have reached anyone already on v0.1.0. Launcher writing is split
into write_linux_launcher() and update refreshes it even when the version has
not moved — the launcher carries real logic and is fixed independently of the
game.

Verified against a stub AppImage on a kernel with
apparmor_restrict_unprivileged_userns=1: the flag is added, the notice appears
once, an unrestricted kernel keeps its sandbox, the override works, and
`update` on a matching version rewrites the launcher.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tures

**The weapon rendered as a solid white blob on every shot.** Three separate
causes, each found by screenshotting the real fire path in a browser rather
than reasoning about it:

  1. The scene runs a GlowLayer for the sodium lamps, and it blooms any
     emissive material. A 3.5 cm emissive tracer came out as a metre-wide beam
     that washed out the frame and hid everything else. The effect meshes are
     now excluded from it, and the tracer is 1.8 cm and additive.
  2. `environmentIntensity` is 2.9 scene-wide, which the yard needs — but a
     near-polished metal held 30 cm from the camera reflects that straight past
     the bloom threshold. It is per-material, so the viewmodel now gets its own
     instances at 0.5.
  3. The muzzle flash light sat *at* the muzzle, centimetres from the weapon,
     so inverse-square falloff gave the gun orders of magnitude more light than
     the scene. `excludedMeshes` did not help — the viewmodel is instantiated
     from an asset container and instances take lighting from the source mesh —
     so the light now sits 0.9 m downrange, which fixes it geometrically. The
     flash quad also went from 0.22 m to 0.075 m: at 0.3 m from a 90-degree
     camera the original covered a third of the screen.

**Impacts were invisible.** They were firing correctly, but 2-7 cm sparks go
sub-pixel past about thirty metres, so a hit down a lane looked like the round
simply stopped. Sparks and dust are larger, more numerous and longer lived, and
there is now a billboarded impact flash scaled with distance so a hit reads at
any range. Verified in-browser: the tracer terminates on the crosshair and the
flash is clearly visible at the hit point.

**The gallery kept showing old screenshots.** Next's image optimiser serves
/_next/image with `Cache-Control: public, max-age=14400`, and the capture
filenames are deliberately stable, so a returning visitor kept the previous
build's frames for four hours after a re-capture. The server was serving the
new bytes the whole time. `minimumCacheTTL: 0` plus an explicit header on
/media/yard/* means always revalidate; with the ETag Next already sends, an
unchanged image costs a 304 rather than a re-download.

A cache-busting query string was the obvious fix and does not work: Next
rejects a query on a local image unless whitelisted in `images.localPatterns`,
which matches `search` exactly, so a per-capture value cannot be whitelisted
and the build fails. Documented in capture-src.ts so it is not retried.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ar fighter

**There was nothing to shoot at.** `character.glb` was built and then had no
consumer — the multiplayer entry point that would draw remote players does not
exist yet — so the sandbox was an empty yard with a working rifle and no enemy.
PRD §40 puts "one enemy and one rifle" ahead of everything else.

Five training targets now stand in the yard. They take hits, topple, and stand
back up after three seconds. Deliberately targets rather than AI: there is no
animation system and no client-side bot pathing, and a figure sliding around
frozen in a standing pose would look worse than one that honestly stands still.

Hit detection is presentation only, like the weapon effects — the server owns
hit registration through resolveHitscan. A target only counts if it is nearer
than the world geometry behind it, so one standing behind a container cannot be
shot through it.

**The character read as a Western regular** — high-cut ballistic helmet, NVG
mount, plate carrier with shoulder yoke, pauldrons. That is the wrong silhouette
for a story whose stated premise is that neither nationality is the default
villain, and whose two factions are fictional.

Restyled as an irregular fighter: cloth head wrap with a tail and face cloth,
field jacket, simple chest rig with rifle pouches and a bandolier, canvas
satchel, rolled blanket, cloth knee wraps. No helmet, no optics mount, no
armour plate.

It stays unmarked and fictional — no insignia, no script, no religious or
ethnic signifiers. Faction identity is carried by an armband and a head-wrap
band in the team colour and nothing else. CLAUDE.md gates Iranian/Farsi content
on native review that has not happened, and a caricature would fail that review
and the PRD's own §21.12 launch gate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ralyodio

Copy link
Copy Markdown
Contributor Author

Superseded by a branch cut from current main — the original was branched off a squash-merged branch and conflicted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant