Skip to content

Module 2 improvements - #57

Merged
RomeXO10 merged 24 commits into
mainfrom
module-2-improvements
Oct 12, 2026
Merged

RomeXO10 merged 24 commits into
mainfrom
module-2-improvements

Conversation

@RomeXO10

@RomeXO10 RomeXO10 commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

No description provided.

PlasticBucket.prefab.meta and PF_Pop_House1.prefab.meta each still contained an
unresolved rename/rename conflict block from 5b918e6. Unity cannot parse a GUID
out of them ("The GUID inside '...' cannot be extracted by the YAML Parser"),
which aborts an Android player build during IL post-processing with a double
fault and a Mono process hang.

Each file keeps its own HEAD-side GUID, chosen from what the live scenes
actually reference:

  b4626cd6089bbfa41a12442e228be28f  PlasticBucket   - 3 live scenes
  aa4a993eaa7f10d4ab43e873f4ffd8ba  PF_Pop_House1   - 2 live scenes

The third GUID appearing in both blocks, 936550419afe37f44af3bf7b596ae57f, is
referenced by nothing outside the conflict markers themselves and is dropped.
Picking the other side would have silently broken every reference to the prefab.

These were the only two .meta files in Assets/ carrying conflict markers.
Gameplay and UI pass over the household vector control module.

New:
  Module2HUD           in-headset HUD, added to M2Manager at runtime
  Module2Text          every player-facing string in one place
  Module2Localization  Module 2's string table, backed by Module2Text.tsv
  Module2Interaction   scene-wide interaction repairs applied at runtime, so
                       they need no prefab or scene edits
  LaoFontTool          editor tool that regenerates the Lao TMP font asset with
                       full coverage of the Lao block
  Lao_SomVang Full SDF the regenerated font asset

Reworked: M2Manager, Jar, WaterButton, FishCon, TrashBin, SwatterHand,
MosquitoSpawn and Mosquto.

SendRecieveData now null-checks both managers before reading the score, instead
of assuming GameManager.instance. Module 2 has M2Manager and no GameManager, so
the old code threw a NullReferenceException whenever that scene reached the GAME
state. Adding the new components from M2Manager at runtime keeps these changes
out of Module_2_MainScene.unity, which is a ~1.1 MB serialized file that cannot
be merged when two people touch it.
Committed replacements for the per-machine helpers that were living in
.git/info/exclude, so that building and sideloading no longer depends on
untracked files.

  Tools/build.sh <1|2|tut2> [--clean]   build a module into an APK
  Tools/deploy.sh <1|2|tut2>            install it on an attached Quest, launch
  Tools/build-and-deploy.sh <1|2|tut2>  both

There is no separate packaging step: Unity's player build writes the signed
.apk directly, debug-signed, which is what sideloading needs.

Modules are built one at a time for now. Scenes are resolved by filename rather
than a fixed path, so the folder reorganisations this repo keeps going through
do not break the scripts, and each build contains only that module's scene
rather than whatever EditorBuildSettings happens to hold.

build.sh checks libil2cpp.so inside the finished APK and re-runs itself with
--clean if it finds a stub; deploy.sh refuses to install one. A build that
crashes part-way can leave the IL2CPP cache holding a few-KB libil2cpp.so, and
every later incremental build then reuses it while still reporting
result=Succeeded. The APK installs, starts the engine and dies, because it
contains no compiled C# at all - so the build report alone is not enough to
trust a build.

deploy.sh also distinguishes the headset failure modes that otherwise look
alike: an empty adb device list means developer mode is off rather than a cable
fault, "unauthorized" means the on-headset prompt is still pending, and Horizon
OS refuses to launch while the headset is asleep or a system dialog is open.

The editor version is read from ProjectSettings/ProjectVersion.txt instead of
being hardcoded, and adb is found inside the editor's bundled Android SDK.

README documents all of this, including headset setup and troubleshooting.
The tip test cast 1.2 m along the container's up axis against the Ground
layer only. The Lao house floor is on the Wall layer, so a vase on the
indoor desk emptied only when held inverted below knee height; at chest
height nothing happened. Found by playtesting through the harness.

Pouring now triggers once the container is tilted past pourAngle (110 deg);
the splash is placed on the ground mask or, failing that, the nearest
surface below.
… round end

