Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

engine.gno

10.02 Kb · 427 lines
  1// Package physics is the Spot Kick penalty engine: a port of the browser
  2// game's gno-shots/src/physics.js that produces bit-identical results.
  3//
  4// The JavaScript engine uses only + - * /, square roots, comparisons and
  5// 32-bit integer mixing inside its simulation, all of which IEEE-754 rounds
  6// exactly; gno does float64 arithmetic in software with the same rounding,
  7// and its math.Sqrt is correctly rounded. So the same inputs give the same
  8// bits here as in any browser, provided every operation happens in the same
  9// order. That is the rule this file follows, line by line:
 10//
 11//   - every float constant is a variable, never a Go constant: constant
 12//     expressions are folded at arbitrary precision, JavaScript rounds after
 13//     each operation;
 14//   - expressions keep the JavaScript order and grouping;
 15//   - JavaScript's Math.min, Math.max and Math.round are reproduced exactly
 16//     (jsMin, jsMax, jsRound), down to the sign of zero.
 17//
 18// Unlike the browser, which keeps simulating for the replay, Simulate stops
 19// as soon as the kick is decided; nothing after that can change the verdict.
 20package physics
 21
 22import "math"
 23
 24// ---------------------------------------------------------------- constants
 25
 26var (
 27	negInf  = math.Inf(-1)
 28	negZero = math.Copysign(0, -1)
 29
 30	simDT   = 0.001
 31	simMaxT = 2.5
 32	simG    = 9.81
 33
 34	ballR         = 0.11
 35	ballM         = 0.43
 36	ballEGround   = 0.55
 37	ballGroundKep = 0.85
 38	ballMinBounce = 1.0
 39	ballRollDecel = 1.2
 40	ballSpinDecay = 0.05
 41	ballI         float64
 42
 43	airRho  = 1.2
 44	airCD   = 0.25
 45	airCL   = 1.0
 46	area    float64
 47	dragK   float64
 48	magnusK float64
 49
 50	goalZ       = -11.0
 51	postR       = 0.06
 52	goalDepth   = 2.0
 53	ePost       = 0.7
 54	netKs       = 2500.0
 55	netCs       = 40.0
 56	netRecoil   = 0.1
 57	netFriction = 3.0
 58	boardsZ     = -16.0
 59	boardsH     = 1.0
 60	boardsHW    = 30.0
 61	netBackZ    float64
 62
 63	kickNormalMix = 0.18
 64	kickSideSpin  = 0.6
 65	kickBackSpin  = 0.25
 66	kickMaxSpin   = 110.0
 67	kickMaxStrike = 0.8
 68	kickAimX      = 6.0
 69	kickAimY      = 4.0
 70
 71	kLineOffset    = 0.25
 72	kHipFactor     = 0.93
 73	kPushTime      = 0.12
 74	kLeanRate      = 8.0
 75	kArmRate       = 10.0
 76	kLeanBias      = -0.3
 77	kLeanMin       = 0.15
 78	kSlideDecel    = 8.0
 79	kStarSpeed     = 1.2
 80	kEGlove        = 0.25
 81	kEBody         = 0.45
 82	kFriction      = 0.3
 83	kShoulderX     = 0.24
 84	kShoulderY     = 0.5
 85	kArmLen        = 0.62
 86	kGloveReach    = 0.73
 87	kGloveR        = 0.125
 88	kReachPivot    = 1.8
 89	kReachSpan     = 2.6
 90	kPerceive      = 0.16
 91	kArmTrackRate  = 12.0
 92	kReadEvery     = 20
 93	kReadBase      = 0.16
 94	kReadReach     = 0.75
 95	kReadHeightGn  = 2.2
 96	kReadShuffleT  = 0.45
 97	kMisreadX      = [2]float64{0.05, 1.2}
 98	kMisreadY      = [2]float64{0.04, 0.5}
 99	kMisreadT      = [2]float64{0.18, 0.28}
100	kMisjudgeVX    = [2]float64{0.12, 0.6}
101	kMisjudgeJump  = [2]float64{0.12, 0.6}
102	kPlantExtra    = 0.08
103	kPlantStretch  = 1.15
104	kRecoverDelay  = 0.08
105	kHoldDownT     = 0.3
106	kRecoverLeanRt = 9.0
107	kRecoverSpeed  = 2.6
108	kRecoverAccel  = 9.0
109	kRediveWindow  = 0.38
110	kRediveMinDX   = 0.45
111	kMaxCommits    = 2
112	kCatchMin      = 9.0
113	kCatchMax      = 25.0
114	kHandsGap      = 0.14
115	kHandsSpan     = 0.32
116	kBodyNear      = 0.25
117	kBodyFar       = 0.8
118	kHandsWeight   = 0.65
119	kHoldPull      = 25.0
120	kTouchGap      = 0.08
121	kMarginGlove   = 0.03
122	kMarginArm     = 0.02
123	kMarginTorso   = 0.02
124	kMarginLeg     = 0.02
125	kMarginHead    = 0.01
126	kReactSpeed    = 12.0
127	kReactReach    = 0.12
128	kGatherSpeed   = 7.5
129	kOneHandMax    = 13.0
130	kLowDropSpeed  = 1.8
131	kGiveHands     = 36.0
132	kGiveGlove     = 31.0
133	kGiveArm       = 14.0
134	kGiveTorso     = 30.0
135	kGiveHead      = 30.0
136	kGiveLeg       = 32.0
137	kGiveLegInAir  = 26.0
138	kReflexBody    = 0.25
139	kFingertipGive = 0.75
140	kParryControl  = 16.0
141	kParryOut      = 3.0
142	kParryWide     = 2.0
143	kHandLateMax   = 0.07
144	kHandLateRate  = 0.25
145	kPounceSpeed   = 3.0
146	kPounceReach   = 1.8
147	kPounceHeight  = 2.2
148	kPounceDelay   = 0.2
149	kPounceNear    = 0.9
150	kPounceMove    = 3.0
151
152	planForwardMax  = 22.0
153	planCentreShare = 0.06
154	planStarMaxSpd  = 1.15
155
156	halfPi = 1.5707963267948966
157	pi     = 3.141592653589793
158	twoPi  = 6.283185307179586
159	sqrt2  = 1.4142135623730951
160
161	armsReady, armsStar, armsHold armPair
162	zeroVec                       vec
163	upVec                         vec
164)
165
166func init() {
167	two, three := 2.0, 3.0
168	ballI = (two / three) * ballM * ballR * ballR
169	area = pi * ballR * ballR
170	dragK = (0.5 * airRho * airCD * area) / ballM
171	magnusK = (0.5 * airRho * airCL * area * ballR) / ballM
172	netBackZ = goalZ - goalDepth
173	upVec = vec{0, 1, 0}
174	armsReady = armPair{norm(vec{-0.55, -0.45, 0.7}), norm(vec{0.55, -0.45, 0.7})}
175	armsStar = armPair{norm(vec{-0.9, 0.6, 0.15}), norm(vec{0.9, 0.6, 0.15})}
176	armsHold = armPair{norm(vec{0.18, 0.12, 0.95}), norm(vec{-0.18, 0.12, 0.95})}
177}
178
179// Params are the constants a game may change (penalty.Config, in SI units).
180// DefaultParams are the browser engine's.
181type Params struct {
182	GoalHalfWidth float64 // m
183	GoalHeight    float64 // m
184	KeeperScale   float64
185	ReactionMin   float64 // s
186	ReactionMax   float64
187	StarShare     float64 // of the speed range
188	ReadShare     float64
189	DiveMin       float64 // m/s
190	DiveMax       float64
191	JumpMin       float64 // m/s
192	JumpMax       float64
193	BallMinSpeed  float64 // m/s
194	BallMaxSpeed  float64
195	FuzzBase      float64 // ball radii
196	FuzzPower     float64
197	MaxContact    float64 // ball radii
198}
199
200// DefaultParams returns the constants of the browser engine.
201func DefaultParams() Params {
202	return Params{
203		GoalHalfWidth: 3.2775, GoalHeight: 2.424, KeeperScale: 1.176,
204		ReactionMin: 0.02, ReactionMax: 0.12, StarShare: 0.06, ReadShare: 0.24,
205		DiveMin: 2.6, DiveMax: 5.2, JumpMin: -1.4, JumpMax: 4.0,
206		BallMinSpeed: 10, BallMaxSpeed: 34, FuzzBase: 0.03, FuzzPower: 0.1, MaxContact: 0.5,
207	}
208}
209
210// ------------------------------------------------------------- vector maths
211
212type vec struct{ x, y, z float64 }
213
214type armPair struct{ l, r vec }
215
216func add(a, b vec) vec           { return vec{a.x + b.x, a.y + b.y, a.z + b.z} }
217func sub(a, b vec) vec           { return vec{a.x - b.x, a.y - b.y, a.z - b.z} }
218func scale(a vec, s float64) vec { return vec{a.x * s, a.y * s, a.z * s} }
219func dot(a, b vec) float64       { return a.x*b.x + a.y*b.y + a.z*b.z }
220func crossVec(a, b vec) vec {
221	return vec{a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x}
222}
223func length(a vec) float64 { return fsqrt(dot(a, a)) }
224func norm(a vec) vec {
225	l := length(a)
226	if l > 0 {
227		return scale(a, 1/l)
228	}
229	return vec{0, 0, 0}
230}
231
232func clamp(x, lo, hi float64) float64 {
233	if x < lo {
234		return lo
235	}
236	if x > hi {
237		return hi
238	}
239	return x
240}
241
242// jsMin and jsMax are Math.min and Math.max for two non-NaN numbers: of two
243// zeros, min prefers -0 and max prefers +0.
244func jsMin(a, b float64) float64 {
245	if a < b {
246		return a
247	}
248	if b < a {
249		return b
250	}
251	if math.Signbit(b) {
252		return b
253	}
254	return a
255}
256
257func jsMax(a, b float64) float64 {
258	if a > b {
259		return a
260	}
261	if b > a {
262		return b
263	}
264	if !math.Signbit(b) {
265		return b
266	}
267	return a
268}
269
270// jsRound is Math.round: the nearest integer, halves rounded up, and -0
271// for -0.5 <= x <= -0.
272func jsRound(x float64) float64 {
273	f := floor(x)
274	if x-f >= 0.5 {
275		f++
276	}
277	if f == 0 {
278		if x < 0 {
279			return negZero
280		}
281		if math.Float64bits(x)>>63 != 0 {
282			return negZero
283		}
284	}
285	return f
286}
287
288// floor is math.Floor (gno's is interpreted and dear): below 2^52 in
289// magnitude by truncation, keeping -0; beyond that, and for infinities and
290// NaN, x itself.
291func floor(x float64) float64 {
292	if x < 4503599627370496 {
293		if x > -4503599627370496 {
294			f := float64(int64(x))
295			if f > x {
296				return f - 1
297			}
298			if f == 0 {
299				return x * 0
300			}
301			return f
302		}
303	}
304	return x
305}
306
307// detSin is the JavaScript engine's sine: range reduction and a Taylor series.
308func detSin(x float64) float64 {
309	x -= twoPi * jsRound(x/twoPi)
310	if x > halfPi {
311		x = pi - x
312	} else if x < -halfPi {
313		x = -pi - x
314	}
315	x2 := x * x
316	term := x
317	sum := x
318	for n := 1; n <= 9; n++ {
319		term *= -x2 / (float64(2*n) * float64(2*n+1))
320		sum += term
321	}
322	return sum
323}
324
325func detCos(x float64) float64 { return detSin(x + halfPi) }
326
327// ------------------------------------------------------------ seeded random
328
329type mulberry struct{ a uint32 }
330
331func (m *mulberry) next() float64 {
332	m.a += 0x6d2b79f5
333	t := m.a
334	t = (t ^ (t >> 15)) * (t | 1)
335	t ^= t + (t^(t>>7))*(t|61)
336	return float64(t^(t>>14)) / 4294967296
337}
338
339func fnvBytes(h uint32, b []byte) uint32 {
340	for _, c := range b {
341		h ^= uint32(c)
342		h *= 0x01000193
343	}
344	return h
345}
346
347func u16(b []byte, i int) uint16 { return uint16(b[i])<<8 | uint16(b[i+1]) }
348
349// ------------------------------------------------------------------- plans
350
351// Plan modes.
352const (
353	ModeStar = 1
354	ModeRead = 2
355	ModeDive = 3
356)
357
358// Plan is a keeper plan decoded from its four uint16.
359type Plan struct {
360	Raw       [4]uint16
361	Mode      int
362	Direction float64 // degrees: 0 = along the line to the taker's right, 180 = left
363	Speed     float64 // m/s
364	Jump      float64 // m/s
365	Reaction  float64 // s
366}
367
368// DecodePlan maps four uint16 onto a plan exactly as the browser does.
369func DecodePlan(raw [4]uint16, p Params) Plan {
370	d := float64(raw[0]) / 65535
371	sp := float64(raw[1]) / 65535
372	j := float64(raw[2]) / 65535
373	r := float64(raw[3]) / 65535
374	pl := Plan{Raw: raw}
375	switch {
376	case sp < p.StarShare:
377		pl.Mode = ModeStar
378	case sp < p.StarShare+p.ReadShare:
379		pl.Mode = ModeRead
380	default:
381		pl.Mode = ModeDive
382	}
383	pl.Direction = planDirection(d)
384	pl.Speed = planSpeed(sp, p)
385	pl.Jump = lin(j, p.JumpMin, p.JumpMax)
386	pl.Reaction = lin(r, p.ReactionMin, p.ReactionMax)
387	return pl
388}
389
390func lin(vv, lo, hi float64) float64 { return lo + (hi-lo)*vv }
391
392func planDirection(vv float64) float64 {
393	s := 2*vv - 1
394	c := planCentreShare
395	F := planForwardMax
396	if s <= -c {
397		return (F * (1 + s)) / (1 - c)
398	}
399	if s >= c {
400		return 180 - (F*(1-s))/(1-c)
401	}
402	return 90 + ((90-F)*s)/c
403}
404
405func planSpeed(vv float64, p Params) float64 {
406	k := p.StarShare
407	r := k + p.ReadShare
408	if vv < k {
409		return (planStarMaxSpd * vv) / k
410	}
411	if vv < r {
412		return 0
413	}
414	return lin((vv-r)/(1-r), p.DiveMin, p.DiveMax)
415}
416
417func (pl *Plan) commitTime() float64 {
418	if pl.Mode == ModeRead {
419		return kReadBase + pl.Reaction
420	}
421	return pl.Reaction
422}
423
424func launchVelocity(pl *Plan) vec {
425	a := pl.Direction * (pi / 180)
426	return vec{pl.Speed * detCos(a), pl.Jump, pl.Speed * detSin(a)}
427}