Mitigate CI cache timeouts and wire venue door-staff dialogue
- Drop actions/cache@v4 from the checks job: the Gitea runner cannot reach GitHub's cache service (/twirp/...CacheService timeouts, ~2min retries); Godot import is fast enough without it. Add GODOT_SILENCE_ROOT_WARNING=1. - Document the cache situation and re-enable path in docs/RELEASING.md - VenueScenario.door_staff_line(context, rng): seeded-random venue lines - GameManager logs id/ticket/wand-check, refusal, and prohibited-item lines from the venue scenario (Joke Shop flavor) during inspection/screening - Test coverage: all Joke Shop contexts, unknown contexts, empty venues, deterministic seeded picks (54 tests pass)
This commit is contained in:
1 parent
a3768c2ce4
commit
0b5cd772a5
8 files changed
+142
-7
No files matched your search
@@ -11,6 +11,9 @@ jobs:
|
||||
checks:
|
||||
name: Import + headless tests
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# The container runs as root; silence the recurring engine notice.
|
||||
GODOT_SILENCE_ROOT_WARNING: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -21,12 +24,6 @@ jobs:
|
||||
unzip -qo /tmp/godot.zip -d /tmp/godot-bin
|
||||
sudo install /tmp/godot-bin/Godot_v4.3-stable_linux.x86_64 /usr/local/bin/godot
|
||||
|
||||
- name: Cache imported assets
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .godot
|
||||
key: godot-import-${{ hashFiles('project.godot', 'src/**/*.gd', 'content/**/*.json') }}
|
||||
|
||||
- name: Import project (build class cache)
|
||||
run: godot --headless --import
|
||||
|
||||
@@ -54,6 +51,8 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
needs: checks
|
||||
env:
|
||||
GODOT_SILENCE_ROOT_WARNING: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
@@ -156,6 +156,16 @@ Completed task sections moved from `TODO.md`. Sections are moved only when every
|
||||
- [x] Add a `V` control that cycles Hollywood Blvd → The Joke Shop → TV Studio and restarts the
|
||||
shift (`GameManager._cycle_venue`), with a `hud.venue_switched` string entry.
|
||||
|
||||
## Gameplay — Venue door-staff dialogue
|
||||
|
||||
- [x] Add `VenueScenario.door_staff_line(context, rng)` picking a seeded-random line from
|
||||
`door_staff_lines` (empty for unknown contexts/venues).
|
||||
- [x] Wire `GameManager` to log venue lines: id/ticket check line when inspection opens,
|
||||
wand-check line when screening starts, prohibited-item line on correct wand refusal,
|
||||
and refusal line on denied entry (The Joke Shop flavor only).
|
||||
- [x] Add a headless test covering all Joke Shop contexts, unknown contexts, empty venues,
|
||||
and deterministic seeded picks.
|
||||
|
||||
## Core Simulation — NPC Perception
|
||||
|
||||
- [x] Implement a perception pass so NPCs react to the guard's gestures and proximity.
|
||||
|
||||
@@ -42,6 +42,11 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
||||
A repo secret `GITEA_TOKEN` is required for tag releases. The old `.github/workflows/ci.yml` was
|
||||
removed so hosts scanning both paths do not double-run.
|
||||
- Venue cycling: `V` switches Hollywood Blvd → The Joke Shop → TV Studio and restarts the shift.
|
||||
- Venue door-staff dialogue: `VenueScenario.door_staff_line(context, rng)` and GameManager logging
|
||||
(id/ticket/wand-check, refusal, prohibited-item) — Joke Shop flavor.
|
||||
- CI cache: `.gitea/workflows/build.yml` no longer uses `actions/cache` (the runner can't reach
|
||||
the GitHub cache service; import is fast). `GODOT_SILENCE_ROOT_WARNING=1` set on jobs.
|
||||
Logs live in `.gitea/logs/` after each run.
|
||||
|
||||
## Known Issues / Notes
|
||||
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# TODO
|
||||
|
||||
## 🔧 CI — cache timeout mitigation (from .gitea/logs/ review)
|
||||
|
||||
`actions/cache@v4` times out against GitHub's cache service on this runner
|
||||
(`/twirp/github.actions.results.api.v1.CacheService/…` — the Gitea host has no
|
||||
local actions-cache configured). Restore/save retry for ~2 min per run. The
|
||||
checks job still passes; this is noise + risk of flaky timeouts.
|
||||
|
||||
- [x] Remove the `actions/cache@v4` step from `checks` in `.gitea/workflows/build.yml`
|
||||
(Godot import takes ~4s; caching .godot is not worth the timeout risk).
|
||||
- [x] Add `GODOT_SILENCE_ROOT_WARNING=1` to godot steps to quiet container-as-root noise.
|
||||
- [ ] Push and confirm the next `.gitea/logs/` run has no `CacheService` timeouts.
|
||||
- [x] Optional (host admin): document in `docs/RELEASING.md` that `actions/cache` needs a
|
||||
Gitea `[cache.actions]` + cache server before it can be reintroduced.
|
||||
|
||||
## 🔴 PRIORITY — Ship downloadable builds (so the README Downloads links work)
|
||||
|
||||
Goal: users hitting the README **Downloads** section can actually download a playable version from
|
||||
@@ -26,6 +40,37 @@ Goal: users hitting the README **Downloads** section can actually download a pla
|
||||
|
||||
## Open Polish
|
||||
|
||||
- [ ] Add venue-specific door-staff dialogue usage (scenario `door_staff_lines` is authored but not yet shown in the prototype UI).
|
||||
- [ ] Add a body/wand cooldown feel: wand screening currently has no reuse penalty; consider a short per-guest scan animation.
|
||||
- [ ] Add an escape-to-circuit handling note: `signals` in `Strings.fmt` are positional; keep `%` placeholders stable when adding keys.
|
||||
|
||||
## Detailed Graphics
|
||||
|
||||
The prototype is entirely procedural `BoxMesh` with flat `StandardMaterial3D` colors (see `src/game/world_builder.gd` and `src/game/npc_avatar.gd`). The items below upgrade it toward a finished look. **When a task needs a texture, sprite, decal, or reference image, use the integrated image-generation function to produce it** (e.g. seamless building facade, sidewalk concrete, asphalt, awning sign, wristband/ID inspection cards, HUD icons). Generated images go under `assets/` and are imported as Godot textures; keep them small (≤1024px) and power-of-two where possible.
|
||||
|
||||
### Environment
|
||||
|
||||
- [ ] Replace flat facade boxes with textured materials: brick/concrete facade, glass door, awning with a readable sign (generate facade + sign textures).
|
||||
- [ ] Add a sidewalk concrete texture and street asphalt texture with a center-line decal (generate seamless textures).
|
||||
- [ ] Add a sky: `ProceduralSkyMaterial` or a generated skybox, plus a sun with soft shadows (currently a bare `DirectionalLight3D` + flat ambient).
|
||||
- [ ] Add simple props for depth: bollards, a trash can, a street lamp, a planter near the entrance (generate or model low-poly props).
|
||||
- [ ] Add a night/evening lighting pass: dim ambient, warm street-lamp point lights, and a visible flashlight cone (ties into the existing `G` flashlight toggle).
|
||||
|
||||
### NPCs
|
||||
|
||||
- [ ] Replace capsule bodies with a simple humanoid rig (head/torso/limbs) or a low-poly character mesh, keeping the archetype color coding as a fallback.
|
||||
- [ ] Add per-archetype visual identity: distinct silhouettes or accessories (e.g. camera for influencers, delivery box, performer prop) instead of color-only.
|
||||
- [ ] Add a simple walk/idle animation (procedural bob or imported animation) so NPCs don't glide.
|
||||
- [ ] Add a facing/attention cue: NPC head turns toward the guard when `perceive_guard` reports `aware`/`near`.
|
||||
|
||||
### UI and feedback
|
||||
|
||||
- [ ] Replace text-only inspection with visual artifact cards: wristband/ID/ticket images showing color, pattern, date, and event marking (generate card art).
|
||||
- [ ] Add HUD icons for the tone picker, wand, flashlight, and door-lock states (generate small icons).
|
||||
- [ ] Add a subtle screen-space vignette or color grade per escalation level to reinforce the existing color-coded escalation label.
|
||||
- [ ] Add a body-camera frame overlay (corner timestamp + REC indicator) to sell the evidence/recording fantasy.
|
||||
|
||||
### Performance and pipeline
|
||||
|
||||
- [ ] Set up an `assets/` import convention (textures, materials, meshes) and document it in `README.md`.
|
||||
- [ ] Profile the crowd: ensure textured materials and props don't hurt the target frame rate around a busy entrance.
|
||||
- [ ] Add LOD or culling for distant NPCs/props if the crowd grows.
|
||||
@@ -40,6 +40,11 @@ The repository ships a Gitea Actions workflow (`.gitea/workflows/build.yml`):
|
||||
For tag builds, set a repo secret named **`GITEA_TOKEN`** (write:repository scope).
|
||||
Without it the workflow still builds and tests, but the release publish step fails.
|
||||
|
||||
> **No `actions/cache` for now:** the runner has no local actions-cache service, so
|
||||
> `actions/cache` times out against GitHub's cache API. We dropped it (Godot import
|
||||
> is only ~4s). If a host admin later enables Gitea `[cache.actions]` with a cache
|
||||
> server, the step can be reintroduced.
|
||||
|
||||
> The workflow lives in `.gitea/workflows/` (Gitea's canonical path). The old
|
||||
> `.github/workflows/ci.yml` was removed to avoid double runs on hosts that scan
|
||||
> both directories.
|
||||
|
||||
@@ -68,6 +68,23 @@ func _spot_for_intent(intent: String) -> String:
|
||||
_:
|
||||
return "sidewalk"
|
||||
|
||||
## Pick a random door-staff line for a context key (e.g. "id_check",
|
||||
## "ticket_check", "wand_check", "refusal", "prohibited_item"). Returns ""
|
||||
## when the venue has no lines for that context. Deterministic when a seeded
|
||||
## RNG is injected; otherwise returns the first line.
|
||||
func door_staff_line(context: String, rng: RandomNumberGenerator = null) -> String:
|
||||
if not door_staff_lines.has(context):
|
||||
return ""
|
||||
var lines: Variant = door_staff_lines[context]
|
||||
if not (lines is Array):
|
||||
return ""
|
||||
var pool: Array = lines
|
||||
if pool.is_empty():
|
||||
return ""
|
||||
if rng != null:
|
||||
return str(pool[rng.randi_range(0, pool.size() - 1)])
|
||||
return str(pool[0])
|
||||
|
||||
func _location_or_default(spot: String, fallback: Vector3) -> Vector3:
|
||||
if locations.has(spot):
|
||||
var v: Variant = locations[spot]
|
||||
|
||||
@@ -50,11 +50,15 @@ var current_npc_truth := {"weapons_seen": false, "danger_level": 0}
|
||||
const VENUE_ORDER := ["hollywood_building", "joke_shop", "tv_studio"]
|
||||
var _venue_index := 0
|
||||
|
||||
## RNG for venue door-staff lines so repeated interactions vary.
|
||||
var _door_rng := RandomNumberGenerator.new()
|
||||
|
||||
## Aim-free targeting: cycle through nearby NPCs with Tab (accessibility).
|
||||
const NEARBY_RADIUS := 12.0
|
||||
var _nearby_index := -1
|
||||
|
||||
func _ready() -> void:
|
||||
_door_rng.randomize()
|
||||
scene_root = Node3D.new()
|
||||
scene_root.name = "SceneRoot"
|
||||
add_child(scene_root)
|
||||
@@ -469,6 +473,10 @@ func _toggle_inspection() -> void:
|
||||
var artifacts := _artifacts_for(controller)
|
||||
var result: Dictionary = verifier.verify(artifacts, selected_scenario.access_policy)
|
||||
hud.show_inspection(controller, artifacts, result)
|
||||
# Venue door-staff flavor: an applicable check line (PLAN.md: door-staff dialogue).
|
||||
var line := _door_context_line()
|
||||
if line != "":
|
||||
hud.log(line)
|
||||
|
||||
# ------------------------------------------------------------- wand screening --
|
||||
|
||||
@@ -490,6 +498,9 @@ func _toggle_wand_screening() -> void:
|
||||
var result: Dictionary = wand_screener.screen(items)
|
||||
wand_open = true
|
||||
hud.show_wand(controller, result)
|
||||
var wand_line := selected_scenario.door_staff_line("wand_check", _door_rng)
|
||||
if wand_line != "":
|
||||
hud.log(wand_line)
|
||||
|
||||
## Resolve a wand screening: the player decides whether to admit the guest.
|
||||
func decide_wand(allowed: bool) -> void:
|
||||
@@ -514,6 +525,11 @@ func decide_wand(allowed: bool) -> void:
|
||||
if decision.get("prohibited", false) and allowed:
|
||||
hud.log(Strings.fmt("wand.admitted_prohibited", [name]))
|
||||
coordinator.reputation.apply("security_failure", -5.0, "admitted prohibited item")
|
||||
elif decision.get("prohibited", false) and not allowed:
|
||||
var ban_line := selected_scenario.door_staff_line("prohibited_item", _door_rng)
|
||||
if ban_line != "":
|
||||
hud.log(ban_line)
|
||||
coordinator.reputation.apply("access_judgment", 1.0, "correct prohibited-item refusal")
|
||||
elif decision.get("false_alarm", false) and not allowed:
|
||||
hud.log(Strings.fmt("wand.denied_false_alarm", [name]))
|
||||
coordinator.reputation.apply("complaint", -2.0, "false-alarm refusal")
|
||||
@@ -557,6 +573,9 @@ func decide_access(allowed: bool) -> void:
|
||||
audio.play_confirm()
|
||||
else:
|
||||
hud.log(Strings.fmt("access.denied", ["; ".join(result.get("issues", []))]))
|
||||
var refusal_line := selected_scenario.door_staff_line("refusal", _door_rng)
|
||||
if refusal_line != "":
|
||||
hud.log(refusal_line)
|
||||
coordinator.reputation.apply("access_judgment", 1.0, "correct refusal")
|
||||
if audio:
|
||||
audio.play_deny()
|
||||
@@ -571,6 +590,17 @@ func decide_access(allowed: bool) -> void:
|
||||
hud.hide_inspection()
|
||||
inspection_open = false
|
||||
|
||||
## Venue-specific opening line for the access inspection (e.g. The Joke Shop's
|
||||
## ID / ticket check lines). Returns "" when the venue has no matching lines.
|
||||
func _door_context_line() -> String:
|
||||
if selected_scenario == null:
|
||||
return ""
|
||||
if selected_scenario.access_policy.require_id:
|
||||
return selected_scenario.door_staff_line("id_check", _door_rng)
|
||||
if selected_scenario.access_policy.requires(Rules.AccessKind.TICKET):
|
||||
return selected_scenario.door_staff_line("ticket_check", _door_rng)
|
||||
return ""
|
||||
|
||||
func _artifacts_for(controller: Object) -> Array:
|
||||
var profile: Object = controller.profile
|
||||
var npc_type: int = profile.npc_type if profile else -1
|
||||
|
||||
@@ -63,6 +63,30 @@ func test_late_phase_reaches_violent_event() -> bool:
|
||||
c = false
|
||||
return a and b and TestUtils.check(c, "all venues declare a LATE phase")
|
||||
|
||||
func test_door_staff_lines_available() -> bool:
|
||||
var scenario: VenueScenario = ScenarioFactory.joke_shop()
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = 7
|
||||
var ok := true
|
||||
for context in ["id_check", "ticket_check", "wand_check", "refusal", "prohibited_item"]:
|
||||
var line: String = scenario.door_staff_line(context, rng)
|
||||
if line == "":
|
||||
ok = false
|
||||
var a := TestUtils.check(ok, "all joke-shop contexts have a door-staff line")
|
||||
var b := TestUtils.check_eq(scenario.door_staff_line("unknown_context", rng), "", "unknown context returns empty")
|
||||
var b_country := ScenarioFactory.hollywood_building()
|
||||
var c := TestUtils.check_eq(b_country.door_staff_line("refusal", rng), "", "venue without lines returns empty")
|
||||
# Same seed picks the same line.
|
||||
var r0 := RandomNumberGenerator.new()
|
||||
r0.seed = 11
|
||||
var r1 := RandomNumberGenerator.new()
|
||||
r1.seed = 11
|
||||
var d := TestUtils.check_eq(
|
||||
scenario.door_staff_line("wand_check", r0),
|
||||
scenario.door_staff_line("wand_check", r1),
|
||||
"seeded door-staff pick is deterministic")
|
||||
return a and b and c and d
|
||||
|
||||
func test_protester_archetype_present() -> bool:
|
||||
var profiles := ScenarioFactory.shared_profiles()
|
||||
var protester_found := false
|
||||
|
||||
Reference in new issue
Block a user