3 Sprung block S S S S # # #
5 Block with gap # # _ # #
36 Forces try to push entities in hexdirs. Forces can necessitate other forces.
37 Resolution involves moving each entity one hex in the given hexdir. These
38 forces can be inconsistent, resulting in nullification or transferral of the
39 forces. Note that inconsistency is a complicated business, pairwise
40 consistency not being enough.
42 Furthermore, we need some notion of equilibrium: a block with springs on both
43 ends should settle with equal numbers wound on each end. The wrench,
44 meanwhile, should be forceful enough to wind arbitrarily many springs.
46 Importantly, forces are always about something moving into a particular hex.
47 This means that multiple forces acting in different directions must be
48 inconsistent and will all fail. So we only need to add when there are forces
49 acting in diametrically opposite directions.
51 Adjacent tiles can be connected together into 'entities'. This can be
52 represented graphically drawing a connected entity in one colour, and ensuring
53 adjacent distinct entities are always drawn in different colours (cf 4-colour
54 theorem; or maybe 5-colour theorem - see linear time algorithm here:
55 https://en.wikipedia.org/wiki/Five_color_theorem ). The physics could also
56 allow non-connected entities, we can't really draw them (requires
57 arbitrarily many colours) so let's disallow that for now (except for the
58 uncoloured fixed frame).
60 Entities which are connected to no spring are fixed in place, and needn't be
61 coloured; they are considered to form a single "lock body" entity.
63 One of the tile types which can form part of an entity is the pivot. Pivots
64 have one-hex arms emanating from them (with colours indicating the
65 connection). Forces on arms translate to torque on the pivot.
67 The hook pick acts as a pivot with one arm and user-applied torque. It can
68 move laterally, but can't apply lateral force.
70 The wrench is always pushing in some direction. You can reset the direction
71 only when it's stopped.
73 Balls are special one-hex pieces which don't experience friction so can
74 transfer momentum - once pushed, they continue moving in the direction until
75 they hit something, then push that.
77 Springs connect two entities in a principal direction. One entity is the root.
78 The spring never given rise to a force on the root, except when it's fully
79 wound and is pushed on rootwards. To avoid unpleasantness, a sprung entity
80 must be rooted by a single spring - although it can act as the root for
81 arbitrarily many other sprung entities.
83 Graphical representation of springs: '|' for the root in the root's colour,
84 '=' for the connection to the sprung entity in its colour, '$' for wound
85 spring, 'S' for unwound (each '$' unwinds to two 'S's).
93 We can also have unsprung yet moveable entities, by having them rooted to
94 points on linear tracks (which can be part of other entities), represented by
104 A force is always actually just of the form "entity E wants to move in
105 hexdir D", and a torque is of the form "pivot P wants to turn +/-". If
106 there's something in the way, the force is transferred to the blocking
107 entity in the same direction. Forces on an arm are converted to torques on
110 We could use something more confusingly ornate, but I suggest the
111 following very simple resolution: as soon as two forces act in different
112 directions on the same entity, even if the directions are diametrically
113 opposed, the piece gets 'stuck' and no motion results.
115 So for each top-level force, we cascade down to determine resulting
116 forces, watching out that we don't get lost in a loop. The force is
117 self-consistent if no entity gets two different forces, and no entity gets
118 a force in a direction in which it is fixed. Two forces are mutually
119 consistent if the union has this property. With these rules, pairwise
120 consistency is enough - so after throwing away first the self-inconsistent
121 top-level forces which are inconsistent with themselves and then those
122 which are inconsistent with some other remaining top-level force, we're
123 left with a consistent set of forces.
125 (note we do need to throw away self-inconsistent forces first, or we have
126 situations where a silly force can block a non-silly one)
128 Problem: this doesn't handle the case of winding springs, where we really
129 do want opposing forces not to lead to stuckness.
131 Solution: there are two levels of force - player-instigated, and
132 environmental. The former overrules the latter.
134 So force resolution works as follows: first remove self-inconsistent
135 forces; then look for pairwise inconsistencies, and remove any force which
136 is inconsistent with a force of equal or greater strength.
138 Problem: we haven't handled moving roots.
139 Solution: have the strength of a spring decrease with depth in the
140 tree of rootedness - which we do require to be a tree.
142 Winding springs: during force propagation, a spring of equal or greater
143 strength will always pass the force through, while one of lesser strength
144 will pass the force through iff it is already fully wound.
146 Ball collisions can be the weakest level of force, so they will never wind
147 springs but can push other balls and tracked entities.
149 A spring-force which is inconsistent should be replaced with a reaction
150 force on the root entity, with the same strength, with this then being
151 checked for self- and (from the top) pairwise-consistency.
153 Hmm. Would be much neater to not have different strengths for different
154 springs. Can we avoid it?
157 A spring will resist a force on the sprung entity which tends to
158 extend/compress the spring, unless the force is player-strength.
160 For remaining forces: a push/pull is transferred through a spring iff
161 the spring is at minimal/maximal extension or the spring is at an
164 A spring not at its rest-length will generate a force on the sprung
165 entity. This never yields a force on the root.
167 Problem: pairwise consistency *isn't* enough.
168 e.g. consider a line of interlocked sprung entities with compressed
169 springs, such that the springs must decompress together.
171 Consider this as a SAT problem, where we favour certain answers?
174 Another problem: colliding entities.
175 If two entities are forced to move into the same hex by forces of the
176 same strength, we need to decide what to do. Currently I think it
177 would be better to block them both, rather than to introduce some
178 arbitrary rule to decide which gets precedence. The idea would be that
179 the gridded nature of the lock isn't just an artifact of our discrete
180 physics simulation, it's part of the underlying architecture of the
183 However, this does mean that checking for consistency is *not* simply
184 a matter of inducing forces and checking for multiple different forces
185 induced on the same entity. Actually it wasn't anyway; pivots can
189 Forget "strengths". Instead, we split into two phases: player-generated
190 movement, then environment-generated movement.
193 Hook and wrench forces are considered equal, and can block each other;
194 the hook never gets pushed around by the wrench.
195 Springs can be stretched and compressed freely within their limits.
197 Environment-generated:
198 A force on a sprung block is blocked if the spring's length isn't such
199 as to be generating the same force.
201 Allowing moveable pivots other than the hook results in unpleasantness
202 when forces and torques combine. So only fixed pivots are allowed.
204 (alternative would be to allow pivots on entities, but consider them
205 'locked' when there's a force on the entity, so any torque is blocked)
208 note we still need to have forces propogating beyond a blockage to
209 block other forces - otherwise we easily get strange loops of the
210 "blocked iff not blocked" sort, leading to unintuitive complexity.
212 Detailed force resolution algorithm:
213 (to be run first for player forces, then for environmental forces)
216 For each source force, propagate:
217 neighbouring entities get pushed,
218 neighbouring pushed arms induce torque on the pivot,
219 springs transmit forces where they can't absorb them.
221 Record with the propagated force the sources which gave rise to it.
224 forces on fixed objects
225 (player objects are fixed during environmental stage),
226 environmental forces on sprung entities not according with the
228 multiple forces in different directions on the same entity,
230 in each case, mark the source forces involved as blocked.
231 Blockedness of a force is irrelevant when determining blockages.
233 Enact all forces with at least one unblocked source.
235 A ball which pushed something loses its momentum; a ball with a
236 resulting force gets momentum as well as moving.
238 A wrench whose force is blocked loses its momentum.
241 Just check for blockages while propagating.
243 Could try to avoid propagating the same force twice from different
244 sources, but it would be more complicated than it's worth.
247 This algorithm doesn't allow for gearing of the kind I'd envisioned.
249 Idea: split the concept of blocking into two: blocks and locks. A blocked
250 force is one which is "immediately self-inconsistent", e.g. which
251 pushes an entity against a fixed object or in a direction in which it
252 is fixed. A lock involves another object or another force or spring
253 tension. The idea then is that when a pivot turns, its arms try to
254 push against things in the obvious direction, but if that's blocked
255 (as opposed to locked) they try in the next-to-obvious direction
258 Quite intuitive, and shouldn't be hard to implement.
265 Board state consists of:
267 type (hook, wrench, pivot, block, ball, frame)
271 root entity and position
272 direction and natural length of spring
273 position on current entity of connection
274 (redundant, but included for debugging)
277 The force resolution algorithm above simply maps states to states.
279 Remarks on unintuitive results of our algorithm:
280 Forces which seem like they should just "cancel out" can nonetheless block
281 third forces due to collisions. e.g. all three forces are blocked in:
285 . One could imagine trying to delay or renege on the blocking of the
286 rightmost force. But this isn't straightforward; e.g. consider a setup like
287 the above whereby the pivot turning clockwise propagates to another
288 mismatch which should cancel out the bottom force, while similarly
289 anticlockwise should cancel the top force.
291 Generally, having forces block forces from blocking other forces opens a
292 can of knotted ouroboroses.
294 Concrete example: consider three pivots and a force on each producing
295 anticlockwise torque, and set things up such that an anticlockwise torque
296 on a pivot propagates to clockwise torque on the next. Then if we want the
297 rule to be that balanced forces don't propagate, we find that the forces
298 are balanced iff they're not.
300 So balanced forces must propagate - which means we're bound to get the
301 kinds of unintuitive nastiness exemplified in the diagram above.
303 So I should switch to trying to make it seem intuitive.
305 Actually, there may be another way, which is to take a hint from CD
306 as to how to cook those ouroboroses: when we get cycles as in the above
307 example, consider something balanced if it's *ever* balanced as we go
308 through the cycle. Would this lead us to surprising consequences with
311 Yes, it really would, just like in CD, only more surprising and harder to
312 think through. This can't be a good idea, can it?
314 Alternatively: work monotonically - once something is balanced, it remains
317 So in other words: we use the current rules, but mismatches don't block.
318 once complete, we look for mismatches - multiple forces on a piece. If
319 there are any, we mark those pieces as 'balanced' (better term?), and run
320 the algorithm again; balanced pieces just resist any force. Iterate, only
321 adding to the list of balanced pieces, until we settle.
323 One thing to consider: what should happen when a force on a piece
324 propagates to some forces, one of which balances out the first force?
325 Wouldn't it be perverse to have that propagation nullified? So we should
326 say that it takes two differently sourced forces to cause a balance, and
327 one-source mismatches just lead to blocking as currently.
329 Simple enough to implement, and fairly intuitive in conception. Does it
330 lead to any pathological results? How unintuitive is a force which is
331 balanced out nonetheless having its propagands balance other forces?
332 Answer: at least as unintuitive as the behaviour which motivated all this.
335 So a CD-style "iterate until we loop" algorithm is the only one I
336 can see which would lead to reasonable behaviour - the problem being that
337 it also leads to a kind of complexity I was hoping to avoid. Hrm.
339 Not only that: at least in any way I can see it working, it would *still*
340 involve forces propagating beyond a balance having an effect, due to the
343 Note that the results of actual physics in these sorts of situations would
344 involve the finite speed of force transferal - which is something I
345 *really* don't want to be part of the game physics.
347 OK, so the current "quantum" physics stays.
349 Thoughts on representing blocks in the SDL client:
350 Draw every blocked (as opposed to instantly resisted) force. For
351 pivots/hooks: draw dimly arms in partially-rotated positions. For
352 blocks/wrenches: draw dimly shifted to the edge of their hex (when not
353 already drawing to the edge).
357 Currently, the wrench can't move east in: - o
359 This is kind of counterintuitive. Similarly with a pushing hook.
360 But if we allowed wrenches to turn cogs like this, while keeping with our
361 simple 'momentum' setup for wrenches and our current resistance mechanics,
362 it seems we would have to have a wrench resist all pushes not in its
363 current direction of motion, which means we lose the neat current mechanic
364 of being able to stop a wrench by pushing it to one side.
366 But on consideration, this is the lesser evil.
369 Following are stable:
376 and so on. Complicated solution: bring back spring strengths.
378 Simpler solution desired!
380 Maybe not simpler, but neater: work directly with the porder of the DAG of
381 connections. A spring source-force dominates any source-force on an
382 ancestor of its root. Say "latter force *> former force" in this case (not
383 that it's transitive) we should see that we can't have x*>y*>x.
385 Indeed: if F:r->e and F':r'->e' are spring forces and F*>F' and F'*>F, then
386 e' >= r > e >= r' > e' (porder of connectivity), which is a contradiction.
388 A sforce with source F can't block any sforce whose source dominates F.
390 Note we're left with some arguably surprising cases, e.g.
396 But that makes a kind of sense if we think of the right piece wobbling
397 leftwards... I think it's fine.
402 Rethinking the physics:
403 I think we took a wrong turning in trying to work one force at a time.
405 Rather, we should fully propagate each source force, and then look for
406 conflicts between the forcegroups. This seems to simplify many things,
407 and make multispring less problematic.
409 Propagation: sources and propagands are allowed to be ordered lists of
410 forces; if one fails, the next is tried; if all fail, the source/propagand
411 fails. Recursively propagate out; a force fails if it is resisted or if
412 one of its propagands fails. No need for special 'mediated resistance',
413 nor for partially compressed springs to convey forces.
415 Then check for pairwise conflicts between forcegroups:
416 mismatches (different forces on the same piece)
417 pairwise collisions (applying each forcegroup to the initial state)
418 using domination rules to determine resulting blocks.