flutter3d_sim 0.6.0 copy "flutter3d_sim: ^0.6.0" to clipboard
flutter3d_sim: ^0.6.0 copied to clipboard

The simulation: a fixed step, an ECS, levels, navigation, saves and replays. Plain Dart, so it runs on a server.

0.6.0 #

  • A floor, and no code. The step, the ECS, levels, navigation, saves and replays are byte for byte 0.5.2's. The one line that changed is the floor on flutter3d_physics, now ^0.6.0, and it is stated for the reason it was stated before: ground is split across the two packages — Heightfield here is the data, CollisionHeightfield there is what a body stands on — so a resolver free to reach further back would hand a caller the first without the second.
  • Still plain Dart. Nothing here imports Flutter, and a server replaying a run needs no SDK to do it.

0.5.2 #

Ground made of samples, and a crowd that walks over it.

  • Heightfield: the first sloped ground this level format has. A Brush is a box, so until now the only slope a level could describe was the ramp a wedge makes. A field is columns * rows heights, cellSize metres apart, with an origin where sample (0, 0) sits, and it answers heightAt, normalAt and slopeAt about the ground rather than building anything. The answers are about the triangles that are drawn, not a bilinear sheet. Four samples make a quad and a quad is two triangles, so a quad is only flat when its corners agree; interpolating bilinearly instead describes a surface nobody draws, and at the centre of a cell the two differ by a quarter of h00 + h11 - h10 - h01 — a unit hovering over one half of the quad and sunk into the other. So the field finds the triangle the point is in. The split is fixed at (0,0)–(1,1) and written down once, because a mesh builder that chose the other diagonal would draw ground this class does not describe and the only symptom would be a body standing slightly in the air. slopeAt reports radians from flat and refuses to say what is walkable — a tank and a scout disagree about the same hillside — and it reaches for Portable.atan2 rather than math.acos, because acos is the platform's libm and the rule a step asks no machine for an answer is what keeps a run verified in a browser agreeing with the run a player made.
  • A level can carry one. Level.heightfield is an optional section, read and written by Heightfield.fromJson / toJson. The heights travel as base64 of a Float32List's bytes, the way LevelVisibility carries its cells: sixteen thousand samples spelled out as JSON digits is a megabyte nobody reads and every editor reformats. The four numbers a person might edit by hand stay plain.
  • HeightfieldGeometry emits a BrushSurface, not a new type. Terrain is not a brush, but what the type holds is plain arrays, and the twenty lines in flutter3d_bridge that interleave them into a vertex layout do not care where the triangles came from — so ground draws with no new code downstream. Its normals are averaged from central differences while Heightfield.normalAt returns the triangle's own: one answers what the ground looks like, the other what a body is standing on, and the disagreement is the point.
  • NavGrid.bakeHeightfield: a second source for the same lattice. bake measures its grid from brushes and stamps each cell with the solid under it; ground made of samples has no brushes and its extent is the field's own, so the two share the cell format and nothing else. Steepness is what makes ground unwalkable here — maxSlope defaults to 0.698 radians, a hair under forty degrees — with blocked for the ground refused for a reason that is not its shape. The step height is derived from the slope rather than taken from a level. On terrain the rise between neighbouring cells is not a ledge, it is the hillside the slope test just allowed, so the default is the tallest rise the steepest walkable cell can have: tan(maxSlope) * cellSize * 1.001, which is 0.42 m at the default half-metre cell. Left at the 0.4 a level's bake uses, a two-metre grid over ground of one part in five puts a rise of 0.4 against a limit of 0.4 and lets float rounding decide: 112 of 240 uphill moves survived the comparison and the rest were called walls. The derived height allows all 240, and the half-metre bake all 4032 of its own.

0.5.1 #

  • Portable: the transcendental functions a step is allowed to call. sin, cos, sinCos, tan, atan, atan2, asin and exp, built out of +, -, *, /, sqrt and the bytes of a double — all of which the specification pins — so two platforms cannot disagree about them. That is not a theoretical worry: parity_test.dart swept twelve dart:math functions over twenty thousand arguments under the VM and under Chrome, and only sqrt and pow gave the same bits. A car built on the rest replayed differently in a browser at twenty-three checkpoints of forty, which is a verifying server that cannot verify. It is forty of forty now. Accuracy is held to two units in the last place against dart:math by portable_math_test.dart, because portable and wrong is a physics bug no parity test could report.
  • solver_parity_test.dart: the rigid-body solver replays bit for bit, in a browser and on the VM. Predicted — flutter3d_physics calls no transcendental — and measured anyway, because the three ways it could still have diverged are the broadphase's ordering, a long chain of non-associative additions, and a browser's int being a double under the spatial grid.
  • Motion.easeFactor, Interpolated and the actor system's facing now call it rather than dart:math. tool/structure.dart's new rule a step asks no machine for an answer is what keeps them there.

0.5.0 #

