Changelog · 2026-06-24 · branch native-port. Picks up where the N1–N5 changelog left off. With the structural waves done (the engine plays the levels), this past day's work was about breadth: giving the individual game objects their real behavior, wiring the campaign's story-progression machine, and making the browser build actually expose both.
After waves N1–N5 the engine had the skeleton — bases, enemies, pickups, vehicles, and the game-flow/menu layer — but most of the ~90 distinct object types placed in the levels were still inert props. This past day closed almost all of that gap, driven by a single instrument: the new resolution/usage-aware Asset Catalog, whose strict query (“authored in a level, but no native behavior”, ranked by reach) gave an objective worklist. Working down it took the count of used-in-level FourCCs with a native behavior from the low-30s to 90 of 93 — the last three are documented resolver/positioning gaps, not behavior gaps. On top of that breadth, two structural pieces landed: the SCENE sequencer (the campaign's story-progress state machine, with both of its writers) and the first C3DGoddard companion slice. Finally, the browser build got a Campaign toggle and a real keyboard-input fix so the story and the talk system are reachable on the web. Every step held the same gate: tools/audit_faithfulness.py at 0 findings across all 35 levels, and a WASM rebuild verified by tools/qa_web_verify.py (16 browser checks).
| area | what landed | result |
|---|---|---|
| Coverage | Per-FourCC behavior, worked off the Asset Catalog lens | used-in-level native behaviors ~30 → 90 / 93 |
| SCENE | Task-state story sequencer + both writers (triggers & talk) | campaign story beats advance; gated actors appear/hide on cue |
| Goddard | C3DGoddard companion + 3MEP fetch + scripted control | follows Jimmy, fetches cans, obeys scripted sit/patrol triggers |
| Web | Campaign toggle button + keyboard-input fix + touch-mode fix | story & talk now reachable in the browser; live |
Rather than guess what to port next, the catalog cross-references every FourCC's visual resolution (which mesh/sprite/canvas it draws) with its level usage (how many instances, in how many levels) and its native behavior (which vtable, if any). The behavior worklist is then a strict query — instances > 0 AND native_behavior IS NULL — ranked by instances × level_count so the highest-reach gaps come first. Each class is then read from its decompiled spec (docs/decomp/<Class>.md) and ported to match, not invented. The catalog is browsable at exentt.com/JN-assets/catalog.
catalog lens (end of this pass): 93 used FourCCs · 90 native vtables · 3 remaining remaining 3 = 3ROK / 3SPR / 3DAI (resolver/positioning gaps, not behavior gaps)
Dozens of classes ported in themed passes, each one read from its decomp spec first and committed per class. Every pass ended on a clean faithfulness sweep.
| pass | classes (FourCC) | shape |
|---|---|---|
| Placed actors (N2.x) | 3TUR turret, 3TES tesla, 3LAS laser, 3YSH, 3EYE, 3DIG, 3HOO, 3CIN Cindy | active hazards + patrol actors + the first friend NPC |
| NPC / friend cast (N2.y) | vt_friend: Nick/Sheen/UltraLord/Libby/Hugh/Benny/Judy/Kitty; 3FLE→Yokian; 3SUM/3PIR escorts; 3AIT AITrigger | idle/patrol NPCs that watch Jimmy; the AI/script mission-wiring volume |
| Actor-focus closeout | 3PHO phone booth, 3RCK placed patrol rocket, 3HUM Humphrey | solid contact prop, autonomous toy rocket, hidden clone-controller |
| Base/effect tails (4 passes) | 3NEU/3RED/3ARR, 3LIO/3OMT/3CON, 3TRO/3LEA/3TAR/3AIO, 3LIG/3FIS/3GIR/3SPA/3STA | neutron pickups, light/OMT data rows, decor billboards, set-dressing creatures |
| Animated sprite + door | 3ANI sprite-frame animator, 3SWN timed swing door | carnival bottle animations; quarter-turn yaw doors |
| Long-tail close-out | vt_creature (Dino/Camel/Sparrow…) + new vt_prop (3HYD/3CUB/3TOL/3SPH/3TEL/…) | shared C3DAI creatures; one gated static-prop leaf, solidity strictly from authored HasCollision |
SCENE is one tag in the CTaskList task-state table — the campaign's story-progress counter, seeded to 30 at New Game. It has no autonomous driver; it only moves when a story event writes it. Reverse-engineering recovered the two writers and they are now both ported:
1. Player triggers. C3DAITrigger::ApplyAITriggerStoryProgress is a hardcoded ObjectTag × current-SCENE → new-SCENE patch table (~25 beats) run when the player trips a story-trigger volume. 2. Friend talks. Each friend's talk-pulse re-reads SCENE and writes the next beat, collapsed into one shared table driven from a single entry point (the T key → nearest friend).
Two previously-deferred consumers are now freed by the live store: 3FOW/C3DFowl (per-level SCENE windows) and 3YCA/C3DYokCargo (LEV5 SCENE>489), plus 3HUM's clone reveal at SCENE==0x5a. Every gate treats “no task loaded” as “show”, so a direct --level / audit launch is unchanged — the gates only bite in a real campaign run.
teleportexplanation advanced SCENE 0x1e→0x23; talking to friends advanced their beats (Carl 0x32→0x3c, …); level4c fowlinv closed the fowl window (visible→hidden); SCENE=0x5a revealed Humphrey + 4 clones.[SCENE] 'teleportexplanation' advanced SCENE 0x1e -> 0x23 [TALK] 'C3DCARL' (3CAR) advanced SCENE 0x32 -> 0x3c
Goddard has no .gam rows — the original spawns him in code — so the native engine synthesizes one C3DGODDARD companion after level load, but only in a campaign run (or an explicit test probe) and only when the level data references him. He honors the documented disabled-level table (he stays hidden in level1c/1d/4a/6a/6/7/vr06), follows Jimmy at a short offset, and runs the first re-activated mechanic: the 3MEP metal-can beacon asks Goddard to fetch a can within range, after which he collects it and returns to follow.
The scripted-control tail then wired the C3DAITrigger branch that targets him: the ~30 3AIT rows with AITarget == C3DGODDARD (SITGODDARD, GOGODDARD, PUTGODDARD, …) now drive his mode — patrol an authored point chain, follow, or hold a scripted sit pose — so a cutscene that places or walks Goddard isn't instantly overridden by his follow loop.
GOGODDARD → Goddard walked GODDARDPAT1 → GODDARDPAT2; SITGODDARD → teleported to gstart and held; the 3MEP fetch/collect/return path was un-regressed.All of the story work above was invisible in the browser, because campaign mode was only reachable from the command line and a real input bug silenced the talk key.
Campaign toggle. A new Campaign button (and a ?newgame=1 URL) begins the New Game task at runtime — campaign mode on, SCENE seeded — and swaps into level1b; toggling off returns to free-roam. So the story progression and the friend talk-rewards can now actually be played in-browser.
input_just_pressed() aliased SDL's live keyboard array, which the browser updates asynchronously between frames — so for any held key previous == current and the “just pressed” edge was never seen. Keyboard jump / throw / respawn / talk all silently did nothing in the browser (only held-key movement and the event-based B key worked). The input layer now keeps its own per-frame snapshot, so edge-triggered keys work in both the native and web builds. The T key (talk to the nearest friend) was also un-bound from a redundant turbo toggle (the Speed button keeps that), and now reports the nearest friend's distance so it's observable.(pointer: coarse), which false-positives on touchscreen laptops and got the page “stuck in mobile mode” (hiding the keyboard controls). It now requires a coarse primary pointer and no fine pointer, with a ?touch=0 / ?touch=1 override.Verified against the live deployed build in headless Chromium: ?newgame=1 → Campaign: On at level1b; a real keydown fires [TALK] and does not toggle turbo; the Campaign button round-trips On/Off with the level swaps logged.
2026-06-24/25 · branch native-port · workflow docs/qa_ticket_resolution_workflow.md · living handoff docs/qa/qa_backlog_campaign_handoff.md
With the behavior-coverage breadth above in place, this pass worked a backlog of four community QA tickets — sandmanfan (2026-06-24, 10 reports), two awefan tickets (level1 + level1b), and lu9 (2026-06-14) — deduped to 24 unique reports. Each was taken through the same contract: read the matching docs/decomp/<Class>.md first, prefer authored data and engine rules over one-off edits, and hold the tools/audit_faithfulness.py gate at 0 findings / 35 levels after every change. 18 reports are fixed-and-verified, 2 are resolved as faithful-to-original (WONTFIX-as-bug), and the remaining open work is Cindy location/pathing plus the documented Group I audio tail.
| area | what landed | result |
|---|---|---|
| Placement | 3SPH invisible; foot-anchor 3HUG/3CIN/3SHE; STRT spawn-yaw on level loads | floating/mis-grounded actors seat correctly; Jimmy faces the authored way on lab-exit |
| Model/visual | authored button meshes + RGB pulse; enemy PROJ→missile; 3FLE→fleet commander; l3c tree04 glb; bigger bouncier red neutrons | the reported wrong/floating models now draw the authored asset |
| Logic gate | l1b 3ARR/LOAD gated on authored RequiredTask + scoped Goddard-bowl ShowArrow | campaign-only arrow/teleport no longer fire out of sequence; bowl arrow drawn |
| Motion/path | l1 3SAI seats SailBoat.glb on the ncwater* surface; l3d 3CIN terminal-route loop mismatch investigated | boat no longer floats above the river; Cindy still needs location/pathing follow-up |
| Audio | l6a doors loop soundeffects.omt[59] while moving, halt at fully-open | door open-SFX no longer one-shots short of the motion |
| Faithful | apple-pie fruit bowl; house02 open-bottom facade | resolved WONTFIX-as-faithful — the “defect” is the original's authored data |
| # | level | model | issue | status |
|---|---|---|---|---|
| 1 | l1 | 3SPH ×2 | draws a stray “fan” | fixed invisible |
| 2 | l4 | 3SHE | character floating | fixed grounded |
| 3 | l4a | 3HUG | not at ground level | fixed foot-anchor |
| 4 | l3d | 3CIN | off-ground + pathing/wall-clip | ground fixed / location deferred |
| 5 | l1 | 3SAI | boat floats above river | fixed water anchor + route audit |
| 6 | l6a | 3BUT | buttons float + should flash | fixed mesh + RGB pulse |
| 7 | l6a | 3DOR | door SFX should loop until stop | fixed loop+halt |
| 8 | l6a | PROJ | should be plasma not baseball | fixed enemy missile |
| 9 | l6a | 3FLE | should be Yokian fleet commander | fixed |
| 10 | l3c | tree04 | wrong tree texture | fixed per-level glb |
| 11 | l5 | 3RED | red neutrons too small | fixed size+pulse |
| 12 | l1c | 3PIC | apple-pie shows fruit bowl | WONTFIX-faithful |
| 13 | l1 | house02 | floor under house missing | WONTFIX-faithful |
| 14 | l1 | 3JIM | faces lab not house on exit | fixed STRT yaw |
| 15 | l3c | 3BUT | button orientation off | fixed authored mesh |
| 16 | l1b | 3ARR | misplaced arrow + wrong teleport | fixed task-gate |
| 17 | l1b | 3PIC | missing coin pickup | verified already drawn |
| 18–21 | l1c | rooms | furniture proximity sounds | open audio feature |
| 22 | l3a | 3JIM | ride-track sound stacks/never stops | open emitter TBD |
| 23 | l1a | 3JIM | shrink-ray SFX plays as music | open music-db resolution |
| 24 | l1 | RocketPad | missing “find rockets” voice line | open audio |
Door opening SFX now loops while the door moves. The rise-open door (3DOR/3DUD/3SCD/3GAT) played soundeffects.omt[59] — a handle literally named “Door opening loop” — as a one-shot, so it ended long before the door finished. It now plays looping (loops=-1) on trigger, stashes the mixer channel in unused door scratch, and halts it the moment the door latches fully-open. Audio-off builds stay safe (the play returns −1, the halt no-ops).
halldoor01 logs [DOOR] opening → [DOOR] open, i.e. the loop starts on trigger and is halted on the fully-open transition. (xvfb has no audio device, so the loop is not audible headless — by-ear confirmation is a desktop-session job.)level1b arrow + teleport gated on the authored story task. The level authors two arrow/load pairs keyed on campaign progress (RequiredTask=SCENE, RequiredLevel=60/65) plus a hidden gdish pickup trigger (ShowArrow=1) over Goddard's bowl. The native build ignored the task gate, so the campaign-only lab-water arrow/teleport showed up out of sequence and the authored bowl arrow never drew. A new behavior_required_task_gate_allows() evaluates the authored level window against the live SCENE task state (and leaves direct --level/audit runs unchanged); LOAD now uses its authored Radius, and a scoped pickup-arrow draws over the bowl trigger only — the global chunk-106 hidden rule (l1 hydrant/nests/pads) is untouched.
pickup-arrow=1 while level1 keeps its hidden referent triggers with no arrow leak; the yellow arrow renders over the GODDARD dish.SpriteIndex=157 is “FruitbowlEmty” — the fruit bowl is correct. The apple pie comes from the unimplemented fruit-fill mechanic (“Apple Pie%”), deferred to mechanic work, not a texture swap.house02.glb is an open-bottomed facade: its only low/horizontal up-facing faces cover ~4% of the footprint (a corner step), and the coverage is identical in level2/level2b — not a level1 export bug. The 6 background houses are all open shells; only Jimmy's house ships a dedicated HOUSE BASE mesh. Adding a floor would invent geometry the original never had; the reporter saw the hollow interior only via the free QA inspection camera, which normal collision keeps the player out of.make # native build python3 tools/audit_faithfulness.py # 0 findings across all 35 levels python3 tools/build_asset_catalog.py # refresh the coverage lens make web python3 tools/qa_web_verify.py # 16/16 browser checks ./tools/deploy_wasm.sh # publish to exentt.com/jn-engine
Live build (with the QA-campaign fixes): jnengine.aef9117b.js, assets 63737d29 — deployed 2026-06-25. Each change is committed individually on the native-port branch.