Playtesting (harness) found a normal clear-the-sites run ended by bites
after ~28 s: 8 bites in 5 s while covering a jar. Mosquitoes emerge 0.3 m
above open containers - where the player is working - and up to 30 home in,
each biting every 4 s.

- M2Manager.biteGracePeriod (2.5 s): bites inside it are ignored.
- Mosquito.firstBiteDelay (3 s): a freshly emerged mosquito cannot bite.
- Score, site clears, swats and binned trash are ignored once the round has
  ended; the debrief showed 'Action score 40, 3/6' while the live state kept
  counting to 60 and 4/6. The completion bonus goes through a private path.
At 2.5 s the swarm, which out-flies a walking player, still landed a bite
at the maximum rate and ended an efficient clear (4/6 sites, no swatting)
at 42 s. Found by harness replay.
…thout bites

The wrist menu has always spawned a repellent tube and the debrief tells
players 'protection alone does not reduce the swarm', but the cream did
nothing except play a particle effect. Now:

- Cream: holding the tube and squeezing (trigger) it onto the other hand -
  nozzle ray or within applyRadius (0.2 m) - calls M2Manager.ApplyRepellent.
  The holding hand never counts.
- M2Manager.repellentDuration (30 s): bites are ignored while active; toasts
  on apply and when it wears off; re-applying refreshes it.
- Mosquitoes within attract range veer away from a protected player.
- HUD shows the time left beside the bite count.
- New strings in Module2Text.tsv (Lao column left for translation).

Protection does not touch the breeding sites, so it cannot move source
reduction.
…d card

Harness screenshots showed the toast stack (from +170 down) drawn over the
objective arrow and distance label (+150/+108), and the end-card subtitle
starting inside the title's box (-94 vs title bottom -112).

- Objective arrow/label move to the free top-centre band (+268/+226); the
  label narrows to 320 so it cannot reach the sites counter.
- Toasts start at +200.
- End subtitle at -116 with room for three lines; stats follow at -214.
The consequence line plus a random fact runs to four lines; in a 90-high
box it overflowed into the stats line and the restart footer (harness
screenshot of a BitesReached debrief).
A five-line consequence + fact still touched the restart footer; facts vary
in length and Lao runs longer, so shrink to fit (23 -> min 15).
None of the Trash-tagged objects had a Rigidbody or XRGrabInteractable, so
a player could not pick any of them up and TrashBin never fired - every
debrief read 'Rubbish 0'. Found by trying to pick up a cup in the harness.
Module2Interaction now adds a light Rigidbody + XRGrabInteractable to
litter before tuning grabbables.
…start

Litter is authored partly sunk into the terrain. With a Rigidbody added, the
solver depenetrated a banana leaf at 8.4 m/s on frame 1 and it flew 3 m up
(harness trace). Cap maxDepenetrationVelocity at 0.3 m/s so embedded pieces
settle onto the surface instead.
XRI restores the lid's kinematic flag on release, so a lid dropped or thrown
anywhere but on its jar froze in the air for 1.5 s and then teleported home
(harness: thrown lid hung at y 0.40). Make it dynamic on release; the
existing return-home path makes it kinematic again.
The watch sits under the left hand, so DontDestroyOnLoad (root objects
only) never took effect and logged a warning on every scene load, and the
'destroy duplicate' branch could only ever misfire. The rig and watch are
rebuilt with the scene; UIHand now simply tracks the current instance.
The previous fix preferred groundLayerMask, which is the terrain - under the
house floor - so an indoor vase splashed 8 cm below the floor, invisible
(harness). Use the nearest non-trigger surface below, skipping the
container itself and the player's CharacterController.
Only PF_VaseWithFlowers has waterSplashEffect assigned; (2), (3) and (4)
emptied with sound but no visible splash (harness). Containers without
their own effect now use the one that is set.
…of release builds

Module2Text.tsv imported as a plain file (.tsv is not a text-asset extension), so
Resources.Load<TextAsset> always returned null and every string fell back to the built-in
English. Renamed it to .txt (same GUID, TextScriptImporter); it now loads 44 keys. Nothing
visible changes today because the Lao column is still empty, but Lao wording would have been
silently ignored.

The briefing's live input readout ("actions:0 ...") was on in the scene. It now also requires a
development build or the editor, so a release APK never shows it to children.
The racket prefab has gravity off and XRI restores that on release, so a dropped racket kept the
speed it left the hand with and drifted away for good. FlySwatterUi then refused a new one because
one "already" existed. The wrist button was the only way to get a swatter, so the player was left
without one for the rest of the round.

