Task.work() takes effort as a float, with the guard on the base class #115
No reviewers
Labels
No labels
assets
bug
duplicate
enhancement
help wanted
invalid
question
testing
ui
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
liamjd/UntitledColonyBuilder!115
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "110-work-effort-float"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #110.
Task.work()takeseffort: floatinstead ofticks: int, and the guard that comes with it moves onto the base class.Why the int had to go
work_remainingwas already afloatandPawnalready stubsPawnLevel {NEWBIE, SKILLED, EXPERT, LEADER}, so the parameter was the odd one out. The failure mode is silent: GDScript converts a float argument to anintparameter without complaint, sowork(ctx, 1.5)became one unit of effort andwork(ctx, 0.5)became none at all. Every existing call passed a whole number, which is exactly why nothing had noticed —1.0converts cleanly.The unit does not change. Effort is still pawn-ticks, per #88. What changes is that a tick of a pawn's time is no longer required to be worth exactly one of them, which is what #69 and #74 need.
work()becomes a template methodThe signature change opened a second hole. The usual
clamps the result, which does nothing for a negative input — subtracting a negative effort grows the remainder. A building under construction would quietly un-build itself rather than erroring.
So
work()is nowvalidate, then delegate— the same splitcan_start()already uses for_can_start()in the same file — with subclasses implementing_work(). No caller changed; they were already callingwork().That placement matters rather than being tidiness. The guard was briefly written once in
ConstructionTaskand once inStubTask, with a third copy owed by each of #72'sDeconstructionTaskand #73'sHaulTask. One rule in four places, where forgetting it is silent, is the shape of a bug that arrives in six months.Zero effort is now a no-op rather than an error.
ConstructionTaskhad guardedeffort <= 0.0, which made a pawn that contributed nothing this tick a caller error. Blocked, or between waypoints, is not a bug — #75's loop will produce that case routinely.A note on the test double
Worth recording, because it nearly shipped a false pass.
The first version of the negative-effort test ran against
StubTask. The stub then grew its own guard to satisfy it, and the test went green whileConstructionTaskwas still unprotected — it could not distinguish "the base class caught it" from "the subclass caught it early".StubTaskis now deliberately unguarded and carries a comment saying so. It is test support, not game behaviour; a guard there protects no task the game actually runs. The pair of tests that replaced it does the real work: one pins the mechanism (a rejected effort never reaches the subclass hook), one pins that a concrete subclass inherits it (a negative effort cannot un-build a real building).Verification
522 passing, 0 failing, 1 pendingacross 523 tests. The pending is the pre-existing #85 marker intest_task_tree.gd. Editor scan run; project imports clean.Eight tests added or renamed:
test_task.gd— fractional effort lands exactly; fractional efforts accumulate to zero; zero effort leaves the remainder alone; negative effort does not add work back; the guard belongs to the base class, not the subclass; a valid effort still reaches the subclass hooktest_construction_task.gd— a fractional effort lands exactly on the building; a fractional first effort still breaks ground; a negative effort cannot un-build a buildingThe four red tests this branch opened with were all the truncation:
1.5arriving as1,0.5arriving as0, and-5.0growing the remainder from3.0to8.0.Not done here
BuildingType.build_work_ticks()still returns anint, deliberately. Build effort is authored in whole pawn-days; it is the per-tick contribution that varies.work()can now carry one, but #74 puts roles on the pawn record and #69 weights selection by them - until then every caller passes1.0.work()at all: #75's pawn work loop is still the consumer.🤖 Generated with Claude Code