Breaking. A step can say what happened, and four types stop being closed.

  • GameEvent and GameEvents: what a step did, drained by whoever owns it. A buffer rather than a stream, because a stream delivers on a later microtask and two simulations here reproduce a recorded run exactly — an event arriving between two steps is the one state no replay can reproduce. ActorSystem writes into the simulation's buffer at the moment of a death, so a monster killed by this step's shot lands after the shot that killed it; two lists read afterwards can only say that both occurred. ActorHurt is now the event rather than a value copied into one, and ActorDied says who caused it. StepEvents.has and .count answer the two questions every reader asks of a drained step.
  • Difficulty: four axes a genre applies where it decides. What the player is hurt by, what their attacks are worth, how quickly the opposition reacts, and how much of the genre's help is on. A value class rather than an enum, so a game writes its own — or builds one from a slider, which a list of four cannot express. opponentReaction is a duration, so the harder settings have less of it.
  • ActivationOutcome is open, and abstract base. Nothing ever switched over its three cases exhaustively, so sealing bought nothing and cost a game the ability to say what happened at its own door.
  • StepSystem takes a StepContext. A function type is frozen the day it is published; the context carries dt and the phase, which the old shape could not, and can grow a field instead of breaking every system.
  • Difficulty, Powers, Scoring and RunStats. Four pieces every genre wanted and at most one of them had written. Powers came out of the shooter, which keeps every question it answered and delegates the counting. Scoring is the question a tally cannot answer — what those counts were worth, and whether they came close enough together to be worth more. RunStats counts events, and counts nothing until a game says what to recognise.
  • CharacterController.groundNormal — it measured the floor's normal to decide whether the body was standing on it and threw it away, so nothing above could tell a flat floor from a ramp.

0.4.2 #

  • A brush can say how it casts, not only whether. shadowCasting in the document is one of on, off, doubleSided or shadowsOnly — the engine has had four modes since ShadowCastingMode was written and the format had two, so the two it could not ask for were the two it most needed: both faces recorded, for a wall one brush thick whose lit side and dark side are a metre apart, and a proxy that casts without being drawn. Brush.castsShadow is now the two-state view of Brush.shadowCasting and goes on meaning what it meant, an unknown word is refused with the four in the message, and a document that said nothing goes on saying nothing. Surfaces are batched by the mode rather than by the boolean, because a batch is the smallest thing that can answer.
  • A breach keeps the baked light on the walls it did not touch. Breaches.origins says which authored brush each current brush was cut out of, and BrushGeometry.build(origins:) uses it to find the planned face a piece's face is part of and measure its place inside it — LightmapLayout.uvOfPoint and isOnPlane are that arithmetic. Before it, redrawing a level after one hole meant no atlas at all and every wall in every room fell back to flat ambient at once. The faces the blast itself made take the neutral texel, which is the one part of a breached wall nothing ever baked.
  • A level can ask to be reflected. EntityTypes.reflectionProbe is the format's word for a point a room is reflected from, and ReflectionProbeKind is the kind that validates one: radius, intensity, faceSize, levels, near and far are all optional and each is refused where the renderer would otherwise assert on it at load. The two planes are resolved against the probe's own defaults before either is judged, so a document naming only a near plane past two hundred metres is refused here rather than at load: the far plane it did not name is still a far plane. Pure data to the simulation — nothing spawns and nothing is revealed — and a word a game has to put in its own vocabulary, so a game without probes reads the entity as unknown rather than growing a reflection it did not ask for.

0.4.1 #

  • Lightmaps. LightmapLayout unwraps every visible brush face onto a planar atlas from the level alone, so the baker and the geometry agree without a table; LightmapBaker bakes the light the walls throw on each other by gathering — direct light with shadows through the level's own collision world, then bounces along cosine-weighted rays — seeded by the texel, so two bakes are the same bytes. Lightmap stores RGBM in RGBA8 with a hash of the brushes, lights and materials, and dart run flutter3d_sim:bake_lightmap writes <level>.lightmap.bin. BrushGeometry.build(lightmap:) hands every vertex its second coordinate.
  • Jump links. NavGrid.bake(jumps:) finds the gaps and ledges a JumpReach clears; a FlowField filters them by its own body's reach with the body's width added, relaxes them backwards at their distance plus two cells so a walk of equal length wins, and jumpAt says when the next step is a jump. Navigation.jumpAhead and ActorSystem take off through the controller's buffered request on the take-off cell.
  • Mind.heading, for turning to face the way the field said.

0.4.0 #

  • First release. It is flutter3d_game's inside, moved out whole: the fixed step, the entity store, the level format and its validator, saves, demos and the rewind buffer, world logic, actors, navigation, the camera rig and the maths. Nothing changed behaviour; the imports moved and the package boundary is new.
  • Plain Dart, and that is the reason it exists. A server that verifies a submitted run has to replay it through the same simulation the player ran, and a Flutter SDK in that container is a blocker rather than an inconvenience. flutter3d_game keeps the eight files that reached Flutter — the touch and keyboard widgets, the MediaQuery read, the diagnostics sink — and re-exports this package, so no existing program changes a line.
  • The boundary is a check, not a comment: the simulation names no Flutter in tool/structure.dart scans lib/, test/ and bin/.
  • StateDigest and DigestTrace come with it — a 32-bit digest over the bits of a snapshot, computed the same way in a browser as in the VM, and a checkpoint trace that names the first step two runs disagree at.
  • dart run flutter3d_sim:bake_visibility moves here from flutter3d_game, where it had never needed Flutter either.
0
likes
150
points
393
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

The simulation: a fixed step, an ECS, levels, navigation, saves and replays. Plain Dart, so it runs on a server.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#game-development #simulation #game-engine #determinism

License

MIT (license)

Dependencies

flutter3d_physics, vector_math

More

Packages that depend on flutter3d_sim