diff --git a/README.md b/README.md index c627cfc..41820fb 100644 --- a/README.md +++ b/README.md @@ -164,11 +164,26 @@ Faults have gameplay sources: a red light at the tree flat-spots the fronts, every 4th wreck rattles the sump loose (oil light), and getting busted kills the battery, because the divvy van jump-starts it wrong out of spite. The whole thing routes through an API (`twist/pump/grab/drop_at/yank_ok/ -steady_tick/clamp_on`), so the smoke suite plays a clean flat-tyre job -(quality 90+, buff verified on next boot) and a sabotaged oil job (stripped -thread, extraction step) headless. Gearbox and gasket still use the 2D panel -(`FAULTS[id].scene` routes each fault to its minigame) -- porting them to 3D -plus tool tiers and driveway customers is phases 2 and 3. +steady_tick/clamp_on/pick_light`), so the smoke suite plays a clean flat-tyre +job (quality 90+, buff verified on next boot) and a sabotaged oil job +(stripped thread, extraction step) headless. + +Phase 2 finishes the port: **gearbox and gasket are wrench jobs now** (the 2D +panel is gone) and both open with **diagnosis** -- three lit warning lights, +one honest, the code reader pointing at it through its cracked screen; a wrong +pick makes every nut in the job run a turn longer. The gearbox job is the +hero: no hoist means dragging 60 kg out by hand and holding the steady ring so +it doesn't land on you, while owning the crane turns it into the ceremony +(hook the chain, pump it out -- and at tier 3 the winch hooks itself). Every +tool line now has **three tiers** (Cashies -> Supercheap -> SNAP-OFF +PROFESSIONAL, buy again to upgrade): better sockets mean fewer turns and a +wider torque window, the tier-2 compressor's **rattle gun turns wheel nuts +into BRRRT clicks** (torque windows keep the hand skill -- guns strip), ramps +speed the jacking, the fluids shelf speeds pours, and the tier-3 scan tool +skips diagnosis entirely. Workmanship reaches the strip: an 85+ gearbox +rebuild takes an **extra blown shift** before grenading, an 85+ gasket job +**raises the overheat ceiling** -- both asserted in the suite. Phase 3 is +driveway customers. **The Shed is also a place** (`levels/the_shed`): a drivable suburban home base — brick house, paling fence, hills hoist mid-spin with the washing still on it, diff --git a/push16.log b/push16.log deleted file mode 100644 index 3d0b8a7..0000000 --- a/push16.log +++ /dev/null @@ -1,10 +0,0 @@ -Enumerating objects: 37, done. -Counting objects: 2% (1/37) Counting objects: 5% (2/37) Counting objects: 8% (3/37) Counting objects: 10% (4/37) Counting objects: 13% (5/37) Counting objects: 16% (6/37) Counting objects: 18% (7/37) Counting objects: 21% (8/37) Counting objects: 24% (9/37) Counting objects: 27% (10/37) Counting objects: 29% (11/37) Counting objects: 32% (12/37) Counting objects: 35% (13/37) Counting objects: 37% (14/37) Counting objects: 40% (15/37) Counting objects: 43% (16/37) Counting objects: 45% (17/37) Counting objects: 48% (18/37) Counting objects: 51% (19/37) Counting objects: 54% (20/37) Counting objects: 56% (21/37) Counting objects: 59% (22/37) Counting objects: 62% (23/37) Counting objects: 64% (24/37) Counting objects: 67% (25/37) Counting objects: 70% (26/37) Counting objects: 72% (27/37) Counting objects: 75% (28/37) Counting objects: 78% (29/37) Counting objects: 81% (30/37) Counting objects: 83% (31/37) Counting objects: 86% (32/37) Counting objects: 89% (33/37) Counting objects: 91% (34/37) Counting objects: 94% (35/37) Counting objects: 97% (36/37) Counting objects: 100% (37/37) Counting objects: 100% (37/37), done. -Delta compression using up to 32 threads -Compressing objects: 4% (1/24) Compressing objects: 8% (2/24) Compressing objects: 12% (3/24) Compressing objects: 16% (4/24) Compressing objects: 20% (5/24) Compressing objects: 25% (6/24) Compressing objects: 29% (7/24) Compressing objects: 33% (8/24) Compressing objects: 37% (9/24) Compressing objects: 41% (10/24) Compressing objects: 45% (11/24) Compressing objects: 50% (12/24) Compressing objects: 54% (13/24) Compressing objects: 58% (14/24) Compressing objects: 62% (15/24) Compressing objects: 66% (16/24) Compressing objects: 70% (17/24) Compressing objects: 75% (18/24) Compressing objects: 79% (19/24) Compressing objects: 83% (20/24) Compressing objects: 87% (21/24) Compressing objects: 91% (22/24) Compressing objects: 95% (23/24) Compressing objects: 100% (24/24) Compressing objects: 100% (24/24), done. -Writing objects: 3% (1/26) Writing objects: 7% (2/26) Writing objects: 11% (3/26) Writing objects: 15% (4/26) Writing objects: 19% (5/26) Writing objects: 23% (6/26) Writing objects: 26% (7/26) Writing objects: 34% (9/26) Writing objects: 38% (10/26) Writing objects: 42% (11/26) Writing objects: 46% (12/26) Writing objects: 50% (13/26) Writing objects: 53% (14/26) Writing objects: 61% (16/26) Writing objects: 65% (17/26) Writing objects: 69% (18/26) Writing objects: 73% (19/26) Writing objects: 76% (20/26) Writing objects: 80% (21/26) Writing objects: 84% (22/26) Writing objects: 88% (23/26) Writing objects: 92% (24/26) Writing objects: 96% (25/26) Writing objects: 100% (26/26) Writing objects: 100% (26/26), 312.96 KiB | 39.12 MiB/s, done. -Total 26 (delta 13), reused 0 (delta 0), pack-reused 0 (from 0) -remote: . Processing 1 references -remote: Processed 1 references in total -To ssh://100.71.119.27:222/monster/burnoutinfinityshitbox.git - fdbb921..17aabd7 main -> main diff --git a/push17.log b/push17.log deleted file mode 100644 index 1f430b1..0000000 --- a/push17.log +++ /dev/null @@ -1,10 +0,0 @@ -Enumerating objects: 16, done. -Counting objects: 6% (1/16) Counting objects: 12% (2/16) Counting objects: 18% (3/16) Counting objects: 25% (4/16) Counting objects: 31% (5/16) Counting objects: 37% (6/16) Counting objects: 43% (7/16) Counting objects: 50% (8/16) Counting objects: 56% (9/16) Counting objects: 62% (10/16) Counting objects: 68% (11/16) Counting objects: 75% (12/16) Counting objects: 81% (13/16) Counting objects: 87% (14/16) Counting objects: 93% (15/16) Counting objects: 100% (16/16) Counting objects: 100% (16/16), done. -Delta compression using up to 32 threads -Compressing objects: 11% (1/9) Compressing objects: 22% (2/9) Compressing objects: 33% (3/9) Compressing objects: 44% (4/9) Compressing objects: 55% (5/9) Compressing objects: 66% (6/9) Compressing objects: 77% (7/9) Compressing objects: 88% (8/9) Compressing objects: 100% (9/9) Compressing objects: 100% (9/9), done. -Writing objects: 11% (1/9) Writing objects: 22% (2/9) Writing objects: 33% (3/9) Writing objects: 44% (4/9) Writing objects: 55% (5/9) Writing objects: 66% (6/9) Writing objects: 77% (7/9) Writing objects: 88% (8/9) Writing objects: 100% (9/9) Writing objects: 100% (9/9), 5.50 KiB | 5.50 MiB/s, done. -Total 9 (delta 7), reused 0 (delta 0), pack-reused 0 (from 0) -remote: . Processing 1 references -remote: Processed 1 references in total -To ssh://100.71.119.27:222/monster/burnoutinfinityshitbox.git - 17aabd7..927b3a9 main -> main diff --git a/push18.log b/push18.log deleted file mode 100644 index 3f28e8e..0000000 --- a/push18.log +++ /dev/null @@ -1,10 +0,0 @@ -Enumerating objects: 21, done. -Counting objects: 4% (1/21) Counting objects: 9% (2/21) Counting objects: 14% (3/21) Counting objects: 19% (4/21) Counting objects: 23% (5/21) Counting objects: 28% (6/21) Counting objects: 33% (7/21) Counting objects: 38% (8/21) Counting objects: 42% (9/21) Counting objects: 47% (10/21) Counting objects: 52% (11/21) Counting objects: 57% (12/21) Counting objects: 61% (13/21) Counting objects: 66% (14/21) Counting objects: 71% (15/21) Counting objects: 76% (16/21) Counting objects: 80% (17/21) Counting objects: 85% (18/21) Counting objects: 90% (19/21) Counting objects: 95% (20/21) Counting objects: 100% (21/21) Counting objects: 100% (21/21), done. -Delta compression using up to 32 threads -Compressing objects: 8% (1/12) Compressing objects: 16% (2/12) Compressing objects: 25% (3/12) Compressing objects: 33% (4/12) Compressing objects: 41% (5/12) Compressing objects: 50% (6/12) Compressing objects: 58% (7/12) Compressing objects: 66% (8/12) Compressing objects: 75% (9/12) Compressing objects: 83% (10/12) Compressing objects: 91% (11/12) Compressing objects: 100% (12/12) Compressing objects: 100% (12/12), done. -Writing objects: 7% (1/13) Writing objects: 15% (2/13) Writing objects: 23% (3/13) Writing objects: 30% (4/13) Writing objects: 38% (5/13) Writing objects: 46% (6/13) Writing objects: 53% (7/13) Writing objects: 61% (8/13) Writing objects: 69% (9/13) Writing objects: 76% (10/13) Writing objects: 84% (11/13) Writing objects: 92% (12/13) Writing objects: 100% (13/13) Writing objects: 100% (13/13), 10.48 KiB | 10.48 MiB/s, done. -Total 13 (delta 8), reused 0 (delta 0), pack-reused 0 (from 0) -remote: . Processing 1 references -remote: Processed 1 references in total -To ssh://100.71.119.27:222/monster/burnoutinfinityshitbox.git - 927b3a9..4f91b34 main -> main diff --git a/push19.log b/push19.log deleted file mode 100644 index d9c3a4a..0000000 --- a/push19.log +++ /dev/null @@ -1,11 +0,0 @@ -Enumerating objects: 113, done. -Counting objects: 0% (1/113) Counting objects: 1% (2/113) Counting objects: 2% (3/113) Counting objects: 3% (4/113) Counting objects: 4% (5/113) Counting objects: 5% (6/113) Counting objects: 6% (7/113) Counting objects: 7% (8/113) Counting objects: 8% (10/113) Counting objects: 9% (11/113) Counting objects: 10% (12/113) Counting objects: 11% (13/113) Counting objects: 12% (14/113) Counting objects: 13% (15/113) Counting objects: 14% (16/113) Counting objects: 15% (17/113) Counting objects: 16% (19/113) Counting objects: 17% (20/113) Counting objects: 18% (21/113) Counting objects: 19% (22/113) Counting objects: 20% (23/113) Counting objects: 21% (24/113) Counting objects: 22% (25/113) Counting objects: 23% (26/113) Counting objects: 24% (28/113) Counting objects: 25% (29/113) Counting objects: 26% (30/113) Counting objects: 27% (31/113) Counting objects: 28% (32/113) Counting objects: 29% (33/113) Counting objects: 30% (34/113) Counting objects: 31% (36/113) Counting objects: 32% (37/113) Counting objects: 33% (38/113) Counting objects: 34% (39/113) Counting objects: 35% (40/113) Counting objects: 36% (41/113) Counting objects: 37% (42/113) Counting objects: 38% (43/113) Counting objects: 39% (45/113) Counting objects: 40% (46/113) Counting objects: 41% (47/113) Counting objects: 42% (48/113) Counting objects: 43% (49/113) Counting objects: 44% (50/113) Counting objects: 45% (51/113) Counting objects: 46% (52/113) Counting objects: 47% (54/113) Counting objects: 48% (55/113) Counting objects: 49% (56/113) Counting objects: 50% (57/113) Counting objects: 51% (58/113) Counting objects: 52% (59/113) Counting objects: 53% (60/113) Counting objects: 54% (62/113) Counting objects: 55% (63/113) Counting objects: 56% (64/113) Counting objects: 57% (65/113) Counting objects: 58% (66/113) Counting objects: 59% (67/113) Counting objects: 60% (68/113) Counting objects: 61% (69/113) Counting objects: 62% (71/113) Counting objects: 63% (72/113) Counting objects: 64% (73/113) Counting objects: 65% (74/113) Counting objects: 66% (75/113) Counting objects: 67% (76/113) Counting objects: 68% (77/113) Counting objects: 69% (78/113) Counting objects: 70% (80/113) Counting objects: 71% (81/113) Counting objects: 72% (82/113) Counting objects: 73% (83/113) Counting objects: 74% (84/113) Counting objects: 75% (85/113) Counting objects: 76% (86/113) Counting objects: 77% (88/113) Counting objects: 78% (89/113) Counting objects: 79% (90/113) Counting objects: 80% (91/113) Counting objects: 81% (92/113) Counting objects: 82% (93/113) Counting objects: 83% (94/113) Counting objects: 84% (95/113) Counting objects: 85% (97/113) Counting objects: 86% (98/113) Counting objects: 87% (99/113) Counting objects: 88% (100/113) Counting objects: 89% (101/113) Counting objects: 90% (102/113) Counting objects: 91% (103/113) Counting objects: 92% (104/113) Counting objects: 93% (106/113) Counting objects: 94% (107/113) Counting objects: 95% (108/113) Counting objects: 96% (109/113) Counting objects: 97% (110/113) Counting objects: 98% (111/113) Counting objects: 99% (112/113) Counting objects: 100% (113/113) Counting objects: 100% (113/113), done. -Delta compression using up to 32 threads -Compressing objects: 0% (1/104) Compressing objects: 1% (2/104) Compressing objects: 2% (3/104) Compressing objects: 3% (4/104) Compressing objects: 4% (5/104) Compressing objects: 5% (6/104) Compressing objects: 6% (7/104) Compressing objects: 7% (8/104) Compressing objects: 8% (9/104) Compressing objects: 9% (10/104) Compressing objects: 10% (11/104) Compressing objects: 11% (12/104) Compressing objects: 12% (13/104) Compressing objects: 13% (14/104) Compressing objects: 14% (15/104) Compressing objects: 15% (16/104) Compressing objects: 16% (17/104) Compressing objects: 17% (18/104) Compressing objects: 18% (19/104) Compressing objects: 19% (20/104) Compressing objects: 20% (21/104) Compressing objects: 21% (22/104) Compressing objects: 22% (23/104) Compressing objects: 23% (24/104) Compressing objects: 24% (25/104) Compressing objects: 25% (26/104) Compressing objects: 26% (28/104) Compressing objects: 27% (29/104) Compressing objects: 28% (30/104) Compressing objects: 29% (31/104) Compressing objects: 30% (32/104) Compressing objects: 31% (33/104) Compressing objects: 32% (34/104) Compressing objects: 33% (35/104) Compressing objects: 34% (36/104) Compressing objects: 35% (37/104) Compressing objects: 36% (38/104) Compressing objects: 37% (39/104) Compressing objects: 38% (40/104) Compressing objects: 39% (41/104) Compressing objects: 40% (42/104) Compressing objects: 41% (43/104) Compressing objects: 42% (44/104) Compressing objects: 43% (45/104) Compressing objects: 44% (46/104) Compressing objects: 45% (47/104) Compressing objects: 46% (48/104) Compressing objects: 47% (49/104) Compressing objects: 48% (50/104) Compressing objects: 49% (51/104) Compressing objects: 50% (52/104) Compressing objects: 51% (54/104) Compressing objects: 52% (55/104) Compressing objects: 53% (56/104) Compressing objects: 54% (57/104) Compressing objects: 55% (58/104) Compressing objects: 56% (59/104) Compressing objects: 57% (60/104) Compressing objects: 58% (61/104) Compressing objects: 59% (62/104) Compressing objects: 60% (63/104) Compressing objects: 61% (64/104) Compressing objects: 62% (65/104) Compressing objects: 63% (66/104) Compressing objects: 64% (67/104) Compressing objects: 65% (68/104) Compressing objects: 66% (69/104) Compressing objects: 67% (70/104) Compressing objects: 68% (71/104) Compressing objects: 69% (72/104) Compressing objects: 70% (73/104) Compressing objects: 71% (74/104) Compressing objects: 72% (75/104) Compressing objects: 73% (76/104) Compressing objects: 74% (77/104) Compressing objects: 75% (78/104) Compressing objects: 76% (80/104) Compressing objects: 77% (81/104) Compressing objects: 78% (82/104) Compressing objects: 79% (83/104) Compressing objects: 80% (84/104) Compressing objects: 81% (85/104) Compressing objects: 82% (86/104) Compressing objects: 83% (87/104) Compressing objects: 84% (88/104) Compressing objects: 85% (89/104) Compressing objects: 86% (90/104) Compressing objects: 87% (91/104) Compressing objects: 88% (92/104) Compressing objects: 89% (93/104) Compressing objects: 90% (94/104) Compressing objects: 91% (95/104) Compressing objects: 92% (96/104) Compressing objects: 93% (97/104) Compressing objects: 94% (98/104) Compressing objects: 95% (99/104) Compressing objects: 96% (100/104) Compressing objects: 97% (101/104) Compressing objects: 98% (102/104) Compressing objects: 99% (103/104) Compressing objects: 100% (104/104) Compressing objects: 100% (104/104), done. -Writing objects: 0% (1/104) Writing objects: 1% (2/104) Writing objects: 2% (3/104) Writing objects: 3% (4/104) Writing objects: 4% (5/104) Writing objects: 5% (6/104) Writing objects: 7% (8/104) Writing objects: 8% (9/104) Writing objects: 10% (11/104) Writing objects: 11% (12/104) Writing objects: 12% (13/104) Writing objects: 13% (14/104) Writing objects: 14% (15/104) Writing objects: 15% (16/104) Writing objects: 16% (17/104) Writing objects: 17% (18/104) Writing objects: 18% (19/104), 3.06 MiB | 4.73 MiB/s Writing objects: 19% (20/104), 3.06 MiB | 4.73 MiB/s Writing objects: 20% (21/104), 3.06 MiB | 4.73 MiB/s Writing objects: 21% (22/104), 3.06 MiB | 4.73 MiB/s Writing objects: 22% (23/104), 3.06 MiB | 4.73 MiB/s Writing objects: 23% (24/104), 3.06 MiB | 4.73 MiB/s Writing objects: 23% (24/104), 5.37 MiB | 4.65 MiB/s Writing objects: 25% (26/104), 5.37 MiB | 4.65 MiB/s Writing objects: 27% (29/104), 5.37 MiB | 4.65 MiB/s Writing objects: 28% (30/104), 5.37 MiB | 4.65 MiB/s Writing objects: 29% (31/104), 5.37 MiB | 4.65 MiB/s Writing objects: 29% (31/104), 8.62 MiB | 3.77 MiB/s Writing objects: 30% (32/104), 8.62 MiB | 3.77 MiB/s Writing objects: 31% (33/104), 8.62 MiB | 3.77 MiB/s Writing objects: 32% (34/104), 8.62 MiB | 3.77 MiB/s Writing objects: 33% (35/104), 8.62 MiB | 3.77 MiB/s Writing objects: 34% (36/104), 9.25 MiB | 3.20 MiB/s Writing objects: 35% (37/104), 9.25 MiB | 3.20 MiB/s Writing objects: 35% (37/104), 9.87 MiB | 2.82 MiB/s Writing objects: 35% (37/104), 10.62 MiB | 2.61 MiB/s Writing objects: 35% (37/104), 12.12 MiB | 2.37 MiB/s Writing objects: 36% (38/104), 12.12 MiB | 2.37 MiB/s Writing objects: 37% (39/104), 12.12 MiB | 2.37 MiB/s Writing objects: 37% (39/104), 13.81 MiB | 1.69 MiB/s Writing objects: 38% (40/104), 13.81 MiB | 1.69 MiB/s Writing objects: 39% (41/104), 13.81 MiB | 1.69 MiB/s Writing objects: 40% (42/104), 13.81 MiB | 1.69 MiB/s Writing objects: 40% (42/104), 15.87 MiB | 1.47 MiB/s Writing objects: 41% (43/104), 15.87 MiB | 1.47 MiB/s Writing objects: 42% (44/104), 15.87 MiB | 1.47 MiB/s Writing objects: 43% (45/104), 15.87 MiB | 1.47 MiB/s Writing objects: 44% (46/104), 15.87 MiB | 1.47 MiB/s Writing objects: 45% (47/104), 15.87 MiB | 1.47 MiB/s Writing objects: 46% (48/104), 15.87 MiB | 1.47 MiB/s Writing objects: 46% (48/104), 17.87 MiB | 1.65 MiB/s Writing objects: 46% (48/104), 18.87 MiB | 1.38 MiB/s Writing objects: 47% (49/104), 19.31 MiB | 1.28 MiB/s Writing objects: 48% (50/104), 19.31 MiB | 1.28 MiB/s Writing objects: 48% (50/104), 19.68 MiB | 1.14 MiB/s Writing objects: 48% (50/104), 20.18 MiB | 1.08 MiB/s Writing objects: 48% (50/104), 21.31 MiB | 942.00 KiB/s Writing objects: 49% (51/104), 21.31 MiB | 942.00 KiB/s Writing objects: 50% (52/104), 21.31 MiB | 942.00 KiB/s Writing objects: 51% (54/104), 21.31 MiB | 942.00 KiB/s Writing objects: 52% (55/104), 22.43 MiB | 900.00 KiB/s Writing objects: 53% (56/104), 22.43 MiB | 900.00 KiB/s Writing objects: 54% (57/104), 22.43 MiB | 900.00 KiB/s Writing objects: 55% (58/104), 22.43 MiB | 900.00 KiB/s Writing objects: 56% (59/104), 22.43 MiB | 900.00 KiB/s Writing objects: 57% (60/104), 22.43 MiB | 900.00 KiB/s Writing objects: 58% (61/104), 23.68 MiB | 1019.00 KiB/s Writing objects: 59% (62/104), 23.68 MiB | 1019.00 KiB/s Writing objects: 60% (63/104), 24.30 MiB | 994.00 KiB/s Writing objects: 61% (64/104), 24.30 MiB | 994.00 KiB/s Writing objects: 62% (65/104), 24.30 MiB | 994.00 KiB/s Writing objects: 63% (66/104), 24.30 MiB | 994.00 KiB/s Writing objects: 63% (66/104), 24.99 MiB | 1.17 MiB/s Writing objects: 64% (67/104), 24.99 MiB | 1.17 MiB/s Writing objects: 65% (68/104), 24.99 MiB | 1.17 MiB/s Writing objects: 66% (69/104), 24.99 MiB | 1.17 MiB/s Writing objects: 67% (70/104), 24.99 MiB | 1.17 MiB/s Writing objects: 68% (71/104), 25.93 MiB | 1.29 MiB/s Writing objects: 69% (72/104), 25.93 MiB | 1.29 MiB/s Writing objects: 69% (72/104), 26.55 MiB | 1.40 MiB/s Writing objects: 69% (72/104), 27.05 MiB | 1.36 MiB/s Writing objects: 70% (73/104), 27.05 MiB | 1.36 MiB/s Writing objects: 71% (74/104), 27.05 MiB | 1.36 MiB/s Writing objects: 72% (75/104), 27.05 MiB | 1.36 MiB/s Writing objects: 73% (76/104), 27.05 MiB | 1.36 MiB/s Writing objects: 74% (77/104), 27.05 MiB | 1.36 MiB/s Writing objects: 75% (78/104), 27.05 MiB | 1.36 MiB/s Writing objects: 75% (78/104), 28.43 MiB | 1.38 MiB/s Writing objects: 76% (80/104), 28.43 MiB | 1.38 MiB/s Writing objects: 77% (81/104), 28.43 MiB | 1.38 MiB/s Writing objects: 78% (82/104), 28.43 MiB | 1.38 MiB/s Writing objects: 79% (83/104), 28.43 MiB | 1.38 MiB/s Writing objects: 79% (83/104), 29.93 MiB | 1.21 MiB/s Writing objects: 80% (84/104), 29.93 MiB | 1.21 MiB/s Writing objects: 81% (85/104), 29.93 MiB | 1.21 MiB/s Writing objects: 82% (86/104), 29.93 MiB | 1.21 MiB/s Writing objects: 83% (87/104), 29.93 MiB | 1.21 MiB/s Writing objects: 84% (88/104), 29.93 MiB | 1.21 MiB/s Writing objects: 85% (89/104), 29.93 MiB | 1.21 MiB/s Writing objects: 86% (90/104), 30.74 MiB | 1.26 MiB/s Writing objects: 87% (91/104), 30.74 MiB | 1.26 MiB/s Writing objects: 88% (92/104), 30.74 MiB | 1.26 MiB/s Writing objects: 89% (93/104), 30.74 MiB | 1.26 MiB/s Writing objects: 90% (94/104), 31.86 MiB | 1.36 MiB/s Writing objects: 91% (95/104), 31.86 MiB | 1.36 MiB/s Writing objects: 92% (96/104), 31.86 MiB | 1.36 MiB/s Writing objects: 93% (97/104), 31.86 MiB | 1.36 MiB/s Writing objects: 94% (98/104), 31.86 MiB | 1.36 MiB/s Writing objects: 95% (99/104), 31.86 MiB | 1.36 MiB/s Writing objects: 96% (100/104), 31.86 MiB | 1.36 MiB/s Writing objects: 97% (101/104), 31.86 MiB | 1.36 MiB/s Writing objects: 98% (102/104), 31.86 MiB | 1.36 MiB/s Writing objects: 99% (103/104), 31.86 MiB | 1.36 MiB/s Writing objects: 100% (104/104), 31.86 MiB | 1.36 MiB/s Writing objects: 100% (104/104), 31.87 MiB | 1.56 MiB/s, done. -Total 104 (delta 50), reused 0 (delta 0), pack-reused 0 (from 0) -remote: Resolving deltas: 0% (0/50) remote: Resolving deltas: 2% (1/50) remote: Resolving deltas: 4% (2/50) remote: Resolving deltas: 6% (3/50) remote: Resolving deltas: 8% (4/50) remote: Resolving deltas: 10% (5/50) remote: Resolving deltas: 12% (6/50) remote: Resolving deltas: 14% (7/50) remote: Resolving deltas: 16% (8/50) remote: Resolving deltas: 18% (9/50) remote: Resolving deltas: 20% (10/50) remote: Resolving deltas: 22% (11/50) remote: Resolving deltas: 24% (12/50) remote: Resolving deltas: 26% (13/50) remote: Resolving deltas: 28% (14/50) remote: Resolving deltas: 30% (15/50) remote: Resolving deltas: 32% (16/50) remote: Resolving deltas: 34% (17/50) remote: Resolving deltas: 36% (18/50) remote: Resolving deltas: 38% (19/50) remote: Resolving deltas: 40% (20/50) remote: Resolving deltas: 42% (21/50) remote: Resolving deltas: 44% (22/50) remote: Resolving deltas: 46% (23/50) remote: Resolving deltas: 48% (24/50) remote: Resolving deltas: 50% (25/50) remote: Resolving deltas: 52% (26/50) remote: Resolving deltas: 54% (27/50) remote: Resolving deltas: 56% (28/50) remote: Resolving deltas: 58% (29/50) remote: Resolving deltas: 60% (30/50) remote: Resolving deltas: 62% (31/50) remote: Resolving deltas: 64% (32/50) remote: Resolving deltas: 66% (33/50) remote: Resolving deltas: 68% (34/50) remote: Resolving deltas: 70% (35/50) remote: Resolving deltas: 72% (36/50) remote: Resolving deltas: 74% (37/50) remote: Resolving deltas: 76% (38/50) remote: Resolving deltas: 78% (39/50) remote: Resolving deltas: 80% (40/50) remote: Resolving deltas: 82% (41/50) remote: Resolving deltas: 84% (42/50) remote: Resolving deltas: 86% (43/50) remote: Resolving deltas: 88% (44/50) remote: Resolving deltas: 90% (45/50) remote: Resolving deltas: 92% (46/50) remote: Resolving deltas: 94% (47/50) remote: Resolving deltas: 96% (48/50) remote: Resolving deltas: 98% (49/50) remote: Resolving deltas: 100% (50/50) remote: Resolving deltas: 100% (50/50), completed with 7 local objects. -remote: . Processing 1 references -remote: Processed 1 references in total -To ssh://100.71.119.27:222/monster/burnoutinfinityshitbox.git - 4f91b34..8967972 main -> main diff --git a/push20.log b/push20.log deleted file mode 100644 index 6c070d9..0000000 --- a/push20.log +++ /dev/null @@ -1,10 +0,0 @@ -Enumerating objects: 38, done. -Counting objects: 2% (1/38) Counting objects: 5% (2/38) Counting objects: 7% (3/38) Counting objects: 10% (4/38) Counting objects: 13% (5/38) Counting objects: 15% (6/38) Counting objects: 18% (7/38) Counting objects: 21% (8/38) Counting objects: 23% (9/38) Counting objects: 26% (10/38) Counting objects: 28% (11/38) Counting objects: 31% (12/38) Counting objects: 34% (13/38) Counting objects: 36% (14/38) Counting objects: 39% (15/38) Counting objects: 42% (16/38) Counting objects: 44% (17/38) Counting objects: 47% (18/38) Counting objects: 50% (19/38) Counting objects: 52% (20/38) Counting objects: 55% (21/38) Counting objects: 57% (22/38) Counting objects: 60% (23/38) Counting objects: 63% (24/38) Counting objects: 65% (25/38) Counting objects: 68% (26/38) Counting objects: 71% (27/38) Counting objects: 73% (28/38) Counting objects: 76% (29/38) Counting objects: 78% (30/38) Counting objects: 81% (31/38) Counting objects: 84% (32/38) Counting objects: 86% (33/38) Counting objects: 89% (34/38) Counting objects: 92% (35/38) Counting objects: 94% (36/38) Counting objects: 97% (37/38) Counting objects: 100% (38/38) Counting objects: 100% (38/38), done. -Delta compression using up to 32 threads -Compressing objects: 4% (1/23) Compressing objects: 8% (2/23) Compressing objects: 13% (3/23) Compressing objects: 17% (4/23) Compressing objects: 21% (5/23) Compressing objects: 26% (6/23) Compressing objects: 30% (7/23) Compressing objects: 34% (8/23) Compressing objects: 39% (9/23) Compressing objects: 43% (10/23) Compressing objects: 47% (11/23) Compressing objects: 52% (12/23) Compressing objects: 56% (13/23) Compressing objects: 60% (14/23) Compressing objects: 65% (15/23) Compressing objects: 69% (16/23) Compressing objects: 73% (17/23) Compressing objects: 78% (18/23) Compressing objects: 82% (19/23) Compressing objects: 86% (20/23) Compressing objects: 91% (21/23) Compressing objects: 95% (22/23) Compressing objects: 100% (23/23) Compressing objects: 100% (23/23), done. -Writing objects: 4% (1/25) Writing objects: 8% (2/25) Writing objects: 12% (3/25) Writing objects: 16% (4/25) Writing objects: 20% (5/25) Writing objects: 24% (6/25) Writing objects: 32% (8/25) Writing objects: 36% (9/25) Writing objects: 40% (10/25) Writing objects: 44% (11/25) Writing objects: 48% (12/25) Writing objects: 52% (13/25) Writing objects: 56% (14/25) Writing objects: 60% (15/25) Writing objects: 64% (16/25) Writing objects: 68% (17/25) Writing objects: 72% (18/25) Writing objects: 76% (19/25) Writing objects: 80% (20/25) Writing objects: 84% (21/25) Writing objects: 88% (22/25) Writing objects: 92% (23/25) Writing objects: 96% (24/25) Writing objects: 100% (25/25) Writing objects: 100% (25/25), 211.82 KiB | 30.26 MiB/s, done. -Total 25 (delta 13), reused 0 (delta 0), pack-reused 0 (from 0) -remote: . Processing 1 references -remote: Processed 1 references in total -To ssh://100.71.119.27:222/monster/burnoutinfinityshitbox.git - 8967972..6d740ad main -> main diff --git a/src/game.gd b/src/game.gd index dff99a0..7458a79 100644 --- a/src/game.gd +++ b/src/game.gd @@ -117,9 +117,11 @@ var _launch_pos := Vector3.ZERO ## Mechanical sympathy: none of these reset until the scene does. var tyre_temp := 0.0 # 0 cold .. 1 hot, built in the burnout box var eng_temp := 0.0 # limiter abuse; at 1.0 the head gasket lets go -var box_strikes := 0 # blown shifts this run; 3 grenades the gearbox +var box_strikes := 0 # blown shifts this run; box_limit grenades it +var box_limit := 3 # 4 if your last gearbox rebuild scored 85+ var box_blown := false var gasket_blown := false +var gasket_cap := 1.0 # 1.18 if your last gasket job scored 85+ var derby: Array[Cop] = [] # arena opponents; Cop AI already hunts a target var derby_centre: Vector3 @@ -290,6 +292,11 @@ func _ready() -> void: sc.add_point(drag_end + Vector3.UP * (drag_end_y + 0.7) + lane + fwd * 120.0) strip.curve = sc add_child(strip) + # workmanship pays here: an 85+ gearbox rebuild takes an extra blown + # shift, an 85+ gasket job raises the overheat ceiling + var wcid := Garage.car_id_of(car_path) + box_limit = 3 + (1 if Garage.quality(wcid, "gearbox") >= 85.0 else 0) + gasket_cap = 1.0 + (0.18 if Garage.quality(wcid, "gasket") >= 85.0 else 0.0) # the ladder: your current rung decides who's in the other lane drag_rung_now = mini(Garage.drag_rung(), DRAG_LADDER.size() - 1) var rung: Dictionary = DRAG_LADDER[drag_rung_now] @@ -826,7 +833,7 @@ func _drag_tick(delta: float) -> void: box_strikes += 1 bog = 0.9 Sfx.shot(car, "bog", -2.0) - if box_strikes >= 3: + if box_strikes >= box_limit: # third mistreated shift: the box keeps what's in it box_blown = true car.mech_pop() @@ -843,10 +850,10 @@ func _drag_tick(delta: float) -> void: if gear <= DRAG_GEARS and revs >= REDLINE: eng_temp += delta * 0.30 elif car.boosting: - eng_temp = minf(eng_temp + delta * 0.06, 0.99 if not gasket_blown else 1.0) + eng_temp = minf(eng_temp + delta * 0.06, gasket_cap - 0.01 if not gasket_blown else gasket_cap) else: eng_temp = maxf(eng_temp - delta * 0.04, 0.0) - if eng_temp >= 1.0 and not gasket_blown: + if eng_temp >= gasket_cap and not gasket_blown: gasket_blown = true car.set_steam(true) Sfx.shot(car, "bog", -2.0, 0.55) diff --git a/src/garage.gd b/src/garage.gd index b38ad08..62c1b55 100644 --- a/src/garage.gd +++ b/src/garage.gd @@ -61,19 +61,65 @@ const CATALOGUE := { ## THE SHED: workshop tools are global, bought once, and each unlocks a ## per-car TUNE-UP. Parts buy bling; the shed buys labour. Provenance of the ## hoist is not to be discussed. +## Each tool line has THREE tiers: Cashies-grade, Supercheap-grade, and +## SNAP-OFF PROFESSIONAL. Higher tiers change how jobs FEEL in the wrench +## minigame (fewer turns, wider torque windows, the rattle gun, a crane that +## does the lifting). Top-level name/price/desc mirror tier 1 for old code. const TOOLS := { "ramps": {"name": "SUPERCHEAP WHEEL RAMPS", "price": 800, - "desc": "Plastic. Rated to \"most cars, probably\"."}, + "desc": "Plastic. Rated to \"most cars, probably\".", + "tiers": [ + {"name": "SUPERCHEAP WHEEL RAMPS", "price": 800, + "desc": "Plastic. Rated to \"most cars, probably\". Jacking: slow."}, + {"name": "STEEL RAMPS + CHOCKS", "price": 2000, + "desc": "Actual metal. The car goes up like it means it."}, + {"name": "QUICK-JACK (KNOCKOFF)", "price": 4000, + "desc": "Whoosh. Three clicks and it's in the air."}]}, "reader": {"name": "CASHIES CODE READER", "price": 1200, - "desc": "One previous owner. Screen cracked. Works if you squint."}, + "desc": "One previous owner. Screen cracked. Works if you squint.", + "tiers": [ + {"name": "CASHIES CODE READER", "price": 1200, + "desc": "Screen cracked. Points at the right light, roughly."}, + {"name": "SUPERCHEAP SCAN TOOL", "price": 3000, + "desc": "Whole screen works. Names the fault outright."}, + {"name": "SNAP-OFF DIAGNOSTICS TABLET", "price": 6000, + "desc": "Knows what's wrong before you park. Skips diagnosis."}]}, "sockets": {"name": "ALDI SOCKET SET", "price": 600, - "desc": "Sunday special. Metric-ish."}, + "desc": "Sunday special. Metric-ish.", + "tiers": [ + {"name": "ALDI SOCKET SET", "price": 600, + "desc": "Sunday special. Metric-ish."}, + {"name": "SUPERCHEAP 200-PIECE", "price": 1500, + "desc": "A socket for everything, 30% fewer turns."}, + {"name": "SNAP-OFF PROFESSIONAL", "price": 3000, + "desc": "Fell off a van. Half the turns, forgiving torque."}]}, "compressor": {"name": "BUNNINGS AIR COMPRESSOR", "price": 1500, - "desc": "Came with three fittings, none of them the right one."}, + "desc": "Came with three fittings, none of them the right one.", + "tiers": [ + {"name": "BUNNINGS AIR COMPRESSOR", "price": 1500, + "desc": "Came with three fittings, none of them the right one."}, + {"name": "COMPRESSOR + RATTLE GUN", "price": 3500, + "desc": "BRRRT. Wheel nuts become a clicking matter."}, + {"name": "SHOP AIR, PLUMBED", "price": 7000, + "desc": "BRRRT but faster, and the neighbours hate you."}]}, "hoist": {"name": "CASHIES CHAIN HOIST (ON WHEELS)", "price": 2500, - "desc": "Definitely not stolen. Rolls beautifully."}, + "desc": "Definitely not stolen. Rolls beautifully.", + "tiers": [ + {"name": "CASHIES CHAIN HOIST (ON WHEELS)", "price": 2500, + "desc": "Definitely not stolen. Does the 60 kg lifting."}, + {"name": "2-TONNE CRANE, NEW CHAINS", "price": 5000, + "desc": "Half the pumping, none of the creaking."}, + {"name": "GANTRY + ELECTRIC WINCH", "price": 9000, + "desc": "Hooks itself up. You just point."}]}, "additives": {"name": "SERVO ADDITIVE CRATE", "price": 400, - "desc": "A crate of bottles that all claim +30 horsepower."}, + "desc": "A crate of bottles that all claim +30 horsepower.", + "tiers": [ + {"name": "SERVO ADDITIVE CRATE", "price": 400, + "desc": "A crate of bottles that all claim +30 horsepower."}, + {"name": "PROPER FLUIDS SHELF", "price": 1000, + "desc": "Funnels that fit. Pours nearly pour themselves."}, + {"name": "BULK DRUMS + PUMP", "price": 2000, + "desc": "Tap the drum, walk away, come back to full."}]}, } ## tune id -> {name, desc, price (labour), tool (required)} const TUNES := { @@ -147,17 +193,39 @@ static func tools() -> Array: static func has_tool(tool: String) -> bool: return tool in tools() -static func buy_tool(tool: String) -> bool: +static func tool_tier(tool: String) -> int: + ## 0 = not owned; 1..3 = owned tier. Pre-tier saves count as tier 1. _load() - if not TOOLS.has(tool) or has_tool(tool): + if not has_tool(tool): + return 0 + return int(_data.get("tool_tiers", {}).get(tool, 1)) + +static func tool_upgrade_price(tool: String) -> int: + ## Price of the NEXT tier, or 0 when maxed. + var t := tool_tier(tool) + var tiers: Array = TOOLS[tool]["tiers"] + return int(tiers[t]["price"]) if t < tiers.size() else 0 + +static func buy_tool(tool: String) -> bool: + ## First purchase = tier 1; buying again upgrades a tier, up to 3. + _load() + if not TOOLS.has(tool): return false - var price: int = TOOLS[tool]["price"] + var t := tool_tier(tool) + var tiers: Array = TOOLS[tool]["tiers"] + if t >= tiers.size(): + return false + var price: int = tiers[t]["price"] if int(_data["bucks"]) < price: return false _data["bucks"] = int(_data["bucks"]) - price - var lst: Array = _data.get("tools", []) - lst.append(tool) - _data["tools"] = lst + if t == 0: + var lst: Array = _data.get("tools", []) + lst.append(tool) + _data["tools"] = lst + var tt: Dictionary = _data.get("tool_tiers", {}) + tt[tool] = t + 1 + _data["tool_tiers"] = tt _flush() return true @@ -197,10 +265,10 @@ static func buy_tune(car_id: String, tune: String) -> bool: ## scene: which minigame fixes it -- "repair" (2D panel) or "wrench" (3D shed) const FAULTS := { "gearbox": {"name": "GRENADED GEARBOX", "light": "DRIVETRAIN", "shop": 900, - "tool": "hoist", "scene": "repair", + "tool": "hoist", "scene": "wrench", "desc": "Third gear is a maraca now. Leaks on everything it loves."}, "gasket": {"name": "BLOWN HEAD GASKET", "light": "ENGINE TEMP", "shop": 700, - "tool": "sockets", "scene": "repair", + "tool": "sockets", "scene": "wrench", "desc": "Mayonnaise under the oil cap. Not the good kind."}, "flat": {"name": "FLAT-SPOTTED TYRES", "light": "TYRE PRESSURE", "shop": 250, "tool": "sockets", "scene": "wrench", diff --git a/src/garage_ui.gd b/src/garage_ui.gd index 9643f71..8165a2c 100644 --- a/src/garage_ui.gd +++ b/src/garage_ui.gd @@ -102,11 +102,19 @@ func _refresh() -> void: var tag := "[FITTED]" if Garage.has(car_id, id) else "$%d" % c["price"] list.add_item("%-28s %s" % [c["name"], tag]) entries.append({"kind": "part", "id": id}) - _header("--- THE SHED (tools, buy once, unlock tune-ups) ---") + _header("--- THE SHED (tools; buy again to upgrade the tier) ---") for id in Garage.TOOLS: var c: Dictionary = Garage.TOOLS[id] - var tag := "[OWNED]" if Garage.has_tool(id) else "$%d" % c["price"] - list.add_item("%-28s %s" % [c["name"], tag]) + var tier := Garage.tool_tier(id) + var shown: String = c["tiers"][maxi(tier - 1, 0)]["name"] + var tag: String + if tier == 0: + tag = "$%d" % int(c["price"]) + elif tier < 3: + tag = "[T%d] UP $%d" % [tier, Garage.tool_upgrade_price(id)] + else: + tag = "[T3 MAXED]" + list.add_item("%-28s %s" % [shown, tag]) entries.append({"kind": "tool", "id": id}) _header("--- TUNE-UPS (this car, needs the tool) ---") for id in Garage.TUNES: @@ -140,8 +148,12 @@ func _entry_info(i: int) -> Dictionary: "done": Garage.has(car_id, e["id"]), "locked": false} "tool": var c: Dictionary = Garage.TOOLS[e["id"]] - return {"desc": c["desc"], "price": c["price"], - "done": Garage.has_tool(e["id"]), "locked": false} + var tier := Garage.tool_tier(e["id"]) + var d: String = c["tiers"][mini(tier, 2)]["desc"] + if tier > 0 and tier < 3: + d = "NEXT: " + String(c["tiers"][tier]["name"]) + " -- " + d + return {"desc": d, "price": Garage.tool_upgrade_price(e["id"]), + "done": tier >= 3, "locked": false} "tune": var c: Dictionary = Garage.TUNES[e["id"]] return {"desc": c["desc"], "price": c["price"], @@ -177,11 +189,10 @@ func _on_buy() -> void: "tune": ok = Garage.buy_tune(car_id, e["id"]) "shopfix": ok = Garage.shop_fix(car_id, e["id"]) "fault": - # DIY: off to whichever minigame owns this fault (repair = 2D panel, - # wrench = 3D shed); it clears the fault on completion + # DIY: off to the wrench minigame; it clears the fault on completion Garage.ui_fault = e["id"] get_tree().change_scene_to_file("res://src/%s.tscn" - % String(Garage.FAULTS[e["id"]].get("scene", "repair"))) + % String(Garage.FAULTS[e["id"]].get("scene", "wrench"))) return if ok: Sfx.shot_2d("clunk") diff --git a/src/repair.tscn b/src/repair.tscn deleted file mode 100644 index 4b03a46..0000000 --- a/src/repair.tscn +++ /dev/null @@ -1,8 +0,0 @@ -[gd_scene load_steps=2 format=3] - -[ext_resource type="Script" path="res://src/repair_ui.gd" id="1"] - -[node name="Repair" type="Control"] -anchor_right = 1.0 -anchor_bottom = 1.0 -script = ExtResource("1") diff --git a/src/repair_ui.gd b/src/repair_ui.gd deleted file mode 100644 index 2961292..0000000 --- a/src/repair_ui.gd +++ /dev/null @@ -1,369 +0,0 @@ -extends Control -## The shed repair minigame: fix what you broke with the tools you actually -## own. Diagnose off the dash lights (the CASHIES CODE READER earns its keep -## here), then work through drag / click / twist steps. Owning the right shed -## tool makes a step civilised; not owning it means vice grips and regret. -## -## Which car and which fault come in via Garage.ui_stats_path / Garage.ui_fault -## (set by the garage screen). The input handlers all route through pick_light / -## twist / pump / drop_at, so the smoke suite can play the whole game headless. - -const LIGHT_POOL := ["ENGINE TEMP", "DRIVETRAIN", "OIL PRESSURE", "BATTERY", "AIRBAG (ALWAYS ON)"] -const NUT_R := 150.0 # grab radius around the nut, px - -var car_id := "" -var fault := "" -var steps: Array = [] # {kind: diagnose|twist|drag|pump, ...} -var step_index := 0 -var done := false -var penalty_turns := 0.0 # wrong diagnosis = extra turns on every nut -var twist_prog := 0.0 -var wrong_way := 0.0 -var pump_count := 0 -var dragging := false -var missing_tool := false # any step done the hard way -> bolts left over - -var title_label: Label -var step_label: Label -var tool_label: Label -var prog_bar: ProgressBar -var count_label: Label -var stage: Control # per-step widgets live here, cleared each step -var nut: Control = null -var drag_item: Panel = null -var drag_target: Panel = null -var light_btns: Array = [] - -func _ready() -> void: - car_id = Garage.car_id_of(Garage.ui_stats_path) - fault = Garage.ui_fault - steps = _build_steps() - - var bg := ColorRect.new() - bg.color = Color(0.06, 0.06, 0.08) - bg.set_anchors_preset(Control.PRESET_FULL_RECT) - # IGNORE or the backdrop eats every click before root._gui_input sees it - bg.mouse_filter = Control.MOUSE_FILTER_IGNORE - add_child(bg) - - var box := VBoxContainer.new() - box.set_anchors_preset(Control.PRESET_FULL_RECT) - box.offset_left = 60 - box.offset_top = 30 - box.offset_right = -60 - box.offset_bottom = -30 - box.add_theme_constant_override("separation", 10) - add_child(box) - - title_label = Label.new() - title_label.text = "THE SHED -- %s" % Garage.FAULTS.get(fault, {}).get("name", "???") - title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - title_label.add_theme_font_size_override("font_size", 36) - title_label.add_theme_color_override("font_color", Color(1, 0.82, 0.35)) - box.add_child(title_label) - - count_label = Label.new() - count_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - count_label.add_theme_font_size_override("font_size", 20) - box.add_child(count_label) - - step_label = Label.new() - step_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - step_label.add_theme_font_size_override("font_size", 30) - box.add_child(step_label) - - tool_label = Label.new() - tool_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - tool_label.add_theme_font_size_override("font_size", 19) - tool_label.add_theme_color_override("font_color", Color(0.65, 0.68, 0.72)) - box.add_child(tool_label) - - stage = Control.new() - stage.size_flags_vertical = Control.SIZE_EXPAND_FILL - box.add_child(stage) - - prog_bar = ProgressBar.new() - prog_bar.custom_minimum_size = Vector2(0, 20) - prog_bar.show_percentage = false - box.add_child(prog_bar) - - var back := Button.new() - back.text = "WALK AWAY (Esc)" - back.add_theme_font_size_override("font_size", 20) - back.pressed.connect(_leave) - box.add_child(back) - - _show_step() - -func _build_steps() -> Array: - ## The job sheet. Tools shorten it or soften it; their absence is content. - var out: Array = [{"kind": "diagnose"}] - match fault: - "gasket": - var t := 4.0 if Garage.has_tool("sockets") else 8.0 - out.append({"kind": "twist", "label": "UNDO THE HEAD BOLTS", "dir": -1.0, "turns": t}) - out.append({"kind": "drag", "label": "OLD GASKET INTO THE BIN", "item": "CRUSTY GASKET", "target": "THE BIN"}) - out.append({"kind": "drag", "label": "NEW GASKET ONTO THE BLOCK", "item": "GASKET (EBAY, \"GENUINE\")", "target": "THE BLOCK"}) - out.append({"kind": "twist", "label": "TORQUE THE HEAD DOWN", "dir": 1.0, "turns": t}) - out.append({"kind": "pump", "label": "TOP UP THE COOLANT", "clicks": 5 if Garage.has_tool("additives") else 8}) - "gearbox": - out.append({"kind": "pump", "label": "JACK IT UP", "clicks": 5 if Garage.has_tool("ramps") else 10}) - var t := 5.0 if Garage.has_tool("sockets") else 10.0 - out.append({"kind": "twist", "label": "UNDO THE BELLHOUSING BOLTS", "dir": -1.0, "turns": t}) - if not Garage.has_tool("hoist"): - out.append({"kind": "drag", "label": "DRAG THE DEAD BOX OUT (it's 60kg)", "item": "DEAD GEARBOX", "target": "THE BENCH"}) - out.append({"kind": "drag", "label": "CASHIES BOX IN", "item": "GEARBOX (ONE OWNER, HONEST)", "target": "THE TUNNEL"}) - out.append({"kind": "twist", "label": "BOLT IT ALL BACK UP", "dir": 1.0, "turns": t}) - return out - -## ---- step display --------------------------------------------------------- - -func _show_step() -> void: - for c in stage.get_children(): - c.queue_free() - nut = null - drag_item = null - drag_target = null - light_btns.clear() - twist_prog = 0.0 - wrong_way = 0.0 - pump_count = 0 - if step_index >= steps.size(): - _finish() - return - var st: Dictionary = steps[step_index] - count_label.text = "STEP %d / %d" % [step_index + 1, steps.size()] - prog_bar.value = 0 - prog_bar.max_value = 1.0 - match st["kind"]: - "diagnose": - _show_diagnose() - "twist": - step_label.text = st["label"] - var need := _turns_needed(st) - tool_label.text = ("ALDI SOCKETS: metric-ish, fits mostly" if Garage.has_tool("sockets") - else "VICE GRIPS: this will end badly") - if not Garage.has_tool("sockets"): - missing_tool = true - prog_bar.max_value = need * TAU - nut = _make_nut(st) - "drag": - step_label.text = st["label"] - tool_label.text = "drag it. with the mouse. like your life." - _make_drag(st) - "pump": - step_label.text = st["label"] - tool_label.text = ("SUPERCHEAP RAMPS: rated to \"most cars, probably\"" - if fault == "gearbox" and Garage.has_tool("ramps") else "click. click. click.") - prog_bar.max_value = int(st["clicks"]) - _make_pump(st) - -func _show_diagnose() -> void: - step_label.text = "WHAT'S THAT LIGHT, THEN?" - var correct: String = Garage.FAULTS[fault]["light"] - var pool := LIGHT_POOL.filter(func(l): return l != correct) - # deterministic decoys: seeded off the fault so tests can rely on layout - var rng := RandomNumberGenerator.new() - rng.seed = hash(fault) - pool.shuffle() - var lights: Array = [correct, pool[0], pool[1]] - lights.shuffle() - if Garage.has_tool("reader"): - tool_label.text = "CASHIES READER SAYS: \"%s\" (screen cracked)" % _garble(correct) - else: - tool_label.text = "no code reader. vibes only." - var row := HBoxContainer.new() - row.add_theme_constant_override("separation", 26) - row.set_anchors_preset(Control.PRESET_CENTER) - row.grow_horizontal = Control.GROW_DIRECTION_BOTH # centre ON the anchor - stage.add_child(row) - for l in lights: - var b := Button.new() - b.text = "(!) %s" % l - b.add_theme_font_size_override("font_size", 24) - if Garage.has_tool("reader") and l == correct: - b.modulate = Color(1.0, 0.75, 0.35) # the reader points, kind of - b.pressed.connect(func() -> void: pick_light(lights.find(l))) - row.add_child(b) - light_btns.append({"btn": b, "light": l}) - -func _garble(s: String) -> String: - var out := "" - for i in s.length(): - out += ["#", "!", "0"][i % 3] if i % 3 == 1 and s[i] != " " else s[i] - return out - -func _make_nut(st: Dictionary) -> Control: - var n := Panel.new() - n.mouse_filter = Control.MOUSE_FILTER_IGNORE # let root handle the twisting - n.custom_minimum_size = Vector2(120, 120) - n.set_anchors_preset(Control.PRESET_CENTER) - n.position = Vector2(-60, -60) - n.pivot_offset = Vector2(60, 60) - n.rotation = 0.0 - stage.add_child(n) - var l := Label.new() - l.text = "NUT" - l.set_anchors_preset(Control.PRESET_CENTER) - l.add_theme_font_size_override("font_size", 26) - n.add_child(l) - var hint := Label.new() - hint.text = "hold + drag in circles (%s)" % ("anticlockwise" if st["dir"] < 0 else "clockwise") - hint.set_anchors_preset(Control.PRESET_CENTER_BOTTOM) - hint.grow_horizontal = Control.GROW_DIRECTION_BOTH - hint.position.y = -60 - hint.add_theme_font_size_override("font_size", 18) - stage.add_child(hint) - return n - -func _make_drag(st: Dictionary) -> void: - drag_target = Panel.new() - drag_target.mouse_filter = Control.MOUSE_FILTER_IGNORE - drag_target.custom_minimum_size = Vector2(260, 150) - drag_target.set_anchors_preset(Control.PRESET_CENTER_RIGHT) - drag_target.position += Vector2(-290, -75) - stage.add_child(drag_target) - var tl := Label.new() - tl.text = String(st["target"]) - tl.set_anchors_preset(Control.PRESET_CENTER) - tl.add_theme_font_size_override("font_size", 20) - drag_target.add_child(tl) - drag_item = Panel.new() - drag_item.mouse_filter = Control.MOUSE_FILTER_IGNORE - drag_item.custom_minimum_size = Vector2(200, 90) - drag_item.self_modulate = Color(0.8, 0.55, 0.3) - drag_item.set_anchors_preset(Control.PRESET_CENTER_LEFT) - drag_item.position += Vector2(60, -45) - stage.add_child(drag_item) - var il := Label.new() - il.text = String(st["item"]) - il.set_anchors_preset(Control.PRESET_CENTER) - il.add_theme_font_size_override("font_size", 17) - drag_item.add_child(il) - -func _make_pump(st: Dictionary) -> void: - var b := Button.new() - b.text = "PUMP" - b.custom_minimum_size = Vector2(220, 120) - b.add_theme_font_size_override("font_size", 34) - b.set_anchors_preset(Control.PRESET_CENTER) - b.position = Vector2(-110, -60) - b.pressed.connect(pump) - stage.add_child(b) - -## ---- the actual game (also the test API) ---------------------------------- - -func pick_light(i: int) -> void: - if done or steps[step_index]["kind"] != "diagnose" or i >= light_btns.size(): - return - if light_btns[i]["light"] == Garage.FAULTS[fault]["light"]: - Sfx.shot_2d("clunk", -6.0) - _advance() - else: - penalty_turns += 2.0 - Sfx.shot_2d("bog", -6.0) - tool_label.text = "nope. pulled the wrong thing apart first. every nut is +2 turns now." - -func _turns_needed(st: Dictionary) -> float: - return float(st["turns"]) + penalty_turns - -func twist(rad: float) -> void: - ## rad > 0 = clockwise on screen. Only the step's direction makes progress; - ## a full turn the wrong way earns commentary. - if done or steps[step_index]["kind"] != "twist": - return - var st: Dictionary = steps[step_index] - var toward: float = rad * float(st["dir"]) - if toward > 0.0: - twist_prog += toward - wrong_way = 0.0 - else: - wrong_way += -toward - if wrong_way > TAU: - wrong_way = 0.0 - tool_label.text = "OTHER WAY." - Sfx.shot_2d("bog", -10.0) - if nut: - nut.rotation = twist_prog * float(st["dir"]) - prog_bar.value = twist_prog - if twist_prog >= _turns_needed(st) * TAU: - Sfx.shot_2d("clunk", -4.0) - _advance() - -func pump() -> void: - if done or steps[step_index]["kind"] != "pump": - return - pump_count += 1 - Sfx.shot_2d("clunk", -12.0) - prog_bar.value = pump_count - if pump_count >= int(steps[step_index]["clicks"]): - _advance() - -func drop_at(pos: Vector2) -> void: - if done or steps[step_index]["kind"] != "drag" or drag_target == null: - return - if drag_target.get_global_rect().has_point(pos): - Sfx.shot_2d("clunk", -4.0) - _advance() - -func _advance() -> void: - step_index += 1 - _show_step() - -func _finish() -> void: - done = true - Garage.clear_fault(car_id, fault) - step_label.text = "FIXED. PROBABLY." - count_label.text = "" - tool_label.text = ("Three bolts left over. They're spares now." if missing_tool - else "Went back together like it wanted to live.") - prog_bar.value = prog_bar.max_value - Sfx.shot_2d("sting_win", -4.0) - var b := Button.new() - b.text = "BACK TO THE GARAGE" - b.add_theme_font_size_override("font_size", 26) - b.set_anchors_preset(Control.PRESET_CENTER) - b.position = Vector2(-160, -30) - b.custom_minimum_size = Vector2(320, 60) - b.pressed.connect(_leave) - stage.add_child(b) - -func _leave() -> void: - get_tree().change_scene_to_file("res://src/garage.tscn") - -## ---- mouse plumbing: everything routes to the API above ------------------- - -var _twisting := false -var _prev_angle := 0.0 - -func _gui_input(event: InputEvent) -> void: - if done or step_index >= steps.size(): - return - var st: Dictionary = steps[step_index] - if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT: - if event.pressed: - if st["kind"] == "twist" and nut != null \ - and event.position.distance_to(nut.get_global_rect().get_center()) < NUT_R: - _twisting = true - _prev_angle = (event.position - nut.get_global_rect().get_center()).angle() - elif st["kind"] == "drag" and drag_item != null \ - and drag_item.get_global_rect().has_point(event.position): - dragging = true - else: - _twisting = false - if dragging: - dragging = false - drop_at(event.position) - elif event is InputEventMouseMotion: - if _twisting and nut != null: - # explicit type: event is base InputEvent here, so .position is a - # Variant and := cannot infer through it (the parse error hangs cold) - var a: float = (event.position - nut.get_global_rect().get_center()).angle() - twist(wrapf(a - _prev_angle, -PI, PI)) - _prev_angle = a - elif dragging and drag_item != null: - drag_item.global_position = event.position - drag_item.size * 0.5 - -func _input(event: InputEvent) -> void: - if event.is_action_pressed("ui_cancel"): - _leave() diff --git a/src/repair_ui.gd.uid b/src/repair_ui.gd.uid deleted file mode 100644 index f726672..0000000 --- a/src/repair_ui.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bray7xq7dem20 diff --git a/src/wrench.gd b/src/wrench.gd index 73db1fd..b6d0fac 100644 --- a/src/wrench.gd +++ b/src/wrench.gd @@ -14,8 +14,16 @@ extends Node3D ## grab, drop, yank_ok, steady_tick, clamp, twist_release, skip_wait) so the ## smoke suite plays entire jobs headless. -const WINDOW := 0.8 # rad of "seated" travel after the turns are in const CAR_AT := Vector3(12, 0.0, -14.2) # the shed's painted bay +## Tool tiers change how jobs FEEL. Tables indexed by Garage.tool_tier() 0..3. +## typed so indexing them stays inference-friendly (the := trap, 4th visit) +const TURN_MULT: Array[float] = [1.6, 1.0, 0.7, 0.5] # sockets tier +const WINDOW_W: Array[float] = [0.8, 0.8, 1.0, 1.3] # torque forgiveness, rad +const JACK_CLICKS: Array[int] = [10, 8, 5, 3] # ramps +const POUR_MULT: Array[float] = [1.3, 1.0, 0.6, 0.4] # additives +const RATTLE_CLICKS: Array[int] = [0, 0, 4, 2] # compressor >=2: BRRRT +const CRANE_CLICKS: Array[int] = [0, 10, 5, 4] # hoist: crane pumping +const LIGHT_POOL := ["ENGINE TEMP", "DRIVETRAIN", "OIL PRESSURE", "BATTERY", "AIRBAG (ALWAYS ON)"] var car_id := "" var fault := "" @@ -31,6 +39,10 @@ var pump_count := 0 var steady_need := 0.0 var wait_left := 0.0 var clamps_done: Array = [] +var penalty_turns := 0.0 # wrong diagnosis: every nut gets this much longer +var light_options: Array = [] +var _light_btns: Array = [] +var _diag_layer: CanvasLayer = null var props := {} # name -> Node3D (spare wheel, battery, tray...) var model: Node3D var cam: Camera3D @@ -134,48 +146,77 @@ func _anchor_engine() -> Vector3: func _anchor_sump() -> Vector3: return CAR_AT + Vector3(0.2, 0.28, -stats.length * 0.18) +func _t(base: float) -> float: + return base * TURN_MULT[Garage.tool_tier("sockets")] + +func _win() -> float: + return WINDOW_W[Garage.tool_tier("sockets")] + +func _sockets_note() -> String: + return ["VICE GRIPS: this will end badly", "ALDI SOCKETS: fits, mostly", + "SUPERCHEAP 200-PIECE: there IS a socket for that", + "SNAP-OFF PROFESSIONAL: it just... undoes"][Garage.tool_tier("sockets")] + +func _apply_tiers(sheet: Array) -> Array: + ## Post-pass: the rattle gun (compressor t2+) turns non-torque nut twists + ## into BRRRT clicks. Torque windows keep the hand skill -- guns strip. + var rattle := RATTLE_CLICKS[Garage.tool_tier("compressor")] + if rattle > 0: + for i in sheet.size(): + var st: Dictionary = sheet[i] + if st["verb"] == "twist" and bool(st.get("nut", false)) and not bool(st.get("window", false)): + sheet[i] = {"verb": "pump", "clicks": rattle, "cam": st["cam"], + "label": st["label"] + " (BRRRT)", "note": "the rattle gun eats this step"} + # gearbox/gasket aren't obvious from the driver's seat: diagnose first, + # unless the SNAP-OFF tablet already told you on the way in + if fault in ["gearbox", "gasket"] and Garage.tool_tier("reader") < 3: + sheet.push_front({"verb": "diagnose", "cam": "bonnet", + "label": "WHAT'S THAT LIGHT, THEN?", "note": ""}) + return sheet + func _job() -> Array: - var have_sockets := Garage.has_tool("sockets") - var t := 3.0 if have_sockets else 6.0 + var pour := POUR_MULT[Garage.tool_tier("additives")] + var jack := JACK_CLICKS[Garage.tool_tier("ramps")] + var hoist := Garage.tool_tier("hoist") match fault: "flat": - return [ - {"verb": "pump", "clicks": 8, "cam": "wheel", "label": "JACK IT UP", + return _apply_tiers([ + {"verb": "pump", "clicks": jack, "cam": "wheel", "label": "JACK IT UP", "note": "click it like you mean it"}, - {"verb": "twist", "dir": -1.0, "turns": t, "window": false, "cam": "wheel", - "label": "CRACK THE NUTS OFF", - "note": "ALDI SOCKETS: fits, mostly" if have_sockets else "VICE GRIPS: this will end badly"}, + {"verb": "twist", "dir": -1.0, "turns": _t(3.0), "window": false, "nut": true, + "cam": "wheel", "label": "CRACK THE NUTS OFF", "note": _sockets_note()}, {"verb": "yank", "cam": "wheel", "label": "POP THE FLAT OFF", "note": "press on it and FLICK"}, {"verb": "grab", "item": "spare", "at": "wheel", "cam": "wheel", "label": "SPARE ON", "note": "grab it, hang it on the studs"}, {"verb": "steady", "secs": 2.0, "cam": "wheel", "label": "LINE UP THE STUDS", "note": "keep the cursor in the ring"}, - {"verb": "twist", "dir": 1.0, "turns": t, "window": true, "cam": "wheel", - "label": "TORQUE THE NUTS", "note": "release IN the green -- past it strips"}, + {"verb": "twist", "dir": 1.0, "turns": _t(3.0), "window": true, "nut": true, + "cam": "wheel", "label": "TORQUE THE NUTS", + "note": "release IN the green -- past it strips"}, {"verb": "pump", "clicks": 4, "cam": "wheel", "label": "LET IT DOWN", "note": "gently. gently."}, - ] + ]) "oil": - return [ + return _apply_tiers([ {"verb": "grab", "item": "tray", "at": "sump", "cam": "under", "label": "TRAY UNDER THE SUMP", "note": "future-you says thanks"}, - {"verb": "twist", "dir": -1.0, "turns": 1.5, "window": false, "cam": "under", + {"verb": "twist", "dir": -1.0, "turns": _t(1.5), "window": false, "cam": "under", "label": "CRACK THE DRAIN PLUG", "note": "it's tighter than it needs to be"}, {"verb": "yank", "cam": "under", "label": "PLUG OUT", "note": "flick -- and stand back"}, {"verb": "wait", "secs": 2.0, "cam": "under", "label": "LET HER DRAIN", "note": "black gold. worthless black gold."}, - {"verb": "twist", "dir": 1.0, "turns": 1.5, "window": true, "cam": "under", + {"verb": "twist", "dir": 1.0, "turns": _t(1.5), "window": true, "cam": "under", "label": "PLUG BACK IN", "note": "release in the green: sump threads are soft"}, {"verb": "grab", "item": "bottle", "at": "engine", "cam": "bonnet", "label": "FRESH OIL UP TOP", "note": "the good stuff ($12/5L)"}, - {"verb": "steady", "secs": 2.5, "cam": "bonnet", "label": "POUR IT IN, STEADY", + {"verb": "steady", "secs": 2.5 * pour, "cam": "bonnet", "label": "POUR IT IN, STEADY", "note": "outside the ring = on the manifold"}, - ] + ]) "battery": - return [ + return _apply_tiers([ {"verb": "yank", "cam": "bonnet", "label": "BONNET UP", "note": "flick it open"}, - {"verb": "twist", "dir": -1.0, "turns": 1.0, "window": false, "cam": "bonnet", + {"verb": "twist", "dir": -1.0, "turns": _t(1.0), "window": false, "cam": "bonnet", "label": "CLAMPS OFF", "note": "10mm. it's always 10mm. where's the 10mm"}, {"verb": "grab", "item": "old_batt", "at": "bench", "cam": "bonnet", "label": "DEAD ONE OUT", "note": "heavier than it looks"}, @@ -185,7 +226,52 @@ func _job() -> Array: {"verb": "clamps", "cam": "bonnet", "label": "CLAMP IT UP: RED THEN BLACK", "note": "get it backwards and you'll know"}, {"verb": "yank", "cam": "bonnet", "label": "BONNET DOWN", "note": "one dignified slam"}, + ]) + "gasket": + return _apply_tiers([ + {"verb": "yank", "cam": "bonnet", "label": "BONNET UP", "note": "flick it open"}, + {"verb": "twist", "dir": -1.0, "turns": _t(4.0), "window": false, "nut": true, + "cam": "bonnet", "label": "UNDO THE HEAD BOLTS", "note": _sockets_note()}, + {"verb": "yank", "cam": "bonnet", "label": "OLD GASKET OUT", + "note": "flick it. mind the mayonnaise."}, + {"verb": "grab", "item": "gasket", "at": "engine", "cam": "bonnet", + "label": "NEW GASKET ON", "note": "EBAY, \"GENUINE\". sure."}, + {"verb": "steady", "secs": 2.0, "cam": "bonnet", "label": "SEAT IT SQUARE", + "note": "keep it in the ring or it weeps forever"}, + {"verb": "twist", "dir": 1.0, "turns": _t(4.0), "window": true, "nut": true, + "cam": "bonnet", "label": "TORQUE THE HEAD DOWN", + "note": "release IN the green -- head bolts do not forgive"}, + {"verb": "steady", "secs": 2.5 * pour, "cam": "bonnet", "label": "COOLANT BACK IN", + "note": "steady pour. the radiator's been through enough"}, + {"verb": "yank", "cam": "bonnet", "label": "BONNET DOWN", "note": "one dignified slam"}, + ]) + "gearbox": + var sheet: Array = [ + {"verb": "pump", "clicks": jack, "cam": "under", "label": "JACK IT UP", + "note": "click it like you mean it"}, + {"verb": "twist", "dir": -1.0, "turns": _t(5.0), "window": false, "nut": true, + "cam": "under", "label": "UNDO THE BELLHOUSING BOLTS", "note": _sockets_note()}, ] + if hoist == 0: + sheet.append({"verb": "grab", "item": "dead_box", "at": "bench", "cam": "under", + "label": "DRAG THE DEAD BOX OUT (it's 60 kg)", "note": "no crane. just you and regret"}) + sheet.append({"verb": "steady", "secs": 2.0, "cam": "under", + "label": "DON'T DROP IT ON YOURSELF", "note": "hold the ring. HOLD IT."}) + else: + if hoist < 3: + sheet.append({"verb": "grab", "item": "chain", "at": "box", "cam": "under", + "label": "HOOK THE CRANE UP", "note": "chain to the box. the crane does the rest"}) + sheet.append({"verb": "pump", "clicks": CRANE_CLICKS[hoist], "cam": "under", + "label": "CRANE IT OUT", "note": ["", "the Cashies crane creaks but lifts", + "new chains. barely any creak", "the winch just... does it"][hoist]}) + sheet.append({"verb": "grab", "item": "cashies_box", "at": "tunnel", "cam": "under", + "label": "CASHIES BOX IN", "note": "GEARBOX -- ONE OWNER, HONEST"}) + sheet.append({"verb": "twist", "dir": 1.0, "turns": _t(5.0), "window": true, "nut": true, + "cam": "under", "label": "BOLT IT ALL BACK UP", + "note": "release IN the green -- you KNOW what happens"}) + sheet.append({"verb": "pump", "clicks": 4, "cam": "under", "label": "LET IT DOWN", + "note": "and breathe"}) + return _apply_tiers(sheet) return [] ## ---- step lifecycle -------------------------------------------------------- @@ -200,6 +286,9 @@ func _show_step() -> void: if step_index >= steps.size(): _finish() return + if _diag_layer: + _diag_layer.queue_free() + _diag_layer = null var st: Dictionary = steps[step_index] step_label.text = "%s (%d/%d)" % [st["label"], step_index + 1, steps.size()] note_label.text = String(st.get("note", "")) @@ -213,13 +302,65 @@ func _show_step() -> void: steady_need = float(st["secs"]) "grab": _spawn_prop(String(st["item"])) - "yank": - if fault == "battery" and step_index == 0: - pass # bonnet handled on success + "diagnose": + _show_diagnose() ring.anchor_world = _step_anchor() ring.cam = cam ring.mode = String(st["verb"]) +func _show_diagnose() -> void: + ## Three lit warning lights, one honest. The reader points (t1 garbled, + ## t2 outright); no reader means vibes. Wrong pick = every nut runs longer. + var correct: String = Garage.FAULTS[fault]["light"] + var pool := LIGHT_POOL.filter(func(l): return l != correct) + pool.shuffle() + light_options = [correct, pool[0], pool[1]] + light_options.shuffle() + var rt := Garage.tool_tier("reader") + if rt >= 2: + note_label.text = "SCAN TOOL SAYS: \"%s\". no drama." % correct + elif rt == 1: + note_label.text = "CASHIES READER SAYS: \"%s\" (screen cracked)" % _garble(correct) + else: + note_label.text = "no reader. vibes only." + _diag_layer = CanvasLayer.new() + add_child(_diag_layer) + var row := HBoxContainer.new() + row.add_theme_constant_override("separation", 26) + row.set_anchors_preset(Control.PRESET_CENTER) + row.grow_horizontal = Control.GROW_DIRECTION_BOTH + _diag_layer.add_child(row) + _light_btns.clear() + for i in light_options.size(): + var b := Button.new() + b.text = "(!) %s" % light_options[i] + b.add_theme_font_size_override("font_size", 24) + if rt >= 1 and light_options[i] == correct: + b.modulate = Color(1.0, 0.75, 0.35) + var idx := i + b.pressed.connect(func() -> void: pick_light(idx)) + row.add_child(b) + _light_btns.append(b) + +func _garble(s: String) -> String: + var out := "" + for i in s.length(): + out += ["#", "!", "0"][i % 3] if i % 3 == 1 and s[i] != " " else s[i] + return out + +func pick_light(i: int) -> void: + if done or steps[step_index]["verb"] != "diagnose" or i >= light_options.size(): + return + if light_options[i] == Garage.FAULTS[fault]["light"]: + Sfx.shot_2d("clunk", -6.0) + _advance() + else: + penalty_turns += 1.0 + quality -= 5.0 + Sfx.shot_2d("bog", -6.0) + note_label.text = "nope. pulled the wrong thing apart first -- every nut runs a turn longer now." + qual_label.text = "QUALITY %d%%" % int(quality) + func _step_anchor() -> Vector3: var st: Dictionary = steps[step_index] match String(st.get("cam", "wheel")): @@ -275,6 +416,25 @@ func _spawn_prop(item: String) -> void: bm.material = _dark(Color(0.08, 0.08, 0.09) if item == "old_batt" else Color(0.12, 0.3, 0.12)) mi.mesh = bm n.position = _anchor_engine() if item == "old_batt" else Vector3(10.5, 1.08, -19.8) + "gasket": + var bm := BoxMesh.new() + bm.size = Vector3(0.45, 0.02, 0.3) + bm.material = _dark(Color(0.55, 0.5, 0.4)) + mi.mesh = bm + n.position = Vector3(10.5, 1.0, -19.8) # on the bench + "dead_box", "cashies_box": + var bm := BoxMesh.new() + bm.size = Vector3(0.35, 0.4, 0.7) + bm.material = _dark(Color(0.10, 0.10, 0.11) if item == "dead_box" else Color(0.16, 0.28, 0.16)) + mi.mesh = bm + n.position = _anchor_sump() - Vector3(0, 0.15, 0) if item == "dead_box" \ + else Vector3(10.5, 1.15, -19.8) + "chain": + var bm := BoxMesh.new() + bm.size = Vector3(0.05, 0.9, 0.05) + bm.material = _dark(Color(0.35, 0.36, 0.38)) + mi.mesh = bm + n.position = Vector3(15.2, 1.4, -17.0) # hanging off the crane n.add_child(mi) n.name = item add_child(n) @@ -305,6 +465,10 @@ func _process(delta: float) -> void: ## ---- the verbs (test API) -------------------------------------------------- +func _need() -> float: + # wrong diagnosis makes every nut longer; better sockets never change THAT + return (float(steps[step_index]["turns"]) + penalty_turns) * TAU + func twist(rad: float) -> void: if done or steps[step_index]["verb"] != "twist": return @@ -313,14 +477,14 @@ func twist(rad: float) -> void: if toward <= 0.0: return twist_prog += toward - var need: float = float(st["turns"]) * TAU + var need := _need() if not bool(st["window"]): if twist_prog >= need: Sfx.shot_2d("clunk", -6.0) _advance() return # torque window: overshoot past the seat and the thread strips - if twist_prog > need + WINDOW and not _stripped: + if twist_prog > need + _win() and not _stripped: _stripped = true quality -= 10.0 Sfx.shot_2d("bog", -4.0) @@ -334,8 +498,8 @@ func twist_release() -> void: ## releasing the mouse ends a torque attempt: clean if inside the window if done or steps[step_index]["verb"] != "twist" or not bool(steps[step_index]["window"]): return - var need: float = float(steps[step_index]["turns"]) * TAU - if twist_prog >= need and twist_prog <= need + WINDOW: + var need := _need() + if twist_prog >= need and twist_prog <= need + _win(): Sfx.shot_2d("clunk", -4.0) _advance() elif twist_prog > 0.5: @@ -354,11 +518,11 @@ func yank_ok() -> void: if done or steps[step_index]["verb"] != "yank": return Sfx.shot_2d("clunk", -6.0) + var label := String(steps[step_index]["label"]) if fault == "flat" and _wheel_mesh: _wheel_mesh.visible = false - if fault == "battery" and _bonnet: - # explicit bool: a Dictionary index is Variant and := can't see through it - var opening: bool = steps[step_index]["label"] == "BONNET UP" + if _bonnet and label.begins_with("BONNET"): + var opening: bool = label == "BONNET UP" var tw := create_tween() tw.tween_property(_bonnet, "rotation:x", -0.9 if opening else 0.0, 0.4) _advance() @@ -369,17 +533,23 @@ func grab(item: String) -> void: if steps[step_index]["item"] == item: carried = item +func _drop_target(at: String) -> Vector3: + match at: + "wheel": return _anchor_wheel() + "sump": return _anchor_sump() - Vector3(0, 0.2, 0) + "engine": return _anchor_engine() + "bench": return Vector3(11.5, 1.0, -19.8) + "box": # the dead gearbox under the car, waiting for the chain + return _anchor_sump() - Vector3(0, 0.15, 0) + "tunnel": + return _anchor_sump() - Vector3(0, 0.1, 0) + return _anchor_wheel() + func drop_at(world: Vector3) -> void: if done or carried == "" or steps[step_index]["verb"] != "grab": return var st: Dictionary = steps[step_index] - var target: Vector3 - match String(st["at"]): - "wheel": target = _anchor_wheel() - "sump": target = _anchor_sump() - Vector3(0, 0.2, 0) - "engine": target = _anchor_engine() - "bench": target = Vector3(11.5, 1.0, -19.8) - _: target = _anchor_wheel() + var target := _drop_target(String(st["at"])) if world.distance_to(target) < 1.2: var p: Node3D = props.get(carried) if p: @@ -558,7 +728,7 @@ class RingDraw extends Control: draw_arc(p, 42.0, 0, TAU, 40, Color(1, 0.82, 0.35, 0.8), 3.0) var w = parent.get("steps")[parent.get("step_index")] if bool(w.get("window", false)): - var need: float = float(w["turns"]) * TAU + var need: float = (float(w["turns"]) + float(parent.get("penalty_turns"))) * TAU var frac: float = clampf(float(parent.get("twist_prog")) / need, 0.0, 1.3) draw_arc(p, 52.0, -PI / 2, -PI / 2 + TAU * minf(frac, 1.0), 40, Color(0.3, 0.9, 0.4, 0.9) if frac >= 1.0 else Color(0.9, 0.9, 0.9, 0.7), 4.0) diff --git a/tests/smoke.gd b/tests/smoke.gd index f6eff5d..461e802 100644 --- a/tests/smoke.gd +++ b/tests/smoke.gd @@ -59,7 +59,15 @@ func _run() -> void: _check(not Garage.record_drag_pb("datto_120y", 15.0), "slower ET counted as a PB") _check(Garage.record_drag_pb("datto_120y", 13.9), "faster ET refused as PB") _check(absf(Garage.drag_pb("datto_120y") - 13.9) < 0.01, "PB not stored") - # faults: break it, boot wounded, pay the shop for one, DIY the other + # tool tiers: buying again upgrades; effects are exercised by wrench jobs + Garage.earn(1500 + 3000) + _check(Garage.tool_tier("sockets") == 1, "owned sockets not tier 1") + _check(Garage.buy_tool("sockets"), "tier 2 upgrade refused") + _check(Garage.buy_tool("sockets"), "tier 3 upgrade refused") + _check(Garage.tool_tier("sockets") == 3 and not Garage.buy_tool("sockets"), + "tier cap broken (tier %d)" % Garage.tool_tier("sockets")) + _check(Garage.has_tool("sockets"), "tiered tool lost has_tool") + # faults: break it, boot wounded, pay the shop for one, wrench the other print(" .. repair") Garage.add_fault("datto_120y", "gearbox") Garage.add_fault("datto_120y", "gasket") @@ -71,36 +79,45 @@ func _run() -> void: Garage.earn(1000) _check(Garage.shop_fix("datto_120y", "gasket"), "shop wouldn't take the money") _check(not Garage.faults("datto_120y").has("gasket"), "shop fix didn't clear the fault") - # DIY the gearbox: play the whole minigame headless through its API + # the gearbox goes through the 3D wrench: diagnose (wrong first, penalty), + # then the toolless 60 kg drag path -- no hoist owned on this save Garage.ui_stats_path = "res://cars/datto_120y/stats.tres" Garage.ui_fault = "gearbox" - var ui = (load("res://src/repair.tscn") as PackedScene).instantiate() + var ui = (load("res://src/wrench.tscn") as PackedScene).instantiate() root.add_child(ui) await process_frame - _check(ui.steps.size() >= 6, "gearbox job suspiciously short (%d steps)" % ui.steps.size()) + _check(ui.steps[0]["verb"] == "diagnose", "gearbox job skipped diagnosis") + _check(ui.steps.size() == 8, "toolless gearbox sheet wrong (%d steps)" % ui.steps.size()) var right := -1 - for i in ui.light_btns.size(): - if ui.light_btns[i]["light"] == Garage.FAULTS["gearbox"]["light"]: + for i in ui.light_options.size(): + if ui.light_options[i] == Garage.FAULTS["gearbox"]["light"]: right = i ui.pick_light((right + 1) % 3) - _check(ui.penalty_turns > 0.0, "wrong diagnosis cost nothing") + _check(ui.penalty_turns > 0.0 and ui.quality < 100.0, "wrong diagnosis cost nothing") ui.pick_light(right) _check(ui.step_index == 1, "right light didn't advance the job") - var guard := 0 - while not ui.done and guard < 12: - guard += 1 - var st: Dictionary = ui.steps[ui.step_index] - match st["kind"]: - "twist": - ui.twist(float(st["dir"]) * ((float(st["turns"]) + ui.penalty_turns) * TAU + 0.1)) - "pump": - for i in int(st["clicks"]): - ui.pump() - "drag": - ui.drop_at(ui.drag_target.get_global_rect().get_center()) - _check(ui.done, "repair never finished (stuck on step %d)" % ui.step_index) - _check(not Garage.faults("datto_120y").has("gearbox"), "DIY fix didn't clear the fault") + await _drive_wrench(ui, false) + _check(ui.done, "gearbox job never finished (step %d)" % ui.step_index) + _check(not Garage.faults("datto_120y").has("gearbox"), "wrench fix didn't clear the fault") + _check(Garage.quality("datto_120y", "gearbox") >= 85.0, "clean-ish gearbox scored %d" + % int(ui.quality)) ui.free() + # a gasket job at 85+ raises the overheat ceiling; gearbox 85+ adds a strike + Garage.add_fault("datto_120y", "gasket") + Garage.ui_fault = "gasket" + ui = (load("res://src/wrench.tscn") as PackedScene).instantiate() + root.add_child(ui) + await process_frame + await _drive_wrench(ui, false) + _check(ui.done and Garage.quality("datto_120y", "gasket") >= 85.0, + "gasket job didn't finish clean (%d)" % int(ui.quality)) + ui.free() + Game.level_path = levels["willowbank_dragway"] + gg = await _boot("drag") + _check(gg.box_limit == 4, "85+ gearbox rebuild paid no extra strike (%d)" % gg.box_limit) + _check(gg.gasket_cap > 1.1, "85+ gasket job raised no ceiling (%.2f)" % gg.gasket_cap) + gg.free() + Game.level_path = levels.get("woolies_carpark", levels.values()[0]) # THE SHED home base: owned tools materialise (sockets were bought above, # ramps were not), and rolling into the bay and holding still opens the garage print(" .. shed") @@ -428,7 +445,13 @@ func _drive_wrench(wr, sabotage: bool) -> void: while not wr.done and guard < 30: guard += 1 var st: Dictionary = wr.steps[wr.step_index] + var need: float = (float(st.get("turns", 0.0)) + wr.penalty_turns) * TAU match String(st["verb"]): + "diagnose": + for i in wr.light_options.size(): + if wr.light_options[i] == Garage.FAULTS[wr.fault]["light"]: + wr.pick_light(i) + break "pump": for i in int(st["clicks"]): wr.pump() @@ -436,18 +459,17 @@ func _drive_wrench(wr, sabotage: bool) -> void: if bool(st.get("window", false)): if sabotage and not sabotaged: sabotaged = true - wr.twist(float(st["dir"]) * (float(st["turns"]) * TAU + 2.0)) + wr.twist(float(st["dir"]) * (need + 4.0)) else: - wr.twist(float(st["dir"]) * (float(st["turns"]) * TAU + 0.3)) + wr.twist(float(st["dir"]) * (need + 0.3)) wr.twist_release() else: - wr.twist(float(st["dir"]) * (float(st["turns"]) * TAU + 0.2)) + wr.twist(float(st["dir"]) * (need + 0.2)) "yank": wr.yank_ok() "grab": wr.grab(String(st["item"])) - wr.drop_at(Vector3(11.5, 1.0, -19.8) if String(st["at"]) == "bench" - else wr._step_anchor()) + wr.drop_at(wr._drop_target(String(st["at"]))) "steady": while not wr.done and wr.steps[wr.step_index]["verb"] == "steady": wr.steady_tick(true, 0.5)