SwatterHand now forces gravity on detach and gets the same continuous collision the scene's other
grabbables get (it is spawned after Module2Interaction has run). FlySwatterUi only refuses while a
racket is actually held; otherwise it replaces any loose one.
Mosquitoes lived until swatted, so covering jars only stopped new ones spawning: 30 stayed alive
with 5 of 6 sites open, and 25 were still buzzing on the "House is clear" card. The bite limiter
was one global grace period, so 30 mosquitoes bit no faster than one. The debrief says fewer
containers means fewer bites; play did not show it.

- Mosquito.Retire(): stop seeking, fly away from the player for 2.5 s, then leave.
- MosquitoSpawn retires the excess (nearest the player first, 2 per check) when the swarm is bigger
  than the open sites support, counting only mosquitoes that are not already leaving.
- A full clear sends the whole swarm away.
- M2Manager.CurrentBiteGrace = biteGracePeriod x sites / sites-still-open, capped at
  maxBiteGraceMultiplier (4). With every site open it is the unchanged 5 s.
… with

ChooseSiteSubset switched off the GameObject of every site it left out, so a round's furniture
changed with the dice: jars, vases and the fish bowl simply vanished with nothing to explain the
gap. Each site now implements IBreedingSite.ShowResolved and is shown as if it had been dealt
with, without any score or toast:

- Jar: lid on, no lid left lying around.
- WaterButton (vase): water off.
- FishCon: fish in the bowl. The single fish ball is then hidden, since it would do nothing when
  dropped in.

Anything that does not implement the interface still falls back to being switched off.
…osquitoes

The swarm is sustained by the open sites, but nothing on screen said so. BreedingSiteFx, added
by M2Manager to each site the round uses, puts a few pale wrigglers swimming on the water and a
slow column of small mosquitoes rising from it. Clearing the site shrinks the wrigglers away and
lets the last mosquitoes drift off; the component then removes itself.

Built at runtime, so no scene or prefab changes:
- Wrigglers are primitive meshes on two joints, one flat colour (Resources/Module2/M2_SiteLarva).
- The mosquitoes are the game's own mosquito, baked once from the prefab's skinned mesh and
  scaled down, so they read as the same animal as the swarm. Falls back to dark specks
  (M2_SiteMote) if the prefab or bake is unavailable.
- Water is found per site type (jar's flat disc, vase's Water child, the fish bowl's own mesh),
  and sizes follow the water's radius so a vase gets fewer, smaller ones.
- M2Manager.showSiteFx turns it off.

Mosquito_Female.fbx is now Read/Write so BakeMesh can be used in a player build; the editor does
not need it, which is why this is worth checking on the headset.
The rig's ContinuousTurnProvider had Enable Turn Around on, so any stick
direction in the back quarter snapped the view round 180 degrees. A thumb
drifting slightly down while turning flipped the player, which felt like
turning could not complete a full circle.
The menu was shown only while a ray along the watch's up axis hit the 8 cm
sphere on the camera or the 1 cm one on the other hand's fingertip. On a
headset that left two ways in: aim the wrist to within ten degrees of the
eyes, or touch the watch - and the second closed the menu the moment the
finger left the ray to reach a button, so the swatter could not be picked.

It now opens when the watch face is turned to within 35 degrees of the eyes
(staying until 55) or when the other fingertip touches the watch, and stays
open while that fingertip is within 20 cm of the menu, with a half-second
linger so tracking jitter cannot make it flicker.
… origin

The swatter and the cream were spawned at a point fixed 30 cm in front of
the rig's origin. In the editor the head is always above that origin and
facing along it; on a headset the player has stepped away and turned round,
so the tool they had just asked for could appear behind them (0.9 m away and
178 degrees off for a player turned 150 degrees). The point is now moved to
40 cm ahead of and 30 cm below the eyes before each spawn.
@RomeXO10
RomeXO10 force-pushed the module-2-improvements branch from b6d8c08 to 16f46e6 Compare October 6, 2026 02:17
@RomeXO10
RomeXO10 merged commit ca3752d into main Oct 12, 2026
1 check passed
@RomeXO10
RomeXO10 deleted the module-2-improvements branch October 12, 2026 02:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant