68 KiB
MEMORY
Session memory for durable decisions, state, and gotchas. Keep concise, remove obsolete entries.
Decisions
-
DuckDuckGo collection tool (2026-11-03):
tools/refcollect.pywraps theduckduckgo_searchpackage (installed locally; renamedddgsupstream). All outbound requests share oneTokenBucket(--rate0.5 req/s,--burst3) and a polite retry routine: full-jitter exponential backoff,Retry-Afterhonoured, permanent HTTP failures fail fast, six attempts maximum. Requests send a complete Firefox header set, not just the UA. DDG rate-limits by IP for a while after probing, so the per-venue collections should still be run in small spaced batches. Downloads arelocal_only/unchecked-localand never committed. -
Comedy Store multi-model concepts (2026-11-03): four scene pairs (facade, street, strip, visitor) now live in
docs/screens/concepts/joke_shop/, each anchored on a local-onlytmp/research/comedy-store/photo with onegoogle/gemini-2.5-flash-imageand oneopenai/gpt-5-image-miniframe (.refgen.jsonrecordsunchecked). Gotcha: DDG photo 07.jpg was titled "Ramada hotel" but is actually an interior stage shot — Gemini refused it with a text-only message; the hotel/parking ramp reference is still missing from the local set. The map-recreation plan is tracked inTODO.md§3D model re-evaluation → K. Comedy Store map recreation. -
Jimmy Kimmel Live = starting point (2026-11-03): the building is the game's starting point: the Hollywood first shift and the late-night TV Studio arrival both begin at the El Capitan Entertainment Centre housing the Kimmel stage/studio. Four photo-anchored entrance concepts (day + night × gemini + gpt) are in
docs/screens/concepts/hollywood_boulevard/, and the fact is stated indocs/hollywood-boulevard-map.mdanddocs/gameplay.md. -
Jimmy Kimmel Live research (2026-11-03): three new local-only DDG sets —
tmp/research/jimmy-kimmel-stage/(15 frames, stage/audience),tmp/research/jimmy-kimmel-entrance/(12 frames, the El Capitan entrance with the Kimmel marquee/signs) andtmp/research/hollywood-block/(11 frames, the Masonic Temple/El Capitan block). The Kimmel entrance frames confirm the starting-point building is the El Capitan Entertainment Centre with "Jimmy Kimmel Live!" signage over the same six-column facade. Some DDG frames arrive as AVIF mislabeled.png/.jpg;refcollect.py sheetnow skips unreadable files and the AVIF frames were converted withffmpegfor the contact sheets. -
Local-only reference override (2026-11-03, owner decision): for development-only photo research the licence gate does not apply. Downloads for this track live in
tmp/research/(gitignored) and may come from DuckDuckGo image search, street-view frames, neighbouring properties and buildings across the street — never committed, never shipped, never used as a committed asset. The moment a frame is promoted todocs/screens/refs/<venue>/it passes the sidecar licence + privacy gate andCREDITS.md; the committed boards and the deferred Gaussian splatting gate are unchanged. Tracked inTODO.md§3D model re-evaluation → A. -
Godot 4.7.2
--importgotcha (2026-11-03): with a stale.godot/cache,--import(and--editor --quit-after N) scans new image files but never imports them — no.importfiles are created, andpreload()of the new texture then fails to compile every dependent script (the confusingCould not resolve external class member "spec"comes fromgame_manager.gd, not the texture). A brand-new file still not importing after several runs is the signature. Workaround that works: run the import in a scratch copy with.godot/removed (rsync -a --exclude .godot --exclude tmp --exclude .git ./ /tmp/proj-copy/thengodot --headless --importthere), and copy back the generated.png.importplus the matching.godot/imported/*.ctex/.md5. CI is unaffected (it imports fresh and.importfiles are committed). -
Facade-realism pass complete (2026-11-03): the masonic facade now uses the photo-anchored
limestone_ashlarfield (see the ashlar decision below), the event banners carry a warm self-light (0.14base,0.28night /0.028day via_marquee_emissive), and the marquee sign face + bulbs are test-verified to brighten at night (test_marquee_emissive_balanced_at_night). The marquee canopy/sign box stayblack_metalby design — it is the fictional "GIG LINE" marquee over the Masonic Temple, not the red El Capitan Theatre marquee next door (which is represented by the blade-sign neighbour). Day and night captures promoted; both three-up boards rebuilt. -
Image-recitation filter (2026-11-03): Gemini image models (
google/gemini-2.5-flash-image) reject a reference image withfinish_reason: content_filter/native_finish_reason: IMAGE_RECITATIONwhen the photo shows a well-known branded facade (the Comedy Store wall) — the same photo is fine as documentation evidence, just not as a generation anchor. Workaround: anchor on a less-branded frame of the same block (the hillside photo worked), or generate the texture text-only when the reference adds no geometric detail (the dark stucco tile is text-only for this reason). -
Ashlar limestone pick (2026-11-03): the masonic facade field uses
limestone_ashlar[_normal].png, generated reference-anchored on the 2009 CC BY facade photo, 768 px tile ≈ 4.6 m of wall (~167 px/m, inside the 40–400 band).stone_stepsstays for trim; facade tint warm-shifted toColor(0.85, 0.81, 0.74). Night emissive balance and banner/canopy materials are still open in the facade-realism item. -
OpenRouter reference-anchored generation (2026-11-03): the
/v1/imagesendpoint takes no input image; the working image-to-image path isPOST /v1/chat/completionswith acontentarray ({"type": "image_url", "image_url": {"url": "data:...;base64,..."}}).google/gemini-2.5-flash-imagehonours reference geometry well (kept the El Capitan column rhythm, motto band, banner slots and blade sign from a photo); response images arrive asmessage.images[].image_url.url(data URL) — the parser also falls back to a data URI inside the message text. Implemented ingenerate_textures.generate_image(reference_path=...)andscene_concepts.py generate --reference; licence gate +.refgen.jsonprovenance sidecar. -
Recent-photo research, gitignored (2026-11-03): Commons geosearch around the El Capitan block found usable last-12-months imagery (Dolby billboards 2026-09-30 CC0 x6, Dolby street + fresh star 2026-07-17 CC0, El Capitan frontage with an impersonator 2026-02-25 CC BY-SA 4.0, Celia Cruz marker and a star series 2025-09-28 CC0, Thunderbolts premiere 2025-04-28 CC BY-SA 2.0). Eight candidates downloaded to
tmp/research/elcapitan-2026/withmeta.json— never commit these; committed references still go through the sidecar licence gate. Finding: no recent photo frames the El Capitan facade straight-on in daylight; the 2009 CC BY facade frame stays the geometry reference. Geosearch (generator=geosearch,ggscoord, radius,ggsnamespace=6) is the reliable "photos of this exact location" query; keyword search is not. Plan for reference-anchored generation (--reference) is in TODO before any tool code is written. -
TV Studio shares the Hollywood location (2026-11-03, owner clarification): the Late Night TV Studio venue is the same El Capitan Entertainment Centre block at 6840 Hollywood Blvd (the building houses Jimmy Kimmel Live!), restaged for a late-night arrival with a bulb canopy, black sedan and barrier line.
VENUE_SPECS["tv_studio"]is a 30.0 m variant of the 29.6 m Hollywood facade. One reference board (docs/screens/refs/hollywood/) serves both venues; do not create a separate TV Studio photo board or treat it as a distinct real site. -
Hollywood reference board (2026-11-03): five Wikimedia photos live in
docs/screens/refs/hollywood/withSIDECAR.md(licences: CC BY 2.0 x2, CC BY 3.0, PD x2), cropped/re-encoded to <=1600 px JPEG q85 with EXIF stripped, foreground crowd cropped out of the facade shot. The board is linked fromdocs/hollywood-boulevard-map.mdand the CC BY files are credited inCREDITS.md. Gotchas: the Commons API needs a descriptive User-Agent and rate-limits rapid thumbnail downloads (HTTP 429) - fetch all metadata in onetitles=A|B|Cquery and space the downloads.check_docsrequires each image to be linked from its folderREADME.md, so each venue folder needs bothREADME.md(board) andSIDECAR.md(provenance). The board grew to nine photos on 2026-11-03 (El Capitan Theatre neighbour CC BY 2.0, terrazzo + star close-up CC0, night street context CC BY-SA 3.0). The CC BY-SA frame is redistributed unmodified for study only with no derived asset, and that review is written into the sidecar; a licensed night photo of the hero facade is still the top missing reference. -
Joke Shop reference board removed (2026-11-03): the committed Comedy Store photo board was removed in favour of the local-only
tmp/research/comedy-store/research set (owner override: development reference, no licence gate, never committed). The three-up board's photo panel remains a historical public-domain frame; the block's current reference lives in the gitignored research folder. -
Blender Skills Pack installed locally (2026-11-03): the MIT
blender-skillspack (v1.3.0, 94 skills + a sharedreferences/folder) is installed at.agents/skills/blender-skills/for Blender asset work..agents/is gitignored and.agents/.gdignorekeeps Godot's scanner out, so it never ships, exports or affects tests. Skills link shared docs as../references/<file>.md, so keep the pack flat when updating:cp -a <pack>/.claude/skills/. .agents/skills/blender-skills/. Pi gates project.agents/skillsbehind project trust, so the trust prompt must be accepted before the skills load. -
Priority order (2026-11-03):
TODO.mdnow leads with the unversioned 3D model and local map re-evaluation track, then Character development, then the versioned 0.10.0 release candidate and later releases. Local map workstreams are H–M inside the same 3D track. Do not attach a placeholder version to an unversioned track; assign one only when its scope enters a release. Character development is generator-first (ADR-0015); the map work evaluates OSM/3D data, licensed reference imagery, AI-assisted composition and development-only Gaussian splatting. -
Commit/build discipline (2026-11-03): commit each coherent verified change as it lands, not only at milestone end, and push shareable commits. Every major, minor and patch version requires a
CHANGELOG.mdentry, a localtools/build_releases.shbuild and artifact/checksum verification before tagging. CI tag builds are the second gate, not a substitute. -
Gaussian splatting / photo-derived maps (unversioned local track): expanded in
TODO.md"Gaussian splatting evaluation". Accepted photo channels are team captures, permissioned user/volunteer photos and clearly licensed downloads; every set needs a provenance/consent sidecar. Sparse web photos are reference/training supplements, not a splat source. Five enhancement paths are ranked (reference/QA, texture bake, mesh proxy, distant backdrop, single-facade user-photo supplement). Splats stay offline/development-only until the ADR proves licence, privacy, Godot Compatibility/Web cost and mesh-based collision; no runtime splat dependency in 0.10.0. -
NPC model variety decision (2026-11-03, ADR-0015): CC0 is acceptable for shipped assets; CC-BY only with attribution in
CREDITS.md; no service assets with redistribution restrictions. The procedural generator is the primary path. Four crowd-variety specs were prototyped with the existing builder (npc_crowd_abun/slim/sunglasses,npc_crowd_bbald/broad/beard/shoulder bag,npc_crowd_ccoat/umbrella,npc_crowd_dhood/cup); all audit clean at 4,550–5,258 tris / 4,356–5,086 verts. Prototype assets stay intmp/npc-research/generator_proto/; do not add them to the current release candidate. Quaternius is a reference, not a shipped base: welding bringsCasual_Hoodiefrom 12,518 to 3,199 verts and a 0.75 decimate to 2,423 verts / 4,654 tris, and its Idle/Walk clips read well, but the T-pose 62-bone realistic rig/proportions do not match the arm-down 13-bone contract. A contract rebuild is a no-go without a body proportion warp; a second rig/animation path needs its own ADR. MakeHuman is deferred to a separate local pipeline: the CC0 base is 19,158 verts / 36,972 tris and decimates to 3,780 / 6,468 at 0.35, but needs retopology, a custom rig and project clothing. Research previews are committed underdocs/blender_previews/sheet_npc_{sources_research,crowd_prototypes}.jpg; the CC-BY entries (CesiumMan/RiggedFigure, © Cesium) are attributed inCREDITS.mdand are development-only. -
0.10.0 balance protocol:
docs/balance-acceptance.mddefines targets and denominator/cohort rules for the 36 venue/difficulty/phase cells, seeds 1–100, access/emergency error rates and matched calm vs emergency-first comparisons. These are acceptance criteria, not measured results; sweeps/playtests remain open. Inspection found_read_npc_truth()is type-derived, not behavior-derived; the pending emergency fairness review must cover this oracle mismatch rather than treating internal truth as independent evidence. -
The visual-alignment path/loader/panel/HUD work and the realism lighting pass are complete and recorded in
DONE.md; remaining visual items are the realism follow-ups inTODO.md. -
Character mesh cap (2026-11-02): the eight library variants now use 8,000 triangles / 8,000 glTF vertices per character (
CHAR_MAX_TRIS/CHAR_MAX_VERTSintools/blender_assets.py), up from 6,000/5,000. The frozennpc_basefallback keeps its 6,000/4,000 contract. This headroom is specifically for the remaining face, hand-grip and clothing realism work; re-run the 24-NPC and Web/Compatibility budgets as geometry lands. Do not add detail without measuring, and do not silently lower the cap back to 5,000. -
PLAN.mdis a compact product/design document, not an implementation log. Historical control and v0.3.0 specs were folded into its Presentation and Controls section; put new step-by-step work inTODO.md, not the plan. -
Editable Blender sources under
assets/models/blender_src/are gitignored and untracked (46.blendfiles removed from the index on 2026-11-01); the.gdignoremarker stays tracked. -
screen_designs.py judgedefaultgoogle/gemini-2.5-flash-liteworked on 2026-11-01 (7 match / 4 review / 0 diff);--modelremains available. -
capture_realism.gdparses--portrait-type=N; it was documented but ignored before the gear pass. Portrait captures need the requested NPC type to have spawned, otherwise the stage can finish with an empty frame; use the Blender character previews (render --only npc_coat) for deterministic gear checks. -
Headgear: the firefighter helmet was lifted 2 cm after the brow/face pass; cap and hood were visually checked on
npc_vest/npc_hoodie. The beanie is unused and shares the cap shell. -
Gear fit is measured with
python3 tools/blender_assets.py fit(stdlib, reads the GLBs): head width uses skin vertices weighted to Head in y 1.55–1.81; clothing radius is the cloth skirt in y 0.30–0.52 with a pants hip fallback. The command reports per-character gear boxes, head-band status and x/radial skirt clearance;test_blender_assetscovers its shape. -
Post-realism work is split by theme, not by date: finished items are archived in
DONE.md, andTODO.mdcarries the 0.x gap register and milestone roadmap. New work belongs there, not in a revived historical plan document. -
Graphics v0.6.0 plan (completed 2026-11-01; ADR-0013): NPCs move to Blender-authored skinned meshes.
tools/npc_pipeline.pybuildsassets/models/generated/npc_base.glb;NpcAvatarkeeps the procedural_build_body()fallback. PreserveRig/Head, the limb node names, and the seed-deterministicoutfit_for/layer_for/hand_item_forhelpers. Blender = flatpak 5.2.0 LTS, sandbox cwd/app/blender→ absolute paths for--python. CI stays offline (committed assets). -
Engine: Godot 4.7.2 stable (official editor build,
~/.local/bin/godot). The system's preinstalledgodot-runnercannot load projects from arbitrary paths because it was compiled without path overrides; use the official editor binary instead. -
Language: GDScript. All cross-file references use explicit
preload()constants (notclass_nameglobals) so scripts compile in headless--scriptruns without the editor's class cache. -
Test framework deviation: the project uses a custom minimal headless test runner (
src/test/test_runner.gd) instead of GUT, because GUT needs the editor plugin. CI runs it with--headless. -
Eventing: no autoload signal bus.
ShiftCoordinatorowns anEventBusinstance (src/core/event_bus.gd) passed to subsystems; subsystems emit only when a bus is assigned. Autoloads at parse time are unreliable under--script. -
Static signals are not available in Godot GDScript ("Expected func or var after static") — do not use them.
-
GDScript lambdas capture local variables by value; mutating a captured local inside a lambda does not affect the outer variable. Use member functions for accumulators in tests.
-
Shift length is the scenario's
shift_duration_s(900 s in all three venues). Tests and the screenshot tool override elapsed time; they do not change the default. -
RNG: NPC reactions and event scheduling are seedable for deterministic tests.
-
Mouse look (v0.7.3): guard look is handled in
GuardController._input()(before GUI), not_unhandled_input; focus/click-back recaptures the cursor and the HUD is made mouse-transparent recursively. Regression tests cover yaw/pitch and HUD filters. -
v0.9.0 production pass: CI/release, local dev and project now use Godot 4.7.2; the version-adaptive light falloff remains for older editors. Audio settings are
master_volume/sfx_volume/ambience_volume; accessibility hasreduce_motionandcaptions_enabled(HUD log is the caption surface). Budget smoke is 24 NPCs (6.94 ms headless). Coat/dress stride is scaled to 0.76 to reduce hem clipping. Localization remains English-table based; pseudo-locale/VO/music are post-0.10. -
Radio submit fix (v0.8.1):
RadioPanel.action_buttonnow connectspressedfor normal reports;button_down/upremains exclusive to emergency hold-to-confirm. Regression test covers both modes. -
Campaign/v0.8.0:
Campaignowns Hollywood → Joke Shop → TV Studio progression.campaign.json,shift_results.json,settings.jsonand versionedshift_state.jsonpersist separately; pause saves a checkpoint, review Continue advances, Settings resets with confirmation. Difficulty keys areeasy/normal/hard(ai_difficulty); scripted QA bypasses tutorial unless--tutorialis passed. Help is searchable and generated fromInputBindings.REMAPPABLE. -
Tutorial (0.8.0 work):
docs/tutorial.mdis the eight-beat design;src/game/tutorial.gdis the pure state machine,TutorialOverlayis the non-blocking card, andGameManagerwires it with a safe visitor, suppressed spawns, persisted completion/skip and Settings replay. Scripted test/capture runs bypass it unless--tutorialis passed;smoke_tutorial.gdcovers completion/replay/skip. -
Pre-build NPC revisit (2026-11-02): the final-context harness is
src/test/capture_npc_review.gd(8 library models, 3 distances, 2 venues, day/night). It found two P1 gear-fit issues: the lanyard badge was hidden under the jacket and the firefighter helmet brim sat over the eyes. Both are fixed at runtime withNpcAvatar.GEAR_OFFSETS(lanyardforward 4 cm,firefighter_helmetup 3 cm); the authored GLBs and the fit report remain asset-space measurements. The guard model is staged through the review-onlycharacter_overridebecause it has no spawned NPC role. Signed off 2026-11-02: 96 final-context captures, no P0; lanyard/helmet P1 fixes; the backpack straps, caps, low-poly hands/faces and optional close-range flashlight wash were accepted or deferred. The 24-NPC budget is 6.94 ms, the gallery is 13 good / 0 bad, the screen-design judge is 13/13 and a clean portrait measured a 0.732 vision gap against the photoreal concept. No release build, version bump or tag was created at this gate. -
NPC texture resolution (0.9.5, 2026-11-02):
tools/npc_pipeline.pynow generates 256² albedo/normal/ORM maps instead of 128² (AI shirt/trousers are resized to 256). The base and all eight library GLBs plus AO maps were rebuilt; texture files total ~0.5 MB, eight GLBs ~5.75 MB, and the 24-NPC smoke is 6.94 ms. -
0.9.5 texture/UI audit (2026-11-02): all 21 screen-art textures are 1280×720 and referenced by their screens; access cards are 256×128 and HUD icons 64×64, with no oversized or unused runtime texture. The 24-NPC smoke now reports 168 meshes / 131,616 triangles / 6.94 ms (50 ms budget). Compressed payloads are picks 24.55 MB, UI art 22.67 MB, NPC maps 0.48 MB. Legacy root textures remain only for
generate_textures.pyand the asset test. -
Environment surface audit (0.9.5, 2026-11-02):
capture_realism.gdgainedsurfaces(Hollywood pavers/asphalt/limestone/facade) andsurfaces_joke(neighbour awning, canopy, pavers) stages with the HUD hidden. The Walk of Fame band now usesT_CONCRETEas dark terrazzo; the neighbour awning uses a neutral tint (0.62) and 4× UV so the red/cream stripes survive; the skyline shader fades its lower 42 m toward a per-phase haze colour. Brick/asphalt picks needed no replacement. No seams or broken UV scales found. -
NPC fabric UV/AO (0.9.5, 2026-11-02): cloth/pants use
Materials.NPC_FABRIC_UV_SCALE = Vector3(4,4,1), andNpcAvatarskips the baked per-character AO on those two slots. The 256² AO was magnifying into large blotches; the fabric now reads as a fine weave. Skin/hair/gear keep baked AO. Testtest_fabric_uv_scale_avoids_the_baked_ao_patternguards this. Current suite is 276/276. -
NPC visual baseline (0.9.4, 2026-11-02):
src/test/capture_npc_baseline.gdis the neutral-studio 8-variant × 6-view baseline (front, three-quarter, profile, back, full body, crowd). It uses a fixed orbit camera, mid-grey studio backdrop, key/fill lights, no HUD/crowd/animation; raw frames stay intmp/, andtools/npc_baseline.py sheetbuilds the committedsheet_npc_baseline.jpg. Criteria and measurements are indocs/npc-visual-review.md. The first ranked gaps were: tourist photo prop duplicates the backpack silhouette; shallow/faceted face; backpack straps read as rigid bars. The tourist prop now hides outside photo/film poses and the backpack straps were refit as flat forward bands (GLB + AO rebuilt); the face remains P1. Before/ after sheet:sheet_npc_baseline_compare.jpg. -
NPC proportion audit (0.9.4, 2026-11-02):
tools/npc_proportions.pymeasures the exported GLBs (height, skin-head size, upper-chest shoulder silhouette, pants hip width, Spine-to-shoulder torso, Spine-to-ground leg, shoulder-to-hand arm, shoe foot length). All eight variants pass the stylized guide indocs/npc-proportion-audit.md; head ~0.190 (upper 0.22) and broad-build shoulders ~0.370/hip ~0.225 are near the upper bounds, not outliers. No skeleton/mesh rescale was needed; run the report after any body change. -
NPC 0.9.4 visual passes (2026-11-02, consolidated): the neck/shoulder, face-depth, torso/hip, footwear, face-detail and hand passes all landed and are baked into
tools/npc_pipeline.py/NpcAvatar. The durable bits are theNpcAvatar.HAND_ITEM_FORWARD = Vector3(0,0,0.04)offset (hand gear is authored around the wrist proxy) and the zero-area-triangle cleanup after the neck branch (see the Blender-tooling note in Known Issues). Exact per-pass values, review sheets and current per-variant counts live indocs/3d-model-review.mdand the code; the library cap is 8,000/8,000. Open: true finger curl/thumb contact and umbrella/sign grips. -
UI flow smoke (2026-11-02):
smoke_ui_flows.gdboots the real scene and exercises inspection allow/deny, normal vs emergency radio, Help filtering, wand allow/deny and review return-to-menu. Keep this smoke in the 0.9.6 handoff; it complements the unit tests that only exercise panels. -
Destructive actions (2026-11-02):
SettingsPanel._open_destructive_dialog()owns one confirmation instance for reset/replay; repeated presses focus the existing dialog, OK emits once, Cancel is inert. Emergency send remains the RadioPanel hold path;_holdingclears before submit so button-up cannot double-send. Tests cover both. -
Font/layout limits (2026-11-02): HUD scale is clamped to 0.75–1.75 and only applies to HUD labels.
Hud.set_target_label()measures the scaled font, widens the chip to 180–420 px and re-measures after a scale change; top labels clip.UiStyles.apply_button()setsclip_text+ ellipsis so long localized labels cannot force a panel past the viewport. Do not replace these with Control.scale. -
Modal close (2026-11-02):
GameManager._close_top_modal()is the single Esc/right-click path for radial, inspection, wand, radio, timeline, Help, Settings, Pause and review. Settings remembers whether it came from the menu and restores it on apply/cancel;_refresh_freeze()recaptures the mouse and_show_start_menu()keeps it visible. Do not add a new modal without adding it to this close path. -
UI focus (2026-11-02):
src/game/ui/ui_focus.gdlinks visible/enabled controls into a wrapping ring; each modal sets default focus on open. Start menu uses its own focus-target list so Continue, venue rows and footer actions share one ring.GuardController._ensure_gamepad_bindings()adds A/B toui_accept/ui_cancelso pad-only users can confirm/cancel; hidden Continue and disabled timeline buttons never enter the ring. -
Button states (2026-11-02):
src/game/ui/ui_styles.gdowns the five action-button states and font colours for the dark session/UI shell. UseUiStyles.apply_button()for every newButton; CheckButton/OptionButton keep the engine theme. Timeline Previous/Next disable at the ends; F1 tutorial skip is keyboard-only by design. -
Version and roadmap renumber (2026-11-02): source was 0.9.3 at the UI polish milestone, 0.9.4 after the NPC pass, now 0.9.5 after the texture and material pass, and 0.9.6 as the integration checkpoint. Next work is the 0.10.0 minor release candidate.
TODO.mdplans only 0.x patch/minor increments; no major-version scope is defined yet. Thev0.9.6tag is the release trigger; no tag was created for 0.9.3. -
Priority change (2026-11-02): the map optimization, pre-build NPC revisit, 0.9.3 UI polish, 0.9.4 NPC visual optimization, 0.9.5 texture/material pass and 0.9.6 integration are complete and in
DONE.md. 0.10.0 release-candidate work is next. The source version is 0.10.0-rc.1. -
0.10.0-rc.1 build (2026-11-03): clean worktree
a9658ecproduced five artifacts (Linux 161.5 MB, Windows 197.2 MB, macOS 148.0 MB, Web 97.8 MB, HQ 241.6 MB); Web/HQ include player docs, SHA-256 manifests are local, Linux/HQ launch headless and Web files serve HTTP 200. Not published: external playtest, AI/legal approval, macOS signing and the Windows/macOS matrix are still open. -
Release/legal blockers (0.10.0 work, 2026-11-03): macOS remains unsigned (quarantine instructions only); AI image provider terms/commercial rights and external storefront/rating approval are not cleared; external playtests and the full hardware matrix are pending.
CREDITS.md,PRIVACY.md,docs/accessibility.mdanddocs/known-limitations.mdrecord these explicitly rather than claiming approval. Do not tag 0.10.0 as final until these are accepted or cleared. -
Save migration (0.10.0 work, 2026-11-03): fixtures in
src/test/fixtures/saves/cover 0.8 settings/campaign/history and a 0.9.0 checkpoint._load_jsonusesJSON.new().parse()so corrupt player files fall back silently instead of logging a parse error; atomic writes leave the old file intact when the temp path cannot be written. The migration suite is 9 tests; total 276. -
0.9.6 integration (2026-11-03): no gameplay/content changes. A clean detached worktree at
f5b8ef2rebuilt Linux/Windows/macOS/Web/HQ without the dirty tree's ignoredtmp//build/payload; the in-repo build was 1.6–3.1 GB per artifact because those folders were packed, so always build release artifacts from a clean checkout/worktree. Verification: 263/263 tests, 101/101 Python, 14 smokes, 24-NPC 6.90 ms / 168 meshes / 131,616 triangles, gallery 21/0, screen judge 21/21, realism judge overall 2.1 / gap 0.291. -
Release v0.9.4/v0.9.5/v0.9.6 (2026-11-02/03): tags
v0.9.4(a57b86c),v0.9.5(4bc6390) andv0.9.6(4bb2896, the 0.10.0 integration checkpoint) each published seven assets (Linux, Windows, macOS, NSIS setup, AppImage, Web, Desktop HQ) with full CI runs; release pages live underreleases/tag/. Never reuse a tag — future fixes need a new patch tag. Full per-version records stay inCHANGELOG.md. -
Hollywood landmark map (2026-11-02): the default venue is the real El Capitan Entertainment Centre block at 6840 Hollywood Blvd, not a generic brick entrance. Its reference/mapping notes live in
docs/hollywood-boulevard-map.md; the map-budget test (test_hollywood_geometry_stays_inside_the_map_budget) currently measures 58,884 triangles across 409 meshes. The facade uses the shared smooth limestone (stone_steps) texture; the noisier granite pick was removed. -
Light falloff gotcha (2026-11-02): in Godot 4.7
omni_attenuation = 0.0is constant falloff, not physical falloff. It made the night facade a flat yellow wall and the street lamps wash the set. All local lights now use1.0; the Hollywood facade was retuned with 0.50/0.28 washes, 1.3 sconces and 2.2 street lamps; the Joke Shop uses 1.8 sconces, 2.8 canopy downlights, 3.0 entrance, 1.35 ground uplights and 2.2 sign. Flashlight spot attenuation is also1.0. Do not reintroduce0.0as a "modern" branch. -
Joke Shop map (2026-11-02): the venue is now a 27 m Sunset Strip club based on The Comedy Store at 8433 Sunset Blvd, not the old brick alley.
docs/joke-shop-map.mdcarries the OSM/Commons references and simplifications;docs/screens/concepts/joke_shop/holds the new day/night/across-street frames. The map-budget test measures 22,680 triangles / 454 meshes. The hill behind the club is a custom hand-built ramp with an unshaded, fog-disabled shader: a rotatedPlaneMeshwas edge-on and scene fog erased it from the street camera. Local light names areJokeSign,JokeCanopy_*,JokeEntrance,JokeSconce_*,JokeGround_*;set_night_mode()owns their energies and the hill tint. The latest local realism judge reports a 0.454 reference gap on the night entrance; the concept is photoreal while the map keeps stylized materials, so texture/foliage detail stays with the 0.9.5 texture pass rather than the map milestone. -
Docs validation (2026-11-02):
python3 tools/check_docs.pyrequires a README only in directories containing tracked files, verifies local links and requires every tracked image in a folder to be linked from its README. Gitignored generated files (notablydocs/screens/realism/) are exempt; the tool also has unit tests intools/test_check_docs.py. -
Documentation ownership (2026-11-01):
docs/architecture.md§12 defines the source-of-truth map and consolidation rules. Obsolete/historical plan documents are removed rather than kept; current work belongs inTODO.md. README/development-status/Blender AO/Forward+ stale claims were corrected. The 0.x gap register and roadmap live inTODO.md. -
Vision judge (v0.7.2):
tools/openrouter_judge.pyreads/api/v1/models, filters image-input models withresponse_format, and ranks by advertised prompt/image/completion prices.--model auto(default) uses the cheapest (z-ai/glm-5.3-flashon 2026-11-01) with fallbacks; explicit--model/GIG_LINE_JUDGE_MODELwins. Auth errors abort, provider/model errors fall through. Full screen judge 13/13 match, 0 diff; A/B judge ~$0.006. -
Desktop HQ preset (v0.7.1):
packaging/desktop_hq/override.cfgswitches a copied executable to Forward+;Materials.forward_plus_active()gates volumetric fog/SDFGI andlight_volumetric_fog_energy, andMaterials.modern_light_falloff()compensates for Godot 4.7's much harsher local-light falloff. LATE volumetric density is 0.020 with flashlight volumetric energy 3.0.tools/build_releases.shpackages the Linux/Windows binaries + override intogig-line-desktop-hq-<version>.zip;publish_release.shuploads it when present. A/B report:docs/screens/realism/2026-11-01-forward-plus/(entrance gap 1.568→1.187). -
Godot toolchain (updated 2026-11-01): local
~/.local/bin/godot, CI andproject.godotnow use Godot 4.7.2; 4.3 remains only as a local backup binary. Export templates for 4.7.2 are installed under~/.local/share/godot/export_templates/4.7.2.stable. Godot 4.7 writes*.gd.uidfiles; they are kept out of this clone's status via.git/info/exclude. CI downloads the binary/templates directly; Gitea Actions cache is currently unreliable (see CI cache evaluation below). -
Blender invocation (v0.6.0):
flatpak run org.blender.Blender --background --python /run/media/system/DATA/Documents/06-games/security-game/tools/npc_pipeline.py -- build. The flatpak cannot read/tmp; use absolute repo paths.assets/models/npc/.gdignoreis required, otherwise Godot's editor tries to import the committed.blendand hangs during--import(also add.gdignoreto local build/squashfs folders when scanning is slow). -
Skinned NPC integration (v0.6.0):
NpcAvatar._build_skinned_body()loadsnpc_base.glband keeps_build_body()as fallback (use_skinned_model=falsein tests). Bone pose rotation is the absolute local orientation, not an offset from rest: compose withget_bone_rest(i).basisor the whole body snaps to a broken pose. Proxies named Hip/Knee/ Shoulder/Elbow/Hand + Head marker preserve the old animation math;Rig/Headis the marker. Five material slots are tinted viaSurfaceMaterials.npc_slot(); keep_rig.scaleuniform on the skinned path. Budget: 16 avatars = 144 meshes, ~6.9 ms headless. -
NPC facing (fixed after v0.5.0): the avatar's visual front is +Z (procedural eyes and the Blender model's face). Godot
look_at()aims -Z, which made every NPC walk backwards. UseNpcAvatar.yaw_towards(dir)and setrotation.y; do not calllook_at()on avatars. Head-turn offsets use+basis.zfor the same reason. Covered bytest_yaw_faces_the_movement_direction. -
Image root (ADR-0014, 2026-11-01):
assets/images/is the runtime root for composed interface/scene art (ui/,scenes/); tiled PBR textures stay underassets/textures/;docs/screens/designs/anddocs/screens/concepts/stay reference-only; QA captures stay indocs/screenshots/. When a mockup becomes runtime art, copy/process it intoassets/images/with a new name (no overwrites) and keep a fallback in code. Mockup copies: 21 design references live inassets/images/ui/;docs/screens/designs/remains the reference-only copy.tools/screen_designs.pyreads/writes the runtime root.
UI / Presentation Decisions
- Runtime UI/scene art is loaded through
src/game/ui/art.gd(assets/images/ui/thenscenes/); missing files return null so panels keep procedural fallbacks. - Screen mockups with baked-in controls are used as backdrops plus an opaque
ArtPanelcover, not as clickable full-screen skins. - Radio, wand, inspection, and body-camera timeline panels use their reference art as backdrops; forms sit on transparent, padded panels over the covered mockup controls.
- HUD target names use a boxed chip under the crosshair; escalation has a five-segment bar below the text label. The body-camera REC/clock block starts at y=92 on the right so it clears the shift/rep/crowd text.
- Staged captures in
docs/screenshots/are committed and were regenerated on 2026-11-03 with the fulltools/check_screenshots.shrun (21 good / 0 bad, 13 PNGs changed). Re-run the whole script, never a partial capture, before replacing them. The README,docs/README.mdquick preview,docs/screenshots/README.mdanddocs/blender_previews/README.mdnow carry captioned galleries; the previews README is curated by hand (all tracked sheets linked, local per-asset PNGs described as untracked). tools/check_screenshots.shis the repeatable capture+compare smoke; both capture scripts accept--out=res://docs/screenshots. Judge verdicts on the staged set were 7 match / 4 review / 0 diff.- Venue selection used to leave
guard.input_lockedtrue and the mouse visible;_start_shift()must call_refresh_freeze()after starting the clock.
Current State
-
v0.3.0 pass (2026-10-31, historical; shipped): rendering, generated prop models, screen catalogue, aim-free selection. See ADR-0011 (selection) and ADR-0012 (materials/props).
src/game/materials.gd(SurfaceMaterials) is now the single source of surface recipes +configure_environment().GameManager.PHASE_LIGHTINGgainedexposure/glow/fog_color/fog_density/fog_energy/contrast/saturation/ambient_energy. Renderer is GL Compatibility, so SSAO/SSIL are unavailable; glow+fog+ACES are used instead and unsupported settings are ignored rather than gated in code.- World: scaled brick (uv 22,10), framed windows with per-window night emission
(
i % 3 != 2lit), stone base course, door trim/handles, curb, lane dashes (the old loop hadVector3(i * 0.0, …)and stacked every dash at x=0), street lamps use the metal profile. The two flat planters that clipped the steps were removed. tools/model_pipeline.pybuildsassets/models/generated/*.glb(bollard, bench, trash_bin, planter_box, newspaper_box, fire_hydrant) from box/cylinder specs;WorldBuilder._add_prop_model()loads them with a procedural fallback and recordsloaded_models/fallback_props. Gotcha: glTFbaseColorFactoris LINEAR — writing sRGB made every prop pastel;srgb_to_linear()is applied inbuild_glb. Gotcha: a new/changed.glbneedsgodot --headless --path . --importbeforeResourceLoader.exists()/load()sees it (test_generated_props fails otherwise).model_pipeline.py concept(OpenRouter, reads repo.env) writes per-prop concept sheets toassets/models/concepts/;dominant_colors()centre-crops and weights by saturation (a grey studio backdrop otherwise wins),assign_roles()maps colours by hue/value. Delete a concept file to fall back to the catalogue palette.- NPC avatars:
outfit_for(type, seed)(pure) gives shirt/trousers/hair/skin + accessory (cap, hood, backpack, sunglasses, lanyard) and height scale;_armsand_legsnow swing (limb_swing) while walking. KeepRig/Headnamed for focus. - Selection:
NpcFocus.rank_indices(angle then distance) —pick_index= first.GuardController.cycle_target(direction)walks nearest-firstnearby_candidates()and setstarget_pinned, which makes_update_target()skip the cone.clear_target()setsfocus_suppress_s = 0.75so the cone does not instantly re-acquire the person. New input actionswalk_slow(Shift),cycle_target_next/cycle_target_prev(wheel down / up). Right click with nothing open clears focus (Esc still pauses). - HUD:
nearby_label+set_nearby_hint(count)showhud.nearby;hud.controlsandhud.targetingmention the wheel and Shift. tools/screen_designs.py: catalogue now 19 screens (added wand_screening, joke_shop_queue, studio_arrival, crowd_panic, responder_arrival, body_camera_timeline, escalation_hud), newlistcommand,generate --only/--force(existing designs are kept by default so a draft run cannot clobber AI mockups), andjudge --dry-runno longer raisesKeyError: 'class'.- Screenshot tool leak:
capture_screenshots.gdintermittently printsObjectDB instances leaked at exit. Verified against agit worktreeof HEAD~ — the baseline leaks more often (3/3 vs 1/3), so it is a pre-existing quit-timing artifact, not a regression. Normal boots are clean.
-
Access artifacts:
src/core/access_loadout.gdbuilds from eventoverride_access+ venue policy. Spawn signal is now(profile, intent, npc_id, event). Recognition is not auto-attached to every guest.AccessVerifier.verify(..., already_screened=false)so Joke Shop scoring usescontroller.screened. Joke Shop/Studio gainedsidewalk/steps_endwaypoints. Wand venues rollcarried_itemsper spawn (seeded). Tests insrc/test/tests/test_access_loadout.gd. -
Wand shortcut is
C, notW(Wis walk).radio_emergencyinput map no longer includes W.hud.controls/hud.screen_firstupdated. -
Control pass (historical; smoke-tested): cone focus via
src/game/npc_focus.gd, accel/decel, gravity only airborne,input_lockedfreeze, Esc no longer on the guard.NpcAvatarin groupnpc; neck/hair/eyes/shoes; blue focus circle +Label3Dname;set_focus_visual.src/game/ui/action_radial.gddoughnut (LMB/Space open, A/D/arrows, Space/click confirm, Esc/RMB cancel). Crosshair CanvasLayer. Emergency stillRonly. Number keys 1–6 when the wheel is closed. -
Named input actions now exist and are used:
action_menu,confirm,cancel(Esc + RMB),inspect(E),wand(C). The old unusedinteractaction was removed. -
Runtime fixes from the smoke:
Headis +1.6 above the guard root (root sits on the floor); guard spawns in front of the door facing the street; building volume moved to z ∈ [-8,-4] so the door, awning, and marquee are visible; step treads rise toward the door;ShiftCoordinatorpulls the event scheduler once per simulated second (was every physics frame → >1200 NPCs in seconds);GameManager.MAX_LIVE_NPCS = 16caps live avatars. -
Session shell (2026-10-31, historical): the game boots into
StartMenu(src/game/ui/start_menu.gd, layer 20) instead of a live shift.GameManager._on_venue_selected()builds the scenario and calls_start_shift();_shift_runninggates input and_refresh_freeze(). Esc with nothing open now pauses (PauseOverlay, layer 18) → resume / restart / settings / quit-to-menu.SettingsPanel(layer 22) writeslook_sensitivity+hud_font_scalethroughSave.save_settings()→user://settings.json;Save.load_settings()merges overSave.DEFAULT_SETTINGSon boot andGameManager._apply_settings()pushes them into the guard/HUD. Shift review "Run another shift" now calls_show_start_menu()(not a silent restart).Vstays a debug venue bind. Smoke:src/test/smoke_session_shell.gd(menu → shift → pause → settings → menu). -
World follow-ups closed (2026-10-31): skyline backdrop restored with
T_SKYLINE(assets/textures/skyline.png, regenerated RGBA with a transparent sky) and anTRANSPARENCY_ALPHA_SCISSORunshaded material (_skyline_mat); steps + landing are aStaticBody3Dwith per-treadBoxShape3Dcolliders; marqueefont_size160 / outline 24; focus circle (blue disc in front of the face) + name tag 48px;NpcAvatar.ephemeral/lifetime_sdespawns routine traffic (authored events stay). Guard spawn height verified on the landing (guard_y=0.84). -
PLAN verbs (2026-10-31, historical): the radial has 12 slices (
Stop, Look, Point, No, Ask, Inspect, Wand, Warn, Block, Reason, Recognize, Radio).eye_contactreuses the Level 1 reaction table via_apply_interaction. Recognition is a player decision:NPCController.player_recognized+IncidentRecorder.add_access_evidence, andGameManager._artifacts_for()prepends aRECOGNITIONartifact only after the player declares it. The body camera is a real flag (GameManager.body_camera_on,Hud.set_body_camera), off by default,Btoggles it, and a formal warning arms it and writes abody_cameraincident event. Blocking also calls_station_between()to physically place the guard 0.9 m from the NPC toward the entrance._request_reason()uses the authoredreasoncontext (falling back to seededdialogue_lines) and stores the pick for the timeline. -
NPC navigation was dead (fixed 2026-10-31): avatars never moved in the live game, for three stacked reasons. (1)
agent.target_positionwas re-assigned every physics frame, which invalidates the path soget_next_path_position()keeps returning the current point — only set it when the target changes. (2) Nav path points sit on the navmesh plane (y=0) while bodies stand at y≈0.5, so a 3D "are we there" test always looked 0.5 m away and the horizontal direction was zero — compare horizontally. (3)path[0]is the old start point, so picking any point "far enough away" can pick the point behind you and oscillate — pick the first point that is closer to the goal than the current position._nav_try_frames/_move_directfallback is still there. -
Crowd reactions listen on the EventBus:
CrowdSystem.apply_event()emitsevent_bus.crowd_changed; its ownchangedsignal is never emitted.GameManager._connect_crowd()therefore connects tocoordinator.event_bus.crowd_changed(reconnect after everycoordinator.setup(), which recreates both crowd and event bus). -
Crowd presentation:
NpcAvatar.start_rubberneck(at)(idle bystanders away from the door stop and face the incident for 12 s) andstart_flee(from)(panic: walk away at 2.4 m/s, then free themselves). Someoneawaiting_interactionwithin 3 m of the door is "engaged" and does not rubberneck. Useposition(notglobal_position) for that distance check so it also works in headless tests outside the tree. -
Perf: a busy 16-NPC entrance measured ~1.2 ms/frame (824 fps with vsync off, 800 samples), so LOD/culling is not warranted at
MAX_LIVE_NPCS = 16. Re-measure before raising the cap. -
Incident recording was dead:
IncidentRecorder.start_incident()was never called, so everyadd_event/add_access_evidencesilently no-opped and the review always showed 0 incidents._begin_incident()now starts one per focused NPC (has_open_incident()guard), verbs call_ensure_incident(), andShiftCoordinator._finish_shift()closes leftovers asunresolvedbefore building the review. -
Editor-ready venues (2026-10-31):
tools/export_venues.gd(a SceneTree script) saves the three factory scenarios tocontent/venues/<id>.treswithResourceSaver; the.tresfiles round-trip throughResourceLoader(events, profiles, policy, shift phases, door lines, locations).test_venue_resources.gdalso fails if the exported files are missing or stale relative to the factory, so re-run the export after changingScenarioFactory. -
Vision judge (2026-10-31):
python3 tools/screen_designs.py judgesends the capture plus its concept mockup to a vision model (DEFAULT_JUDGE_MODEL = google/gemini-2.5-flash-lite, override with--model;openai/gpt-4o-minialso works). Reads the repo.envvia_load_env_file()(never overrides real env). Verdicts are classedmatch/review(mismatch below--fail-confidence, default 0.6) /diff(confident mismatch → exit 1) /unknown.--dry-run,--json,--onlysupported. The prompt explicitly ignores art style, because the references are stylised mockups; the live run gave 3 match, 3 review, 0 diff. Python tests:cd tools && python3 -m unittest test_asset_pipeline test_screen_designs. -
String newlines (2026-11-02):
content/strings.en.jsonneeds one escaped newline (\n), not two. Two backslashes reached the HUD as literal\nin the shift status and wand panel;test_strings_formatnow guards this. -
String formatting safety (2026-10-31):
Strings.placeholder_count()counts%specs (ignoring%%),fmt()warns and returns the template when the arg count does not match (instead of raising an engine error), andvalidate()lints the table for trailing%, unsupported conversions, and mixed positional/sequential placeholders. Tests cover all three plus a formatting pass over every placeholder key with type-correct dummy args. -
Authored scenario content (2026-10-31): events can now carry
hint(a string key logged when the avatar spawns),extra_spawns(celebrity arrivals pull in tourists/influencers via_spawn_npcwithkind: routine), andrefuses_screening(copied toNPCController.refuses_screening;_toggle_wand_screeninglogswand.refused, records awand_refusedevent, applies a small prop hit, and does not open the panel).film_boundaryis a new intent that maps tosteps_endso the influencer stands past the posted line. -
Accessibility (2026-10-31): escalation text is
"<name> [###--] L3/5"viaHud.escalation_meter(level)(pure, tested) so the cue survives grayscale. HUD font scale usesadd_theme_font_size_overrideon the core labels (set_font_scale), notControl.scale, which pushed the right-anchored clock label off-screen. Settings load before_build_ui(), so_ready()re-applies player settings after the HUD exists. -
Audio (2026-10-31):
AudioFeedback.start_crowd_bed()builds a 4 s loopingAudioStreamWAV(one-pole-filtered noise + 68/103 Hz drone, seeded) andset_crowd_level(mood)maps 0..1 to -34..-12 dB.play_radio_open/closepush short filtered-noise bursts. All procedural; with a dummy (headless) audio server the calls are no-ops._free_player()returns null when_readyhas not run (tests), and callers handle null. -
NPC theater (2026-10-31):
NpcAvatar.theater_pose()mapsintent+arrivedto idle/sit/photo/film/perform. Pure helperscrouch_for_pose,prop_lift_for_pose,sway_for_posekeep the math testable. Sit lowers the rig by 0.34 and swings the legs forward (negative X pitch; positive looked like kneeling)._photo_propis set for influencer/tourist cameras,_sign_propfor protester/performer boards; their base Y values are captured at build time so the animation offsets from rest. -
Body-camera timeline (2026-10-31):
src/game/ui/event_timeline.gdflattensreview.incidents.incidents[].eventsinto one time-ordered list (flatten), formats each row withdescribe, and steps with Left/Right or the buttons.ShiftCoordinator.started_at_msgives the review an absolute start for mm:ss stamps.GameManager._open_timelinehides the review dim (two stacked overlays made the screen nearly black) and_timeline_from_reviewrestores it on close. Smoke:src/test/smoke_timeline.gd. -
Radio dispatch was dead after the first shift:
coordinator.setup()builds a newRadioSystem, so theresponder_arrivedconnection made in_readywas lost on every venue switch/restart (responders never resolved anything)._connect_radio()now re-wires it next to_connect_crowd()after every setup. Same class of bug as the crowd signal. -
Emergency aftermath:
ShiftCoordinator.responder_outcomes/investigationsfeed the review.evacuation(emergency call, low danger, no weapon) locks the door, makes every NPC flee, marks themleave_requested, and applies asecurity_failurehit. An emergency placed whilecurrent_npc_truthhas no weapon anddanger_level <= 1appends an investigation note, recordsinvestigation_opened, and appliesfalse_report-6. -
Skyline backdrop (2026-10-31):
PlaneMeshdefaults to a horizontal XZ plane — setmesh.orientation = PlaneMesh.FACE_Zor the "backdrop" renders as a band overhead. The skyline is now a ring of four 400x60 planes at 200 m (center y=30, so building bases meet the ground),TEXTURE_FILTERdefault,TRANSPARENCY_ALPHA_SCISSORwith threshold 0.5 (0.3 left faint mip dashes across the sky), unshaded with a lifted albedo (Color(2.6,2.7,3.0)) so it is not pure black. Ground is 500x500 so no sky gap shows under the buildings. Theskyline.pngimport needsmipmaps/generate=true. -
Asset gotcha:
assets/textures/game_ready/is not imported (no.import). Use the importedassets/textures/skyline.pngpath. Untracked duplicate folder should not be referenced. -
Host sign-in follow-up is done:
REQUIRE_SIGNIN_VIEW = falsein/etc/gitea/app.ini(backupapp.ini.bak-*alongside), Gitea restarted; anonymous repo page/API return 200 and release assets download in full. Reverse by setting it back totrueand restarting. -
Release publishing (fixed 2026-10-31):
publish_release.shfalls back to the local git extraheader thatactions/checkoutwrites (git config --local --get-regexp '^http\..*\.extraheader$') because this Gitea/act runner provides noGITEA_TOKEN. Always build release artifacts from a clean checkout/worktree (the dirty tree's ignoredtmp//build/payload blew artifact size to 1.6–3.1 GB once). Per-version records live inCHANGELOG.md. -
NSIS gotchas (validated in a local ubuntu:24.04 docker repro): makensis uses
-Ddefines (never/D), resolves relative-DICON/-DARTIFACTagainst the script directory so CI passes$PWD-absolute paths; installed exe must be space-free (gig-line.exe); ICO must be classic BMP entries (ImageMagickicon:auto-resize=48,32,16; PIL emits PNG-compressed ICOs NSIS cannot load). -
appimagetool runs system
appstreamcliif present (strict, exit 3 on warnings) — project metadata now passes--pedantic. -
~/.git-credentialsholds the Gitea creds (basic-auth usable);.envonly holdsOPENROUTER_API_KEY. -
NPC avatars now walk to per-intent waypoints (
VenueScenario.waypoint_for_intent): enter/trespass/deliver/arrive → entrance, photo/film/perform/wander/roam → sidewalk, sit → steps_end.NpcAvatar.setuptakes an explicitdoor_position. -
Returning-trespasser behavior:
NpcProfile.return_probability(trespasser 0.6, mental-crisis 0.4, others 0).NPCController._maybe_schedule_return()moves a directed-away NPC toLOITERING_DISTANT;tick_return(delta)fires after 20-60s and transitions back toAPPROACH.NpcAvatar._advance_fsmre-targets the door on return. -
Perception pass:
NPCController.perceive_guard(distance)returns{noticed, band, reaction}with bandsunaware(>6m),aware(2-6m),near(<2m). Near + temperament ≥0.6 → ARGUE, else COMPLY.GameManager._perception_pass()feeds real guard distance each physics tick and logs first-time notice. -
Responder dispatch:
RadioSystemtrackspending_callsand decrementsarrival_delay_sintick; emitsresponder_arrivedwhen a call's delay elapses.RadioSystem.responder_outcome(call)maps danger/weapons → command/restraint/evacuation/arrest.GameManager._on_responder_arrivedspawns aRESPONDERavatar at the entrance and logs the outcome. HUD shows an emergency-radio cooldown label via_update_radio_cooldown. -
Wand screening (Joke Shop):
WandScreenerclassifies carried items as prohibited vs false-alarm vs clear.NpcProfile.carried_itemsis filled at spawn on wand venues.GameManagerbindsCto_toggle_wand_screeningandA/Dtodecide_wandwhile inspect/wand is open.decide_accessstill blocks admission untilcontroller.screenedis true, andverifynow takes that flag. Door-staff lines log through GameManager. -
Design change (2026-10-31): the selectable dialogue tone-of-voice system was removed.
Rules.Tone/TONE_NAMES,NpcProfile.tone_modifiers, and the tone param onNPCController.roll_reaction/apply_player_actionno longer exist;NpcProfile.dialogue_linesis a flatArray[String];GameManageruses_apply_interaction(level, action_name); theTkey is gone. HUDtone_labelremoved andhud.tone_prefixstring deleted from JSON. -
Polish/accessibility: venue cycling (
VcyclesVENUE_ORDERand restarts the shift), aim-free targeting (Tabcycles nearby NPCs withinNEARBY_RADIUS), color-coded escalation indicator (Hud.set_escalation), procedural audio (AudioFeedbacknode generates sine tones for confirm/radio/escalation/deny), and a centralizedStringstable (src/core/strings.gd,lookup/fmt) backed bycontent/strings.en.json. NOTE:Strings.getwas renamed toStrings.lookupbecausegetcollides withObject.get. -
Newly added: LATE shift phase (start 700s) with a reachability test; PROTESTER archetype + events + colors; CALL_SOMEONE reaction;
DoorStatecore class +Lbinding + visual indicator; flashlight (G, SpotLight3D, perception bonus at evening/late); persistence wired (Save.save_shift_resulton shift end, history + best score on the review screen);NavigationRegion3Dflat navmesh +NavigationAgent3DNPC paths (steps now visual-only); strings moved tores://content/strings.en.json(41 keys, loader keepslookup/fmt);export_presets.cfg(Linux/Windows/macOS) +tools/build_releases.sh+docs/RELEASING.md; README Downloads section. -
Run tests:
godot --headless --path . --script res://src/test/test_runner.gd -
Boot check:
godot --headless --path . --quit-after 120 -
Screenshot tools:
godot --path . -s res://src/test/take_screenshot.gd(single shot touser://screenshot.png);godot --path . -s res://src/test/capture_screenshots.gd(staged README gallery shots touser://screenshots/, copied todocs/screenshots/). -
Release build verified locally: export templates for 4.3 installed at
~/.local/share/godot/export_templates/4.3.stable/;./tools/build_releases.shproduced Linux/Windows/macOS artifacts for 0.1.0 inbuild/and the Linux binary passed a headless smoke test. Publishing to Gitea still needs a token (seetools/publish_release.sh,docs/RELEASING.md). -
CI: the repo now carries a Gitea Actions workflow at
.gitea/workflows/build.yml(checks on push/PR; full export + artifact upload + optional release publish on tags andworkflow_dispatch). A repo secretGITEA_TOKENis required for tag releases. The old.github/workflows/ci.ymlwas removed so hosts scanning both paths do not double-run. -
Venue cycling:
Vswitches Hollywood Blvd → The Joke Shop → TV Studio and restarts the shift. -
Venue door-staff dialogue:
VenueScenario.door_staff_line(context, rng)and GameManager logging (wristband, ID/ticket, wand-check, refusal, prohibited-item). Hollywood uses a wristband line, the Joke Shop uses ID/ticket/wand lines, and the Studio uses ticket/credential/guest-list lines. -
CI cache evaluation: Gitea Actions cache was tested for the Godot binary and trimmed 4.7.2 templates. Restores were inconsistent and saves timed out in
CreateCacheEntryor were rejected as reserved by another job. The cache steps are disabled until the host cache service is healthy; keep direct downloads.GODOT_SILENCE_ROOT_WARNING=1is set on jobs. Logs live in.gitea/logs/after each run. -
Visual polish (v0.2.0): phase lighting (GameManager.PHASE_LIGHTING + ProceduralSkyMaterial), street lamps + night mode in WorldBuilder.set_night_mode, marquee Label3D, crosswalk/planters/ trees, NPC per-seed variation. capture_screenshots.gd shoots day + fast-forwarded LATE night (6 shots in docs/screenshots/). Version bumped to 0.2.0; binaries rebuilt.
-
Assets pipeline:
python3 tools/generate_textures.pyproduces all authored PNGs underassets/textures/(≤1024px, pow2); regenerate after changing the script, then rungodot --headless --importbefore tests. 57 headless tests guard the pipeline. -
General image pipeline:
python3 tools/asset_pipeline.pydefaults toopenai/gpt-5-image-mini, requests supported 1:1/3:2 sources, then fits each role's target. It provides crop, resize, tile edge blending, alpha-glyph recoloring, and role-aware audits. Screenshot auditing checks 1280x720 minimum, 16:9 aspect, and contrast; visual composition remains a manual check. Python tools require Pillow fromrequirements-tools.txt. -
AI texture mode:
python3 tools/generate_textures.py --aigenerates textures via the OpenRouter image API (POST https://openrouter.ai/api/v1/images, modelbytedance-seed/seedream-4.5by default, base64 PNG indata[].b64_json). RequiresOPENROUTER_API_KEY(a gitignored.envin the repo root holds it locally; source it withset -a && . ./.env && set +a).--prompt/--name/--model/--sizeoverride the default batch. Output is downscaled to ≤1024px. NOTE: seedream rejects sizes below 3,686,400 px (1920×1920), so the script requestsGEN_SIZE=2048and downscales — do not lowerGEN_SIZEbelow 1920. Verified end-to-end 2026-10-31 (3 textures generated). Legacy--airemains on Seedream 4.5 and still ignoresbackground: transparent; do not use it for alpha-dependent assets. Gemini 3.1 Flash Image advertised broad ratios but the current provider rejected authentication. The validated role-aware model is GPT-5 Image Mini. -
Runtime assets: all 15 loaded world/HUD/card textures now live in
assets/textures/ai/game_ready/; each passes role audit. Generated tile edges are blended, icon alpha masks are recolored white where needed, and the vignette uses the existing deterministic generator because the API returned an empty transparent image. The sixdocs/screenshots/captures were regenerated after wiring and pass screenshot audit. -
Detailed graphics: textured facade/awning/skyline, humanoid NPC rigs + props + walk bob + attention head-turn, inspection artifact cards, mode icons, escalation vignette, body-cam overlay (REC + shift clock). Screenshot set regenerated (
docs/screenshots/, day + night). -
Polish: wand scan cooldown (
WAND_COOLDOWN_S+wand.rechargemessage). -
NOTE: collaborators occasionally add uncommitted work to this shared tree (e.g. the radio easter egg); check
git statusbefore committing and leave foreign changes untouched unless asked. -
Screen-design references (
tools/screen_designs.py, tests intools/test_screen_designs.py):generateturns the screen descriptions indocs/(README captions, ADRs 0007–0010, PLAN, architecture) into reference images underdocs/screens/designs/—--draftdraws offline Pillow wireframes,--aigenerates realistic 3D scenes / in-game UI via OpenRouter (default modelopenai/gpt-5-image-mini). Screens are taggedsceneorui; prompts get a shared art-direction suffix, and requests use a 3:2 landscape source cropped to 16:9 (not a square centre-crop).comparechecksdocs/screenshots/*.pngfor aspect/size/ blank-frame and coarse layout/palette/detail drift. Drift distances are warnings, not failures: a mockup never matches a real render, so semantic composition is a follow-up vision-model judge (tracked in TODO).
Known Issues / Notes
-
Capture/pose harness gotchas: scripts that read captured-mouse input must run windowed (
smoke_props.gd); the NPC FSM re-writesrotation.yevery physics tick, so pose/portrait captures set the yaw afterset_physics_process(false); andvisible = falsedoes not keep the crowd out of a portrait frame (park the rest of the crowd away instead). -
Blender tooling:
bisect_planeand thin bevels leave zero-area triangles (Godot warns "non-finite normal in LOD generation");_clean()+ a weld after bisect fix them and the audit rejects them. -
Image models have per-model size floors/caps (Seedream needs 1920+, gpt-5-image-mini caps lower);
generate_imageapplies model-specific sizing, and concept generation defaults to--request-size 1536. Do not hand-set a single size for every model. -
Frozen decisions: do not re-open
override_access. Do not bind wand toW. Do not remove the radio easter egg. -
Reference cycles are fixed:
FSMholds its owner viaWeakRef,ShiftCoordinatorisRefCounted, and persistence tests free theSavenode — the suite exits with no "resources still in use" warnings (was ~13). If a future subsystem introduces a cycle, repeat the WeakRef pattern. -
Shift length:
GameManageruses the scenario'sshift_duration_s(900s ≈ 10-15 min); tests and the screenshot tool override elapsed time directly. -
Headless (dummy renderer) logs "Parameter m is null" from
mesh_get_surface_count; it does not occur with a real GL driver and does not affect logic. -
Steps have per-tread colliders (
_build_steps()adds aStaticBody3Dwith oneBoxShape3Dper tread), so the guard and NPCs walk up the entrance; the guard spawns on the landing. -
Persistence tests write into the real
user://shift_results.json(harmless local pollution of the demo history). -
macOS export required
rendering/textures/vram_compression/import_etc2_astc=trueinproject.godot(universal build demands ETC2 ASTC enabled). -
Venue
door_staff_linesare logged throughGameManager(inspect / wand / refuse). They are not a separate dialogue UI. -
Access loadout is implemented; do not re-fix
override_accessas if it were still unread. -
Save.SETTINGS_FILEis declared and unused; settings persistence goes throughSave.load_settings()/save_settings(). -
Headless suite is 276/276; Python tooling is 143/143; screenshot compare is 21 good / 0 bad and the automatic judge is 21/21 match, 0 diff. Remaining visual follow-ups are in
TODO.md. -
Typed spawn signal extra args: handlers must accept the 4th
event: Dictionaryor Godot will not call them (test_shift_coordinator_runs_a_shiftfailed until_on_spawntook four args). -
GDScript inference trap that broke the main scene: a
vartyped as a base class (radial: CanvasLayer) exposes script members asVariant, sovar x := radial.is_openis a parse error. Type it as the script type (ActionRadial) or annotate the local explicitly.--check-only --scriptcannot see theSaveautoload; use a real boot (--quit-after 180) to verify a full-scene compile. -
Do not snapshot right after changing a transform in the same frame:
capture_screenshots.gdneeds a state/frame gap or the capture uses the old camera orientation. -
EventSchedulerprobabilities are per simulated second, not per call. Never poll it per frame. -
Screenshot staging lives in
src/test/capture_screenshots.gd(guard at z≈10.5 facing the door) andsrc/test/capture_ui_states.gd(UI states 14–21).tools/check_screenshots.shrenders all captures intodocs/screenshots/; do not regenerate them by hand.
Open Questions
- Whether the responder avatar should be playable/controllable or purely scripted.
- Localization target languages beyond English.
- macOS notarization approach for broad distribution.
Plan vs code
PLAN.md describes intended design; README.md and DONE.md describe what shipped. As of
2026-11-03 the playable build covers the three venues, tutorials/campaign/checkpoints, the 0.9.3
UI/accessibility pass, the 0.9.4 NPC visual optimization, the 0.9.5 texture/material pass, the
0.9.6 integration checkpoint, and the Blender + realism asset pipeline. TODO.md holds the remaining
0.9.6–0.10.0 work. The project uses a custom headless runner, not GUT, and Gitea Actions, not
GitHub Actions.
Uncommitted Workstreams
- None (2026-11-03). The 0.9.6 integration checkpoint is committed, tagged
v0.9.6and published with seven assets; the workspace is clean apart from local/gitignored realism and capture output. Keep the rule fromAGENTS.md: checkgit statusbefore committing, and never revert another contributor's in-flight files.